Index: src/main/java/org/apache/pdfbox/cos/COSArray.java
===================================================================
--- src/main/java/org/apache/pdfbox/cos/COSArray.java	(revision 1023660)
+++ src/main/java/org/apache/pdfbox/cos/COSArray.java	(working copy)
@@ -21,8 +21,6 @@
 import java.util.Iterator;
 import java.util.List;
 
-
-
 import org.apache.pdfbox.exceptions.COSVisitorException;
 import org.apache.pdfbox.pdmodel.common.COSObjectable;
 
@@ -32,7 +30,7 @@
  * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
  * @version $Revision: 1.24 $
  */
-public class COSArray extends COSBase
+public class COSArray extends COSBase implements Iterable<COSBase>
 {
     private List<COSBase> objects = new ArrayList<COSBase>();
 
@@ -235,7 +233,7 @@
     public int getInt( int index, int defaultValue )
     {
         int retval = defaultValue;
-        if ( index < size() ) 
+        if ( index < size() )
         {
             Object obj = objects.get( index );
             if( obj instanceof COSNumber )
@@ -377,7 +375,7 @@
     /**
      * This will remove an element from the array.
      * This method will also remove a reference to the object.
-     * 
+     *
      * @param o The object to remove.
      * @return <code>true</code> if the object was removed, <code>false</code>
      *  otherwise
@@ -443,7 +441,7 @@
     /**
      * This will return the index of the entry or -1 if it is not found.
      * This method will also find references to indirect objects.
-     * 
+     *
      * @param object The object to search for.
      * @return The index of the object or -1.
      */
@@ -538,11 +536,11 @@
             add( new COSFloat( value[i] ) );
         }
     }
-    
+
     /**
      *  Return contents of COSArray as a Java List.
-     *  
-     *  @return the COSArray as List 
+     *
+     *  @return the COSArray as List
      */
     public List<COSBase> toList()
     {
Index: src/main/java/org/apache/pdfbox/cos/COSDocument.java
===================================================================
--- src/main/java/org/apache/pdfbox/cos/COSDocument.java	(revision 1023660)
+++ src/main/java/org/apache/pdfbox/cos/COSDocument.java	(working copy)
@@ -18,7 +18,6 @@
 
 import java.io.File;
 import java.io.IOException;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -29,7 +28,6 @@
 import org.apache.pdfbox.exceptions.COSVisitorException;
 import org.apache.pdfbox.io.RandomAccess;
 import org.apache.pdfbox.io.RandomAccessFile;
-
 import org.apache.pdfbox.pdfparser.PDFObjectStreamParser;
 import org.apache.pdfbox.pdfparser.PDFXrefStreamParser;
 import org.apache.pdfbox.persistence.util.COSObjectKey;
@@ -524,6 +522,17 @@
     }
 
     /**
+     * Removes an object from the object pool.
+     * @param key the object key
+     * @return the object that was removed or null if the object was not found
+     */
+    public COSObject removeObject(COSObjectKey key)
+    {
+        COSObject obj = objectPool.remove(key);
+        return obj;
+    }
+
+    /**
      * Used to populate the XRef HashMap. Will add an Xreftable entry
      * that maps ObjectKeys to byte offsets in the file.
      * @param objKey The objkey, with id and gen numbers
Index: src/main/java/org/apache/pdfbox/cos/COSName.java
===================================================================
--- src/main/java/org/apache/pdfbox/cos/COSName.java	(revision 1023660)
+++ src/main/java/org/apache/pdfbox/cos/COSName.java	(working copy)
@@ -18,10 +18,9 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
-
 import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.Map;
 
 import org.apache.pdfbox.exceptions.COSVisitorException;
 import org.apache.pdfbox.persistence.util.COSHEXTable;
@@ -129,9 +128,13 @@
     * A common COSName value.
     */
     public static final COSName BASE_FONT = new COSName( "BaseFont" );
+
+    /** the COSName for "BaseState". */
+    public static final COSName BASE_STATE = new COSName( "BaseState" );
+
     /**
-    * A common COSName value.
-    */
+     * A common COSName value.
+     */
     public static final COSName BBOX = new COSName( "BBox" );
     /**
      * A common COSName value.
@@ -141,7 +144,7 @@
      * A common COSName value.
      */
     public static final COSName BLACK_POINT = new COSName( "BlackPoint" );
-        
+
     /**
      * A common COSName value.
      */
@@ -651,12 +654,26 @@
      * "O"
      */
     public static final COSName O = new COSName("O");
-    
+
     /**
      * "Obj"
      */
     public static final COSName OBJ = new COSName("Obj");
 
+    /** the COSName for the content group tag. */
+    public static final COSName OC = new COSName("OC");
+    /** the COSName for an optional content group. */
+    public static final COSName OCG = new COSName("OCG");
+    /** the COSName for the optional content group list. */
+    public static final COSName OCGS = new COSName("OCGs");
+    /** the COSName for the optional content properties. */
+    public static final COSName OCPROPERTIES = new COSName("OCProperties");
+
+    /** the COSName for the "OFF" value. */
+    public static final COSName OFF = new COSName("OFF");
+    /** the COSName for the "ON" value. */
+    public static final COSName ON = new COSName("ON");
+
     /**
      * A common COSName value.
      */
@@ -730,7 +747,7 @@
      */
     public static final COSName PREV = new COSName( "Prev" );
 
-    /** "ProcSet" */
+    /** The COSName value for "ProcSet". */
     public static final COSName PROC_SET = new COSName( "ProcSet" );
 
     /**
@@ -738,6 +755,9 @@
      */
     public static final COSName PRODUCER = new COSName( "Producer" );
 
+    /** The COSName value for "Properties". */
+    public static final COSName PROPERTIES = new COSName( "Properties" );
+
     /**
      * A common COSName value.
      */
@@ -828,7 +848,7 @@
      * A common COSName value.
      */
     public static final COSName STR_F = new COSName( "StrF" );
-  
+
     /** "StructTreeRoot" */
     public static final COSName STRUCT_TREE_ROOT = new COSName("StructTreeRoot");
 
@@ -848,7 +868,7 @@
      * A common COSName value.
      */
     public static final COSName SUBTYPE = new COSName( "Subtype" );
-    
+
     /**
      * "T"
      */
@@ -902,6 +922,8 @@
      * A common COSName value.
      */
     public static final COSName U = new COSName( "U" );
+    /** the COSName for the "Unchanged" value. */
+    public static final COSName UNCHANGED = new COSName("Unchanged");
     /** "URI" */
     public static final COSName URI = new COSName("URI");
 
@@ -937,7 +959,7 @@
      * A common COSName value.
      */
     public static final COSName WHITE_POINT = new COSName( "WhitePoint" );
-    
+
     /** "XObject" */
     public static final COSName XOBJECT = new COSName( "XObject" );
 
@@ -986,7 +1008,7 @@
      * that are created.
      *
      * @param aName The name of the COSName object.
-     * @param staticValue Indicates if the COSName object is static so that it can 
+     * @param staticValue Indicates if the COSName object is static so that it can
      *        be stored in the HashMap without synchronizing.
      */
     private COSName( String aName, boolean staticValue )
Index: src/main/java/org/apache/pdfbox/pdmodel/PDDocumentCatalog.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/PDDocumentCatalog.java	(revision 1023660)
+++ src/main/java/org/apache/pdfbox/pdmodel/PDDocumentCatalog.java	(working copy)
@@ -25,7 +25,6 @@
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSStream;
-
 import org.apache.pdfbox.pdmodel.common.COSArrayList;
 import org.apache.pdfbox.pdmodel.common.COSObjectable;
 import org.apache.pdfbox.pdmodel.common.PDDestinationOrAction;
@@ -33,13 +32,13 @@
 import org.apache.pdfbox.pdmodel.common.PDPageLabels;
 import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
 import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
-import org.apache.pdfbox.pdmodel.interactive.action.type.PDURIDictionary;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties;
 import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
 import org.apache.pdfbox.pdmodel.interactive.action.PDDocumentCatalogAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDURIDictionary;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
 import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
-
 import org.apache.pdfbox.pdmodel.interactive.pagenavigation.PDThread;
 import org.apache.pdfbox.pdmodel.interactive.viewerpreferences.PDViewerPreferences;
 
@@ -551,17 +550,37 @@
     }
 
     /**
+     * Returns the PDF specification version this document conforms to.
+     *
+     * @return The PDF version.
+     */
+    public String getVersion()
+    {
+        return root.getNameAsString(COSName.VERSION);
+    }
+
+    /**
+     * Sets the PDF specification version this document conforms to.
+     *
+     * @param version the PDF version (ex. "1.4")
+     */
+    public void setVersion(String version)
+    {
+        root.setName(COSName.VERSION, version);
+    }
+
+    /**
      * Returns the page labels descriptor of the document.
-     * 
+     *
      * @return the page labels descriptor of the document.
-     * 
+     *
      * @throws IOException If there is a problem retrieving the page labels.
      */
-    public PDPageLabels getPageLabels() throws IOException 
+    public PDPageLabels getPageLabels() throws IOException
     {
         PDPageLabels labels = null;
         COSDictionary dict = (COSDictionary) root.getDictionaryObject(COSName.PAGE_LABELS);
-        if (dict != null) 
+        if (dict != null)
         {
             labels = new PDPageLabels(document, dict);
         }
@@ -573,9 +592,39 @@
      *
      * @param labels the new page label descriptor to set.
      */
-    public void setPageLabels(PDPageLabels labels) 
+    public void setPageLabels(PDPageLabels labels)
     {
         root.setItem(COSName.PAGE_LABELS, labels);
     }
 
+    /**
+     * Get the optional content properties dictionary associated with this document.
+     *
+     * @return the optional properties dictionary or null if it is not present
+     * @since PDF 1.5
+     */
+    public PDOptionalContentProperties getOCProperties()
+    {
+        PDOptionalContentProperties retval = null;
+        COSDictionary dict = (COSDictionary)root.getDictionaryObject(COSName.OCPROPERTIES);
+        if (dict != null)
+        {
+            retval = new PDOptionalContentProperties(dict);
+        }
+
+        return retval;
+    }
+
+    /**
+     * Set the optional content properties dictionary.
+     *
+     * @param ocProperties the optional properties dictionary
+     * @since PDF 1.5
+     */
+    public void setOCProperties(PDOptionalContentProperties ocProperties)
+    {
+        //TODO Check for PDF 1.5 or higher
+        root.setItem(COSName.OCPROPERTIES, ocProperties);
+    }
+
 }
Index: src/main/java/org/apache/pdfbox/pdmodel/PDResources.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/PDResources.java	(revision 1023660)
+++ src/main/java/org/apache/pdfbox/pdmodel/PDResources.java	(working copy)
@@ -17,7 +17,6 @@
 package org.apache.pdfbox.pdmodel;
 
 import java.io.IOException;
