aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/pdfbox/pdmodel/edit/PDPageContentStream.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/pdfbox/pdmodel/edit/PDPageContentStream.java')
-rw-r--r--src/main/java/org/pdfbox/pdmodel/edit/PDPageContentStream.java648
1 files changed, 648 insertions, 0 deletions
diff --git a/src/main/java/org/pdfbox/pdmodel/edit/PDPageContentStream.java b/src/main/java/org/pdfbox/pdmodel/edit/PDPageContentStream.java
new file mode 100644
index 0000000..1e3b25f
--- /dev/null
+++ b/src/main/java/org/pdfbox/pdmodel/edit/PDPageContentStream.java
@@ -0,0 +1,648 @@
+/**
+ * Copyright (c) 2004, www.pdfbox.org
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of pdfbox; nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://www.pdfbox.org
+ *
+ */
+package org.pdfbox.pdmodel.edit;
+
+import java.awt.Color;
+import java.awt.color.ColorSpace;
+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.pdfbox.pdmodel.PDDocument;
+import org.pdfbox.pdmodel.PDPage;
+import org.pdfbox.pdmodel.PDResources;
+
+import org.pdfbox.pdmodel.common.COSStreamArray;
+import org.pdfbox.pdmodel.common.PDStream;
+
+import org.pdfbox.pdmodel.font.PDFont;
+import org.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+
+import org.pdfbox.cos.COSArray;
+import org.pdfbox.cos.COSName;
+import org.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.
+ *
+ * @author Ben Litchfield (ben@csh.rit.edu)
+ * @version $Revision: 1.14 $
+ */
+public class PDPageContentStream
+{
+ private PDPage page;
+ private OutputStream output;
+ private boolean inTextMode = false;
+ private Map fontMappings = new HashMap();
+ private Map imageMappings = new HashMap();
+ private int fontNumber = 1;
+ private int imageNumber = 1;
+ private PDResources resources;
+
+ //cached storage component for getting color values
+ private float[] colorComponents = new float[4];
+
+ private NumberFormat formatDecimal = NumberFormat.getNumberInstance( Locale.US );
+
+ private static final String BEGIN_TEXT = "BT\n";
+ private static final String END_TEXT = "ET\n";
+ private static final String SET_FONT = "Tf\n";
+ private static final String MOVE_TEXT_POSITION = "Td\n";
+ private static final String SHOW_TEXT = "Tj\n";
+
+ private static final String SAVE_GRAPHICS_STATE = "q\n";
+ private static final String RESTORE_GRAPHICS_STATE = "Q\n";
+ private static final String CONCATENATE_MATRIX = "cm\n";
+ private static final String XOBJECT_DO = "Do\n";
+ private static final String RG_STROKING = "RG\n";
+ private static final String RG_NON_STROKING = "rg\n";
+ private static final String K_STROKING = "K\n";
+ private static final String K_NON_STROKING = "k\n";
+ private static final String G_STROKING = "G\n";
+ private static final String G_NON_STROKING = "g\n";
+ private static final String APPEND_RECTANGLE = "re\n";
+ private static final String FILL = "f\n";
+
+
+ private static final int SPACE = 32;
+
+
+ /**
+ * Create a new PDPage content stream.
+ *
+ * @param document The document the page is part of.
+ * @param sourcePage The page to write the contents to.
+ * @throws IOException If there is an error writing to the page contents.
+ */
+ public PDPageContentStream( PDDocument document, PDPage sourcePage ) throws IOException
+ {
+ this(document,sourcePage,false,true);
+ }
+
+ /**
+ * Create a new PDPage content stream.
+ *
+ * @param document The document the page is part of.
+ * @param sourcePage The page to write the contents to.
+ * @param appendContent Indicates whether content will be overwritten. If false all previous content is deleted.
+ * @param compress Tell if the content stream should compress the page contents.
+ * @throws IOException If there is an error writing to the page contents.
+ */
+ public PDPageContentStream( PDDocument document, PDPage sourcePage, boolean appendContent, boolean compress )
+ throws IOException
+ {
+ page = sourcePage;
+ resources = page.getResources();
+ if( resources == null )
+ {
+ resources = new PDResources();
+ page.setResources( resources );
+ }
+ // If request specifies the need to append to the document
+ if(appendContent)
+ {
+ // Get the pdstream from the source page instead of creating a new one
+ PDStream contents = sourcePage.getContents();
+
+ // Create a pdstream to append new content
+ PDStream contentsToAppend = new PDStream( document );
+
+ // This will be the resulting COSStreamArray after existing and new streams are merged
+ COSStreamArray compoundStream = null;
+
+ // If contents is already an array, a new stream is simply appended to it
+ if(contents.getStream() instanceof COSStreamArray)
+ {
+ compoundStream = (COSStreamArray)contents.getStream();
+ compoundStream.appendStream( contentsToAppend.getStream());
+ }
+ else
+ {
+ // Creates the COSStreamArray and adds the current stream plus a new one to it
+ COSArray newArray = new COSArray();
+ newArray.add(contents.getCOSObject());
+ newArray.add(contentsToAppend.getCOSObject());
+ compoundStream = new COSStreamArray(newArray);
+ }
+
+ if( compress )
+ {
+ List filters = new ArrayList();
+ filters.add( COSName.FLATE_DECODE );
+ contentsToAppend.setFilters( filters );
+ }
+
+ // Sets the compoundStream as page contents
+ sourcePage.setContents( new PDStream(compoundStream) );
+ output = contentsToAppend.createOutputStream();
+ }
+ else
+ {
+ PDStream contents = new PDStream( document );
+ if( compress )
+ {
+ List filters = new ArrayList();
+ filters.add( COSName.FLATE_DECODE );
+ contents.setFilters( filters );
+ }
+ sourcePage.setContents( contents );
+ output = contents.createOutputStream();
+ }
+ formatDecimal.setMaximumFractionDigits( 10 );
+ formatDecimal.setGroupingUsed( false );
+ }
+
+ /**
+ * Begin some text operations.
+ *
+ * @throws IOException If there is an error writing to the stream or if you attempt to
+ * nest beginText calls.
+ */
+ public void beginText() throws IOException
+ {
+ if( inTextMode )
+ {
+ throw new IOException( "Error: Nested beginText() calls are not allowed." );
+ }
+ appendRawCommands( BEGIN_TEXT );
+ inTextMode = true;
+ }
+
+ /**
+ * End some text operations.
+ *
+ * @throws IOException If there is an error writing to the stream or if you attempt to
+ * nest endText calls.
+ */
+ public void endText() throws IOException
+ {
+ if( !inTextMode )
+ {
+ throw new IOException( "Error: You must call beginText() before calling endText." );
+ }
+ appendRawCommands( END_TEXT );
+ inTextMode = false;
+ }
+
+ /**
+ * Set the font to draw text with.
+ *
+ * @param font The font to use.
+ * @param fontSize The font size to draw the text.
+ * @throws IOException If there is an error writing the font information.
+ */
+ public void setFont( PDFont font, float fontSize ) throws IOException
+ {
+ String fontMapping = (String)fontMappings.get( font );
+ if( fontMapping == null )
+ {
+ fontMapping = "F" + fontNumber++;
+ fontMappings.put( font, fontMapping );
+ resources.getFonts().put( fontMapping, font );
+ }
+ appendRawCommands( "/");
+ appendRawCommands( fontMapping );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( fontSize ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( SET_FONT );
+ }
+
+ /**
+ * Draw an image at the x,y coordinates, with the default size of the image.
+ *
+ * @param image The image to draw.
+ * @param x The x-coordinate to draw the image.
+ * @param y The y-coordinate to draw the image.
+ *
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void drawImage( PDXObjectImage image, int x, int y ) throws IOException
+ {
+ drawImage( image, x, y, image.getWidth(), image.getHeight() );
+ }
+
+ /**
+ * Draw an image at the x,y coordinates and a certain width and height.
+ *
+ * @param image The image to draw.
+ * @param x The x-coordinate to draw the image.
+ * @param y The y-coordinate to draw the image.
+ * @param width The width of the image to draw.
+ * @param height The height of the image to draw.
+ *
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void drawImage( PDXObjectImage image, int x, int y, int width, int height ) throws IOException
+ {
+ String imageMapping = (String)imageMappings.get( image );
+ if( imageMapping == null )
+ {
+ imageMapping = "Im" + imageNumber++;
+ imageMappings.put( image, imageMapping );
+ resources.getImages().put( imageMapping, image );
+ }
+ appendRawCommands( SAVE_GRAPHICS_STATE );
+ appendRawCommands( formatDecimal.format( width ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( 0 ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( 0 ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( height ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( x ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( CONCATENATE_MATRIX );
+ appendRawCommands( SPACE );
+ appendRawCommands( "/" );
+ appendRawCommands( imageMapping );
+ appendRawCommands( SPACE );
+ appendRawCommands( XOBJECT_DO );
+ appendRawCommands( SPACE );
+ appendRawCommands( RESTORE_GRAPHICS_STATE );
+ }
+
+ /**
+ * The Td operator.
+ * @param x The x coordinate.
+ * @param y The y coordinate.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void moveTextPositionByAmount( float x, float y ) throws IOException
+ {
+ if( !inTextMode )
+ {
+ throw new IOException( "Error: must call beginText() before moveTextPositionByAmount");
+ }
+ appendRawCommands( formatDecimal.format( x ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( MOVE_TEXT_POSITION );
+ }
+
+ /**
+ * This will draw a string at the current location on the screen.
+ *
+ * @param text The text to draw.
+ * @throws IOException If an io exception occurs.
+ */
+ public void drawString( String text ) throws IOException
+ {
+ if( !inTextMode )
+ {
+ throw new IOException( "Error: must call beginText() before drawString");
+ }
+ COSString string = new COSString( text );
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ string.writePDF( buffer );
+ appendRawCommands( new String( buffer.toByteArray(), "ISO-8859-1"));
+ appendRawCommands( SPACE );
+ appendRawCommands( SHOW_TEXT );
+ }
+
+ /**
+ * Set the stroking color, specified as RGB.
+ *
+ * @param color The color to set.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setStrokingColor( Color color ) throws IOException
+ {
+ ColorSpace colorSpace = color.getColorSpace();
+ if( colorSpace.getType() == ColorSpace.TYPE_RGB )
+ {
+ setStrokingColor( color.getRed(), color.getGreen(), color.getBlue() );
+ }
+ else if( colorSpace.getType() == ColorSpace.TYPE_GRAY )
+ {
+ color.getColorComponents( colorComponents );
+ setStrokingColor( colorComponents[0] );
+ }
+ else if( colorSpace.getType() == ColorSpace.TYPE_CMYK )
+ {
+ color.getColorComponents( colorComponents );
+ setStrokingColor( colorComponents[0], colorComponents[2], colorComponents[2], colorComponents[3] );
+ }
+ else
+ {
+ throw new IOException( "Error: unknown colorspace:" + colorSpace );
+ }
+ }
+
+ /**
+ * Set the non stroking color, specified as RGB.
+ *
+ * @param color The color to set.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setNonStrokingColor( Color color ) throws IOException
+ {
+ ColorSpace colorSpace = color.getColorSpace();
+ if( colorSpace.getType() == ColorSpace.TYPE_RGB )
+ {
+ setNonStrokingColor( color.getRed(), color.getGreen(), color.getBlue() );
+ }
+ else if( colorSpace.getType() == ColorSpace.TYPE_GRAY )
+ {
+ color.getColorComponents( colorComponents );
+ setNonStrokingColor( colorComponents[0] );
+ }
+ else if( colorSpace.getType() == ColorSpace.TYPE_CMYK )
+ {
+ color.getColorComponents( colorComponents );
+ setNonStrokingColor( colorComponents[0], colorComponents[2], colorComponents[2], colorComponents[3] );
+ }
+ else
+ {
+ throw new IOException( "Error: unknown colorspace:" + colorSpace );
+ }
+ }
+
+ /**
+ * Set the stroking color, specified as RGB, 0-255.
+ *
+ * @param r The red value.
+ * @param g The green value.
+ * @param b The blue value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setStrokingColor( int r, int g, int b ) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( r/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( g/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( b/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( RG_STROKING );
+ }
+
+ /**
+ * Set the stroking color, specified as CMYK, 0-255.
+ *
+ * @param c The cyan value.
+ * @param m The magenta value.
+ * @param y The yellow value.
+ * @param k The black value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setStrokingColor( int c, int m, int y, int k) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( c/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( m/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( k/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( K_STROKING );
+ }
+
+ /**
+ * Set the stroking color, specified as CMYK, 0.0-1.0.
+ *
+ * @param c The cyan value.
+ * @param m The magenta value.
+ * @param y The yellow value.
+ * @param k The black value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setStrokingColor( double c, double m, double y, double k) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( c ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( m ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( k ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( K_STROKING );
+ }
+
+ /**
+ * Set the stroking color, specified as grayscale, 0-255.
+ *
+ * @param g The gray value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setStrokingColor( int g ) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( g/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( G_STROKING );
+ }
+
+ /**
+ * Set the stroking color, specified as Grayscale 0.0-1.0.
+ *
+ * @param g The gray value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setStrokingColor( double g ) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( g ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( G_STROKING );
+ }
+
+ /**
+ * Set the non stroking color, specified as RGB, 0-255.
+ *
+ * @param r The red value.
+ * @param g The green value.
+ * @param b The blue value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setNonStrokingColor( int r, int g, int b ) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( r/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( g/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( b/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( RG_NON_STROKING );
+ }
+
+ /**
+ * Set the non stroking color, specified as CMYK, 0-255.
+ *
+ * @param c The cyan value.
+ * @param m The magenta value.
+ * @param y The yellow value.
+ * @param k The black value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setNonStrokingColor( int c, int m, int y, int k) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( c/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( m/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( k/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( K_NON_STROKING );
+ }
+
+ /**
+ * Set the non stroking color, specified as CMYK, 0.0-1.0.
+ *
+ * @param c The cyan value.
+ * @param m The magenta value.
+ * @param y The yellow value.
+ * @param k The black value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setNonStrokingColor( double c, double m, double y, double k) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( c ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( m ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( k ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( K_NON_STROKING );
+ }
+
+ /**
+ * Set the non stroking color, specified as grayscale, 0-255.
+ *
+ * @param g The gray value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setNonStrokingColor( int g ) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( g/255d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( G_NON_STROKING );
+ }
+
+ /**
+ * Set the non stroking color, specified as Grayscale 0.0-1.0.
+ *
+ * @param g The gray value.
+ * @throws IOException If an IO error occurs while writing to the stream.
+ */
+ public void setNonStrokingColor( double g ) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( g ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( G_NON_STROKING );
+ }
+
+ /**
+ * Draw a rectangle on the page using the current non stroking color.
+ *
+ * @param x The lower left x coordinate.
+ * @param y The lower left y coordinate.
+ * @param width The width of the rectangle.
+ * @param height The height of the rectangle.
+ * @throws IOException If there is an error while drawing on the screen.
+ */
+ public void fillRect( float x, float y, float width, float height ) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( x ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( width ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( height ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( APPEND_RECTANGLE );
+ appendRawCommands( FILL );
+ }
+
+
+ /**
+ * This will append raw commands to the content stream.
+ *
+ * @param commands The commands to append to the stream.
+ * @throws IOException If an error occurs while writing to the stream.
+ */
+ public void appendRawCommands( String commands ) throws IOException
+ {
+ appendRawCommands( commands.getBytes( "ISO-8859-1" ) );
+ }
+
+ /**
+ * This will append raw commands to the content stream.
+ *
+ * @param commands The commands to append to the stream.
+ * @throws IOException If an error occurs while writing to the stream.
+ */
+ public void appendRawCommands( byte[] commands ) throws IOException
+ {
+ output.write( commands );
+ }
+
+ /**
+ * This will append raw commands to the content stream.
+ *
+ * @param data Append a raw byte to the stream.
+ *
+ * @throws IOException If an error occurs while writing to the stream.
+ */
+ public void appendRawCommands( int data ) throws IOException
+ {
+ output.write( data );
+ }
+
+ /**
+ * 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.
+ */
+ public void close() throws IOException
+ {
+ output.close();
+ }
+} \ No newline at end of file