-
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -33,6 +32,7 @@
 import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
 import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
 import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+import org.apache.pdfbox.pdmodel.markedcontent.PDPropertyList;
 
 /**
  * This represents a set of resources available at the page/pages/stream level.
@@ -292,4 +292,31 @@
         }
         resources.setItem( COSName.EXT_G_STATE, dic );
     }
+
+    /**
+     * Returns the dictionary mapping resource names to property list dictionaries for marked
+     * content.
+     * @return the property list
+     */
+    public PDPropertyList getProperties()
+    {
+        PDPropertyList retval = null;
+        COSDictionary props = (COSDictionary)resources.getDictionaryObject(COSName.PROPERTIES);
+
+        if (props != null)
+        {
+            retval = new PDPropertyList(props);
+        }
+        return retval;
+    }
+
+    /**
+     * Sets the dictionary mapping resource names to property list dictionaries for marked
+     * content.
+     * @param props the property list
+     */
+    public void setProperties(PDPropertyList props)
+    {
+        resources.setItem(COSName.PROPERTIES, props.getCOSObject());
+    }
 }
Index: src/main/java/org/apache/pdfbox/pdmodel/common/COSDictionaryMap.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/common/COSDictionaryMap.java	(revision 1023660)
+++ src/main/java/org/apache/pdfbox/pdmodel/common/COSDictionaryMap.java	(working copy)
@@ -154,7 +154,7 @@
      */
     public Set entrySet()
     {
-        throw new RuntimeException( "Not yet implemented" );
+        return Collections.unmodifiableSet(actuals.entrySet());
     }
 
     /**
Index: src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java	(revision 1023660)
+++ src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java	(working copy)
@@ -18,26 +18,26 @@
 
 import java.awt.Color;
 import java.awt.color.ColorSpace;
+import java.awt.geom.AffineTransform;
 import java.awt.geom.PathIterator;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-
 import java.text.NumberFormat;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.HashMap;
 
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.PDResources;
-
 import org.apache.pdfbox.pdmodel.common.COSStreamArray;
 import org.apache.pdfbox.pdmodel.common.PDStream;
-
 import org.apache.pdfbox.pdmodel.font.PDFont;
 import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
 import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
@@ -51,12 +51,7 @@
 import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
 import org.apache.pdfbox.util.MapUtil;
 
-import org.apache.pdfbox.cos.COSArray;
-import org.apache.pdfbox.cos.COSDictionary;
-import org.apache.pdfbox.cos.COSName;
-import org.apache.pdfbox.cos.COSString;
 
-
 /**
  * This class will is a convenience for creating page content streams.  You MUST
  * call close() when you are finished with this object.
@@ -69,8 +64,8 @@
     private PDPage page;
     private OutputStream output;
     private boolean inTextMode = false;
-    private Map<PDFont,String> fontMappings = new HashMap<PDFont,String>();
-    private Map<PDXObject,String> xobjectMappings = new HashMap<PDXObject,String>();
+    private Map<PDFont,String> fontMappings;
+    private Map<PDXObject,String> xobjectMappings;
     private PDResources resources;
     private Map fonts;
     private Map xobjects;
@@ -115,8 +110,13 @@
     private static final String BEZIER_312 = "c\n";
     private static final String BEZIER_32 = "v\n";
     private static final String BEZIER_313 = "y\n";
-    
 
+    private static final String MP = "MP\n";
+    private static final String DP = "DP\n";
+    private static final String BMC = "BMC\n";
+    private static final String BDC = "BDC\n";
+    private static final String EMC = "EMC\n";
+
     private static final String SET_STROKING_COLORSPACE = "CS\n";
     private static final String SET_NON_STROKING_COLORSPACE = "cs\n";
 
@@ -161,8 +161,15 @@
             resources = new PDResources();
             page.setResources( resources );
         }
+
+        //Fonts including reverse lookup
         fonts = resources.getFonts();
-        xobjects = resources.getImages();
+        fontMappings = reverseMap(fonts, PDFont.class);
+
+        //XObjects including reverse lookup
+        xobjects = resources.getXObjects();
+        xobjectMappings = reverseMap(xobjects, PDXObject.class);
+
         // If request specifies the need to append to the document
         if(appendContent)
         {
@@ -217,6 +224,17 @@
         formatDecimal.setGroupingUsed( false );
     }
 
+    private <T> Map<T, String> reverseMap(Map map, Class<T> keyClass)
+    {
+        Map<T, String> reversed = new java.util.HashMap<T, String>();
+        for (Object o : map.entrySet())
+        {
+            Map.Entry entry = (Map.Entry)o;
+            reversed.put(keyClass.cast(entry.getValue()), (String)entry.getKey());
+        }
+        return reversed;
+    }
+
     /**
      * Begin some text operations.
      *
@@ -300,6 +318,20 @@
      */
     public void drawXObject( PDXObject xobject, float x, float y, float width, float height ) throws IOException
     {
+        AffineTransform transform = new AffineTransform(width, 0, 0, height, x, y);
+        drawXObject(xobject, transform);
+    }
+
+    /**
+     * Draw an xobject(form or image) using the given {@link AffineTransform} to position
+     * the xobject.
+     *
+     * @param xobject The xobject to draw.
+     * @param transform the transformation matrix
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void drawXObject( PDXObject xobject, AffineTransform transform ) throws IOException
+    {
         String xObjectPrefix = null;
         if( xobject instanceof PDXObjectImage )
         {
@@ -318,19 +350,20 @@
             xobjects.put( objMapping, xobject );
         }
         saveGraphicsState();
-        concatenate2CTM(width, 0, 0, height, x, y);
         appendRawCommands( SPACE );
+        concatenate2CTM(transform);
+        appendRawCommands( SPACE );
         appendRawCommands( "/" );
         appendRawCommands( objMapping );
         appendRawCommands( SPACE );
         appendRawCommands( XOBJECT_DO );
-        appendRawCommands( SPACE );
         restoreGraphicsState();
     }
 
+
     /**
      * The Td operator.
-     * A current text matrix will be replaced with a new one (1 0 0 1 x y). 
+     * A current text matrix will be replaced with a new one (1 0 0 1 x y).
      * @param x The x coordinate.
      * @param y The y coordinate.
      * @throws IOException If there is an error writing to the stream.
@@ -350,7 +383,7 @@
 
     /**
      * The Tm operator. Sets the text matrix to the given values.
-     * A current text matrix will be replaced with the new one. 
+     * A current text matrix will be replaced with the new one.
      * @param a The a value of the matrix.
      * @param b The b value of the matrix.
      * @param c The c value of the matrix.
@@ -381,8 +414,20 @@
     }
 
     /**
+    * The Tm operator. Sets the text matrix to the given values.
+    * A current text matrix will be replaced with the new one.
+    * @param matrix the transformation matrix
+    * @throws IOException If there is an error writing to the stream.
+    */
+    public void setTextMatrix(AffineTransform matrix) throws IOException
+    {
+        appendMatrix(matrix);
+        appendRawCommands(SET_TEXT_MATRIX);
+    }
+
+    /**
      * The Tm operator. Sets the text matrix to the given scaling and translation values.
-     * A current text matrix will be replaced with the new one. 
+     * A current text matrix will be replaced with the new one.
      * @param sx The scaling factor in x-direction.
      * @param sy The scaling factor in y-direction.
      * @param tx The translation value in x-direction.
@@ -396,7 +441,7 @@
 
     /**
      * The Tm operator. Sets the text matrix to the given translation values.
-     * A current text matrix will be replaced with the new one. 
+     * A current text matrix will be replaced with the new one.
      * @param tx The translation value in x-direction.
      * @param ty The translation value in y-direction.
      * @throws IOException If there is an error writing to the stream.
@@ -408,7 +453,7 @@
 
     /**
      * The Tm operator. Sets the text matrix to the given rotation and translation values.
-     * A current text matrix will be replaced with the new one. 
+     * A current text matrix will be replaced with the new one.
      * @param angle The angle used for the counterclockwise rotation in radians.
      * @param tx The translation value in x-direction.
      * @param ty The translation value in y-direction.
@@ -449,6 +494,18 @@
     }
 
     /**
+     * The Cm operator. Concatenates the current transformation matrix with the given
+     * {@link AffineTransform}.
+     * @param at the transformation matrix
+     * @throws IOException If there is an error writing to the stream.
+     */
+    public void concatenate2CTM(AffineTransform at) throws IOException
+    {
+        appendMatrix(at);
+        appendRawCommands(CONCATENATE_MATRIX);
+    }
+
+    /**
      * This will draw a string at the current location on the screen.
      *
      * @param text The text to draw.
@@ -858,7 +915,7 @@
     }
 
     /**
-     * Append a cubic Bézier curve to the current path. The curve extends from the current 
+     * Append a cubic Bézier curve to the current path. The curve extends from the current
      * point to the point (x3 , y3 ), using (x1 , y1 ) and (x2 , y2 ) as the Bézier control points
      * @param x1 x coordinate of the point 1
      * @param y1 y coordinate of the point 1
@@ -886,7 +943,7 @@
     }
 
     /**
-     * Append a cubic Bézier curve to the current path. The curve extends from the current 
+     * Append a cubic Bézier curve to the current path. The curve extends from the current
      * point to the point (x3 , y3 ), using the current point and (x2 , y2 ) as the Bézier control points
      * @param x2 x coordinate of the point 2
      * @param y2 y coordinate of the point 2
@@ -908,7 +965,7 @@
     }
 
     /**
-     * Append a cubic Bézier curve to the current path. The curve extends from the current 
+     * Append a cubic Bézier curve to the current path. The curve extends from the current
      * point to the point (x3 , y3 ), using (x1 , y1 ) and (x3 , y3 ) as the Bézier control points
      * @param x1 x coordinate of the point 1
      * @param y1 y coordinate of the point 1
@@ -928,10 +985,10 @@
         appendRawCommands( SPACE );
         appendRawCommands( BEZIER_313 );
     }
-    
-    
+
+
     /**
-     * Add a line to the given coordinate. 
+     * Add a line to the given coordinate.
      *
      * @param x The x coordinate.
      * @param y The y coordinate.
@@ -979,7 +1036,7 @@
         // lineTo
         lineTo(xEnd, yEnd);
     }
-    
+
     /**
      * Draw a line on the page using the current non stroking color and the current line width.
      *
@@ -995,7 +1052,7 @@
         // stroke
         stroke();
     }
-    
+
     /**
      * Add a polygon to the current path.
      * @param x x coordinate of each points
@@ -1021,7 +1078,7 @@
         }
         closeSubPath();
     }
-       
+
     /**
      * Draw a polygon on the page using the current non stroking color.
      * @param x x coordinate of each points
@@ -1045,27 +1102,27 @@
         addPolygon(x, y);
         fill(PathIterator.WIND_NON_ZERO);
     }
-       
+
     /**
      * Stroke the path.
      */
-    public void stroke() throws IOException 
+    public void stroke() throws IOException
     {
         appendRawCommands( STROKE );
     }
-    
+
     /**
      * Close and stroke the path.
      */
-    public void closeAndStroke() throws IOException 
+    public void closeAndStroke() throws IOException
     {
         appendRawCommands( CLOSE_STROKE );
     }
-    
+
     /**
      * Fill the path.
      */
-    public void fill(int windingRule) throws IOException 
+    public void fill(int windingRule) throws IOException
     {
         if (windingRule == PathIterator.WIND_NON_ZERO)
         {
@@ -1075,17 +1132,17 @@
         {
             appendRawCommands( FILL_EVEN_ODD );
         }
-        else 
+        else
         {
             throw new IOException( "Error: unknown value for winding rule" );
         }
-            
+
     }
 
     /**
      * Close subpath.
      */
-    public void closeSubPath() throws IOException 
+    public void closeSubPath() throws IOException
     {
         appendRawCommands( CLOSE_SUBPATH );
     }
@@ -1093,7 +1150,7 @@
     /**
      * Clip path.
      */
-    public void clipPath(int windingRule) throws IOException 
+    public void clipPath(int windingRule) throws IOException
     {
         if (windingRule == PathIterator.WIND_NON_ZERO)
         {
@@ -1105,7 +1162,7 @@
             appendRawCommands( CLIP_PATH_EVEN_ODD );
             appendRawCommands( NOP );
         }
-        else 
+        else
         {
             throw new IOException( "Error: unknown value for winding rule" );
         }
@@ -1125,10 +1182,47 @@
     }
 
     /**
+     * Begin a marked content sequence.
+     * @param tag the tag
+     * @throws IOException if an I/O error occurs
+     */
+    public void beginMarkedContentSequence(COSName tag) throws IOException
+    {
+        appendCOSName(tag);
+        appendRawCommands(SPACE);
+        appendRawCommands(BMC);
+    }
+
+    /**
+     * Begin a marked content sequence with a reference to an entry in the page resources'
+     * Properties dictionary.
+     * @param tag the tag
+     * @param propsName the properties reference
+     * @throws IOException if an I/O error occurs
+     */
+    public void beginMarkedContentSequence(COSName tag, COSName propsName) throws IOException
+    {
+        appendCOSName(tag);
+        appendRawCommands(SPACE);
+        appendCOSName(propsName);
+        appendRawCommands(SPACE);
+        appendRawCommands(BDC);
+    }
+
+    /**
+     * End a marked content sequence.
+     * @throws IOException if an I/O error occurs
+     */
+    public void endMarkedContentSequence() throws IOException
+    {
+        appendRawCommands(EMC);
+    }
+
+    /**
      * q operator. Saves the current graphics state.
      * @throws IOException If an error occurs while writing to the stream.
      */
-    public void saveGraphicsState() throws IOException 
+    public void saveGraphicsState() throws IOException
     {
         appendRawCommands( SAVE_GRAPHICS_STATE);
     }
@@ -1137,7 +1231,7 @@
      * Q operator. Restores the current graphics state.
      * @throws IOException If an error occurs while writing to the stream.
      */
-    public void restoreGraphicsState() throws IOException 
+    public void restoreGraphicsState() throws IOException
     {
         appendRawCommands( RESTORE_GRAPHICS_STATE );
     }
@@ -1177,6 +1271,27 @@
     }
 
     /**
+     * This will append a {@link COSName} to the content stream.
+     * @param name the name
+     * @throws IOException If an error occurs while writing to the stream.
+     */
+    public void appendCOSName(COSName name) throws IOException
+    {
+        name.writePDF(output);
+    }
+
+    private void appendMatrix(AffineTransform transform) throws IOException
+    {
+        double[] values = new double[6];
+        transform.getMatrix(values);
+        for (double v : values)
+        {
+            appendRawCommands(formatDecimal.format(v));
+            appendRawCommands(SPACE);
+        }
+    }
+
+    /**
      * Close the content stream.  This must be called when you are done with this
      * object.
      * @throws IOException If the underlying stream has a problem being written to.
Index: src/main/java/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentGroup.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentGroup.java	(revision 0)
+++ src/main/java/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentGroup.java	(revision 0)
@@ -0,0 +1,93 @@
+/*
+ * 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.graphics.optionalcontent;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents an optional content group (OCG).
+ *
+ * @since PDF 1.5
+ * @version $Revision$
+ */
+public class PDOptionalContentGroup implements COSObjectable
+{
+
+    private COSDictionary ocg;
+
+    /**
+     * Creates a new optional content group (OCG).
+     * @param name the name of the content group
+     */
+    public PDOptionalContentGroup(String name)
+    {
+        this.ocg = new COSDictionary();
+        this.ocg.setItem(COSName.TYPE, COSName.OCG);
+        setName(name);
+    }
+
+    /**
+     * Creates a new instance based on a given {@link COSDictionary}.
+     * @param dict the dictionary
+     */
+    public PDOptionalContentGroup(COSDictionary dict)
+    {
+        if (!dict.getItem(COSName.TYPE).equals(COSName.OCG))
+        {
+            throw new IllegalArgumentException(
+                    "Provided dictionary is not of type '" + COSName.OCG + "'");
+        }
+        this.ocg = dict;
+    }
+
+    /** {@inheritDoc} */
+    public COSBase getCOSObject()
+    {
+        return this.ocg;
+    }
+
+    /**
+     * Returns the name of the optional content group.
+     * @return the name
+     */
+    public String getName()
+    {
+        return this.ocg.getString(COSName.NAME);
+    }
+
+    /**
+     * Sets the name of the optional content group.
+     * @param name the name
+     */
+    public void setName(String name)
+    {
+        this.ocg.setString(COSName.NAME, name);
+    }
+
+    //TODO Add support for "Intent" and "Usage"
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString()
+    {
+        return super.toString() + " (" + getName() + ")";
+    }
+
+}

Property changes on: src\main\java\org\apache\pdfbox\pdmodel\graphics\optionalcontent\PDOptionalContentGroup.java
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Index: src/main/java/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentProperties.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentProperties.java	(revision 0)
+++ src/main/java/org/apache/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentProperties.java	(revision 0)
@@ -0,0 +1,357 @@
+/*
+ * 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.graphics.optionalcontent;
+
+import java.util.Collection;
+
+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.cos.COSObject;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents the optional content properties dictionary.
+ *
+ * @since PDF 1.5
+ * @version $Revision$
+ */
+public class PDOptionalContentProperties implements COSObjectable
+{
+
+    /**
+     * Enumeration for the BaseState dictionary entry on the "D" dictionary.
+     */
+    public static enum BaseState
+    {
+
+        /** The "ON" value. */
+        ON(COSName.ON),
+        /** The "OFF" value. */
+        OFF(COSName.OFF),
+        /** The "Unchanged" value. */
+        UNCHANGED(COSName.UNCHANGED);
+
+        private COSName name;
+
+        private BaseState(COSName value)
+        {
+            this.name = value;
+        }
+
+        /**
+         * Returns the PDF name for the state.
+         * @return the name of the state
+         */
+        public COSName getName()
+        {
+            return this.name;
+        }
+
+        /**
+         * Returns the base state represented by the given {@link COSName}.
+         * @param state the state name
+         * @return the state enum value
+         */
+        public static BaseState valueOf(COSName state)
+        {
+            if (state == null)
+            {
+                return BaseState.ON;
+            }
+            return BaseState.valueOf(state.getName().toUpperCase());
+        }
+
+    }
+
+    private COSDictionary dict;
+
+    /**
+     * Creates a new optional content properties dictionary.
+     */
+    public PDOptionalContentProperties()
+    {
+        this.dict = new COSDictionary();
+        this.dict.setItem(COSName.OCGS, new COSArray());
+        this.dict.setItem(COSName.D, new COSDictionary());
+    }
+
+    /**
+     * Creates a new instance based on a given {@link COSDictionary}.
+     * @param props the dictionary
+     */
+    public PDOptionalContentProperties(COSDictionary props)
+    {
+        this.dict = props;
+    }
+
+    /** {@inheritDoc} */
+    public COSBase getCOSObject()
+    {
+        return this.dict;
+    }
+
+    private COSArray getOCGs()
+    {
+        COSArray ocgs = (COSArray)this.dict.getItem(COSName.OCGS);
+        if (ocgs == null)
+        {
+            ocgs = new COSArray();
+            this.dict.setItem(COSName.OCGS, ocgs); //OCGs is required
+        }
+        return ocgs;
+    }
+
+    private COSDictionary getD()
+    {
+        COSDictionary d = (COSDictionary)this.dict.getDictionaryObject(COSName.D);
+        if (d == null)
+        {
+            d = new COSDictionary();
+            this.dict.setItem(COSName.D, d); //D is required
+        }
+        return d;
+    }
+
+    /**
+     * Returns the optional content group of the given name.
+     * @param name the group name
+     * @return the optional content group or null, if there is no such group
+     */
+    public PDOptionalContentGroup getGroup(String name)
+    {
+        COSArray ocgs = getOCGs();
+        for (COSBase o : ocgs)
+        {
+            COSDictionary ocg = toDictionary(o);
+            String groupName = ocg.getString(COSName.NAME);
+            if (groupName.equals(name))
+            {
+                return new PDOptionalContentGroup(ocg);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds an optional content group (OCG).
+     * @param ocg the optional content group
+     */
+    public void addGroup(PDOptionalContentGroup ocg)
+    {
+        COSArray ocgs = getOCGs();
+        ocgs.add(ocg.getCOSObject());
+    }
+
+    /**
+     * Returns the collection of all optional content groups.
+     * @return the optional content groups
+     */
+    public Collection<PDOptionalContentGroup> getOptionalContentGroups()
+    {
+        Collection<PDOptionalContentGroup> coll = new java.util.ArrayList<PDOptionalContentGroup>();
+        COSArray ocgs = getOCGs();
+        for (COSBase base : ocgs)
+        {
+            COSObject obj = (COSObject)base; //Children must be indirect references
+            coll.add(new PDOptionalContentGroup((COSDictionary)obj.getObject()));
+        }
+        return coll;
+    }
+
+    /**
+     * Returns the base state for optional content groups.
+     * @return the base state
+     */
+    public BaseState getBaseState()
+    {
+        COSDictionary d = getD();
+        COSName name = (COSName)d.getItem(COSName.BASE_STATE);
+        return BaseState.valueOf(name);
+    }
+
+    /**
+     * Sets the base state for optional content groups.
+     * @param state the base state
+     */
+    public void setBaseState(BaseState state)
+    {
+        COSDictionary d = getD();
+        d.setItem(COSName.BASE_STATE, state.getName());
+    }
+
+    /**
+     * Lists all optional content group names.
+     * @return an array of all names
+     */
+    public String[] getGroupNames()
+    {
+        COSArray ocgs = (COSArray)dict.getDictionaryObject(COSName.OCGS);
+        int size = ocgs.size();
+        String[] groups = new String[size];
+        for (int i = 0; i < size; i++)
+        {
+            COSBase obj = (COSBase)ocgs.get(i);
+            COSDictionary ocg = toDictionary(obj);
+            groups[i] = ocg.getString(COSName.NAME);
+        }
+        return groups;
+    }
+
+    /**
+     * Indicates whether a particular optional content group is found in the PDF file.
+     * @param groupName the group name
+     * @return true if the group exists, false otherwise
+     */
+    public boolean hasGroup(String groupName)
+    {
+        String[] layers = getGroupNames();
+        for (String layer : layers)
+        {
+            if (layer.equals(groupName))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Indicates whether an optional content group is enabled.
+     * @param groupName the group name
+     * @return true if the group is enabled
+     */
+    public boolean isGroupEnabled(String groupName)
+    {
+        //TODO handle Optional Content Configuration Dictionaries,
+        //i.e. OCProperties/Configs
+
+        COSDictionary d = getD();
+        COSArray on = (COSArray)d.getDictionaryObject(COSName.ON);
+        if (on != null)
+        {
+            for (COSBase o : on)
+            {
+                COSDictionary group = toDictionary(o);
+                String name = group.getString(COSName.NAME);
+                if (name.equals(groupName))
+                {
+                    return true;
+                }
+            }
+        }
+
+        COSArray off = (COSArray)d.getDictionaryObject(COSName.OFF);
+        if (off != null)
+        {
+            for (COSBase o : off)
+            {
+                COSDictionary group = toDictionary(o);
+                String name = group.getString(COSName.NAME);
+                if (name.equals(groupName))
+                {
+                    return false;
+                }
+            }
+        }
+
+        BaseState baseState = getBaseState();
+        boolean enabled = !baseState.equals(BaseState.OFF);
+        //TODO What to do with BaseState.Unchanged?
+        return enabled;
+    }
+
+    private COSDictionary toDictionary(COSBase o)
+    {
+        if (o instanceof COSObject)
+        {
+            return (COSDictionary)((COSObject)o).getObject();
+        }
+        else
+        {
+            return (COSDictionary)o;
+        }
+    }
+
+    /**
+     * Enables or disables an optional content group.
+     * @param groupName the group name
+     * @param enable true to enable, false to disable
+     * @return true if the group already had an on or off setting, false otherwise
+     */
+    public boolean setGroupEnabled(String groupName, boolean enable)
+    {
+        COSDictionary d = getD();
+        COSArray on = (COSArray)d.getDictionaryObject(COSName.ON);
+        if (on == null)
+        {
+            on = new COSArray();
+            d.setItem(COSName.ON, on);
+        }
+        COSArray off = (COSArray)d.getDictionaryObject(COSName.OFF);
+        if (off == null)
+        {
+            off = new COSArray();
+            d.setItem(COSName.OFF, off);
+        }
+
+        boolean found = false;
+        for (COSBase o : on)
+        {
+            COSDictionary group = toDictionary(o);
+            String name = group.getString(COSName.NAME);
+            if (!enable && name.equals(groupName))
+            {
+                //disable group
+                on.remove(group);
+                off.add(group);
+                found = true;
+                break;
+            }
+        }
+        for (COSBase o : off)
+        {
+            COSDictionary group = toDictionary(o);
+            String name = group.getString(COSName.NAME);
+            if (enable && name.equals(groupName))
+            {
+                //enable group
+                off.remove(group);
+                on.add(group);
+                found = true;
+                break;
+            }
+        }
+
+        if (!found)
+        {
+            PDOptionalContentGroup ocg = getGroup(groupName);
+            if (enable)
+            {
+                on.add(ocg.getCOSObject());
+            }
+            else
+            {
+                off.add(ocg.getCOSObject());
+            }
+        }
+        return found;
+    }
+
+
+}

Property changes on: src\main\java\org\apache\pdfbox\pdmodel\graphics\optionalcontent\PDOptionalContentProperties.java
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Index: src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java	(revision 1023660)
+++ src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java	(working copy)
@@ -16,12 +16,14 @@
  */
 package org.apache.pdfbox.pdmodel.graphics.xobject;
 
+import java.awt.geom.AffineTransform;
+
 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.COSNumber;
 import org.apache.pdfbox.cos.COSStream;
-
 import org.apache.pdfbox.pdmodel.PDResources;
 import org.apache.pdfbox.pdmodel.common.PDRectangle;
 import org.apache.pdfbox.pdmodel.common.PDStream;
@@ -145,13 +147,13 @@
             getCOSStream().setItem( COSName.BBOX, bbox.getCOSArray() );
         }
     }
-    
+
     /**
      * 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() 
+    public Matrix getMatrix()
     {
         Matrix retval = null;
         COSArray array = (COSArray)getCOSStream().getDictionaryObject( COSName.MATRIX );
@@ -167,5 +169,21 @@
         }
         return retval;
     }
-    
+
+    /**
+     * Sets the optional Matrix entry for the form XObject.
+     * @param transform the transformation matrix
+     */
+    public void setMatrix(AffineTransform transform)
+    {
+        COSArray matrix = new COSArray();
+        double[] values = new double[6];
+        transform.getMatrix(values);
+        for (double v : values)
+        {
+            matrix.add(new COSFloat((float)v));
+        }
+        getCOSStream().setItem(COSName.MATRIX, matrix);
+    }
+
 }
Index: src/main/java/org/apache/pdfbox/pdmodel/markedcontent/PDPropertyList.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/markedcontent/PDPropertyList.java	(revision 0)
+++ src/main/java/org/apache/pdfbox/pdmodel/markedcontent/PDPropertyList.java	(revision 0)
@@ -0,0 +1,93 @@
+/*
+ * 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.markedcontent;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup;
+
+/**
+ * This class represents a property list used for the marked content feature to map a resource name
+ * to a dictionary.
+ *
+ * @since PDF 1.2
+ * @version $Revision$
+ */
+public class PDPropertyList implements COSObjectable
+{
+
+    private COSDictionary props;
+
+    /**
+     * Creates a new property list.
+     */
+    public PDPropertyList()
+    {
+        this.props = new COSDictionary();
+    }
+
+    /**
+     * Creates a new instance based on a given {@link COSDictionary}.
+     * @param dict the dictionary
+     */
+    public PDPropertyList(COSDictionary dict)
+    {
+        this.props = dict;
+    }
+
+    /** {@inheritDoc} */
+    public COSBase getCOSObject()
+    {
+        return this.props;
+    }
+
+    /**
+     * Returns the optional content group belonging to the given resource name.
+     * @param name the resource name
+     * @return the optional content group or null if the group was not found
+     */
+    public PDOptionalContentGroup getOptionalContentGroup(COSName name)
+    {
+        COSDictionary dict = (COSDictionary)props.getDictionaryObject(name);
+        if (dict != null)
+        {
+            if (COSName.OCG.equals(dict.getItem(COSName.TYPE)))
+            {
+                return new PDOptionalContentGroup(dict);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Puts a mapping from a resource name to an optional content group.
+     * @param name the resource name
+     * @param ocg the optional content group
+     */
+    public void putMapping(COSName name, PDOptionalContentGroup ocg)
+    {
+        putMapping(name, (COSDictionary)ocg.getCOSObject());
+    }
+
+    private void putMapping(COSName name, COSDictionary dict)
+    {
+        props.setItem(name, dict);
+    }
+
+}

Property changes on: src\main\java\org\apache\pdfbox\pdmodel\markedcontent\PDPropertyList.java
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Index: src/main/java/org/apache/pdfbox/persistence/util/COSObjectKey.java
===================================================================
--- src/main/java/org/apache/pdfbox/persistence/util/COSObjectKey.java	(revision 1023660)
+++ src/main/java/org/apache/pdfbox/persistence/util/COSObjectKey.java	(working copy)
@@ -24,7 +24,7 @@
  * @author Michael Traut
  * @version $Revision: 1.5 $
  */
-public class COSObjectKey
+public class COSObjectKey implements Comparable<COSObjectKey>
 {
     private long number;
     private long generation;
@@ -113,4 +113,33 @@
     {
         return "" + getNumber() + " " + getGeneration() + " R";
     }
+
+    /** {@inheritDoc} */
+    public int compareTo(COSObjectKey other)
+    {
+        if (getNumber() < other.getNumber())
+        {
+            return -1;
+        }
+        else if (getNumber() > other.getNumber())
+        {
+            return 1;
+        }
+        else
+        {
+            if (getGeneration() < other.getGeneration())
+            {
+                return -1;
+            }
+            else if (getGeneration() > other.getGeneration())
+            {
+                return 1;
+            }
+            else
+            {
+                return 0;
+            }
+        }
+    }
+
 }
Index: src/main/java/org/apache/pdfbox/util/LayerUtility.java
===================================================================
--- src/main/java/org/apache/pdfbox/util/LayerUtility.java	(revision 0)
+++ src/main/java/org/apache/pdfbox/util/LayerUtility.java	(revision 0)
@@ -0,0 +1,268 @@
+/*
+ * 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.util;
+
+import java.awt.geom.AffineTransform;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fontbox.util.BoundingBox;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+import org.apache.pdfbox.pdmodel.markedcontent.PDPropertyList;
+
+/**
+ * This class allows to import pages as Form XObjects into a PDF file and use them to create
+ * layers (optional content groups).
+ *
+ * @version $Revision$
+ */
+public class LayerUtility
+{
+    private static final boolean DEBUG = true;
+
+    private PDDocument targetDoc;
+    private PDFCloneUtility cloner;
+
+    /**
+     * Creates a new instance.
+     * @param document the PDF document to modify
+     */
+    public LayerUtility(PDDocument document)
+    {
+        this.targetDoc = document;
+        this.cloner = new PDFCloneUtility(document);
+    }
+
+    /**
+     * Returns the PDF document we work on.
+     * @return the PDF document
+     */
+    public PDDocument getDocument()
+    {
+        return this.targetDoc;
+    }
+
+    /**
+     * Imports a page from some PDF file as a Form XObject so it can be placed on another page
+     * in the target document.
+     * @param sourceDoc the source PDF document that contains the page to be copied
+     * @param pageNumber the page number of the page to be copied
+     * @return a Form XObject containing the original page's content
+     * @throws IOException if an I/O error occurs
+     */
+    public PDXObjectForm importPageAsForm(PDDocument sourceDoc, int pageNumber) throws IOException
+    {
+        PDPage page = (PDPage)sourceDoc.getDocumentCatalog().getAllPages().get(pageNumber);
+        return importPageAsForm(sourceDoc, page);
+    }
+
+    private static final Set<String> PAGE_TO_FORM_FILTER = new java.util.HashSet<String>(
+            Arrays.asList(new String[] {"Group", "LastModified", "Metadata"}));
+
+    /**
+     * Imports a page from some PDF file as a Form XObject so it can be placed on another page
+     * in the target document.
+     * @param sourceDoc the source PDF document that contains the page to be copied
+     * @param page the page in the source PDF document to be copied
+     * @return a Form XObject containing the original page's content
+     * @throws IOException if an I/O error occurs
+     */
+    public PDXObjectForm importPageAsForm(PDDocument sourceDoc, PDPage page) throws IOException
+    {
+        COSStream pageStream = (COSStream)page.getContents().getCOSObject();
+        PDStream newStream = new PDStream(targetDoc,
+                pageStream.getUnfilteredStream(), false);
+        PDXObjectForm form = new PDXObjectForm(newStream);
+
+        //Copy resources
+        PDResources pageRes = page.findResources();
+        PDResources formRes = new PDResources();
+        cloner.cloneMerge(pageRes, formRes);
+        form.setResources(formRes);
+
+        //Transfer some values from page to form
+        transferDict(page.getCOSDictionary(), form.getCOSStream(), PAGE_TO_FORM_FILTER, true);
+
+        Matrix matrix = form.getMatrix();
+        AffineTransform at = matrix != null ? matrix.createAffineTransform() : new AffineTransform();
+        PDRectangle mediaBox = page.findMediaBox();
+        PDRectangle cropBox = page.findCropBox();
+        PDRectangle viewBox = (cropBox != null ? cropBox : mediaBox);
+
+        //Handle the /Rotation entry on the page dict
+        int rotation = getNormalizedRotation(page);
+
+        //Transform to FOP's user space
+        //at.scale(1 / viewBox.getWidth(), 1 / viewBox.getHeight());
+        at.translate(mediaBox.getLowerLeftX() - viewBox.getLowerLeftX(),
+                mediaBox.getLowerLeftY() - viewBox.getLowerLeftY());
+        switch (rotation)
+        {
+        case 90:
+            at.scale(viewBox.getWidth() / viewBox.getHeight(), viewBox.getHeight() / viewBox.getWidth());
+            at.translate(0, viewBox.getWidth());
+            at.rotate(-Math.PI / 2.0);
+            break;
+        case 180:
+            at.translate(viewBox.getWidth(), viewBox.getHeight());
+            at.rotate(-Math.PI);
+            break;
+        case 270:
+            at.scale(viewBox.getWidth() / viewBox.getHeight(), viewBox.getHeight() / viewBox.getWidth());
+            at.translate(viewBox.getHeight(), 0);
+            at.rotate(-Math.PI * 1.5);
+        default:
+            //no additional transformations necessary
+        }
+        //Compensate for Crop Boxes not starting at 0,0
+        at.translate(-viewBox.getLowerLeftX(), -viewBox.getLowerLeftY());
+        if (!at.isIdentity())
+        {
+            form.setMatrix(at);
+        }
+
+        BoundingBox bbox = new BoundingBox();
+        bbox.setLowerLeftX(viewBox.getLowerLeftX());
+        bbox.setLowerLeftY(viewBox.getLowerLeftY());
+        bbox.setUpperRightX(viewBox.getUpperRightX());
+        bbox.setUpperRightY(viewBox.getUpperRightY());
+        form.setBBox(new PDRectangle(bbox));
+
+        return form;
+    }
+
+    /**
+     * Places the given form over the existing content of the indicated page (like an overlay).
+     * The form is enveloped in a marked content section to indicate that it's part of an
+     * optional content group (OCG), here used as a layer. This optional group is returned and
+     * can be enabled and disabled through methods on {@link PDOptionalContentProperties}.
+     * @param targetPage the target page
+     * @param form the form to place
+     * @param transform the transformation matrix that controls the placement
+     * @param layerName the name for the layer/OCG to produce
+     * @return the optional content group that was generated for the form usage
+     * @throws IOException if an I/O error occurs
+     */
+    public PDOptionalContentGroup appendFormAsLayer(PDPage targetPage,
+            PDXObjectForm form, AffineTransform transform,
+            String layerName) throws IOException
+    {
+        PDDocumentCatalog catalog = targetDoc.getDocumentCatalog();
+        PDOptionalContentProperties ocprops = catalog.getOCProperties();
+        if (ocprops == null)
+        {
+            ocprops = new PDOptionalContentProperties();
+            catalog.setOCProperties(ocprops);
+        }
+        if (ocprops.hasGroup(layerName))
+        {
+            throw new IllegalArgumentException("Optional group (layer) already exists: " + layerName);
+        }
+
+        PDOptionalContentGroup layer = new PDOptionalContentGroup(layerName);
+        ocprops.addGroup(layer);
+
+        PDResources resources = targetPage.findResources();
+        PDPropertyList props = resources.getProperties();
+        if (props == null)
+        {
+            props = new PDPropertyList();
+            resources.setProperties(props);
+        }
+
+        //Find first free resource name with the pattern "MC<index>"
+        int index = 0;
+        PDOptionalContentGroup ocg;
+        COSName resourceName;
+        do
+        {
+            resourceName = COSName.getPDFName("MC" + index);
+            ocg = props.getOptionalContentGroup(resourceName);
+            index++;
+        } while (ocg != null);
+        //Put mapping for our new layer/OCG
+        props.putMapping(resourceName, layer);
+
+        PDPageContentStream contentStream = new PDPageContentStream(
+                targetDoc, targetPage, true, !DEBUG);
+        contentStream.beginMarkedContentSequence(COSName.OC, resourceName);
+        contentStream.drawXObject(form, transform);
+        contentStream.endMarkedContentSequence();
+        contentStream.close();
+
+        return layer;
+    }
+
+    private void transferDict(COSDictionary orgDict, COSDictionary targetDict,
+            Set<String> filter, boolean inclusive) throws IOException
+    {
+        for (Map.Entry<COSName, COSBase> entry : orgDict.entrySet())
+        {
+            COSName key = entry.getKey();
+            if (inclusive && !filter.contains(key.getName()))
+            {
+                continue;
+            }
+            else if (!inclusive && filter.contains(key.getName()))
+            {
+                continue;
+            }
+            targetDict.setItem(key,
+                    cloner.cloneForNewDocument(entry.getValue()));
+        }
+    }
+
+    private static int getNormalizedRotation(PDPage page)
+    {
+        //Handle the /Rotation entry on the page dict
+        int rotation = page.findRotation();
+        while (rotation >= 360)
+        {
+            rotation -= 360;
+        }
+        if (rotation < 0)
+        {
+            rotation = 0;
+        }
+        switch (rotation)
+        {
+        case 90:
+        case 180:
+        case 270:
+            return rotation;
+        default:
+            return 0;
+        }
+    }
+
+
+}

Property changes on: src\main\java\org\apache\pdfbox\util\LayerUtility.java
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Index: src/main/java/org/apache/pdfbox/util/PDFCloneUtility.java
===================================================================
--- src/main/java/org/apache/pdfbox/util/PDFCloneUtility.java	(revision 0)
+++ src/main/java/org/apache/pdfbox/util/PDFCloneUtility.java	(revision 0)
@@ -0,0 +1,244 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * Utility class used to clone PDF objects. It keeps track of objects it has already cloned.
+ *
+ * @version $Revision$
+ */
+public class PDFCloneUtility
+{
+
+    private PDDocument destination;
+    private Map<Object,COSBase> clonedVersion = new HashMap<Object,COSBase>();
+
+    /**
+     * Creates a new instance for the given target document.
+     * @param dest the destination PDF document that will receive the clones
+     */
+    public PDFCloneUtility(PDDocument dest)
+    {
+        this.destination = dest;
+    }
+
+    /**
+     * Returns the destination PDF document this cloner instance is set up for.
+     * @return the destination PDF document
+     */
+    public PDDocument getDestination()
+    {
+        return this.destination;
+    }
+
+    /**
+     * Deep-clones the given object for inclusion into a different PDF document identified by
+     * the destination parameter.
+     * @param base the initial object as the root of the deep-clone operation
+     * @return the cloned instance of the base object
+     * @throws IOException if an I/O error occurs
+     */
+      public COSBase cloneForNewDocument( Object base ) throws IOException
+      {
+          if( base == null )
+          {
+              return null;
+          }
+          COSBase retval = (COSBase)clonedVersion.get( base );
+          if( retval != null )
+          {
+              //we are done, it has already been converted.
+          }
+          else if( base instanceof List )
+          {
+              COSArray array = new COSArray();
+              List list = (List)base;
+              for( int i=0; i<list.size(); i++ )
+              {
+                  array.add( cloneForNewDocument( list.get( i ) ) );
+              }
+              retval = array;
+          }
+          else if( base instanceof COSObjectable && !(base instanceof COSBase) )
+          {
+              retval = cloneForNewDocument( ((COSObjectable)base).getCOSObject() );
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSObject )
+          {
+              COSObject object = (COSObject)base;
+              retval = cloneForNewDocument( object.getObject() );
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSArray )
+          {
+              COSArray newArray = new COSArray();
+              COSArray array = (COSArray)base;
+              for( int i=0; i<array.size(); i++ )
+              {
+                  newArray.add( cloneForNewDocument( array.get( i ) ) );
+              }
+              retval = newArray;
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSStream )
+          {
+              COSStream originalStream = (COSStream)base;
+              PDStream stream = new PDStream( destination, originalStream.getFilteredStream(), true );
+              clonedVersion.put( base, stream.getStream() );
+              for( Map.Entry<COSName, COSBase> entry :  originalStream.entrySet() )
+              {
+                  stream.getStream().setItem(
+                          entry.getKey(),
+                          cloneForNewDocument(entry.getValue()));
+              }
+              retval = stream.getStream();
+          }
+          else if( base instanceof COSDictionary )
+          {
+              COSDictionary dic = (COSDictionary)base;
+              retval = new COSDictionary();
+              clonedVersion.put( base, retval );
+              for( Map.Entry<COSName, COSBase> entry : dic.entrySet() )
+              {
+                  ((COSDictionary)retval).setItem(
+                          entry.getKey(),
+                          cloneForNewDocument(entry.getValue()));
+              }
+          }
+          else
+          {
+              retval = (COSBase)base;
+          }
+          clonedVersion.put( base, retval );
+          return retval;
+      }
+
+
+      /**
+       * Merges two objects of the same type by deep-cloning its members.
+       * <br/>
+       * Base and target must be instances of the same class.
+       * @param base the base object to be cloned
+       * @param target the merge target
+       * @throws IOException if an I/O error occurs
+       */
+      public void cloneMerge( COSObjectable base, COSObjectable target) throws IOException
+      {
+          if( base == null )
+          {
+              return;
+          }
+          COSBase retval = (COSBase)clonedVersion.get( base );
+          if( retval != null )
+          {
+              return;
+              //we are done, it has already been converted. // ### Is that correct for cloneMerge???
+          }
+          else if( base instanceof List )
+          {
+              COSArray array = new COSArray();
+              List list = (List)base;
+              for( int i = 0; i < list.size(); i++ )
+              {
+                  array.add( cloneForNewDocument( list.get( i ) ) );
+              }
+              ((List)target).add(array);
+          }
+          else if( base instanceof COSObjectable && !(base instanceof COSBase) )
+          {
+              cloneMerge(((COSObjectable)base).getCOSObject(), ((COSObjectable)target).getCOSObject() );
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSObject )
+          {
+              if(target instanceof COSObject)
+              {
+                  cloneMerge(((COSObject) base).getObject(),((COSObject) target).getObject() );
+              }
+              else if(target instanceof COSDictionary)
+              {
+                  cloneMerge(((COSObject)base).getObject(), ((COSDictionary)target));
+              }
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSArray )
+          {
+              COSArray array = (COSArray)base;
+              for( int i=0; i<array.size(); i++ )
+              {
+                  ((COSArray)target).add( cloneForNewDocument( array.get( i ) ) );
+              }
+              clonedVersion.put( base, retval );
+          }
+          else if( base instanceof COSStream )
+          {
+            // does that make sense???
+              COSStream originalStream = (COSStream)base;
+              PDStream stream = new PDStream( destination, originalStream.getFilteredStream(), true );
+              clonedVersion.put( base, stream.getStream() );
+              for( Map.Entry<COSName, COSBase> entry : originalStream.entrySet() )
+              {
+                  stream.getStream().setItem(
+                          entry.getKey(),
+                          cloneForNewDocument(entry.getValue()));
+              }
+              retval = stream.getStream();
+              target = retval;
+          }
+          else if( base instanceof COSDictionary )
+          {
+              COSDictionary dic = (COSDictionary)base;
+              clonedVersion.put( base, retval );
+              for( Map.Entry<COSName, COSBase> entry : dic.entrySet() )
+              {
+                  COSName key = entry.getKey();
+                  COSBase value = entry.getValue();
+                  if (((COSDictionary)target).getItem(key) != null)
+                  {
+                      cloneMerge(value, ((COSDictionary)target).getItem(key));
+                  }
+                  else
+                  {
+                      ((COSDictionary)target).setItem( key, cloneForNewDocument(value));
+                  }
+              }
+          }
+          else
+          {
+              retval = (COSBase)base;
+          }
+          clonedVersion.put( base, retval );
+
+      }
+
+}

Property changes on: src\main\java\org\apache\pdfbox\util\PDFCloneUtility.java
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Index: src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java
===================================================================
--- src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java	(revision 1023660)
+++ src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java	(working copy)
@@ -22,18 +22,14 @@
 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 java.util.Map;
 
 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.COSObject;
 import org.apache.pdfbox.cos.COSStream;
 import org.apache.pdfbox.exceptions.COSVisitorException;
 import org.apache.pdfbox.pdmodel.PDDocument;
@@ -42,7 +38,6 @@
 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.COSObjectable;
 import org.apache.pdfbox.pdmodel.common.PDStream;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
@@ -91,7 +86,7 @@
     {
         this.destinationFileName = destination;
     }
-    
+
     /**
      * Get the destination OutputStream.
      * @return Returns the destination OutputStream.
@@ -110,7 +105,7 @@
     {
         this.destinationStream = destinationStream;
     }
-    
+
     /**
      * Add a source file to the list of files to merge.
      *
@@ -118,11 +113,11 @@
      */
     public void addSource(String source)
     {
-        try 
+        try
         {
             sources.add(new FileInputStream(new File(source)));
-        } 
-        catch(Exception e) 
+        }
+        catch(Exception e)
         {
             throw new RuntimeException(e);
         }
@@ -135,7 +130,7 @@
      */
     public void addSource(File source)
     {
-        try 
+        try
         {
             sources.add(new FileInputStream(source));
         }
@@ -144,7 +139,7 @@
             throw new RuntimeException(e);
         }
     }
-    
+
     /**
      * Add a source to the list of documents to merge.
      *
@@ -154,7 +149,7 @@
     {
         sources.add(source);
     }
-    
+
     /**
      * Add a list of sources to the list of documents to merge.
      *
@@ -178,8 +173,8 @@
         PDDocument source;
         if (sources != null && sources.size() > 0)
         {
-        	java.util.Vector<PDDocument> tobeclosed = new java.util.Vector<PDDocument>();
-        	
+            java.util.Vector<PDDocument> tobeclosed = new java.util.Vector<PDDocument>();
+
             try
             {
                 Iterator<InputStream> sit = sources.iterator();
@@ -194,9 +189,13 @@
                     appendDocument(destination, source);
                 }
                 if(destinationStream == null)
+                {
                     destination.save(destinationFileName);
+                }
                 else
+                {
                     destination.save(destinationStream);
+                }
             }
             finally
             {
@@ -204,9 +203,10 @@
                 {
                     destination.close();
                 }
-            	for(PDDocument doc : tobeclosed){
-            		doc.close();
-            	} 
+                for (PDDocument doc : tobeclosed)
+                {
+                    doc.close();
+                }
             }
         }
     }
@@ -242,20 +242,22 @@
             destCatalog.setOpenAction( srcCatalog.getOpenAction() );
         }
 
+        PDFCloneUtility cloner = new PDFCloneUtility(destination);
+
         try
         {
             PDAcroForm destAcroForm = destCatalog.getAcroForm();
             PDAcroForm srcAcroForm = srcCatalog.getAcroForm();
             if( destAcroForm == null )
             {
-                cloneForNewDocument( destination, srcAcroForm );
+                cloner.cloneForNewDocument( srcAcroForm );
                 destCatalog.setAcroForm( srcAcroForm );
             }
             else
             {
                 if( srcAcroForm != null )
                 {
-                    mergeAcroForm(destination, destAcroForm, srcAcroForm);
+                    mergeAcroForm(cloner, destAcroForm, srcAcroForm);
                 }
             }
         }
@@ -270,8 +272,7 @@
 
         COSArray destThreads = (COSArray)destCatalog.getCOSDictionary().getDictionaryObject(
                 COSName.THREADS);
-        COSArray srcThreads = (COSArray)cloneForNewDocument(
-                destination,
+        COSArray srcThreads = (COSArray)cloner.cloneForNewDocument(
                 destCatalog.getCOSDictionary().getDictionaryObject( COSName.THREADS ));
         if( destThreads == null )
         {
@@ -288,13 +289,14 @@
         {
             if( destNames == null )
             {
-                destCatalog.getCOSDictionary().setItem( COSName.NAMES, cloneForNewDocument( destination, srcNames ) );
+                destCatalog.getCOSDictionary().setItem( COSName.NAMES,
+                        cloner.cloneForNewDocument( srcNames ) );
             }
             else
             {
-                cloneMerge(destination, srcNames, destNames);
-            }   
-                
+                cloner.cloneMerge(srcNames, destNames);
+            }
+
         }
 
         PDDocumentOutline destOutline = destCatalog.getDocumentOutline();
@@ -304,7 +306,7 @@
             if( destOutline == null )
             {
                 PDDocumentOutline cloned =
-                    new PDDocumentOutline( (COSDictionary)cloneForNewDocument( destination, srcOutline ) );
+                    new PDDocumentOutline( (COSDictionary)cloner.cloneForNewDocument( srcOutline ) );
                 destCatalog.setDocumentOutline( cloned );
             }
             else
@@ -312,8 +314,8 @@
                 PDOutlineItem first = srcOutline.getFirstChild();
                 if(first != null)
                 {
-                    PDOutlineItem clonedFirst = new PDOutlineItem( (COSDictionary)cloneForNewDocument(
-                            destination, first ));
+                    PDOutlineItem clonedFirst = new PDOutlineItem(
+                            (COSDictionary)cloner.cloneForNewDocument( first ));
                     destOutline.appendChild( clonedFirst );
                 }
             }
@@ -326,8 +328,10 @@
             destCatalog.setPageMode( srcPageMode );
         }
 
-        COSDictionary destLabels = (COSDictionary)destCatalog.getCOSDictionary().getDictionaryObject( COSName.PAGE_LABELS );
-        COSDictionary srcLabels = (COSDictionary)srcCatalog.getCOSDictionary().getDictionaryObject( COSName.PAGE_LABELS );
+        COSDictionary destLabels = (COSDictionary)destCatalog.getCOSDictionary().getDictionaryObject(
+                COSName.PAGE_LABELS);
+        COSDictionary srcLabels = (COSDictionary)srcCatalog.getCOSDictionary().getDictionaryObject(
+                COSName.PAGE_LABELS);
         if( srcLabels != null )
         {
             int destPageCount = destination.getNumberOfPages();
@@ -351,7 +355,7 @@
                     COSNumber labelIndex = (COSNumber)srcNums.getObject( i );
                     long labelIndexValue = labelIndex.intValue();
                     destNums.add( COSInteger.get( labelIndexValue + destPageCount ) );
-                    destNums.add( cloneForNewDocument( destination, srcNums.getObject( i+1 ) ) );
+                    destNums.add( cloner.cloneForNewDocument( srcNums.getObject( i+1 ) ) );
                 }
             }
         }
@@ -373,7 +377,7 @@
         {
             PDPage page = pageIter.next();
             PDPage newPage =
-                new PDPage( (COSDictionary)cloneForNewDocument( destination, page.getCOSDictionary() ) );
+                new PDPage( (COSDictionary)cloner.cloneForNewDocument( page.getCOSDictionary() ) );
             newPage.setCropBox( page.findCropBox() );
             newPage.setMediaBox( page.findMediaBox() );
             newPage.setRotation( page.findRotation() );
@@ -381,204 +385,19 @@
         }
     }
 
-    Map<Object,COSBase> clonedVersion = new HashMap<Object,COSBase>();
 
-
-  /**
-   * 
-   * @param destination
-   * @param base
-   * @return
-   * @throws IOException
-   */
-    private COSBase cloneForNewDocument( PDDocument destination, Object base ) throws IOException
-    {
-        if( base == null )
-        {
-            return null;
-        }
-        COSBase retval = (COSBase)clonedVersion.get( base );
-        if( retval != null )
-        {
-            //we are done, it has already been converted.
-        }
-        else if( base instanceof List )
-        {
-            COSArray array = new COSArray();
-            List list = (List)base;
-            for( int i=0; i<list.size(); i++ )
-            {
-                array.add( cloneForNewDocument( destination, list.get( i ) ) );
-            }
-            retval = array;
-        }
-        else if( base instanceof COSObjectable && !(base instanceof COSBase) )
-        {
-            retval = cloneForNewDocument( destination, ((COSObjectable)base).getCOSObject() );
-            clonedVersion.put( base, retval );
-        }
-        else if( base instanceof COSObject )
-        {
-            COSObject object = (COSObject)base;
-            retval = cloneForNewDocument( destination, object.getObject() );
-            clonedVersion.put( base, retval );
-        }
-        else if( base instanceof COSArray )
-        {
-            COSArray newArray = new COSArray();
-            COSArray array = (COSArray)base;
-            for( int i=0; i<array.size(); i++ )
-            {
-                newArray.add( cloneForNewDocument( destination, array.get( i ) ) );
-            }
-            retval = newArray;
-            clonedVersion.put( base, retval );
-        }
-        else if( base instanceof COSStream )
-        {
-            COSStream originalStream = (COSStream)base;
-            PDStream stream = new PDStream( destination, originalStream.getFilteredStream(), true );
-            clonedVersion.put( base, stream.getStream() );
-            for( Map.Entry<COSName, COSBase> entry :  originalStream.entrySet() )
-            {
-                stream.getStream().setItem(
-                        entry.getKey(),
-                        cloneForNewDocument(destination, entry.getValue()));
-            }
-            retval = stream.getStream();
-        }
-        else if( base instanceof COSDictionary )
-        {
-            COSDictionary dic = (COSDictionary)base;
-            retval = new COSDictionary();
-            clonedVersion.put( base, retval );
-            for( Map.Entry<COSName, COSBase> entry : dic.entrySet() )
-            {
-                ((COSDictionary)retval).setItem(
-                        entry.getKey(),
-                        cloneForNewDocument(destination, entry.getValue()));
-            }
-        }
-        else
-        {
-            retval = (COSBase)base;
-        }
-        clonedVersion.put( base, retval );
-        return retval;
-    }
-
-
-    /**
-     * Deep clone and Merge from Base to Target.<br/>
-     * base and target must be instances of the same class.
-     * @param destination
-     * @param base
-     * @param target
-     * @throws IOException
-     */
-    private void cloneMerge( PDDocument destination, COSObjectable base, COSObjectable target) throws IOException
-    {
-        if( base == null )
-        {
-            return;
-        }
-        COSBase retval = (COSBase)clonedVersion.get( base );
-        if( retval != null )
-        {
-          return;
-          //we are done, it has already been converted. // ### Is that correct for cloneMerge???
-        }
-        else if( base instanceof List )
-        {
-            COSArray array = new COSArray();
-            List list = (List)base;
-            for( int i=0; i<list.size(); i++ )
-            {
-                array.add( cloneForNewDocument( destination, list.get( i ) ) );
-            }
-            ((List)target).add(array);
-        }
-        else if( base instanceof COSObjectable && !(base instanceof COSBase) )
-        {
-            cloneMerge(destination, ((COSObjectable)base).getCOSObject(), ((COSObjectable)target).getCOSObject() );
-            clonedVersion.put( base, retval );
-        }
-        else if( base instanceof COSObject )
-        {
-            if(target instanceof COSObject)
-            {
-                cloneMerge(destination, ((COSObject) base).getObject(),((COSObject) target).getObject() );
-            }
-            else if(target instanceof COSDictionary)
-            {
-                cloneMerge(destination, ((COSObject)base).getObject(), ((COSDictionary)target));
-            }
-            clonedVersion.put( base, retval );
-        }
-        else if( base instanceof COSArray )
-        {
-            COSArray array = (COSArray)base;
-            for( int i=0; i<array.size(); i++ )
-            {
-              ((COSArray)target).add( cloneForNewDocument( destination, array.get( i ) ) );
-            }
-            clonedVersion.put( base, retval );
-        }
-        else if( base instanceof COSStream )
-        {
-          // does that make sense???
-            COSStream originalStream = (COSStream)base;
-            PDStream stream = new PDStream( destination, originalStream.getFilteredStream(), true );
-            clonedVersion.put( base, stream.getStream() );
-            for( Map.Entry<COSName, COSBase> entry : originalStream.entrySet() )
-            {
-                stream.getStream().setItem(
-                        entry.getKey(),
-                        cloneForNewDocument(destination, entry.getValue()));
-            }
-            retval = stream.getStream(); 
-            target = retval;
-        }
-        else if( base instanceof COSDictionary )
-        {
-            COSDictionary dic = (COSDictionary)base;
-            clonedVersion.put( base, retval );
-            for( Map.Entry<COSName, COSBase> entry : dic.entrySet() )
-            {
-                COSName key = entry.getKey();
-                COSBase value = entry.getValue();
-                if (((COSDictionary)target).getItem(key)!=null)
-                {
-                   cloneMerge(destination, value,((COSDictionary)target).getItem(key));
-                } 
-                else 
-                {
-                  ((COSDictionary)target).setItem( key, cloneForNewDocument(destination, value));
-                }
-            }
-        }
-        else
-        {
-            retval = (COSBase)base;
-        }
-        clonedVersion.put( base, retval );
-        
-    }
-
-    
-    
     private int nextFieldNum = 1;
 
     /**
      * Merge the contents of the source form into the destination form
      * for the destination file.
      *
-     * @param destination the destination document
+     * @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(PDDocument destination, PDAcroForm destAcroForm, PDAcroForm srcAcroForm)
+    private void mergeAcroForm(PDFCloneUtility cloner, PDAcroForm destAcroForm, PDAcroForm srcAcroForm)
         throws IOException
     {
         List destFields = destAcroForm.getFields();
@@ -597,7 +416,7 @@
                 PDField destField =
                     PDFieldFactory.createField(
                         destAcroForm,
-                        (COSDictionary)cloneForNewDocument(destination, srcField.getDictionary() ));
+                        (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 )
@@ -618,6 +437,6 @@
     {
         this.ignoreAcroFormErrors = ignoreAcroFormErrors;
     }
-    
 
+
 }
Index: src/test/java/org/apache/pdfbox/pdmodel/graphics/optionalcontent/TestOptionalContentGroups.java
===================================================================
--- src/test/java/org/apache/pdfbox/pdmodel/graphics/optionalcontent/TestOptionalContentGroups.java	(revision 0)
+++ src/test/java/org/apache/pdfbox/pdmodel/graphics/optionalcontent/TestOptionalContentGroups.java	(revision 0)
@@ -0,0 +1,236 @@
+/*
+ * 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.graphics.optionalcontent;
+
+import java.awt.Color;
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties.BaseState;
+import org.apache.pdfbox.pdmodel.markedcontent.PDPropertyList;
+
+/**
+ * Tests optional content group functionality (also called layers).
+ *
+ * @version $Revision$
+ */
+public class TestOptionalContentGroups extends TestCase
+{
+
+    private File testResultsDir = new File("target/test-output");
+
+    /** {@inheritDoc} */
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        testResultsDir.mkdirs();
+    }
+
+    /**
+     * Tests OCG generation.
+     * @throws Exception if an error occurs
+     */
+    public void testOCGGeneration() throws Exception
+    {
+        PDDocument doc = new PDDocument();
+        try
+        {
+            //OCGs have been introduced with PDF 1.5
+            doc.getDocument().setHeaderString("%PDF-1.5");
+            PDDocumentCatalog catalog = doc.getDocumentCatalog();
+            catalog.setVersion("1.5");
+
+            //Create new page
+            PDPage page = new PDPage();
+            doc.addPage(page);
+            PDResources resources = page.findResources();
+            if( resources == null )
+            {
+                resources = new PDResources();
+                page.setResources( resources );
+            }
+
+            //Prepare OCG functionality
+            PDOptionalContentProperties ocprops = new PDOptionalContentProperties();
+            catalog.setOCProperties(ocprops);
+            //ocprops.setBaseState(BaseState.ON); //ON=default
+
+            //Create OCG for background
+            PDOptionalContentGroup background = new PDOptionalContentGroup("background");
+            ocprops.addGroup(background);
+            assertTrue(ocprops.isGroupEnabled("background"));
+
+            //Create OCG for enabled
+            PDOptionalContentGroup enabled = new PDOptionalContentGroup("enabled");
+            ocprops.addGroup(enabled);
+            assertFalse(ocprops.setGroupEnabled("enabled", true));
+            assertTrue(ocprops.isGroupEnabled("enabled"));
+
+            //Create OCG for disabled
+            PDOptionalContentGroup disabled = new PDOptionalContentGroup("disabled");
+            ocprops.addGroup(disabled);
+            assertFalse(ocprops.setGroupEnabled("disabled", true));
+            assertTrue(ocprops.isGroupEnabled("disabled"));
+            assertTrue(ocprops.setGroupEnabled("disabled", false));
+            assertFalse(ocprops.isGroupEnabled("disabled"));
+
+
+            //Add mapping to page
+            PDPropertyList props = new PDPropertyList();
+            resources.setProperties(props);
+            COSName mc0 = COSName.getPDFName("MC0");
+            props.putMapping(mc0, background);
+            COSName mc1 = COSName.getPDFName("MC1");
+            props.putMapping(mc1, enabled);
+            COSName mc2 = COSName.getPDFName("MC2");
+            props.putMapping(mc2, disabled);
+
+            //Setup page content stream and paint background/title
+            PDPageContentStream contentStream = new PDPageContentStream(doc, page, false, false);
+            PDFont font = PDType1Font.HELVETICA_BOLD;
+            contentStream.beginMarkedContentSequence(COSName.OC, mc0);
+            contentStream.beginText();
+            contentStream.setFont(font, 14);
+            contentStream.moveTextPositionByAmount(80, 700);
+            contentStream.drawString("PDF 1.5: Optional Content Groups");
+            contentStream.endText();
+            font = PDType1Font.HELVETICA;
+            contentStream.beginText();
+            contentStream.setFont(font, 12);
+            contentStream.moveTextPositionByAmount(80, 680);
+            contentStream.drawString("You should see a green textline, but no red text line.");
+            contentStream.endText();
+            contentStream.endMarkedContentSequence();
+
+            //Paint enabled layer
+            contentStream.beginMarkedContentSequence(COSName.OC, mc1);
+            contentStream.setNonStrokingColor(Color.GREEN);
+            contentStream.beginText();
+            contentStream.setFont(font, 12);
+            contentStream.moveTextPositionByAmount(80, 600);
+            contentStream.drawString(
+                    "This is from an enabled layer. If you see this, that's good.");
+            contentStream.endText();
+            contentStream.endMarkedContentSequence();
+
+            //Paint disabled layer
+            contentStream.beginMarkedContentSequence(COSName.OC, mc2);
+            contentStream.setNonStrokingColor(Color.RED);
+            contentStream.beginText();
+            contentStream.setFont(font, 12);
+            contentStream.moveTextPositionByAmount(80, 500);
+            contentStream.drawString(
+                    "This is from a disabled layer. If you see this, that's NOT good!");
+            contentStream.endText();
+            contentStream.endMarkedContentSequence();
+
+            contentStream.close();
+
+            PDPageContentStream c1 = new PDPageContentStream(doc, page, true, false);
+            c1.beginText();
+            c1.setFont(font, 14);
+            c1.moveTextPositionByAmount(80, 300);
+            c1.drawString("Bla1");
+            c1.endText();
+            c1.close();
+
+            c1 = new PDPageContentStream(doc, page, true, false);
+            c1.beginText();
+            c1.setFont(font, 14);
+            c1.moveTextPositionByAmount(80, 350);
+            c1.drawString("Bla2");
+            c1.endText();
+            c1.close();
+
+            File targetFile = new File(testResultsDir, "ocg-generation.pdf");
+            doc.save(targetFile.getAbsolutePath());
+        }
+        finally
+        {
+            doc.close();
+        }
+    }
+
+    /**
+     * Tests OCG functions on a loaded PDF.
+     * @throws Exception if an error occurs
+     */
+    public void testOCGConsumption() throws Exception
+    {
+        File pdfFile = new File(testResultsDir, "ocg-generation.pdf");
+        if (!pdfFile.exists())
+        {
+            testOCGGeneration();
+        }
+
+        PDDocument doc = PDDocument.load(pdfFile);
+        try
+        {
+            assertEquals("%PDF-1.5", doc.getDocument().getHeaderString());
+            PDDocumentCatalog catalog = doc.getDocumentCatalog();
+            assertEquals("1.5", catalog.getVersion());
+
+            PDPage page = (PDPage)catalog.getAllPages().get(0);
+            PDPropertyList props = page.findResources().getProperties();
+            assertNotNull(props);
+            PDOptionalContentGroup ocg = props.getOptionalContentGroup(COSName.getPDFName("MC0"));
+            assertNotNull(ocg);
+            assertEquals("background", ocg.getName());
+
+            assertNull(props.getOptionalContentGroup(COSName.getPDFName("inexistent")));
+
+            PDOptionalContentProperties ocgs = catalog.getOCProperties();
+            assertEquals(BaseState.ON, ocgs.getBaseState());
+            Set<String> names = new java.util.HashSet<String>(Arrays.asList(ocgs.getGroupNames()));
+            assertEquals(3, names.size());
+            assertTrue(names.contains("background"));
+
+            assertTrue(ocgs.isGroupEnabled("background"));
+            assertTrue(ocgs.isGroupEnabled("enabled"));
+            assertFalse(ocgs.isGroupEnabled("disabled"));
+
+            ocgs.setGroupEnabled("background", false);
+            assertFalse(ocgs.isGroupEnabled("background"));
+
+            PDOptionalContentGroup background = ocgs.getGroup("background");
+            assertEquals(ocg.getName(), background.getName());
+            assertNull(ocgs.getGroup("inexistent"));
+
+            Collection<PDOptionalContentGroup> coll = ocgs.getOptionalContentGroups();
+            coll.contains(background);
+
+        }
+        finally
+        {
+            doc.close();
+        }
+    }
+
+}

Property changes on: src\test\java\org\apache\pdfbox\pdmodel\graphics\optionalcontent\TestOptionalContentGroups.java
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Index: src/test/java/org/apache/pdfbox/util/TestLayerUtility.java
===================================================================
--- src/test/java/org/apache/pdfbox/util/TestLayerUtility.java	(revision 0)
+++ src/test/java/org/apache/pdfbox/util/TestLayerUtility.java	(revision 0)
@@ -0,0 +1,215 @@
+/*
+ * 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.util;
+
+import java.awt.Color;
+import java.awt.geom.AffineTransform;
+import java.io.File;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup;
+import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+import org.apache.pdfbox.pdmodel.markedcontent.PDPropertyList;
+
+/**
+ * Tests the {@link LayerUtility} class.
+ *
+ * @version $Revision$
+ */
+public class TestLayerUtility extends TestCase
+{
+
+    private File testResultsDir = new File("target/test-output");
+
+    /** {@inheritDoc} */
+    @Override
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        testResultsDir.mkdirs();
+    }
+
+    /**
+     * Tests layer import.
+     * @throws Exception if an error occurs
+     */
+    public void testLayerImport() throws Exception
+    {
+        File mainPDF = createMainPDF();
+        File overlay1 = createOverlay1();
+        File targetFile = new File(testResultsDir, "text-with-form-overlay.pdf");
+
+        PDDocument targetDoc = PDDocument.load(mainPDF);
+        PDDocument overlay1Doc = PDDocument.load(overlay1);
+        try
+        {
+            LayerUtility layerUtil = new LayerUtility(targetDoc);
+            PDXObjectForm form = layerUtil.importPageAsForm(overlay1Doc, 0);
+            PDDocumentCatalog catalog = targetDoc.getDocumentCatalog();
+            PDPage targetPage = (PDPage)catalog.getAllPages().get(0);
+            AffineTransform at = new AffineTransform();
+            PDOptionalContentGroup ocg = layerUtil.appendFormAsLayer(
+                    targetPage, form, at, "overlay");
+
+            //This is how the layer could be disabled after adding it
+            //catalog.getOCProperties().setGroupEnabled(ocg.getName(), false);
+
+            targetDoc.save(targetFile.getAbsolutePath());
+        }
+        finally
+        {
+            targetDoc.close();
+            overlay1Doc.close();
+        }
+
+        PDDocument doc = PDDocument.load(targetFile);
+        PDDocumentCatalog catalog = doc.getDocumentCatalog();
+
+        //OCGs require PDF 1.5 or later
+        //TODO need some comfortable way to enable/check the PDF version
+        //assertEquals("%PDF-1.5", doc.getDocument().getHeaderString());
+        //assertEquals("1.5", catalog.getVersion());
+
+        PDPage page = (PDPage)catalog.getAllPages().get(0);
+        PDPropertyList props = page.findResources().getProperties();
+        assertNotNull(props);
+        PDOptionalContentGroup ocg = props.getOptionalContentGroup(COSName.getPDFName("MC0"));
+        assertNotNull(ocg);
+        assertEquals("overlay", ocg.getName());
+
+        PDOptionalContentProperties ocgs = catalog.getOCProperties();
+        PDOptionalContentGroup overlay = ocgs.getGroup("overlay");
+        assertEquals(ocg.getName(), overlay.getName());
+
+    }
+
+    private File createMainPDF() throws IOException, COSVisitorException
+    {
+        File targetFile = new File(testResultsDir, "text-doc.pdf");
+        PDDocument doc = new PDDocument();
+        try
+        {
+            //Create new page
+            PDPage page = new PDPage();
+            doc.addPage(page);
+            PDResources resources = page.findResources();
+            if( resources == null )
+            {
+                resources = new PDResources();
+                page.setResources( resources );
+            }
+
+            final String[] text = new String[] {
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer fermentum lacus in eros",
+                    "condimentum eget tristique risus viverra. Sed ac sem et lectus ultrices placerat. Nam",
+                    "fringilla tincidunt nulla id euismod. Vivamus eget mauris dui. Mauris luctus ullamcorper",
+                    "leo, et laoreet diam suscipit et. Nulla viverra commodo sagittis. Integer vitae rhoncus velit.",
+                    "Mauris porttitor ipsum in est sagittis non luctus purus molestie. Sed placerat aliquet",
+                    "vulputate."
+            };
+
+            //Setup page content stream and paint background/title
+            PDPageContentStream contentStream = new PDPageContentStream(doc, page, false, false);
+            PDFont font = PDType1Font.HELVETICA_BOLD;
+            contentStream.beginText();
+            contentStream.moveTextPositionByAmount(50, 720);
+            contentStream.setFont(font, 14);
+            contentStream.drawString("Simple test document with text.");
+            contentStream.endText();
+            font = PDType1Font.HELVETICA;
+            contentStream.beginText();
+            int fontSize = 12;
+            contentStream.setFont(font, fontSize);
+            contentStream.moveTextPositionByAmount(50, 700);
+            for (String line : text)
+            {
+                contentStream.moveTextPositionByAmount(0, -fontSize * 1.2f);
+                contentStream.drawString(line);
+            }
+            contentStream.endText();
+            contentStream.close();
+
+            doc.save(targetFile.getAbsolutePath());
+        }
+        finally
+        {
+            doc.close();
+        }
+        return targetFile;
+    }
+
+    private File createOverlay1() throws IOException, COSVisitorException
+    {
+        File targetFile = new File(testResultsDir, "overlay1.pdf");
+        PDDocument doc = new PDDocument();
+        try
+        {
+            //Create new page
+            PDPage page = new PDPage();
+            doc.addPage(page);
+            PDResources resources = page.findResources();
+            if( resources == null )
+            {
+                resources = new PDResources();
+                page.setResources( resources );
+            }
+
+            //Setup page content stream and paint background/title
+            PDPageContentStream contentStream = new PDPageContentStream(doc, page, false, false);
+            PDFont font = PDType1Font.HELVETICA_BOLD;
+            contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
+            contentStream.beginText();
+            float fontSize = 96;
+            contentStream.setFont(font, fontSize);
+            String text = "OVERLAY";
+            //float sw = font.getStringWidth(text);
+            //Too bad, base 14 fonts don't return character metrics.
+            PDRectangle crop = page.findCropBox();
+            float cx = crop.getWidth() / 2f;
+            float cy = crop.getHeight() / 2f;
+            AffineTransform transform = new AffineTransform();
+            transform.translate(cx, cy);
+            transform.rotate(Math.toRadians(45));
+            transform.translate(-190 /* sw/2 */, 0);
+            contentStream.setTextMatrix(transform);
+            contentStream.drawString(text);
+            contentStream.endText();
+            contentStream.close();
+
+            doc.save(targetFile.getAbsolutePath());
+        }
+        finally
+        {
+            doc.close();
+        }
+        return targetFile;
+    }
+
+}

Property changes on: src\test\java\org\apache\pdfbox\util\TestLayerUtility.java
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

