--- /dev/null
+$label=Make images\r
-application.args=--input C:\\Users\\Vincent\\Documents\\layout\\document.pdf --layout C:\\Users\\Vincent\\Documents\\layout\\p%d.layout\r
--- /dev/null
+application.args=--input C:\\Users\\Vincent\\Documents\\layout\\document.pdf --image C:\\Users\\Vincent\\Documents\\layout\\p\r
<?xml version="1.0" encoding="UTF-8"?>\r
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">\r
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>\r
- <open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/1">\r
- <file>file:/H:/Works/cubeExtranet/fluidbook/tools/fwstk/src/com/fluidbook/fwstk/Main.java</file>\r
- <file>file:/H:/Works/cubeExtranet/fluidbook/tools/fwstk/src/cube/util/StringUtil.java</file>\r
- <file>file:/H:/Works/cubeExtranet/fluidbook/tools/fwstk/src/cube/files/FileIO.java</file>\r
- <file>file:/H:/Works/cubeExtranet/fluidbook/tools/fwstk/src/com/fluidbook/fwstk/LayoutStripper.java</file>\r
- </open-files>\r
</project-private>\r
file.reference.icu4j-4_6_1.jar=H:\\Works\\Java\\jar\\icu4j-4_6_1.jar\r
file.reference.Java-resources=../../../../Java/resources\r
file.reference.jempbox-1.6.0.jar=H:\\Works\\Java\\jar\\jempbox-1.6.0.jar\r
-file.reference.pdfbox-1.6.0.jar=H:\\Works\\Java\\jar\\pdfbox-1.6.0.jar\r
file.reference.Works-Java=../../../../Java\r
includes=**\r
jar.archive.disabled=${jnlp.enabled}\r
${file.reference.commons-logging-1.1.1.jar}:\\r
${file.reference.fontbox-1.6.0.jar}:\\r
${file.reference.jempbox-1.6.0.jar}:\\r
- ${file.reference.pdfbox-1.6.0.jar}\r
+ ${libs.PDFBox.classpath}\r
# Space-separated list of extra javac options\r
javac.compilerargs=\r
javac.deprecation=false\r
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * This is the main program that simply parses the pdf document and replace
+ * change a PDF to use a specific colorspace.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Pierre-Yves Landuré (pierre-yves@landure.org)
+ * @version $Revision: 1.5 $
+ */
+public class ConvertColorspace
+{
+
+ private static final String PASSWORD = "-password";
+ private static final String CONVERSION = "-equiv";
+ private static final String DEST_COLORSPACE = "-toColorspace";
+
+ /**
+ * private constructor.
+ */
+ private ConvertColorspace()
+ {
+ //static class
+ }
+
+ /**
+ * The method that replace RGB colors by CMYK ones.
+ *
+ * @param inputFile input file name.
+ * @param colorEquivalents a dictionnary for the color equivalents.
+ * @param destColorspace The destination colorspace, currently CMYK is supported.
+ *
+ * @throws IOException If there is an error parsing the document.
+ */
+ private void replaceColors( PDDocument inputFile,
+ Hashtable colorEquivalents,
+ String destColorspace ) throws IOException
+ {
+ if( !destColorspace.equals( "CMYK" ) )
+ {
+ throw new IOException( "Error: Unknown colorspace " + destColorspace );
+ }
+ List pagesList = inputFile.getDocumentCatalog().getAllPages();
+
+ PDPage currentPage = null;
+ PDFStreamParser parser = null;
+ List pageTokens = null;
+ List editedPageTokens = null;
+
+ for(int pageCounter = 0; pageCounter < pagesList.size(); pageCounter++) // For each document page
+ {
+ currentPage = (PDPage)pagesList.get( pageCounter );
+
+ parser = new PDFStreamParser(currentPage.getContents().getStream());
+ parser.parse();
+ pageTokens = parser.getTokens();
+ editedPageTokens = new ArrayList();
+
+ for( int counter = 0; counter < pageTokens.size(); counter++) // For each page token
+ {
+ Object token = pageTokens.get( counter );
+ if( token instanceof PDFOperator ) // Test if PDFOperator
+ {
+ PDFOperator tokenOperator = (PDFOperator)token;
+
+ if(tokenOperator.getOperation().equals("rg")) // Test if "rg" Operator.
+ {
+ if( destColorspace.equals( "CMYK" ) )
+ {
+ replaceRGBTokensWithCMYKTokens( editedPageTokens, pageTokens, counter, colorEquivalents );
+ editedPageTokens.add( PDFOperator.getOperator( "k" ));
+ }
+ }
+ else if(tokenOperator.getOperation().equals("RG")) // Test if "rg" Operator.
+ {
+ if( destColorspace.equals( "CMYK" ) )
+ {
+ replaceRGBTokensWithCMYKTokens( editedPageTokens, pageTokens, counter, colorEquivalents );
+ editedPageTokens.add( PDFOperator.getOperator( "K" ));
+ }
+ }
+ else if(tokenOperator.getOperation().equals("g")) // Test if "rg" Operator.
+ {
+ if( destColorspace.equals( "CMYK" ) )
+ {
+ replaceGrayTokensWithCMYKTokens( editedPageTokens, pageTokens, counter, colorEquivalents );
+ editedPageTokens.add( PDFOperator.getOperator( "k" ));
+ }
+ }
+ else if(tokenOperator.getOperation().equals("G")) // Test if "rg" Operator.
+ {
+ if( destColorspace.equals( "CMYK" ) )
+ {
+ replaceGrayTokensWithCMYKTokens( editedPageTokens, pageTokens, counter, colorEquivalents );
+ editedPageTokens.add( PDFOperator.getOperator( "K" ));
+ }
+ }
+ else
+ {
+ editedPageTokens.add( token );
+ }
+ }
+ else // Test if PDFOperator
+ {
+ editedPageTokens.add( token );
+ }
+ } // For each page token
+
+ // We replace original page content by the edited one.
+ PDStream updatedPageContents = new PDStream(inputFile);
+ ContentStreamWriter contentWriter = new ContentStreamWriter( updatedPageContents.createOutputStream() );
+ contentWriter.writeTokens( editedPageTokens );
+ currentPage.setContents( updatedPageContents );
+
+ } // For each document page
+ }
+
+ private void replaceRGBTokensWithCMYKTokens( List editedPageTokens,
+ List pageTokens,
+ int counter,
+ Hashtable colorEquivalents )
+ {
+// Get current RGB color.
+ float red = ((COSNumber)pageTokens.get( counter - 3 )).floatValue();
+ float green = ((COSNumber)pageTokens.get( counter - 2 )).floatValue();
+ float blue = ((COSNumber)pageTokens.get( counter - 1 )).floatValue();
+
+ int intRed = Math.round(red * 255.0f);
+ int intGreen = Math.round(green * 255.0f);
+ int intBlue = Math.round(blue * 255.0f);
+
+ ColorSpaceInstance rgbColor = new ColorSpaceInstance();
+ rgbColor.colorspace = "RGB";
+ rgbColor.colorspaceValues = new int[] { intRed, intGreen, intBlue };
+ ColorSpaceInstance cmykColor = (ColorSpaceInstance)colorEquivalents.get(rgbColor);
+ float[] cmyk = null;
+
+ if( cmykColor != null )
+ {
+ cmyk = new float[] {
+ cmykColor.colorspaceValues[0] / 100.0f,
+ cmykColor.colorspaceValues[1] / 100.0f,
+ cmykColor.colorspaceValues[2] / 100.0f,
+ cmykColor.colorspaceValues[3] / 100.0f
+ };
+ }
+ else
+ {
+ cmyk = convertRGBToCMYK( red, green, blue );
+ }
+
+ //remove the RGB components that are already part of the editedPageTokens list
+ editedPageTokens.remove( editedPageTokens.size() -1 );
+ editedPageTokens.remove( editedPageTokens.size() -1 );
+ editedPageTokens.remove( editedPageTokens.size() -1 );
+
+ // Add the new CMYK color
+ editedPageTokens.add( new COSFloat( cmyk[0] ) );
+ editedPageTokens.add( new COSFloat( cmyk[1] ) );
+ editedPageTokens.add( new COSFloat( cmyk[2] ) );
+ editedPageTokens.add( new COSFloat( cmyk[3] ) );
+ }
+
+ private void replaceGrayTokensWithCMYKTokens( List editedPageTokens,
+ List pageTokens,
+ int counter,
+ Hashtable colorEquivalents )
+ {
+// Get current RGB color.
+ float gray = ((COSNumber)pageTokens.get( counter - 1 )).floatValue();
+
+ ColorSpaceInstance grayColor = new ColorSpaceInstance();
+ grayColor.colorspace = "Grayscale";
+ grayColor.colorspaceValues = new int[] { Math.round( gray * 100 ) };
+ ColorSpaceInstance cmykColor = (ColorSpaceInstance)colorEquivalents.get(grayColor);
+ float[] cmyk = null;
+
+ if( cmykColor != null )
+ {
+ cmyk = new float[] {
+ cmykColor.colorspaceValues[0] / 100.0f,
+ cmykColor.colorspaceValues[1] / 100.0f,
+ cmykColor.colorspaceValues[2] / 100.0f,
+ cmykColor.colorspaceValues[3] / 100.0f
+ };
+ }
+ else
+ {
+ cmyk = new float[] {0,0,0,gray};
+ }
+
+ //remove the Gray components that are already part of the editedPageTokens list
+ editedPageTokens.remove( editedPageTokens.size() -1 );
+
+ // Add the new CMYK color
+ editedPageTokens.add( new COSFloat( cmyk[0] ) );
+ editedPageTokens.add( new COSFloat( cmyk[1] ) );
+ editedPageTokens.add( new COSFloat( cmyk[2] ) );
+ editedPageTokens.add( new COSFloat( cmyk[3] ) );
+ }
+
+ private static float[] convertRGBToCMYK( float red, float green, float blue )
+ {
+ //
+ // RGB->CMYK from From
+ // http://en.wikipedia.org/wiki/Talk:CMYK_color_model
+ //
+ float c = 1.0f - red;
+ float m = 1.0f - green;
+ float y = 1.0f - blue;
+ float k = 1.0f;
+
+ k = Math.min( Math.min( Math.min( c,k ), m), y );
+
+ c = ( c - k ) / ( 1 - k );
+ m = ( m - k ) / ( 1 - k );
+ y = ( y - k ) / ( 1 - k );
+ return new float[] { c,m,y,k};
+ }
+
+ private static int[] stringToIntArray( String string )
+ {
+ String[] ints = string.split( "," );
+ int[] retval = new int[ints.length];
+ for( int i=0; i<ints.length; i++ )
+ {
+ retval[i] = Integer.parseInt( ints[i] );
+ }
+ return retval;
+ }
+
+ /**
+ * Infamous main method.
+ *
+ * @param args Command line arguments, should be one and a reference to a file.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ String password = "";
+ String inputFile = null;
+ String outputFile = null;
+ String destColorspace = "CMYK";
+
+ Pattern colorEquivalentPattern = Pattern.compile(
+ "^(.*):\\((.*)\\)" +
+ "=(.*):\\((.*)\\)$");
+ Matcher colorEquivalentMatcher = null;
+
+ //key= value=java.awt.Color
+ Hashtable colorEquivalents = new Hashtable();
+
+ for( int i=0; i<args.length; i++ )
+ {
+ if( args[i].equals( PASSWORD ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ password = args[i];
+ }
+ if( args[i].equals( DEST_COLORSPACE ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ destColorspace = args[i];
+ }
+ if(args[i].equals( CONVERSION ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+
+ colorEquivalentMatcher = colorEquivalentPattern.matcher(args[i]);
+ if(!colorEquivalentMatcher.matches())
+ {
+ usage();
+ }
+ String srcColorSpace = colorEquivalentMatcher.group(1);
+ String srcColorvalues = colorEquivalentMatcher.group(2);
+ String destColorSpace = colorEquivalentMatcher.group(3);
+ String destColorvalues = colorEquivalentMatcher.group(4);
+
+ ConvertColorspace.ColorSpaceInstance source = new ColorSpaceInstance();
+ source.colorspace = srcColorSpace;
+ source.colorspaceValues = stringToIntArray( srcColorvalues );
+
+ ColorSpaceInstance dest = new ColorSpaceInstance();
+ dest.colorspace = destColorSpace;
+ dest.colorspaceValues = stringToIntArray( destColorvalues );
+
+ colorEquivalents.put(source, dest);
+
+ }
+ else
+ {
+ if( inputFile == null )
+ {
+ inputFile = args[i];
+ }
+ else
+ {
+ outputFile = args[i];
+ }
+ }
+ }
+
+ if( inputFile == null )
+ {
+ usage();
+ }
+
+ if( outputFile == null || outputFile.equals(inputFile))
+ {
+ usage();
+ }
+
+ PDDocument doc = null;
+ try
+ {
+ doc = PDDocument.load( inputFile );
+ if( doc.isEncrypted() )
+ {
+ try
+ {
+ doc.decrypt( password );
+ }
+ catch( InvalidPasswordException e )
+ {
+ if( !password.equals( "" ) )//they supplied the wrong password
+ {
+ System.err.println( "Error: The supplied password is incorrect." );
+ System.exit( 2 );
+ }
+ else
+ {
+ //they didn't suppply a password and the default of "" was wrong.
+ System.err.println( "Error: The document is encrypted." );
+ usage();
+ }
+ }
+ }
+ ConvertColorspace converter = new ConvertColorspace();
+ converter.replaceColors(doc, colorEquivalents, destColorspace );
+ doc.save( outputFile );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+
+
+ }
+
+
+ /**
+ * This will print the usage requirements and exit.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.ConvertColorspace [OPTIONS] <PDF Input file> "
+ +"<PDF Output File>\n" +
+ " -password <password> Password to decrypt document\n" +
+ " -equiv <color equivalent> Color equivalent to use for conversion.\n" +
+ " -destColorspace <color equivalent> The destination colorspace, CMYK is the only '" +
+ "supported colorspace." +
+ " \n" +
+ " The equiv format is : <source colorspace>:(colorspace value)=<dest colorspace>:(colorspace value)" +
+ " This option can be used as many times as necessary\n" +
+ " The supported equiv colorspaces are RGB and CMYK.\n" +
+ " RGB color values are integers between 0 and 255" +
+ " CMYK color values are integer between 0 and 100.\n" +
+ " Example: java org.apache.pdfbox.ConvertColorspace -equiv RGB:(255,0,0)=CMYK(0,99,100,0)" +
+ " input.pdf output.pdf\n" +
+ " <PDF Input file> The PDF document to use\n" +
+ " <PDF Output file> The PDF file to write the result to. Must be different of input file\n"
+ );
+ System.exit( 1 );
+ }
+
+ /**
+ *
+ *
+ */
+ private static class ColorSpaceInstance
+ {
+ private String colorspace = null;
+ private int[] colorspaceValues = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ int code = colorspace.hashCode();
+ for( int i=0; i<colorspaceValues.length; i++ )
+ {
+ code += colorspaceValues[i];
+ }
+ return code;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals( Object o )
+ {
+ boolean retval = false;
+ if( o instanceof ColorSpaceInstance )
+ {
+ ColorSpaceInstance other = (ColorSpaceInstance)o;
+ if( this.colorspace.equals( other.colorspace ) &&
+ colorspaceValues.length == other.colorspaceValues.length )
+ {
+ retval = true;
+ for( int i=0; i<colorspaceValues.length && retval; i++ )
+ {
+ retval = retval && colorspaceValues[i] == other.colorspaceValues[i];
+ }
+ }
+ }
+ return retval;
+ }
+ }
+}
+
--- /dev/null
+/*
+ * 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;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
+import org.apache.pdfbox.pdmodel.encryption.DecryptionMaterial;
+import org.apache.pdfbox.pdmodel.encryption.PublicKeyDecryptionMaterial;
+import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
+
+/**
+ * This will read a document from the filesystem, decrypt it and and then write
+ * the results to the filesystem. <br/><br/>
+ *
+ * usage: java org.apache.pdfbox.Decrypt <password> <inputfile> <outputfile>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class Decrypt
+{
+ private static final String ALIAS = "-alias";
+ private static final String PASSWORD = "-password";
+ private static final String KEYSTORE = "-keyStore";
+
+ private Decrypt()
+ {
+ }
+ /**
+ * This is the entry point for the application.
+ *
+ * @param args The command-line arguments.
+ *
+ * @throws Exception If there is an error decrypting the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ Decrypt decrypt = new Decrypt();
+ decrypt.decrypt( args );
+ }
+
+ private void decrypt( String[] args ) throws Exception
+ {
+ if( args.length < 2 || args.length > 5 )
+ {
+ usage();
+ }
+ else
+ {
+ String password = null;
+ String infile = null;
+ String outfile = null;
+ String alias = null;
+ String keyStore = null;
+ for( int i=0; i<args.length; i++ )
+ {
+ if( args[i].equals( ALIAS ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ alias = args[i];
+ }
+ else if( args[i].equals( KEYSTORE ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ keyStore = args[i];
+ }
+ else if( args[i].equals( PASSWORD ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ password = args[i];
+ }
+ else if( infile == null )
+ {
+ infile = args[i];
+ }
+ else if( outfile == null )
+ {
+ outfile = args[i];
+ }
+ else
+ {
+ usage();
+ }
+ }
+ if( infile == null )
+ {
+ usage();
+ }
+ if( outfile == null )
+ {
+ outfile = infile;
+ }
+ if( password == null )
+ {
+ password = "";
+ }
+
+
+ PDDocument document = null;
+
+ try
+ {
+ document = PDDocument.load( infile );
+
+ if( document.isEncrypted() )
+ {
+ DecryptionMaterial decryptionMaterial = null;
+ if( keyStore != null )
+ {
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(new FileInputStream(keyStore), password.toCharArray());
+
+ decryptionMaterial = new PublicKeyDecryptionMaterial(ks, alias, password);
+ }
+ else
+ {
+ decryptionMaterial = new StandardDecryptionMaterial(password);
+ }
+ document.openProtection(decryptionMaterial);
+ AccessPermission ap = document.getCurrentAccessPermission();
+ if(ap.isOwnerPermission())
+ {
+ document.save( outfile );
+ }
+ else
+ {
+ throw new IOException(
+ "Error: You are only allowed to decrypt a document with the owner password." );
+ }
+ }
+ else
+ {
+ System.err.println( "Error: Document is not encrypted." );
+ }
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print a usage message.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: java -jar pdfbox-app-x.y.z.jar Decrypt " +
+ "[options] <inputfile> [outputfile]" );
+ System.err.println( "-alias The alias of the key in the certificate file " +
+ "(mandatory if several keys are available)");
+ System.err.println( "-password The password to open the certificate and extract the private key from it." );
+ System.err.println( "-keyStore The KeyStore that holds the certificate." );
+ System.exit( -1 );
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
+import org.apache.pdfbox.pdmodel.encryption.PublicKeyProtectionPolicy;
+import org.apache.pdfbox.pdmodel.encryption.PublicKeyRecipient;
+import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
+
+/**
+ * This will read a document from the filesystem, encrypt it and and then write
+ * the results to the filesystem. <br/><br/>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class Encrypt
+{
+ private Encrypt()
+ {
+ }
+
+ /**
+ * This is the entry point for the application.
+ *
+ * @param args The command-line arguments.
+ *
+ * @throws Exception If there is an error decrypting the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ Encrypt encrypt = new Encrypt();
+ encrypt.encrypt( args );
+ }
+
+ private void encrypt( String[] args ) throws Exception
+ {
+ if( args.length < 1 )
+ {
+ usage();
+ }
+ else
+ {
+ AccessPermission ap = new AccessPermission();
+
+ String infile = null;
+ String outfile = null;
+ String certFile = null;
+ String userPassword = "";
+ String ownerPassword = "";
+
+ int keyLength = 40;
+
+ PDDocument document = null;
+
+ try
+ {
+ for( int i=0; i<args.length; i++ )
+ {
+ String key = args[i];
+ if( key.equals( "-O" ) )
+ {
+ ownerPassword = args[++i];
+ }
+ else if( key.equals( "-U" ) )
+ {
+ userPassword = args[++i];
+ }
+ else if( key.equals( "-canAssemble" ) )
+ {
+ ap.setCanAssembleDocument(args[++i].equalsIgnoreCase( "true" ));
+ }
+ else if( key.equals( "-canExtractContent" ) )
+ {
+ ap.setCanExtractContent( args[++i].equalsIgnoreCase( "true" ) );
+ }
+ else if( key.equals( "-canExtractForAccessibility" ) )
+ {
+ ap.setCanExtractForAccessibility( args[++i].equalsIgnoreCase( "true" ) );
+ }
+ else if( key.equals( "-canFillInForm" ) )
+ {
+ ap.setCanFillInForm( args[++i].equalsIgnoreCase( "true" ) );
+ }
+ else if( key.equals( "-canModify" ) )
+ {
+ ap.setCanModify( args[++i].equalsIgnoreCase( "true" ) );
+ }
+ else if( key.equals( "-canModifyAnnotations" ) )
+ {
+ ap.setCanModifyAnnotations( args[++i].equalsIgnoreCase( "true" ) );
+ }
+ else if( key.equals( "-canPrint" ) )
+ {
+ ap.setCanPrint( args[++i].equalsIgnoreCase( "true" ) );
+ }
+ else if( key.equals( "-canPrintDegraded" ) )
+ {
+ ap.setCanPrintDegraded( args[++i].equalsIgnoreCase( "true" ) );
+ }
+ else if( key.equals( "-certFile" ) )
+ {
+ certFile = args[++i];
+ }
+ else if( key.equals( "-keyLength" ) )
+ {
+ try
+ {
+ keyLength = Integer.parseInt( args[++i] );
+ }
+ catch( NumberFormatException e )
+ {
+ throw new NumberFormatException(
+ "Error: -keyLength is not an integer '" + args[i] + "'" );
+ }
+ }
+ else if( infile == null )
+ {
+ infile = key;
+ }
+ else if( outfile == null )
+ {
+ outfile = key;
+ }
+ else
+ {
+ usage();
+ }
+ }
+ if( infile == null )
+ {
+ usage();
+ }
+ if( outfile == null )
+ {
+ outfile = infile;
+ }
+ document = PDDocument.load( infile );
+
+ if( !document.isEncrypted() )
+ {
+ if( certFile != null )
+ {
+ PublicKeyProtectionPolicy ppp = new PublicKeyProtectionPolicy();
+ PublicKeyRecipient recip = new PublicKeyRecipient();
+ recip.setPermission(ap);
+
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ InputStream inStream = new FileInputStream(certFile);
+ X509Certificate certificate = (X509Certificate)cf.generateCertificate(inStream);
+ inStream.close();
+
+ recip.setX509(certificate);
+
+ ppp.addRecipient(recip);
+
+ ppp.setEncryptionKeyLength(keyLength);
+
+ document.protect(ppp);
+ }
+ else
+ {
+ StandardProtectionPolicy spp =
+ new StandardProtectionPolicy(ownerPassword, userPassword, ap);
+ spp.setEncryptionKeyLength(keyLength);
+ document.protect(spp);
+ }
+ document.save( outfile );
+ }
+ else
+ {
+ System.err.println( "Error: Document is already encrypted." );
+ }
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print a usage message.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: java -jar pdfbox-app-x.y.z.jar Encrypt [options] <inputfile> [outputfile]" );
+ System.err.println( " -O <password> " +
+ "Set the owner password(ignored if cert is set)" );
+ System.err.println( " -U <password> " +
+ "Set the user password(ignored if cert is set)" );
+ System.err.println( " -certFile <path to cert> Path to X.509 certificate" );
+ System.err.println( " -canAssemble <true|false> Set the assemble permission" );
+ System.err.println( " -canExtractContent <true|false> Set the extraction permission" );
+ System.err.println( " -canExtractForAccessibility <true|false> Set the extraction permission" );
+ System.err.println( " -canFillInForm <true|false> Set the fill in form permission" );
+ System.err.println( " -canModify <true|false> Set the modify permission" );
+ System.err.println( " -canModifyAnnotations <true|false> Set the modify annots permission" );
+ System.err.println( " -canPrint <true|false> Set the print permission" );
+ System.err.println( " -canPrintDegraded <true|false> Set the print degraded permission" );
+ System.err.println( " -keyLength <length> The length of the key in bits(40)" );
+ System.err.println( "\nNote: By default all permissions are set to true!" );
+ System.exit( 1 );
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+
+/**
+ * This example will take a PDF document and fill the fields with data from the
+ * FDF fields.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ExportFDF
+{
+ /**
+ * Creates a new instance of ImportFDF.
+ */
+ public ExportFDF()
+ {
+ }
+
+ /**
+ * This will import an fdf document and write out another pdf.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args command line arguments
+ *
+ * @throws Exception If there is an error importing the FDF document.
+ */
+ public static void main(String[] args) throws Exception
+ {
+ ExportFDF exporter = new ExportFDF();
+ exporter.exportFDF( args );
+ }
+
+ private void exportFDF( String[] args ) throws Exception
+ {
+ PDDocument pdf = null;
+ FDFDocument fdf = null;
+
+ try
+ {
+ if( args.length != 1 && args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ pdf = PDDocument.load( args[0] );
+ PDAcroForm form = pdf.getDocumentCatalog().getAcroForm();
+ if( form == null )
+ {
+ System.err.println( "Error: This PDF does not contain a form." );
+ }
+ else
+ {
+ String fdfName = null;
+ if( args.length == 2 )
+ {
+ fdfName = args[1];
+ }
+ else
+ {
+ if( args[0].length() > 4 )
+ {
+ fdfName = args[0].substring( 0, args[0].length() -4 ) + ".fdf";
+ }
+ }
+ fdf = form.exportFDF();
+ fdf.save( fdfName );
+ }
+ }
+ }
+ finally
+ {
+ close( fdf );
+ close( pdf );
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: org.apache.pdfbox.ExortFDF <pdf-file> [output-fdf-file]" );
+ System.err.println( " [output-fdf-file] - Default is pdf name, test.pdf->test.fdf" );
+ }
+
+ /**
+ * Close the document.
+ *
+ * @param doc The doc to close.
+ *
+ * @throws IOException If there is an error closing the document.
+ */
+ public void close( FDFDocument doc ) throws IOException
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+
+ /**
+ * Close the document.
+ *
+ * @param doc The doc to close.
+ *
+ * @throws IOException If there is an error closing the document.
+ */
+ public void close( PDDocument doc ) throws IOException
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+
+/**
+ * This example will take a PDF document and fill the fields with data from the
+ * FDF fields.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ExportXFDF
+{
+ /**
+ * Creates a new instance of ImportFDF.
+ */
+ public ExportXFDF()
+ {
+ }
+
+ /**
+ * This will import an fdf document and write out another pdf.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args command line arguments
+ *
+ * @throws Exception If there is an error importing the FDF document.
+ */
+ public static void main(String[] args) throws Exception
+ {
+ ExportXFDF exporter = new ExportXFDF();
+ exporter.exportXFDF( args );
+ }
+
+ private void exportXFDF( String[] args ) throws Exception
+ {
+ PDDocument pdf = null;
+ FDFDocument fdf = null;
+
+ try
+ {
+ if( args.length != 1 && args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ pdf = PDDocument.load( args[0] );
+ PDAcroForm form = pdf.getDocumentCatalog().getAcroForm();
+ if( form == null )
+ {
+ System.err.println( "Error: This PDF does not contain a form." );
+ }
+ else
+ {
+ String fdfName = null;
+ if( args.length == 2 )
+ {
+ fdfName = args[1];
+ }
+ else
+ {
+ if( args[0].length() > 4 )
+ {
+ fdfName = args[0].substring( 0, args[0].length() -4 ) + ".xfdf";
+ }
+ }
+ fdf = form.exportFDF();
+ fdf.saveXFDF( fdfName );
+ }
+ }
+ }
+ finally
+ {
+ close( fdf );
+ close( pdf );
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: org.apache.pdfbox.ExortXFDF <pdf-file> [output-xfdf-file]" );
+ System.err.println( " [output-xfdf-file] - Default is pdf name, test.pdf->test.xfdf" );
+ }
+
+ /**
+ * Close the document.
+ *
+ * @param doc The doc to close.
+ *
+ * @throws IOException If there is an error closing the document.
+ */
+ public void close( FDFDocument doc ) throws IOException
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+
+ /**
+ * Close the document.
+ *
+ * @param doc The doc to close.
+ *
+ * @throws IOException If there is an error closing the document.
+ */
+ public void close( PDDocument doc ) throws IOException
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
+import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+
+/**
+ * This will read a read pdf and extract images. <br/><br/>
+ *
+ * usage: java org.apache.pdfbox.ExtractImages <pdffile> <password> [imageprefix]
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class ExtractImages
+{
+ private int imageCounter = 1;
+
+ private static final String PASSWORD = "-password";
+ private static final String PREFIX = "-prefix";
+ private static final String ADDKEY = "-addkey";
+
+ private ExtractImages()
+ {
+ }
+
+ /**
+ * This is the entry point for the application.
+ *
+ * @param args The command-line arguments.
+ *
+ * @throws Exception If there is an error decrypting the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ ExtractImages extractor = new ExtractImages();
+ extractor.extractImages( args );
+ }
+
+ private void extractImages( String[] args ) throws Exception
+ {
+ if( args.length < 1 || args.length > 3 )
+ {
+ usage();
+ }
+ else
+ {
+ String pdfFile = null;
+ String password = "";
+ String prefix = null;
+ boolean addKey = false;
+ for( int i=0; i<args.length; i++ )
+ {
+ if( args[i].equals( PASSWORD ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ password = args[i];
+ }
+ else if( args[i].equals( PREFIX ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ prefix = args[i];
+ }
+ else if( args[i].equals( ADDKEY ) )
+ {
+ addKey = true;
+ }
+ else
+ {
+ if( pdfFile == null )
+ {
+ pdfFile = args[i];
+ }
+ }
+ }
+ if(pdfFile == null)
+ {
+ usage();
+ }
+ else
+ {
+ if( prefix == null && pdfFile.length() >4 )
+ {
+ prefix = pdfFile.substring( 0, pdfFile.length() -4 );
+ }
+
+ PDDocument document = null;
+
+ try
+ {
+ document = PDDocument.load( pdfFile );
+
+ if( document.isEncrypted() )
+ {
+
+ StandardDecryptionMaterial spm = new StandardDecryptionMaterial(password);
+ document.openProtection(spm);
+ AccessPermission ap = document.getCurrentAccessPermission();
+
+
+ if( ! ap.canExtractContent() )
+ {
+ throw new IOException(
+ "Error: You do not have permission to extract images." );
+ }
+ }
+
+ List pages = document.getDocumentCatalog().getAllPages();
+ Iterator iter = pages.iterator();
+ while( iter.hasNext() )
+ {
+ PDPage page = (PDPage)iter.next();
+ PDResources resources = page.getResources();
+ Map images = resources.getImages();
+ if( images != null )
+ {
+ Iterator imageIter = images.keySet().iterator();
+ while( imageIter.hasNext() )
+ {
+ String key = (String)imageIter.next();
+ PDXObjectImage image = (PDXObjectImage)images.get( key );
+ String name = null;
+ if (addKey)
+ {
+ name = getUniqueFileName( prefix + "_" + key, image.getSuffix() );
+ }
+ else
+ {
+ name = getUniqueFileName( prefix, image.getSuffix() );
+ }
+ System.out.println( "Writing image:" + name );
+ image.write2file( name );
+ }
+ }
+ }
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+ }
+
+ private String getUniqueFileName( String prefix, String suffix )
+ {
+ String uniqueName = null;
+ File f = null;
+ while( f == null || f.exists() )
+ {
+ uniqueName = prefix + "-" + imageCounter;
+ f = new File( uniqueName + "." + suffix );
+ imageCounter++;
+ }
+ return uniqueName;
+ }
+
+ /**
+ * This will print the usage requirements and exit.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.ExtractImages [OPTIONS] <PDF file>\n" +
+ " -password <password> Password to decrypt document\n" +
+ " -prefix <image-prefix> Image prefix(default to pdf name)\n" +
+ " -addkey add the internal image key to the file name\n" +
+ " <PDF file> The PDF document to use\n"
+ );
+ System.exit( 1 );
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
+import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
+import org.apache.pdfbox.util.PDFText2HTML;
+import org.apache.pdfbox.util.PDFTextStripper;
+
+/**
+ * This is the main program that simply parses the pdf document and transforms it
+ * into text.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class ExtractText
+{
+ private static final String PASSWORD = "-password";
+ private static final String ENCODING = "-encoding";
+ private static final String CONSOLE = "-console";
+ private static final String START_PAGE = "-startPage";
+ private static final String END_PAGE = "-endPage";
+ private static final String SORT = "-sort";
+ private static final String IGNORE_BEADS = "-ignoreBeads";
+ private static final String DEBUG = "-debug";
+ private static final String HTML = "-html"; // jjb - added simple HTML output
+ private static final String FORCE = "-force"; //enables pdfbox to skip corrupt objects
+
+ /*
+ * debug flag
+ */
+ private boolean debug = false;
+
+ /**
+ * private constructor.
+ */
+ private ExtractText()
+ {
+ //static class
+ }
+
+ /**
+ * Infamous main method.
+ *
+ * @param args Command line arguments, should be one and a reference to a file.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ ExtractText extractor = new ExtractText();
+ extractor.startExtraction(args);
+ }
+
+ public void startExtraction( String[] args ) throws Exception
+ {
+ boolean toConsole = false;
+ boolean toHTML = false;
+ boolean force = false;
+ boolean sort = false;
+ boolean separateBeads = true;
+ String password = "";
+ String encoding = null;
+ String pdfFile = null;
+ String outputFile = null;
+ // Defaults to text files
+ String ext = ".txt";
+ int startPage = 1;
+ int endPage = Integer.MAX_VALUE;
+ for( int i=0; i<args.length; i++ )
+ {
+ if( args[i].equals( PASSWORD ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ password = args[i];
+ }
+ else if( args[i].equals( ENCODING ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ encoding = args[i];
+ }
+ else if( args[i].equals( START_PAGE ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ startPage = Integer.parseInt( args[i] );
+ }
+ else if( args[i].equals( HTML ) )
+ {
+ toHTML = true;
+ ext = ".html";
+ }
+ else if( args[i].equals( SORT ) )
+ {
+ sort = true;
+ }
+ else if( args[i].equals( IGNORE_BEADS ) )
+ {
+ separateBeads = false;
+ }
+ else if( args[i].equals( DEBUG ) )
+ {
+ debug = true;
+ }
+ else if( args[i].equals( END_PAGE ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ endPage = Integer.parseInt( args[i] );
+ }
+ else if( args[i].equals( CONSOLE ) )
+ {
+ toConsole = true;
+ }
+ else if( args[i].equals( FORCE ) )
+ {
+ force = true;
+ }
+ else
+ {
+ if( pdfFile == null )
+ {
+ pdfFile = args[i];
+ }
+ else
+ {
+ outputFile = args[i];
+ }
+ }
+ }
+
+ if( pdfFile == null )
+ {
+ usage();
+ }
+ else
+ {
+
+ Writer output = null;
+ PDDocument document = null;
+ try
+ {
+ long startTime = startProcessing("Loading PDF "+pdfFile);
+ try
+ {
+ //basically try to load it from a url first and if the URL
+ //is not recognized then try to load it from the file system.
+ URL url = new URL( pdfFile );
+ document = PDDocument.load(url, force);
+ String fileName = url.getFile();
+ if( outputFile == null && fileName.length() >4 )
+ {
+ outputFile = new File( fileName.substring( 0, fileName.length() -4 ) + ext ).getName();
+ }
+ }
+ catch( MalformedURLException e )
+ {
+ document = PDDocument.load(pdfFile, force);
+ if( outputFile == null && pdfFile.length() >4 )
+ {
+ outputFile = pdfFile.substring( 0, pdfFile.length() -4 ) + ext;
+ }
+ }
+ stopProcessing("Time for loading: ", startTime);
+
+ if( document.isEncrypted() )
+ {
+ StandardDecryptionMaterial sdm = new StandardDecryptionMaterial( password );
+ document.openProtection( sdm );
+ AccessPermission ap = document.getCurrentAccessPermission();
+
+ if( ! ap.canExtractContent() )
+ {
+ throw new IOException( "You do not have permission to extract text" );
+ }
+ }
+
+ if ((encoding == null) && (toHTML))
+ {
+ encoding = "UTF-8";
+ }
+
+ if( toConsole )
+ {
+ output = new OutputStreamWriter( System.out );
+ }
+ else
+ {
+ if( encoding != null )
+ {
+ output = new OutputStreamWriter(
+ new FileOutputStream( outputFile ), encoding );
+ }
+ else
+ {
+ //use default encoding
+ output = new OutputStreamWriter(
+ new FileOutputStream( outputFile ) );
+ }
+ }
+
+ PDFTextStripper stripper = null;
+ if(toHTML)
+ {
+ stripper = new PDFText2HTML(encoding);
+ }
+ else
+ {
+ stripper = new PDFTextStripper(encoding);
+ }
+ stripper.setForceParsing( force );
+ stripper.setSortByPosition( sort );
+ stripper.setShouldSeparateByBeads( separateBeads );
+ stripper.setStartPage( startPage );
+ stripper.setEndPage( endPage );
+
+ startTime = startProcessing("Starting text extraction");
+ stripper.writeText( document, output );
+ stopProcessing("Time for extraction: ", startTime);
+ }
+ finally
+ {
+ if( output != null )
+ {
+ output.close();
+ }
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ private long startProcessing(String message) {
+ if (debug)
+ {
+ System.err.println(message);
+ }
+ return System.currentTimeMillis();
+ }
+
+ private void stopProcessing(String message, long startTime) {
+ if (debug)
+ {
+ long stopTime = System.currentTimeMillis();
+ float elapsedTime = ((float)(stopTime - startTime))/1000;
+ System.err.println(message + elapsedTime + " seconds");
+ }
+ }
+
+ /**
+ * This will print the usage requirements and exit.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java -jar pdfbox-app-x.y.z.jar ExtractText [OPTIONS] <PDF file> [Text File]\n" +
+ " -password <password> Password to decrypt document\n" +
+ " -encoding <output encoding> (ISO-8859-1,UTF-16BE,UTF-16LE,...)\n" +
+ " -console Send text to console instead of file\n" +
+ " -html Output in HTML format instead of raw text\n" +
+ " -sort Sort the text before writing\n" +
+ " -ignoreBeads Disables the separation by beads\n" +
+ " -force Enables pdfbox to ignore corrupt objects\n" +
+ " -debug Enables debug output about the time consumption of every stage\n" +
+ " -startPage <number> The first page to start extraction(1 based)\n" +
+ " -endPage <number> The last page to extract(inclusive)\n" +
+ " <PDF file> The PDF document to use\n" +
+ " [Text File] The file to write the text to\n"
+ );
+ System.exit( 1 );
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+
+/**
+ * This example will take a PDF document and fill the fields with data from the
+ * FDF fields.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ImportFDF
+{
+ /**
+ * Creates a new instance of ImportFDF.
+ */
+ public ImportFDF()
+ {
+ }
+
+ /**
+ * This will takes the values from the fdf document and import them into the
+ * PDF document.
+ *
+ * @param pdfDocument The document to put the fdf data into.
+ * @param fdfDocument The FDF document to get the data from.
+ *
+ * @throws IOException If there is an error setting the data in the field.
+ */
+ public void importFDF( PDDocument pdfDocument, FDFDocument fdfDocument ) throws IOException
+ {
+ PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
+ PDAcroForm acroForm = docCatalog.getAcroForm();
+ acroForm.setCacheFields( true );
+ acroForm.importFDF( fdfDocument );
+ }
+
+ /**
+ * This will import an fdf document and write out another pdf.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args command line arguments
+ *
+ * @throws Exception If there is an error importing the FDF document.
+ */
+ public static void main(String[] args) throws Exception
+ {
+ ImportFDF importer = new ImportFDF();
+ importer.importFDF( args );
+ }
+
+ private void importFDF( String[] args ) throws Exception
+ {
+ PDDocument pdf = null;
+ FDFDocument fdf = null;
+
+ try
+ {
+ if( args.length != 3 )
+ {
+ usage();
+ }
+ else
+ {
+ ImportFDF importer = new ImportFDF();
+
+ pdf = PDDocument.load( args[0] );
+ fdf = FDFDocument.load( args[1] );
+ importer.importFDF( pdf, fdf );
+
+ pdf.save( args[2] );
+ }
+ }
+ finally
+ {
+ close( fdf );
+ close( pdf );
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: org.apache.pdfbox.ImportFDF <pdf-file> <fdf-file> <output-file>" );
+ }
+
+ /**
+ * Close the document.
+ *
+ * @param doc The doc to close.
+ *
+ * @throws IOException If there is an error closing the document.
+ */
+ public void close( FDFDocument doc ) throws IOException
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+
+ /**
+ * Close the document.
+ *
+ * @param doc The doc to close.
+ *
+ * @throws IOException If there is an error closing the document.
+ */
+ public void close( PDDocument doc ) throws IOException
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+
+import java.io.IOException;
+
+
+/**
+ * This example will take a PDF document and fill the fields with data from the
+ * XFDF fields.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ImportXFDF
+{
+ /**
+ * Creates a new instance of ImportFDF.
+ */
+ public ImportXFDF()
+ {
+ }
+
+ /**
+ * This will takes the values from the fdf document and import them into the
+ * PDF document.
+ *
+ * @param pdfDocument The document to put the fdf data into.
+ * @param fdfDocument The FDF document to get the data from.
+ *
+ * @throws IOException If there is an error setting the data in the field.
+ */
+ public void importFDF( PDDocument pdfDocument, FDFDocument fdfDocument ) throws IOException
+ {
+ PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
+ PDAcroForm acroForm = docCatalog.getAcroForm();
+ acroForm.setCacheFields( true );
+ acroForm.importFDF( fdfDocument );
+ }
+
+ /**
+ * This will import an fdf document and write out another pdf.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args command line arguments
+ *
+ * @throws Exception If there is an error importing the FDF document.
+ */
+ public static void main(String[] args) throws Exception
+ {
+ ImportXFDF importer = new ImportXFDF();
+ importer.importXFDF( args );
+ }
+
+ private void importXFDF( String[] args ) throws Exception
+ {
+ PDDocument pdf = null;
+ FDFDocument fdf = null;
+
+ try
+ {
+ if( args.length != 3 )
+ {
+ usage();
+ }
+ else
+ {
+ ImportFDF importer = new ImportFDF();
+ pdf = PDDocument.load( args[0] );
+ fdf = FDFDocument.loadXFDF( args[1] );
+
+ importer.importFDF( pdf, fdf );
+ pdf.save( args[2] );
+ fdf.save( "tmp/outputXFDFtoPDF.fdf");
+ }
+ }
+ finally
+ {
+ close( fdf );
+ close( pdf );
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: org.apache.pdfbox.ImportXFDF <pdf-file> <fdf-file> <output-file>" );
+ }
+
+ /**
+ * Close the document.
+ *
+ * @param doc The doc to close.
+ *
+ * @throws IOException If there is an error closing the document.
+ */
+ public void close( FDFDocument doc ) throws IOException
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+
+ /**
+ * Close the document.
+ *
+ * @param doc The doc to close.
+ *
+ * @throws IOException If there is an error closing the document.
+ */
+ public void close( PDDocument doc ) throws IOException
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+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.COSStream;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdfparser.PDFParser;
+import org.apache.pdfbox.pdfwriter.COSWriter;
+
+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 java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeMap;
+import java.util.Map;
+
+/**
+ * Overlay on document with another one.<br>
+ * e.g. Overlay an invoice with your company layout<br>
+ * <br>
+ * How it (should) work:<br>
+ * If the document has 10 pages, and the layout 2 the following is the result:<br>
+ * <pre>
+ * Document: 1234567890
+ * Layout : 1212121212
+ * </pre>
+ * <br>
+ *
+ * @author Mario Ivankovits (mario@ops.co.at)
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ *
+ * @version $Revision: 1.7 $
+ */
+public class Overlay
+{
+ /**
+ * @deprecated use the {@link COSName#XOBJECT} constant instead
+ */
+ public static final COSName XOBJECT = COSName.XOBJECT;
+
+ /**
+ * @deprecated use the {@link COSName#PROC_SET} constant instead
+ */
+ public static final COSName PROC_SET = COSName.PROC_SET;
+
+ /**
+ * @deprecated use the {@link COSName#EXT_G_STATE} constant instead
+ */
+ public static final COSName EXT_G_STATE = COSName.EXT_G_STATE;
+
+ private List layoutPages = new ArrayList(10);
+
+ private PDDocument pdfOverlay;
+ private PDDocument pdfDocument;
+ private int pageCount = 0;
+ private COSStream saveGraphicsStateStream;
+ private COSStream restoreGraphicsStateStream;
+
+ /**
+ * This will overlay a document and write out the results.<br/><br/>
+ *
+ * usage: java org.apache.pdfbox.Overlay <overlay.pdf> <document.pdf> <result.pdf>
+ *
+ * @param args The command line arguments.
+ *
+ * @throws IOException If there is an error reading/writing the document.
+ * @throws COSVisitorException If there is an error writing the document.
+ */
+ public static void main( String[] args ) throws IOException, COSVisitorException
+ {
+ if( args.length != 3 )
+ {
+ usage();
+ System.exit(1);
+ }
+ else
+ {
+ PDDocument overlay = null;
+ PDDocument pdf = null;
+
+ try
+ {
+ overlay = getDocument( args[0] );
+ pdf = getDocument( args[1] );
+ Overlay overlayer = new Overlay();
+ overlayer.overlay( overlay, pdf );
+ writeDocument( pdf, args[2] );
+ }
+ finally
+ {
+ if( overlay != null )
+ {
+ overlay.close();
+ }
+ if( pdf != null )
+ {
+ pdf.close();
+ }
+ }
+ }
+ }
+
+ private static void writeDocument( PDDocument pdf, String filename ) throws IOException, COSVisitorException
+ {
+ FileOutputStream output = null;
+ COSWriter writer = null;
+ try
+ {
+ output = new FileOutputStream( filename );
+ writer = new COSWriter( output );
+ writer.write( pdf );
+ }
+ finally
+ {
+ if( writer != null )
+ {
+ writer.close();
+ }
+ if( output != null )
+ {
+ output.close();
+ }
+ }
+ }
+
+ private static PDDocument getDocument( String filename ) throws IOException
+ {
+ FileInputStream input = null;
+ PDFParser parser = null;
+ PDDocument result = null;
+ try
+ {
+ input = new FileInputStream( filename );
+ parser = new PDFParser( input );
+ parser.parse();
+ result = parser.getPDDocument();
+ }
+ finally
+ {
+ if( input != null )
+ {
+ input.close();
+ }
+ }
+ return result;
+ }
+
+ private static void usage()
+ {
+ System.err.println( "usage: java -jar pdfbox-app-x.y.z.jar Overlay <overlay.pdf> <document.pdf> <result.pdf>" );
+ }
+
+ /**
+ * Private class.
+ */
+ private static class LayoutPage
+ {
+ private final COSBase contents;
+ private final COSDictionary res;
+ private final Map objectNameMap;
+
+ /**
+ * Constructor.
+ *
+ * @param contentsValue The contents.
+ * @param resValue The resource dictionary
+ * @param objectNameMapValue The map
+ */
+ public LayoutPage(COSBase contentsValue, COSDictionary resValue, Map objectNameMapValue)
+ {
+ contents = contentsValue;
+ res = resValue;
+ objectNameMap = objectNameMapValue;
+ }
+ }
+
+ /**
+ * This will overlay two documents onto each other. The overlay document is
+ * repeatedly overlayed onto the destination document for every page in the
+ * destination.
+ *
+ * @param overlay The document to copy onto the destination
+ * @param destination The file that the overlay should be placed on.
+ *
+ * @return The destination pdf, same as argument passed in.
+ *
+ * @throws IOException If there is an error accessing data.
+ */
+ public PDDocument overlay( PDDocument overlay, PDDocument destination ) throws IOException
+ {
+ pdfOverlay = overlay;
+ pdfDocument = destination;
+
+ PDDocumentCatalog overlayCatalog = pdfOverlay.getDocumentCatalog();
+ collectLayoutPages( overlayCatalog.getAllPages() );
+
+ COSDictionary saveGraphicsStateDic = new COSDictionary();
+ saveGraphicsStateStream = new COSStream( saveGraphicsStateDic, pdfDocument.getDocument().getScratchFile() );
+ OutputStream saveStream = saveGraphicsStateStream.createUnfilteredStream();
+ saveStream.write( " q\n".getBytes("ISO-8859-1") );
+ saveStream.flush();
+
+ restoreGraphicsStateStream = new COSStream( saveGraphicsStateDic, pdfDocument.getDocument().getScratchFile() );
+ OutputStream restoreStream = restoreGraphicsStateStream.createUnfilteredStream();
+ restoreStream.write( " Q\n".getBytes("ISO-8859-1") );
+ restoreStream.flush();
+
+
+ PDDocumentCatalog pdfCatalog = pdfDocument.getDocumentCatalog();
+ processPages( pdfCatalog.getAllPages() );
+
+ return pdfDocument;
+ }
+
+ private void collectLayoutPages( List pages) throws IOException
+ {
+ Iterator pagesIter = pages.iterator();
+ while( pagesIter.hasNext() )
+ {
+ PDPage page = (PDPage)pagesIter.next();
+ COSBase contents = page.getCOSDictionary().getDictionaryObject( COSName.CONTENTS );
+ PDResources resources = page.findResources();
+ if( resources == null )
+ {
+ resources = new PDResources();
+ page.setResources( resources );
+ }
+ COSDictionary res = resources.getCOSDictionary();
+
+ if( contents instanceof COSStream )
+ {
+ COSStream stream = (COSStream) contents;
+ Map objectNameMap = new TreeMap();
+ stream = makeUniqObjectNames(objectNameMap, stream);
+
+ layoutPages.add(new LayoutPage(stream, res, objectNameMap));
+ }
+ else if( contents instanceof COSArray )
+ {
+ throw new UnsupportedOperationException("Layout pages with COSArray currently not supported.");
+ // layoutPages.add(new LayoutPage(contents, res));
+ }
+ else
+ {
+ throw new IOException( "Contents are unknown type:" + contents.getClass().getName() );
+ }
+ }
+ }
+
+ private COSStream makeUniqObjectNames(Map objectNameMap, COSStream stream) throws IOException
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(10240);
+
+ byte[] buf = new byte[10240];
+ int read;
+ InputStream is = stream.getUnfilteredStream();
+ while ((read = is.read(buf)) > -1)
+ {
+ baos.write(buf, 0, read);
+ }
+
+ buf = baos.toByteArray();
+ baos = new ByteArrayOutputStream(buf.length + 100);
+ StringBuffer sbObjectName = new StringBuffer(10);
+ boolean bInObjectIdent = false;
+ boolean bInText = false;
+ boolean bInEscape = false;
+ for (int i = 0; i<buf.length; i++)
+ {
+ byte b = buf[i];
+
+ if (!bInEscape)
+ {
+ if (!bInText && b == '(')
+ {
+ bInText = true;
+ }
+ if (bInText && b == ')')
+ {
+ bInText = false;
+ }
+ if (b == '\\')
+ {
+ bInEscape = true;
+ }
+
+ if (!bInText && !bInEscape)
+ {
+ if (b == '/')
+ {
+ bInObjectIdent = true;
+ }
+ else if (bInObjectIdent && Character.isWhitespace((char) b))
+ {
+ bInObjectIdent = false;
+
+ // System.err.println(sbObjectName);
+ // String object = sbObjectName.toString();
+
+ String objectName = sbObjectName.toString().substring(1);
+ String newObjectName = objectName + "overlay";
+ baos.write('/');
+ baos.write(newObjectName.getBytes("ISO-8859-1"));
+
+ objectNameMap.put(objectName, COSName.getPDFName(newObjectName));
+
+ sbObjectName.delete(0, sbObjectName.length());
+ }
+ }
+
+ if (bInObjectIdent)
+ {
+ sbObjectName.append((char) b);
+ continue;
+ }
+ }
+ else
+ {
+ bInEscape = false;
+ }
+
+ baos.write(b);
+ }
+
+ COSDictionary streamDict = new COSDictionary();
+ streamDict.setInt(COSName.LENGTH, baos.size());
+ COSStream output = new COSStream(streamDict, pdfDocument.getDocument().getScratchFile());
+ output.setFilters(stream.getFilters());
+ OutputStream os = output.createUnfilteredStream();
+ baos.writeTo(os);
+ os.close();
+
+ return output;
+ }
+
+ private void processPages( List pages ) throws IOException
+ {
+ Iterator pageIter = pages.iterator();
+ while( pageIter.hasNext() )
+ {
+ PDPage page = (PDPage)pageIter.next();
+ COSDictionary pageDictionary = page.getCOSDictionary();
+ COSBase contents = pageDictionary.getDictionaryObject( COSName.CONTENTS );
+ if( contents instanceof COSStream )
+ {
+ COSStream contentsStream = (COSStream)contents;
+ // System.err.println("stream");
+
+ COSArray array = new COSArray();
+
+ array.add(contentsStream);
+
+ mergePage( array, page );
+
+ pageDictionary.setItem(COSName.CONTENTS, array);
+ }
+ else if( contents instanceof COSArray )
+ {
+ COSArray contentsArray = (COSArray)contents;
+
+ mergePage( contentsArray, page );
+ }
+ else
+ {
+ throw new IOException( "Contents are unknown type:" + contents.getClass().getName() );
+ }
+ pageCount++;
+ }
+ }
+
+ private void mergePage(COSArray array, PDPage page )
+ {
+ int layoutPageNum = pageCount % layoutPages.size();
+ LayoutPage layoutPage = (LayoutPage) layoutPages.get(layoutPageNum);
+ PDResources resources = page.findResources();
+ if( resources == null )
+ {
+ resources = new PDResources();
+ page.setResources( resources );
+ }
+ COSDictionary docResDict = resources.getCOSDictionary();
+ COSDictionary layoutResDict = layoutPage.res;
+ mergeArray(COSName.PROC_SET, docResDict, layoutResDict);
+ mergeDictionary(COSName.FONT, docResDict, layoutResDict, layoutPage.objectNameMap);
+ mergeDictionary(COSName.XOBJECT, docResDict, layoutResDict, layoutPage.objectNameMap);
+ mergeDictionary(COSName.EXT_G_STATE, docResDict, layoutResDict, layoutPage.objectNameMap);
+
+ //we are going to wrap the existing content around some save/restore
+ //graphics state, so the result is
+ //
+ //<save graphics state>
+ //<all existing content streams>
+ //<restore graphics state>
+ //<overlay content>
+ array.add(0, saveGraphicsStateStream );
+ array.add( restoreGraphicsStateStream );
+ array.add(layoutPage.contents);
+ }
+
+ /**
+ * merges two dictionaries.
+ *
+ * @param dest
+ * @param source
+ */
+ private void mergeDictionary(COSName name, COSDictionary dest, COSDictionary source, Map objectNameMap)
+ {
+ COSDictionary destDict = (COSDictionary) dest.getDictionaryObject(name);
+ COSDictionary sourceDict = (COSDictionary) source.getDictionaryObject(name);
+
+ if (destDict == null)
+ {
+ destDict = new COSDictionary();
+ dest.setItem(name, destDict);
+ }
+ if( sourceDict != null )
+ {
+
+ for (Map.Entry<COSName, COSBase> entry : sourceDict.entrySet())
+ {
+ COSName mappedKey = (COSName) objectNameMap.get(entry.getKey().getName());
+ if (mappedKey != null)
+ {
+ destDict.setItem(mappedKey, entry.getValue());
+ }
+ }
+ }
+ }
+
+ /**
+ * merges two arrays.
+ *
+ * @param dest
+ * @param source
+ */
+ private void mergeArray(COSName name, COSDictionary dest, COSDictionary source)
+ {
+ COSArray destDict = (COSArray) dest.getDictionaryObject(name);
+ COSArray sourceDict = (COSArray) source.getDictionaryObject(name);
+
+ if (destDict == null)
+ {
+ destDict = new COSArray();
+ dest.setItem(name, destDict);
+ }
+
+ for (int sourceDictIdx = 0; sourceDict != null && sourceDictIdx<sourceDict.size(); sourceDictIdx++)
+ {
+ COSBase key = sourceDict.get(sourceDictIdx);
+ if (key instanceof COSName)
+ {
+ COSName keyname = (COSName) key;
+
+ boolean bFound = false;
+ for (int destDictIdx = 0; destDictIdx<destDict.size(); destDictIdx++)
+ {
+ COSBase destkey = destDict.get(destDictIdx);
+ if (destkey instanceof COSName)
+ {
+ COSName destkeyname = (COSName) destkey;
+ if (destkeyname.equals(keyname))
+ {
+ bFound = true;
+ break;
+ }
+ }
+ }
+ if (!bFound)
+ {
+ destDict.add(keyname);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+
+/**
+ * Simple wrapper around all the command line utilities included in PDFBox.
+ * Used as the main class in the runnable standalone PDFBox jar.
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-687">PDFBOX-687</a>
+ */
+public class PDFBox {
+
+ public static void main(String[] args) {
+ if (args.length > 0) {
+ String command = args[0];
+ String[] arguments = new String[args.length - 1];
+ System.arraycopy(args, 1, arguments, 0, arguments.length);
+ boolean exitAfterCallingMain = true;
+ try {
+ if (command.equals("ConvertColorspace")) {
+ ConvertColorspace.main(arguments);
+ } else if (command.equals("Decrypt")) {
+ Decrypt.main(arguments);
+ } else if (command.equals("Encrypt")) {
+ Encrypt.main(arguments);
+ } else if (command.equals("ExtractText")) {
+ ExtractText.main(arguments);
+ } else if (command.equals("Overlay")) {
+ Overlay.main(arguments);
+ } else if (command.equals("PrintPDF")) {
+ PrintPDF.main(arguments);
+ } else if (command.equals("PDFDebugger")) {
+ PDFDebugger.main(arguments);
+ exitAfterCallingMain = false;
+ } else if (command.equals("PDFMerger")) {
+ PDFMerger.main(arguments);
+ } else if (command.equals("PDFReader")) {
+ PDFReader.main(arguments);
+ exitAfterCallingMain = false;
+ } else if (command.equals("PDFSplit")) {
+ PDFSplit.main(arguments);
+ } else if (command.equals("PDFToImage")) {
+ PDFToImage.main(arguments);
+ } else if (command.equals("TextToPDF")) {
+ TextToPDF.main(arguments);
+ } else if (command.equals("WriteDecodedDoc")) {
+ WriteDecodedDoc.main(arguments);
+ }
+ else {
+ showMessageAndExit();
+ }
+ if (exitAfterCallingMain) {
+ System.exit(0);
+ }
+ } catch (Exception e) {
+ System.err.println(
+ command + " failed with the following exception:");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+ else {
+ showMessageAndExit();
+ }
+ }
+
+ private static void showMessageAndExit() {
+ System.err.println(
+ "usage: java pdfbox-app-x.y.z.jar <command> <args..>");
+ System.exit(1);
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+import org.apache.pdfbox.pdfviewer.PDFTreeModel;
+import org.apache.pdfbox.pdfviewer.PDFTreeCellRenderer;
+import org.apache.pdfbox.pdfviewer.ArrayEntry;
+import org.apache.pdfbox.pdfviewer.MapEntry;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.util.ExtensionFileFilter;
+
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+//import javax.swing.tree.*;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.JFileChooser;
+import javax.swing.JScrollPane;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ *
+ * @author wurtz
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDFDebugger extends javax.swing.JFrame
+{
+ private File currentDir=new File(".");
+ private PDDocument document = null;
+
+ /**
+ * Constructor.
+ */
+ public PDFDebugger()
+ {
+ initComponents();
+ }
+
+ /**
+ * This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ private void initComponents()
+ {
+ jSplitPane1 = new javax.swing.JSplitPane();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ jTree1 = new javax.swing.JTree();
+ jScrollPane2 = new javax.swing.JScrollPane();
+ jTextPane1 = new javax.swing.JTextPane();
+ menuBar = new javax.swing.JMenuBar();
+ fileMenu = new javax.swing.JMenu();
+ openMenuItem = new javax.swing.JMenuItem();
+ saveMenuItem = new javax.swing.JMenuItem();
+ saveAsMenuItem = new javax.swing.JMenuItem();
+ exitMenuItem = new javax.swing.JMenuItem();
+ editMenu = new javax.swing.JMenu();
+ cutMenuItem = new javax.swing.JMenuItem();
+ copyMenuItem = new javax.swing.JMenuItem();
+ pasteMenuItem = new javax.swing.JMenuItem();
+ deleteMenuItem = new javax.swing.JMenuItem();
+ helpMenu = new javax.swing.JMenu();
+ contentsMenuItem = new javax.swing.JMenuItem();
+ aboutMenuItem = new javax.swing.JMenuItem();
+
+ jTree1.setCellRenderer( new PDFTreeCellRenderer() );
+ jTree1.setModel( null );
+
+ setTitle("PDFBox - PDF Viewer");
+ addWindowListener(new java.awt.event.WindowAdapter()
+ {
+ public void windowClosing(java.awt.event.WindowEvent evt)
+ {
+ exitForm(evt);
+ }
+ });
+
+
+ jScrollPane1.setBorder(new javax.swing.border.BevelBorder(javax.swing.border.BevelBorder.RAISED));
+ jScrollPane1.setPreferredSize(new java.awt.Dimension(300, 500));
+ jTree1.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener()
+ {
+ public void valueChanged(javax.swing.event.TreeSelectionEvent evt)
+ {
+ jTree1ValueChanged(evt);
+ }
+ });
+
+ jScrollPane1.setViewportView(jTree1);
+
+ jSplitPane1.setRightComponent(jScrollPane2);
+
+ jScrollPane2.setPreferredSize(new java.awt.Dimension(300, 500));
+ jScrollPane2.setViewportView(jTextPane1);
+
+ jSplitPane1.setLeftComponent(jScrollPane1);
+
+ JScrollPane documentScroller = new JScrollPane();
+ //documentScroller.setPreferredSize( new Dimension( 300, 500 ) );
+ documentScroller.setViewportView( documentPanel );
+
+ getContentPane().add( jSplitPane1, java.awt.BorderLayout.CENTER );
+
+ fileMenu.setText("File");
+ openMenuItem.setText("Open");
+ openMenuItem.setToolTipText("Open PDF file");
+ openMenuItem.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ openMenuItemActionPerformed(evt);
+ }
+ });
+
+ fileMenu.add(openMenuItem);
+
+ saveMenuItem.setText("Save");
+ //fileMenu.add(saveMenuItem);
+
+ saveAsMenuItem.setText("Save As ...");
+ //fileMenu.add(saveAsMenuItem);
+
+ exitMenuItem.setText("Exit");
+ exitMenuItem.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ exitMenuItemActionPerformed(evt);
+ }
+ });
+
+ fileMenu.add(exitMenuItem);
+
+ menuBar.add(fileMenu);
+
+ editMenu.setText("Edit");
+ cutMenuItem.setText("Cut");
+ editMenu.add(cutMenuItem);
+
+ copyMenuItem.setText("Copy");
+ editMenu.add(copyMenuItem);
+
+ pasteMenuItem.setText("Paste");
+ editMenu.add(pasteMenuItem);
+
+ deleteMenuItem.setText("Delete");
+ editMenu.add(deleteMenuItem);
+
+ //menuBar.add(editMenu);
+
+ helpMenu.setText("Help");
+ contentsMenuItem.setText("Contents");
+ helpMenu.add(contentsMenuItem);
+
+ aboutMenuItem.setText("About");
+ helpMenu.add(aboutMenuItem);
+
+ //menuBar.add(helpMenu);
+
+ setJMenuBar(menuBar);
+
+
+ java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
+ setBounds((screenSize.width-700)/2, (screenSize.height-600)/2, 700, 600);
+ }//GEN-END:initComponents
+
+ private void openMenuItemActionPerformed(java.awt.event.ActionEvent evt)
+ {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setCurrentDirectory(currentDir);
+
+ ExtensionFileFilter pdfFilter = new ExtensionFileFilter(new String[] {"pdf", "PDF"}, "PDF Files");
+ chooser.setFileFilter(pdfFilter);
+ int result = chooser.showOpenDialog(PDFDebugger.this);
+ if (result == JFileChooser.APPROVE_OPTION)
+ {
+ String name = chooser.getSelectedFile().getPath();
+ currentDir = new File(name).getParentFile();
+ try
+ {
+ readPDFFile(name);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }//GEN-LAST:event_openMenuItemActionPerformed
+
+ private void jTree1ValueChanged(javax.swing.event.TreeSelectionEvent evt)
+ {
+ TreePath path = jTree1.getSelectionPath();
+ if (path != null)
+ {
+ try
+ {
+ Object selectedNode = path.getLastPathComponent();
+ String data=convertToString(selectedNode);
+
+
+
+ if (data != null)
+ {
+ jTextPane1.setText(data);
+ }
+ else
+ {
+ jTextPane1.setText( "" );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }//GEN-LAST:event_jTree1ValueChanged
+
+ private String convertToString( Object selectedNode )
+ {
+ String data = null;
+ if(selectedNode instanceof COSBoolean)
+ {
+ data = "" + ((COSBoolean)selectedNode).getValue();
+ }
+ else if( selectedNode instanceof COSFloat )
+ {
+ data = "" + ((COSFloat)selectedNode).floatValue();
+ }
+ else if( selectedNode instanceof COSNull )
+ {
+ data = "null";
+ }
+ else if( selectedNode instanceof COSInteger )
+ {
+ data = "" + ((COSInteger)selectedNode).intValue();
+ }
+ else if( selectedNode instanceof COSName )
+ {
+ data = "" + ((COSName)selectedNode).getName();
+ }
+ else if( selectedNode instanceof COSString )
+ {
+ data = "" + ((COSString)selectedNode).getString();
+ }
+ else if( selectedNode instanceof COSStream )
+ {
+ try
+ {
+ COSStream stream = (COSStream)selectedNode;
+ InputStream ioStream = stream.getUnfilteredStream();
+ ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int amountRead = 0;
+ while( (amountRead = ioStream.read( buffer, 0, buffer.length ) ) != -1 )
+ {
+ byteArray.write( buffer, 0, amountRead );
+ }
+ data = byteArray.toString();
+ }
+ catch( IOException e )
+ {
+ e.printStackTrace();
+ }
+ }
+ else if( selectedNode instanceof MapEntry )
+ {
+ data = convertToString( ((MapEntry)selectedNode).getValue() );
+ }
+ else if( selectedNode instanceof ArrayEntry )
+ {
+ data = convertToString( ((ArrayEntry)selectedNode).getValue() );
+ }
+ return data;
+ }
+
+ private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt)
+ {
+ if( document != null )
+ {
+ try
+ {
+ document.close();
+ }
+ catch( IOException io )
+ {
+ io.printStackTrace();
+ }
+ }
+ System.exit(0);
+ }
+
+ /**
+ * Exit the Application.
+ */
+ private void exitForm(java.awt.event.WindowEvent evt)
+ {
+ if( document != null )
+ {
+ try
+ {
+ document.close();
+ }
+ catch( IOException io )
+ {
+ io.printStackTrace();
+ }
+ }
+ System.exit(0);
+ }
+
+ /**
+ * @param args the command line arguments
+ *
+ * @throws Exception If anything goes wrong.
+ */
+ public static void main(String[] args) throws Exception
+ {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ PDFDebugger viewer = new PDFDebugger();
+ if( args.length >0 )
+ {
+ viewer.readPDFFile( args[0] );
+ }
+ viewer.setVisible(true);
+ }
+
+ private void readPDFFile(String file) throws Exception
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ InputStream input = null;
+ File f = new File( file );
+ input = new FileInputStream(f);
+ document = parseDocument( input );
+ TreeModel model=new PDFTreeModel(document);
+ jTree1.setModel(model);
+ setTitle( "PDFBox - " + f.getAbsolutePath() );
+ /*
+ List pages = document.getDocumentCatalog().getAllPages();
+ for( int i=0; i<pages.size(); i++ )
+ {
+ PageWrapper wrapper = new PageWrapper();
+ wrapper.displayPage( (PDPage)pages.get(i) );
+ documentPanel.add( wrapper.getPanel() );
+ }*/
+ }
+ /**
+ * This will parse a document.
+ *
+ * @param input The input stream for the document.
+ *
+ * @return The document.
+ *
+ * @throws IOException If there is an error parsing the document.
+ */
+ private static PDDocument parseDocument( InputStream input )throws IOException
+ {
+ PDDocument document = PDDocument.load( input );
+ if( document.isEncrypted() )
+ {
+ try
+ {
+ document.decrypt( "" );
+ }
+ catch( InvalidPasswordException e )
+ {
+ System.err.println( "Error: The document is encrypted." );
+ }
+ catch( org.apache.pdfbox.exceptions.CryptographyException e )
+ {
+ e.printStackTrace();
+ }
+ }
+
+ return document;
+ }
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JMenuItem aboutMenuItem;
+ private javax.swing.JMenuItem contentsMenuItem;
+ private javax.swing.JMenuItem copyMenuItem;
+ private javax.swing.JMenuItem cutMenuItem;
+ private javax.swing.JMenuItem deleteMenuItem;
+ private javax.swing.JMenu editMenu;
+ private javax.swing.JMenuItem exitMenuItem;
+ private javax.swing.JMenu fileMenu;
+ private javax.swing.JMenu helpMenu;
+ private javax.swing.JScrollPane jScrollPane1;
+ private javax.swing.JScrollPane jScrollPane2;
+ private javax.swing.JSplitPane jSplitPane1;
+ private javax.swing.JTextPane jTextPane1;
+ private javax.swing.JTree jTree1;
+ private javax.swing.JMenuBar menuBar;
+ private javax.swing.JMenuItem openMenuItem;
+ private javax.swing.JMenuItem pasteMenuItem;
+ private javax.swing.JMenuItem saveAsMenuItem;
+ private javax.swing.JMenuItem saveMenuItem;
+ private JPanel documentPanel = new JPanel();
+ // End of variables declaration//GEN-END:variables
+
+}
--- /dev/null
+/*
+ * 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;
+
+import org.apache.pdfbox.util.PDFMergerUtility;
+
+/**
+ * This is the main program that will take a list of pdf documents and merge them,
+ * saving the result in a new document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDFMerger
+{
+
+ private PDFMerger()
+ {
+ }
+ /**
+ * Infamous main method.
+ *
+ * @param args Command line arguments, should be at least 3.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ PDFMerger merge = new PDFMerger();
+ merge.merge( args );
+ }
+
+ private void merge( String[] args ) throws Exception
+ {
+ String destinationFileName = "";
+ String sourceFileName = null;
+
+ if ( args.length < 3 )
+ {
+ usage();
+ }
+
+ PDFMergerUtility merger = new PDFMergerUtility();
+ for( int i=0; i<args.length-1; i++ )
+ {
+ sourceFileName = args[i];
+ merger.addSource(sourceFileName);
+ }
+
+ destinationFileName = args[args.length-1];
+
+ merger.setDestinationFileName(destinationFileName);
+
+ merger.mergeDocuments();
+ }
+
+ /**
+ * This will print the usage requirements and exit.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java -jar pdfbox-app-x.y.z.jar PDFMerger <Source PDF File 2..n> <Destination PDF File>\n" +
+ " <Source PDF File 2..n> 2 or more source PDF documents to merge\n" +
+ " <Destination PDF File> The PDF document to save the merged documents to\n"
+ );
+ System.exit( 1 );
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.pdfviewer.PageWrapper;
+import org.apache.pdfbox.pdfviewer.ReaderBottomPanel;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.util.ExtensionFileFilter;
+
+import javax.swing.JFileChooser;
+import javax.swing.JScrollPane;
+import javax.swing.JPanel;
+import javax.swing.KeyStroke;
+
+import java.awt.print.PrinterException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * An application to read PDF documents. This will provide Acrobat Reader like
+ * funtionality.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDFReader extends javax.swing.JFrame
+{
+ private File currentDir=new File(".");
+ private javax.swing.JMenuItem aboutMenuItem;
+ private javax.swing.JMenuItem contentsMenuItem;
+ private javax.swing.JMenuItem exitMenuItem;
+ private javax.swing.JMenu fileMenu;
+ private javax.swing.JMenu helpMenu;
+ private javax.swing.JMenuBar menuBar;
+ private javax.swing.JMenuItem openMenuItem;
+ private javax.swing.JMenuItem printMenuItem;
+ private javax.swing.JMenu viewMenu;
+ private javax.swing.JMenuItem nextPageItem;
+ private javax.swing.JMenuItem previousPageItem;
+ private JPanel documentPanel = new JPanel();
+ private ReaderBottomPanel bottomStatusPanel = new ReaderBottomPanel();
+
+ private PDDocument document = null;
+ private List pages= null;
+
+ private int currentPage = 0;
+ private int numberOfPages = 0;
+ private String currentFilename = null;
+
+ /**
+ * Constructor.
+ */
+ public PDFReader()
+ {
+ initComponents();
+ }
+
+ /**
+ * This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ private void initComponents()
+ {
+ menuBar = new javax.swing.JMenuBar();
+ fileMenu = new javax.swing.JMenu();
+ openMenuItem = new javax.swing.JMenuItem();
+ exitMenuItem = new javax.swing.JMenuItem();
+ helpMenu = new javax.swing.JMenu();
+ contentsMenuItem = new javax.swing.JMenuItem();
+ aboutMenuItem = new javax.swing.JMenuItem();
+ printMenuItem = new javax.swing.JMenuItem();
+ viewMenu = new javax.swing.JMenu();
+ nextPageItem = new javax.swing.JMenuItem();
+ previousPageItem = new javax.swing.JMenuItem();
+
+
+ setTitle("PDFBox - PDF Reader");
+ addWindowListener(new java.awt.event.WindowAdapter()
+ {
+ public void windowClosing(java.awt.event.WindowEvent evt)
+ {
+ exitApplication();
+ }
+ });
+
+
+ JScrollPane documentScroller = new JScrollPane();
+ documentScroller.setViewportView( documentPanel );
+
+
+ getContentPane().add( documentScroller, java.awt.BorderLayout.CENTER );
+ getContentPane().add( bottomStatusPanel, java.awt.BorderLayout.SOUTH );
+
+ fileMenu.setText("File");
+ openMenuItem.setText("Open");
+ openMenuItem.setToolTipText("Open PDF file");
+ openMenuItem.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ openMenuItemActionPerformed(evt);
+ }
+ });
+
+ fileMenu.add(openMenuItem);
+
+ printMenuItem.setText( "Print" );
+ printMenuItem.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ try
+ {
+ if (document != null)
+ {
+ document.print();
+ }
+ }
+ catch( PrinterException e )
+ {
+ e.printStackTrace();
+ }
+ }
+ });
+ fileMenu.add( printMenuItem );
+
+ exitMenuItem.setText("Exit");
+ exitMenuItem.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ exitApplication();
+ }
+ });
+
+ fileMenu.add(exitMenuItem);
+
+ menuBar.add(fileMenu);
+
+ helpMenu.setText("Help");
+ contentsMenuItem.setText("Contents");
+ helpMenu.add(contentsMenuItem);
+
+ aboutMenuItem.setText("About");
+ helpMenu.add(aboutMenuItem);
+
+ //menuBar.add(helpMenu);
+
+ viewMenu.setText("View");
+ nextPageItem.setText("Next page");
+ nextPageItem.setAccelerator(KeyStroke.getKeyStroke('+'));
+ nextPageItem.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ nextPage();
+ }
+ });
+ viewMenu.add(nextPageItem);
+
+ previousPageItem.setText("Previous page");
+ previousPageItem.setAccelerator(KeyStroke.getKeyStroke('-'));
+ previousPageItem.addActionListener(new java.awt.event.ActionListener()
+ {
+ public void actionPerformed(java.awt.event.ActionEvent evt)
+ {
+ previousPage();
+ }
+ });
+ viewMenu.add(previousPageItem);
+
+ menuBar.add(viewMenu);
+
+ setJMenuBar(menuBar);
+
+
+ java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
+ setBounds((screenSize.width-700)/2, (screenSize.height-600)/2, 700, 600);
+ }
+
+
+ private void updateTitle() {
+ setTitle( "PDFBox - " + currentFilename + " ("+(currentPage+1)+"/"+numberOfPages+")");
+ }
+
+ private void nextPage()
+ {
+ if (currentPage < numberOfPages-1)
+ {
+ currentPage++;
+ updateTitle();
+ showPage(currentPage);
+ }
+ }
+
+ private void previousPage()
+ {
+ if (currentPage > 0 )
+ {
+ currentPage--;
+ updateTitle();
+ showPage(currentPage);
+ }
+ }
+
+ private void openMenuItemActionPerformed(java.awt.event.ActionEvent evt)
+ {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setCurrentDirectory(currentDir);
+
+ ExtensionFileFilter pdfFilter = new ExtensionFileFilter(new String[] {"PDF"}, "PDF Files");
+ chooser.setFileFilter(pdfFilter);
+ int result = chooser.showOpenDialog(PDFReader.this);
+ if (result == JFileChooser.APPROVE_OPTION)
+ {
+ String name = chooser.getSelectedFile().getPath();
+ currentDir = new File(name).getParentFile();
+ try
+ {
+ openPDFFile(name);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void exitApplication()
+ {
+ try
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ catch( IOException io )
+ {
+ //do nothing because we are closing the application
+ }
+ this.setVisible( false );
+ this.dispose();
+ }
+
+ /**
+ * @param args the command line arguments
+ *
+ * @throws Exception If anything goes wrong.
+ */
+ public static void main(String[] args) throws Exception
+ {
+ PDFReader viewer = new PDFReader();
+ if( args.length >0 )
+ {
+ viewer.openPDFFile( args[0] );
+ }
+ viewer.setVisible(true);
+ }
+
+ private void openPDFFile(String file) throws Exception
+ {
+ if( document != null )
+ {
+ document.close();
+ documentPanel.removeAll();
+ }
+ InputStream input = null;
+ File f = new File( file );
+ input = new FileInputStream(f);
+ document = parseDocument( input );
+ pages = document.getDocumentCatalog().getAllPages();
+ numberOfPages = pages.size();
+ currentFilename = f.getAbsolutePath();
+ currentPage = 0;
+ updateTitle();
+ showPage(0);
+ }
+
+ private void showPage(int pageNumber)
+ {
+ try
+ {
+ PageWrapper wrapper = new PageWrapper( this );
+ wrapper.displayPage( (PDPage)pages.get(pageNumber) );
+ if (documentPanel.getComponentCount() > 0)
+ {
+ documentPanel.remove(0);
+ }
+ documentPanel.add( wrapper.getPanel() );
+ pack();
+ }
+ catch (IOException exception)
+ {
+ exception.printStackTrace();
+ }
+ }
+ /**
+ * This will parse a document.
+ *
+ * @param input The input stream for the document.
+ *
+ * @return The document.
+ *
+ * @throws IOException If there is an error parsing the document.
+ */
+ private static PDDocument parseDocument( InputStream input )throws IOException
+ {
+ PDDocument document = PDDocument.load( input );
+ if( document.isEncrypted() )
+ {
+ try
+ {
+ document.decrypt( "" );
+ }
+ catch( InvalidPasswordException e )
+ {
+ System.err.println( "Error: The document is encrypted." );
+ }
+ catch( org.apache.pdfbox.exceptions.CryptographyException e )
+ {
+ e.printStackTrace();
+ }
+ }
+
+ return document;
+ }
+
+ /**
+ * Get the bottom status panel.
+ *
+ * @return The bottom status panel.
+ */
+ public ReaderBottomPanel getBottomStatusPanel()
+ {
+ return bottomStatusPanel;
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+import java.util.List;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdfparser.PDFParser;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+
+import org.apache.pdfbox.util.Splitter;
+
+/**
+ * This is the main program that will take a pdf document and split it into
+ * a number of other documents.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDFSplit
+{
+ private static final String PASSWORD = "-password";
+ private static final String SPLIT = "-split";
+
+ private PDFSplit()
+ {
+ }
+ /**
+ * Infamous main method.
+ *
+ * @param args Command line arguments, should be one and a reference to a file.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ PDFSplit split = new PDFSplit();
+ split.split( args );
+ }
+
+ private void split( String[] args ) throws Exception
+ {
+ String password = "";
+ String split = "1";
+
+ Splitter splitter = new Splitter();
+ String pdfFile = null;
+ for( int i=0; i<args.length; i++ )
+ {
+ if( args[i].equals( PASSWORD ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ password = args[i];
+ }
+ else if( args[i].equals( SPLIT ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ split = args[i];
+ }
+ else
+ {
+ if( pdfFile == null )
+ {
+ pdfFile = args[i];
+ }
+ }
+ }
+
+ if( pdfFile == null )
+ {
+ usage();
+ }
+ else
+ {
+
+ InputStream input = null;
+ PDDocument document = null;
+ List documents = null;
+ try
+ {
+ input = new FileInputStream( pdfFile );
+ document = parseDocument( input );
+
+ if( document.isEncrypted() )
+ {
+ try
+ {
+ document.decrypt( password );
+ }
+ catch( InvalidPasswordException e )
+ {
+ if( args.length == 4 )//they supplied the wrong password
+ {
+ System.err.println( "Error: The supplied password is incorrect." );
+ System.exit( 2 );
+ }
+ else
+ {
+ //they didn't suppply a password and the default of "" was wrong.
+ System.err.println( "Error: The document is encrypted." );
+ usage();
+ }
+ }
+ }
+
+ splitter.setSplitAtPage( Integer.parseInt( split ) );
+ documents = splitter.split( document );
+ for( int i=0; i<documents.size(); i++ )
+ {
+ PDDocument doc = (PDDocument)documents.get( i );
+ String fileName = pdfFile.substring(0, pdfFile.length()-4 ) + "-" + i + ".pdf";
+ writeDocument( doc, fileName );
+ doc.close();
+ }
+
+ }
+ finally
+ {
+ if( input != null )
+ {
+ input.close();
+ }
+ if( document != null )
+ {
+ document.close();
+ }
+ for( int i=0; documents != null && i<documents.size(); i++ )
+ {
+ PDDocument doc = (PDDocument)documents.get( i );
+ doc.close();
+ }
+ }
+ }
+ }
+
+ private static final void writeDocument( PDDocument doc, String fileName ) throws IOException, COSVisitorException
+ {
+ FileOutputStream output = null;
+ COSWriter writer = null;
+ try
+ {
+ output = new FileOutputStream( fileName );
+ writer = new COSWriter( output );
+ writer.write( doc );
+ }
+ finally
+ {
+ if( output != null )
+ {
+ output.close();
+ }
+ if( writer != null )
+ {
+ writer.close();
+ }
+ }
+ }
+
+ /**
+ * This will parse a document.
+ *
+ * @param input The input stream for the document.
+ *
+ * @return The document.
+ *
+ * @throws IOException If there is an error parsing the document.
+ */
+ private static PDDocument parseDocument( InputStream input )throws IOException
+ {
+ PDFParser parser = new PDFParser( input );
+ parser.parse();
+ return parser.getPDDocument();
+ }
+
+ /**
+ * This will print the usage requirements and exit.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java -jar pdfbox-app-x.y.z.jar PDFSplit [OPTIONS] <PDF file>\n" +
+ " -password <password> Password to decrypt document\n" +
+ " -split <integer> split after this many pages\n" +
+ " <PDF file> The PDF document to use\n"
+ );
+ System.exit( 1 );
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.awt.HeadlessException;
+import java.awt.Toolkit;
+import java.awt.image.BufferedImage;
+
+import javax.imageio.ImageIO;
+
+import java.util.List;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.util.PDFImageWriter;
+
+/**
+ * Convert a PDF document to an image.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDFToImage
+{
+
+ private static final String PASSWORD = "-password";
+ private static final String START_PAGE = "-startPage";
+ private static final String END_PAGE = "-endPage";
+ private static final String IMAGE_FORMAT = "-imageType";
+ private static final String OUTPUT_PREFIX = "-outputPrefix";
+ private static final String COLOR = "-color";
+ private static final String RESOLUTION = "-resolution";
+ private static final String CROPBOX = "-cropbox";
+
+ /**
+ * private constructor.
+ */
+ private PDFToImage()
+ {
+ //static class
+ }
+
+ /**
+ * Infamous main method.
+ *
+ * @param args Command line arguments, should be one and a reference to a file.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ String password = "";
+ String pdfFile = null;
+ String outputPrefix = null;
+ String imageFormat = "jpg";
+ int startPage = 1;
+ int endPage = Integer.MAX_VALUE;
+ String color = "rgb";
+ int resolution;
+ float cropBoxLowerLeftX = 0;
+ float cropBoxLowerLeftY = 0;
+ float cropBoxUpperRightX = 0;
+ float cropBoxUpperRightY = 0;
+ try
+ {
+ resolution = Toolkit.getDefaultToolkit().getScreenResolution();
+ }
+ catch( HeadlessException e )
+ {
+ resolution = 96;
+ }
+ for( int i = 0; i < args.length; i++ )
+ {
+ if( args[i].equals( PASSWORD ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ password = args[i];
+ }
+ else if( args[i].equals( START_PAGE ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ startPage = Integer.parseInt( args[i] );
+ }
+ else if( args[i].equals( END_PAGE ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ endPage = Integer.parseInt( args[i] );
+ }
+ else if( args[i].equals( IMAGE_FORMAT ) )
+ {
+ i++;
+ imageFormat = args[i];
+ }
+ else if( args[i].equals( OUTPUT_PREFIX ) )
+ {
+ i++;
+ outputPrefix = args[i];
+ }
+ else if( args[i].equals( COLOR ) )
+ {
+ i++;
+ color = args[i];
+ }
+ else if( args[i].equals( RESOLUTION ) )
+ {
+ i++;
+ resolution = Integer.parseInt(args[i]);
+ }
+ else if( args[i].equals( CROPBOX ) )
+ {
+ i++;
+ cropBoxLowerLeftX = Float.valueOf(args[i]).floatValue();
+ i++;
+ cropBoxLowerLeftY = Float.valueOf(args[i]).floatValue();
+ i++;
+ cropBoxUpperRightX = Float.valueOf(args[i]).floatValue();
+ i++;
+ cropBoxUpperRightY = Float.valueOf(args[i]).floatValue();
+ }
+ else
+ {
+ if( pdfFile == null )
+ {
+ pdfFile = args[i];
+ }
+ }
+ }
+ if( pdfFile == null )
+ {
+ usage();
+ }
+ else
+ {
+ if(outputPrefix == null)
+ {
+ outputPrefix = pdfFile.substring( 0, pdfFile.lastIndexOf( '.' ));
+ }
+
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( pdfFile );
+
+
+ //document.print();
+ if( document.isEncrypted() )
+ {
+ try
+ {
+ document.decrypt( password );
+ }
+ catch( InvalidPasswordException e )
+ {
+ if( args.length == 4 )//they supplied the wrong password
+ {
+ System.err.println( "Error: The supplied password is incorrect." );
+ System.exit( 2 );
+ }
+ else
+ {
+ //they didn't supply a password and the default of "" was wrong.
+ System.err.println( "Error: The document is encrypted." );
+ usage();
+ }
+ }
+ }
+ int imageType = 24;
+ if ("bilevel".equalsIgnoreCase(color))
+ {
+ imageType = BufferedImage.TYPE_BYTE_BINARY;
+ }
+ else if ("indexed".equalsIgnoreCase(color))
+ {
+ imageType = BufferedImage.TYPE_BYTE_INDEXED;
+ }
+ else if ("gray".equalsIgnoreCase(color))
+ {
+ imageType = BufferedImage.TYPE_BYTE_GRAY;
+ }
+ else if ("rgb".equalsIgnoreCase(color))
+ {
+ imageType = BufferedImage.TYPE_INT_RGB;
+ }
+ else if ("rgba".equalsIgnoreCase(color))
+ {
+ imageType = BufferedImage.TYPE_INT_ARGB;
+ }
+ else
+ {
+ System.err.println( "Error: the number of bits per pixel must be 1, 8 or 24." );
+ System.exit( 2 );
+ }
+
+ //si une cropBox a ete specifier, appeler la methode de modification de cropbox
+ //changeCropBoxes(PDDocument document,float a, float b, float c,float d)
+ if ( cropBoxLowerLeftX!=0 || cropBoxLowerLeftY!=0 || cropBoxUpperRightX!=0 || cropBoxUpperRightY!=0 )
+ {
+ changeCropBoxes(document,cropBoxLowerLeftX, cropBoxLowerLeftY, cropBoxUpperRightX, cropBoxUpperRightY);
+ }
+
+ //Make the call
+ PDFImageWriter imageWriter = new PDFImageWriter();
+ boolean success = imageWriter.writeImage(document, imageFormat, password,
+ startPage, endPage, outputPrefix, imageType, resolution);
+ if (!success)
+ {
+ System.err.println( "Error: no writer found for image format '"
+ + imageFormat + "'" );
+ System.exit(1);
+ }
+ }
+ catch (Exception e)
+ {
+ System.err.println(e);
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage requirements and exit.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java -jar pdfbox-app-x.y.z.jar PDFToImage [OPTIONS] <PDF file>\n" +
+ " -password <password> Password to decrypt document\n" +
+ " -imageType <image type> (" + getImageFormats() + ")\n" +
+ " -outputPrefix <output prefix> Filename prefix for image files\n" +
+ " -startPage <number> The first page to start extraction(1 based)\n" +
+ " -endPage <number> The last page to extract(inclusive)\n" +
+ " -color <string> The color depth (valid: bilevel, indexed, gray, rgb, rgba)\n" +
+ " -resolution <number> The bitmap resolution in dpi\n" +
+ " -cropbox <number> <number> <number> <number> The page area to export\n" +
+ " <PDF file> The PDF document to use\n"
+ );
+ System.exit( 1 );
+ }
+
+ private static String getImageFormats()
+ {
+ StringBuffer retval = new StringBuffer();
+ String[] formats = ImageIO.getReaderFormatNames();
+ for( int i = 0; i < formats.length; i++ )
+ {
+ retval.append( formats[i] );
+ if( i + 1 < formats.length )
+ {
+ retval.append( "," );
+ }
+ }
+ return retval.toString();
+ }
+
+ private static void changeCropBoxes(PDDocument document,float a, float b, float c,float d)
+ {
+ List pages = document.getDocumentCatalog().getAllPages();
+ for( int i = 0; i < pages.size(); i++ )
+ {
+ System.out.println("resizing page");
+ PDPage page = (PDPage)pages.get( i );
+ PDRectangle rectangle = new PDRectangle();
+ rectangle.setLowerLeftX(a);
+ rectangle.setLowerLeftY(b);
+ rectangle.setUpperRightX(c);
+ rectangle.setUpperRightY(d);
+ page.setMediaBox(rectangle);
+ page.setCropBox(rectangle);
+
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2010 adam.
+ *
+ * Licensed 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.
+ * under the License.
+ */
+
+package org.apache.pdfbox;
+
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdfparser.PDFObjectStreamParser;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This program will just take all of the stream objects in a PDF and dereference
+ * them. The streams will be gone in the resulting file and the objects will be
+ * present. This is very helpful when trying to debug problems as it'll make
+ * it possible to easily look through a PDF using a text editor. It also exposes
+ * problems which stem from objects inside object streams overwriting other
+ * objects.
+ * @author <a href="adam@apache.org">Adam Nichols</a>
+ */
+public class PdfDecompressor {
+
+ /**
+ * This is a very simple program, so everything is in the main method.
+ * @param args arguments to the program
+ */
+ public static void main(String[] args) {
+ if(args.length < 1)
+ usage();
+
+ String inputFilename = args[0];
+ String outputFilename;
+ if(args.length > 1) {
+ outputFilename = args[1];
+ } else {
+ if(inputFilename.matches(".*\\.[pP][dD][fF]$"))
+ outputFilename = inputFilename.replaceAll("\\.[pP][dD][fF]$", ".unc.pdf");
+ else
+ outputFilename = inputFilename + ".unc.pdf";
+ }
+
+ PDDocument doc = null;
+ try {
+ doc = PDDocument.load(inputFilename);
+ for(COSObject objStream : doc.getDocument().getObjectsByType("ObjStm")) {
+ COSStream stream = (COSStream)objStream.getObject();
+ PDFObjectStreamParser sp = new PDFObjectStreamParser(stream, doc.getDocument());
+ sp.parse();
+ for(COSObject next : sp.getObjects()) {
+ COSObjectKey key = new COSObjectKey(next);
+ COSObject obj = doc.getDocument().getObjectFromPool(key);
+ obj.setObject(next.getObject());
+ }
+ doc.getDocument().removeObject(new COSObjectKey(objStream));
+ }
+ doc.save(outputFilename);
+ } catch(Exception e) {
+ System.out.println("Error processing file: " + e.getMessage());
+ } finally {
+ if(doc != null)
+ try { doc.close(); } catch(Exception e) { }
+ }
+ }
+
+ /**
+ * Explains how to use the program.
+ */
+ private static void usage() {
+ System.err.println( "Usage: java -cp /path/to/pdfbox.jar;/path/to/commons-logging-api.jar "
+ + "org.apache.pdfbox.PdfDecompressor <input PDF File> [<Output PDF File>]\n"
+ + " <input PDF File> The PDF document to decompress\n"
+ + " <output PDF File> The output filename (default is to replace .pdf with .unc.pdf)");
+ System.exit(1);
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.awt.print.PrinterJob;
+
+import javax.print.PrintService;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import java.io.File;
+
+/**
+ * This is a command line program that will print a PDF document.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PrintPDF
+{
+
+ private static final String PASSWORD = "-password";
+ private static final String SILENT = "-silentPrint";
+ private static final String PRINTER_NAME = "-printerName";
+
+ /**
+ * private constructor.
+ */
+ private PrintPDF()
+ {
+ //static class
+ }
+
+ /**
+ * Infamous main method.
+ *
+ * @param args Command line arguments, should be one and a reference to a file.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ String password = "";
+ String pdfFile = null;
+ boolean silentPrint = false;
+ String printerName = null;
+ for( int i=0; i<args.length; i++ )
+ {
+ if( args[i].equals( PASSWORD ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ password = args[i];
+ }
+ else if( args[i].equals( PRINTER_NAME ) )
+ {
+ i++;
+ if( i >= args.length )
+ {
+ usage();
+ }
+ printerName = args[i];
+ }
+ else if( args[i].equals( SILENT ) )
+ {
+ silentPrint = true;
+ }
+ else
+ {
+ pdfFile = args[i];
+ }
+ }
+
+ if( pdfFile == null )
+ {
+ usage();
+ }
+
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( pdfFile );
+
+ if( document.isEncrypted() )
+ {
+ document.decrypt( password );
+ }
+
+ PrinterJob printJob = PrinterJob.getPrinterJob();
+ printJob.setJobName(new File(pdfFile).getName());
+
+ if(printerName != null )
+ {
+ PrintService[] printService = PrinterJob.lookupPrintServices();
+ boolean printerFound = false;
+ for(int i = 0; !printerFound && i < printService.length; i++)
+ {
+ if(printService[i].getName().indexOf(printerName) != -1)
+ {
+ printJob.setPrintService(printService[i]);
+ printerFound = true;
+ }
+ }
+ }
+
+ if( silentPrint )
+ {
+ document.silentPrint( printJob );
+ }
+ else
+ {
+ document.print( printJob );
+ }
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+
+ /**
+ * This will print the usage requirements and exit.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java -jar pdfbox-app-x.y.z.jar PrintPDF [OPTIONS] <PDF file>\n" +
+ " -password <password> Password to decrypt document\n" +
+ " -silentPrint Print without prompting for printer info\n"
+ );
+ System.exit( 1 );
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDSimpleFont;
+import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+
+/**
+ * This will take a text file and ouput a pdf with that text.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class TextToPDF
+{
+ private int fontSize = 10;
+ private PDSimpleFont font = PDType1Font.HELVETICA;
+
+ /**
+ * Create a PDF document with some text.
+ *
+ * @param text The stream of text data.
+ *
+ * @return The document with the text in it.
+ *
+ * @throws IOException If there is an error writing the data.
+ */
+ public PDDocument createPDFFromText( Reader text ) throws IOException
+ {
+ PDDocument doc = null;
+ try
+ {
+
+ final int margin = 40;
+ float height = font.getFontDescriptor().getFontBoundingBox().getHeight()/1000;
+
+ //calculate font height and increase by 5 percent.
+ height = height*fontSize*1.05f;
+ doc = new PDDocument();
+ BufferedReader data = new BufferedReader( text );
+ String nextLine = null;
+ PDPage page = new PDPage();
+ PDPageContentStream contentStream = null;
+ float y = -1;
+ float maxStringLength = page.getMediaBox().getWidth() - 2*margin;
+
+ // There is a special case of creating a PDF document from an empty string.
+ boolean textIsEmpty = true;
+
+ while( (nextLine = data.readLine()) != null )
+ {
+
+ // The input text is nonEmpty. New pages will be created and added
+ // to the PDF document as they are needed, depending on the length of
+ // the text.
+ textIsEmpty = false;
+
+ String[] lineWords = nextLine.trim().split( " " );
+ int lineIndex = 0;
+ while( lineIndex < lineWords.length )
+ {
+ StringBuffer nextLineToDraw = new StringBuffer();
+ float lengthIfUsingNextWord = 0;
+ do
+ {
+ nextLineToDraw.append( lineWords[lineIndex] );
+ nextLineToDraw.append( " " );
+ lineIndex++;
+ if( lineIndex < lineWords.length )
+ {
+ String lineWithNextWord = nextLineToDraw.toString() + lineWords[lineIndex];
+ lengthIfUsingNextWord =
+ (font.getStringWidth( lineWithNextWord )/1000) * fontSize;
+ }
+ }
+ while( lineIndex < lineWords.length &&
+ lengthIfUsingNextWord < maxStringLength );
+ if( y < margin )
+ {
+ // We have crossed the end-of-page boundary and need to extend the
+ // document by another page.
+ page = new PDPage();
+ doc.addPage( page );
+ if( contentStream != null )
+ {
+ contentStream.endText();
+ contentStream.close();
+ }
+ contentStream = new PDPageContentStream(doc, page);
+ contentStream.setFont( font, fontSize );
+ contentStream.beginText();
+ y = page.getMediaBox().getHeight() - margin + height;
+ contentStream.moveTextPositionByAmount(
+ margin, y );
+
+ }
+ //System.out.println( "Drawing string at " + x + "," + y );
+
+ if( contentStream == null )
+ {
+ throw new IOException( "Error:Expected non-null content stream." );
+ }
+ contentStream.moveTextPositionByAmount( 0, -height);
+ y -= height;
+ contentStream.drawString( nextLineToDraw.toString() );
+ }
+
+
+ }
+
+ // If the input text was the empty string, then the above while loop will have short-circuited
+ // and we will not have added any PDPages to the document.
+ // So in order to make the resultant PDF document readable by Adobe Reader etc, we'll add an empty page.
+ if (textIsEmpty)
+ {
+ doc.addPage(page);
+ }
+
+ if( contentStream != null )
+ {
+ contentStream.endText();
+ contentStream.close();
+ }
+ }
+ catch( IOException io )
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ throw io;
+ }
+ return doc;
+ }
+
+ /**
+ * This will create a PDF document with some text in it.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ *
+ * @throws IOException If there is an error with the PDF.
+ */
+ public static void main(String[] args) throws IOException
+ {
+ TextToPDF app = new TextToPDF();
+ PDDocument doc = null;
+ try
+ {
+ if( args.length < 2 )
+ {
+ app.usage();
+ }
+ else
+ {
+ for( int i=0; i<args.length-2; i++ )
+ {
+ if( args[i].equals( "-standardFont" ))
+ {
+ i++;
+ app.setFont( PDType1Font.getStandardFont( args[i] ));
+ }
+ else if( args[i].equals( "-ttf" ))
+ {
+ i++;
+ PDTrueTypeFont font = PDTrueTypeFont.loadTTF( doc, new File( args[i]));
+ app.setFont( font );
+ }
+ else if( args[i].equals( "-fontSize" ))
+ {
+ i++;
+ app.setFontSize( Integer.parseInt( args[i] ) );
+ }
+ else
+ {
+ throw new IOException( "Unknown argument:" + args[i] );
+ }
+ }
+ doc = app.createPDFFromText( new FileReader( args[args.length-1] ) );
+ doc.save( args[args.length-2] );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ String[] std14 = PDType1Font.getStandard14Names();
+ System.err.println( "usage: jar -jar pdfbox-app-x.y.z.jar TextToPDF [options] <output-file> <text-file>" );
+ System.err.println( " -standardFont <name> default:" + PDType1Font.HELVETICA.getBaseFont() );
+ for( int i=0; i<std14.length; i++ )
+ {
+ System.err.println( " " + std14[i] );
+ }
+ System.err.println( " -ttf <ttf file> The TTF font to use.");
+ System.err.println( " -fontSize <fontSize> default:10" );
+
+
+ }
+ /**
+ * @return Returns the font.
+ */
+ public PDSimpleFont getFont()
+ {
+ return font;
+ }
+ /**
+ * @param aFont The font to set.
+ */
+ public void setFont(PDSimpleFont aFont)
+ {
+ this.font = aFont;
+ }
+ /**
+ * @return Returns the fontSize.
+ */
+ public int getFontSize()
+ {
+ return fontSize;
+ }
+ /**
+ * @param aFontSize The fontSize to set.
+ */
+ public void setFontSize(int aFontSize)
+ {
+ this.fontSize = aFontSize;
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+
+import java.util.Properties;
+
+import org.apache.pdfbox.util.ResourceLoader;
+
+
+/**
+ * A simple command line utility to get the version of PDFBox.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class Version
+{
+ private static final String PDFBOX_VERSION_PROPERTIES =
+ "org/apache/pdfbox/resources/pdfbox.properties";
+
+ private Version()
+ {
+ //should not be constructed.
+ }
+
+ /**
+ * Get the version of PDFBox or unknown if it is not known.
+ *
+ * @return The version of pdfbox that is being used.
+ */
+ public static String getVersion()
+ {
+ String version = "unknown";
+ try
+ {
+ Properties props = ResourceLoader.loadProperties( PDFBOX_VERSION_PROPERTIES, false );
+ version = props.getProperty( "pdfbox.version", version );
+ }
+ catch( IOException io )
+ {
+ //if there is a problem loading the properties then don't throw an
+ //exception, 'unknown' will be returned instead.
+ io.printStackTrace();
+ }
+ return version;
+ }
+
+ /**
+ * This will print out the version of PDF to System.out.
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ if( args.length != 0 )
+ {
+ usage();
+ return;
+ }
+ System.out.println( "Version:" + getVersion() );
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: " + Version.class.getName() );
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+/**
+ * load document and write with all streams decoded.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.8 $
+ */
+public class WriteDecodedDoc
+{
+
+ /**
+ * Constructor.
+ */
+ public WriteDecodedDoc()
+ {
+ super();
+ }
+
+ /**
+ * This will perform the document reading, decoding and writing.
+ *
+ * @param in The filename used for input.
+ * @param out The filename used for output.
+ *
+ * @throws IOException If there is an error parsing the document.
+ * @throws COSVisitorException If there is an error while copying the document.
+ */
+ public void doIt(String in, String out) throws IOException, COSVisitorException
+ {
+ PDDocument doc = null;
+ try
+ {
+ doc = PDDocument.load( in );
+ if( doc.isEncrypted() )
+ {
+ try
+ {
+ doc.decrypt( "" );
+ }
+ catch( InvalidPasswordException e )
+ {
+ System.err.println( "Error: The document is encrypted." );
+ }
+ catch( org.apache.pdfbox.exceptions.CryptographyException e )
+ {
+ e.printStackTrace();
+ }
+ }
+
+ for (Iterator<COSObject> i = doc.getDocument().getObjects().iterator(); i.hasNext();)
+ {
+ COSBase base = ((COSObject) i.next()).getObject();
+ if (base instanceof COSStream)
+ {
+ // just kill the filters
+ COSStream cosStream = (COSStream)base;
+ cosStream.getUnfilteredStream();
+ cosStream.setFilters(null);
+ }
+ }
+ doc.save( out );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will write a PDF document with completely decoded streams.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args command line arguments
+ */
+ public static void main(String[] args)
+ {
+ WriteDecodedDoc app = new WriteDecodedDoc();
+ try
+ {
+ if( args.length != 2 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt( args[0], args[1]);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: java -jar pdfbox-app-x.y.z.jar WriteDecodedDoc <input-file> <output-file>" );
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * An array of PDFBase objects as part of the PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.24 $
+ */
+public class COSArray extends COSBase implements Iterable<COSBase>
+{
+ private List<COSBase> objects = new ArrayList<COSBase>();
+
+ /**
+ * Constructor.
+ */
+ public COSArray()
+ {
+ //default constructor
+ }
+
+ /**
+ * This will add an object to the array.
+ *
+ * @param object The object to add to the array.
+ */
+ public void add( COSBase object )
+ {
+ objects.add( object );
+ }
+
+ /**
+ * This will add an object to the array.
+ *
+ * @param object The object to add to the array.
+ */
+ public void add( COSObjectable object )
+ {
+ objects.add( object.getCOSObject() );
+ }
+
+ /**
+ * Add the specified object at the ith location and push the rest to the
+ * right.
+ *
+ * @param i The index to add at.
+ * @param object The object to add at that index.
+ */
+ public void add( int i, COSBase object)
+ {
+ objects.add( i, object );
+ }
+
+ /**
+ * This will remove all of the objects in the collection.
+ */
+ public void clear()
+ {
+ objects.clear();
+ }
+
+ /**
+ * This will remove all of the objects in the collection.
+ *
+ * @param objectsList The list of objects to remove from the collection.
+ */
+ public void removeAll( Collection<COSBase> objectsList )
+ {
+ objects.removeAll( objectsList );
+ }
+
+ /**
+ * This will retain all of the objects in the collection.
+ *
+ * @param objectsList The list of objects to retain from the collection.
+ */
+ public void retainAll( Collection<COSBase> objectsList )
+ {
+ objects.retainAll( objectsList );
+ }
+
+ /**
+ * This will add an object to the array.
+ *
+ * @param objectsList The object to add to the array.
+ */
+ public void addAll( Collection<COSBase> objectsList )
+ {
+ objects.addAll( objectsList );
+ }
+
+ /**
+ * This will add all objects to this array.
+ *
+ * @param objectList The objects to add.
+ */
+ public void addAll( COSArray objectList )
+ {
+ if( objectList != null )
+ {
+ objects.addAll( objectList.objects );
+ }
+ }
+
+ /**
+ * Add the specified object at the ith location and push the rest to the
+ * right.
+ *
+ * @param i The index to add at.
+ * @param objectList The object to add at that index.
+ */
+ public void addAll( int i, Collection<COSBase> objectList )
+ {
+ objects.addAll( i, objectList );
+ }
+
+ /**
+ * This will set an object at a specific index.
+ *
+ * @param index zero based index into array.
+ * @param object The object to set.
+ */
+ public void set( int index, COSBase object )
+ {
+ objects.set( index, object );
+ }
+
+ /**
+ * This will set an object at a specific index.
+ *
+ * @param index zero based index into array.
+ * @param intVal The object to set.
+ */
+ public void set( int index, int intVal )
+ {
+ objects.set( index, COSInteger.get(intVal) );
+ }
+
+ /**
+ * This will set an object at a specific index.
+ *
+ * @param index zero based index into array.
+ * @param object The object to set.
+ */
+ public void set( int index, COSObjectable object )
+ {
+ COSBase base = null;
+ if( object != null )
+ {
+ base = object.getCOSObject();
+ }
+ objects.set( index, base );
+ }
+
+ /**
+ * This will get an object from the array. This will dereference the object.
+ * If the object is COSNull then null will be returned.
+ *
+ * @param index The index into the array to get the object.
+ *
+ * @return The object at the requested index.
+ */
+ public COSBase getObject( int index )
+ {
+ Object obj = objects.get( index );
+ if( obj instanceof COSObject )
+ {
+ obj = ((COSObject)obj).getObject();
+ }
+ else if( obj instanceof COSNull )
+ {
+ obj = null;
+ }
+ return (COSBase)obj;
+ }
+
+ /**
+ * This will get an object from the array. This will NOT derefernce
+ * the COS object.
+ *
+ * @param index The index into the array to get the object.
+ *
+ * @return The object at the requested index.
+ */
+ public COSBase get( int index )
+ {
+ return objects.get( index );
+ }
+
+ /**
+ * Get the value of the array as an integer.
+ *
+ * @param index The index into the list.
+ *
+ * @return The value at that index or -1 if it is null.
+ */
+ public int getInt( int index )
+ {
+ return getInt( index, -1 );
+ }
+
+ /**
+ * Get the value of the array as an integer, return the default if it does
+ * not exist.
+ *
+ * @param index The value of the array.
+ * @param defaultValue The value to return if the value is null.
+ * @return The value at the index or the defaultValue.
+ */
+ public int getInt( int index, int defaultValue )
+ {
+ int retval = defaultValue;
+ if ( index < size() )
+ {
+ Object obj = objects.get( index );
+ if( obj instanceof COSNumber )
+ {
+ retval = ((COSNumber)obj).intValue();
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * Set the value in the array as an integer.
+ *
+ * @param index The index into the array.
+ * @param value The value to set.
+ */
+ public void setInt( int index, int value )
+ {
+ set( index, COSInteger.get( value ) );
+ }
+
+ /**
+ * Set the value in the array as a name.
+ * @param index The index into the array.
+ * @param name The name to set in the array.
+ */
+ public void setName( int index, String name )
+ {
+ set( index, COSName.getPDFName( name ) );
+ }
+
+ /**
+ * Get the value of the array as a string.
+ *
+ * @param index The index into the array.
+ * @return The name converted to a string or null if it does not exist.
+ */
+ public String getName( int index )
+ {
+ return getName( index, null );
+ }
+
+ /**
+ * Get an entry in the array that is expected to be a COSName.
+ * @param index The index into the array.
+ * @param defaultValue The value to return if it is null.
+ * @return The value at the index or defaultValue if none is found.
+ */
+ public String getName( int index, String defaultValue )
+ {
+ String retval = defaultValue;
+ if( index < size() )
+ {
+ Object obj = objects.get( index );
+ if( obj instanceof COSName )
+ {
+ retval = ((COSName)obj).getName();
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * Set the value in the array as a string.
+ * @param index The index into the array.
+ * @param string The string to set in the array.
+ */
+ public void setString( int index, String string )
+ {
+ set( index, new COSString( string ) );
+ }
+
+ /**
+ * Get the value of the array as a string.
+ *
+ * @param index The index into the array.
+ * @return The string or null if it does not exist.
+ */
+ public String getString( int index )
+ {
+ return getString( index, null );
+ }
+
+ /**
+ * Get an entry in the array that is expected to be a COSName.
+ * @param index The index into the array.
+ * @param defaultValue The value to return if it is null.
+ * @return The value at the index or defaultValue if none is found.
+ */
+ public String getString( int index, String defaultValue )
+ {
+ String retval = defaultValue;
+ if( index < size() )
+ {
+ Object obj = objects.get( index );
+ if( obj instanceof COSString )
+ {
+ retval = ((COSString)obj).getString();
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the size of this array.
+ *
+ * @return The number of elements in the array.
+ */
+ public int size()
+ {
+ return objects.size();
+ }
+
+ /**
+ * This will remove an element from the array.
+ *
+ * @param i The index of the object to remove.
+ *
+ * @return The object that was removed.
+ */
+ public COSBase remove( int i )
+ {
+ return objects.remove( i );
+ }
+
+ /**
+ * This will remove an element from the array.
+ *
+ * @param o The object to remove.
+ *
+ * @return <code>true</code> if the object was removed, <code>false</code>
+ * otherwise
+ */
+ public boolean remove( COSBase o )
+ {
+ return objects.remove( o );
+ }
+
+ /**
+ * 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
+ */
+ public boolean removeObject(COSBase o)
+ {
+ boolean removed = this.remove(o);
+ if (!removed)
+ {
+ for (int i = 0; i < this.size(); i++)
+ {
+ COSBase entry = this.get(i);
+ if (entry instanceof COSObject)
+ {
+ COSObject objEntry = (COSObject) entry;
+ if (objEntry.getObject().equals(o))
+ {
+ return this.remove(entry);
+ }
+ }
+ }
+ }
+ return removed;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "COSArray{" + objects + "}";
+ }
+
+ /**
+ * Get access to the list.
+ *
+ * @return an iterator over the array elements
+ */
+ public Iterator<COSBase> iterator()
+ {
+ return objects.iterator();
+ }
+
+ /**
+ * This will return the index of the entry or -1 if it is not found.
+ *
+ * @param object The object to search for.
+ * @return The index of the object or -1.
+ */
+ public int indexOf( COSBase object )
+ {
+ int retval = -1;
+ for( int i=0; retval < 0 && i<size(); i++ )
+ {
+ if( get( i ).equals( object ) )
+ {
+ retval = i;
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * 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.
+ */
+ public int indexOfObject(COSBase object)
+ {
+ int retval = -1;
+ for (int i = 0; retval < 0 && i < this.size(); i++)
+ {
+ COSBase item = this.get(i);
+ if (item.equals(object))
+ {
+ retval = i;
+ break;
+ }
+ else if (item instanceof COSObject)
+ {
+ if (((COSObject) item).getObject().equals(object))
+ {
+ retval = i;
+ break;
+ }
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will add null values until the size of the array is at least
+ * as large as the parameter. If the array is already larger than the
+ * parameter then nothing is done.
+ *
+ * @param size The desired size of the array.
+ */
+ public void growToSize( int size )
+ {
+ growToSize( size, null );
+ }
+
+ /**
+ * This will add the object until the size of the array is at least
+ * as large as the parameter. If the array is already larger than the
+ * parameter then nothing is done.
+ *
+ * @param size The desired size of the array.
+ * @param object The object to fill the array with.
+ */
+ public void growToSize( int size, COSBase object )
+ {
+ while( size() < size )
+ {
+ add( object );
+ }
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ @Override
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return visitor.visitFromArray(this);
+ }
+
+ /**
+ * This will take an COSArray of numbers and convert it to a float[].
+ *
+ * @return This COSArray as an array of float numbers.
+ */
+ public float[] toFloatArray()
+ {
+ float[] retval = new float[size()];
+ for( int i=0; i<size(); i++ )
+ {
+ retval[i] = ((COSNumber)getObject( i )).floatValue();
+ }
+ return retval;
+ }
+
+ /**
+ * Clear the current contents of the COSArray and set it with the float[].
+ *
+ * @param value The new value of the float array.
+ */
+ public void setFloatArray( float[] value )
+ {
+ this.clear();
+ for( int i=0; i<value.length; i++ )
+ {
+ add( new COSFloat( value[i] ) );
+ }
+ }
+
+ /**
+ * Return contents of COSArray as a Java List.
+ *
+ * @return the COSArray as List
+ */
+ public List<COSBase> toList()
+ {
+ ArrayList<COSBase> retList = new ArrayList<COSBase>(size());
+ for (int i = 0; i < size(); i++)
+ {
+ retList.add(get(i));
+ }
+ return retList;
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import org.apache.pdfbox.filter.FilterManager;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * The base object that all objects in the PDF document will extend.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public abstract class COSBase implements COSObjectable
+{
+ /**
+ * Constructor.
+ */
+
+ private boolean needToBeUpdate;
+
+ private boolean direct;
+
+ public COSBase()
+ {
+ needToBeUpdate = false;
+ }
+
+ /**
+ * This will get the filter manager to use to filter streams.
+ *
+ * @return The filter manager.
+ */
+ public FilterManager getFilterManager()
+ {
+ /**
+ * @todo move this to PDFdocument or something better
+ */
+ return new FilterManager();
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return this;
+ }
+
+
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ public abstract Object accept(ICOSVisitor visitor) throws COSVisitorException;
+
+ public void setNeedToBeUpdate(boolean flag)
+ {
+ needToBeUpdate = flag;
+ }
+
+ /**
+ * If the state is set true, the dictionary will be written direct into the called object.
+ * This means, no indirect object will be created.
+ *
+ * @return the state
+ */
+ public boolean isDirect()
+ {
+ return direct;
+ }
+
+ /**
+ * Set the state true, if the dictionary should be written as a direct object and not indirect.
+ *
+ * @param direct set it true, for writting direct object
+ */
+ public void setDirect(boolean direct)
+ {
+ this.direct = direct;
+ }
+
+ public boolean isNeedToBeUpdate()
+ {
+ return needToBeUpdate;
+ }
+
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * This class represents a boolean value in the PDF document.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class COSBoolean extends COSBase
+{
+ /**
+ * The true boolean token.
+ */
+ public static final byte[] TRUE_BYTES = new byte[]{ 116, 114, 117, 101 }; //"true".getBytes( "ISO-8859-1" );
+ /**
+ * The false boolean token.
+ */
+ public static final byte[] FALSE_BYTES = new byte[]{ 102, 97, 108, 115, 101 }; //"false".getBytes( "ISO-8859-1" );
+
+ /**
+ * The PDF true value.
+ */
+ public static final COSBoolean TRUE = new COSBoolean( true );
+
+ /**
+ * The PDF false value.
+ */
+ public static final COSBoolean FALSE = new COSBoolean( false );
+
+ private boolean value;
+
+ /**
+ * Constructor.
+ *
+ * @param aValue The boolean value.
+ */
+ private COSBoolean(boolean aValue )
+ {
+ value = aValue;
+ }
+
+ /**
+ * This will get the value that this object wraps.
+ *
+ * @return The boolean value of this object.
+ */
+ public boolean getValue()
+ {
+ return value;
+ }
+
+ /**
+ * This will get the value that this object wraps.
+ *
+ * @return The boolean value of this object.
+ */
+ public Boolean getValueAsObject()
+ {
+ return (value?Boolean.TRUE:Boolean.FALSE);
+ }
+
+ /**
+ * This will get the boolean value.
+ *
+ * @param value Parameter telling which boolean value to get.
+ *
+ * @return The single boolean instance that matches the parameter.
+ */
+ public static COSBoolean getBoolean( boolean value )
+ {
+ return (value?TRUE:FALSE);
+ }
+
+ /**
+ * This will get the boolean value.
+ *
+ * @param value Parameter telling which boolean value to get.
+ *
+ * @return The single boolean instance that matches the parameter.
+ */
+ public static COSBoolean getBoolean( Boolean value )
+ {
+ return getBoolean( value.booleanValue() );
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return visitor.visitFromBoolean(this);
+ }
+
+ /**
+ * Return a string representation of this object.
+ *
+ * @return The string value of this object.
+ */
+ public String toString()
+ {
+ return String.valueOf( value );
+ }
+
+ /**
+ * This will write this object out to a PDF stream.
+ *
+ * @param output The stream to write this object out to.
+ *
+ * @throws IOException If an error occurs while writing out this object.
+ */
+ public void writePDF( OutputStream output ) throws IOException
+ {
+ if( value )
+ {
+ output.write( TRUE_BYTES );
+ }
+ else
+ {
+ output.write( FALSE_BYTES );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.util.DateConverter;
+
+/**
+ * This class represents a dictionary where name/value pairs reside.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.32 $
+ */
+public class COSDictionary extends COSBase
+{
+ private static final String PATH_SEPARATOR = "/";
+
+ /**
+ * The name-value pairs of this dictionary. The pairs are kept in the
+ * order they were added to the dictionary.
+ */
+ private final Map<COSName, COSBase> items =
+ new LinkedHashMap<COSName, COSBase>();
+
+ /**
+ * Constructor.
+ */
+ public COSDictionary()
+ {
+ //default constructor
+ }
+
+ /**
+ * Copy Constructor. This will make a shallow copy of this dictionary.
+ *
+ * @param dict The dictionary to copy.
+ */
+ public COSDictionary( COSDictionary dict )
+ {
+ items.putAll( dict.items );
+ }
+
+ /**
+ * @see java.util.Map#containsValue(java.lang.Object)
+ *
+ * @param value The value to find in the map.
+ *
+ * @return true if the map contains this value.
+ */
+ public boolean containsValue( Object value )
+ {
+ boolean contains = items.containsValue( value );
+ if( !contains && value instanceof COSObject )
+ {
+ contains = items.containsValue( ((COSObject)value).getObject());
+ }
+ return contains;
+ }
+
+ /**
+ * Search in the map for the value that matches the parameter
+ * and return the first key that maps to that value.
+ *
+ * @param value The value to search for in the map.
+ * @return The key for the value in the map or null if it does not exist.
+ */
+ public COSName getKeyForValue( Object value )
+ {
+ for( Map.Entry<COSName, COSBase> entry : items.entrySet() )
+ {
+ Object nextValue = entry.getValue();
+ if( nextValue.equals( value ) ||
+ (nextValue instanceof COSObject &&
+ ((COSObject)nextValue).getObject().equals( value))
+ )
+ {
+ return entry.getKey();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * This will return the number of elements in this dictionary.
+ *
+ * @return The number of elements in the dictionary.
+ */
+ public int size()
+ {
+ return items.size();
+ }
+
+ /**
+ * This will clear all items in the map.
+ */
+ public void clear()
+ {
+ items.clear();
+ }
+
+ /**
+ * This will get an object from this dictionary. If the object is a reference then it will
+ * dereference it and get it from the document. If the object is COSNull then
+ * null will be returned.
+ *
+ * @param key The key to the object that we are getting.
+ *
+ * @return The object that matches the key.
+ */
+ public COSBase getDictionaryObject( String key )
+ {
+ return getDictionaryObject( COSName.getPDFName( key ) );
+ }
+
+ /**
+ * This is a special case of getDictionaryObject that takes multiple keys, it will handle
+ * the situation where multiple keys could get the same value, ie if either CS or ColorSpace
+ * is used to get the colorspace.
+ * This will get an object from this dictionary. If the object is a reference then it will
+ * dereference it and get it from the document. If the object is COSNull then
+ * null will be returned.
+ *
+ * @param firstKey The first key to try.
+ * @param secondKey The second key to try.
+ *
+ * @return The object that matches the key.
+ *
+ * @deprecated use {@link #getDictionaryObject(COSName, COSName)} using COSName constants instead
+ */
+ public COSBase getDictionaryObject( String firstKey, String secondKey )
+ {
+ COSBase retval = getDictionaryObject( COSName.getPDFName( firstKey ) );
+ if( retval == null )
+ {
+ retval = getDictionaryObject( COSName.getPDFName( secondKey ) );
+ }
+ return retval;
+ }
+
+ /**
+ * This is a special case of getDictionaryObject that takes multiple keys, it will handle
+ * the situation where multiple keys could get the same value, ie if either CS or ColorSpace
+ * is used to get the colorspace.
+ * This will get an object from this dictionary. If the object is a reference then it will
+ * dereference it and get it from the document. If the object is COSNull then
+ * null will be returned.
+ *
+ * @param firstKey The first key to try.
+ * @param secondKey The second key to try.
+ *
+ * @return The object that matches the key.
+ */
+ public COSBase getDictionaryObject( COSName firstKey, COSName secondKey )
+ {
+ COSBase retval = getDictionaryObject( firstKey );
+ if( retval == null && secondKey != null)
+ {
+ retval = getDictionaryObject( secondKey );
+ }
+ return retval;
+ }
+ /**
+ * This is a special case of getDictionaryObject that takes multiple keys, it will handle
+ * the situation where multiple keys could get the same value, ie if either CS or ColorSpace
+ * is used to get the colorspace.
+ * This will get an object from this dictionary. If the object is a reference then it will
+ * dereference it and get it from the document. If the object is COSNull then
+ * null will be returned.
+ *
+ * @param keyList The list of keys to find a value.
+ *
+ * @return The object that matches the key.
+ */
+ public COSBase getDictionaryObject( String[] keyList )
+ {
+ COSBase retval = null;
+ for( int i=0; i<keyList.length && retval == null; i++ )
+ {
+ retval = getDictionaryObject( COSName.getPDFName( keyList[i] ) );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get an object from this dictionary. If the object is a reference then it will
+ * dereference it and get it from the document. If the object is COSNull then
+ * null will be returned.
+ *
+ * @param key The key to the object that we are getting.
+ *
+ * @return The object that matches the key.
+ */
+ public COSBase getDictionaryObject( COSName key )
+ {
+ COSBase retval = items.get( key );
+ if( retval instanceof COSObject )
+ {
+ retval = ((COSObject)retval).getObject();
+ }
+ if( retval instanceof COSNull )
+ {
+ retval = null;
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an item in the dictionary. If value is null then the result
+ * will be the same as removeItem( key ).
+ *
+ * @param key The key to the dictionary object.
+ * @param value The value to the dictionary object.
+ */
+ public void setItem( COSName key, COSBase value )
+ {
+ if( value == null )
+ {
+ removeItem( key );
+ }
+ else
+ {
+ items.put( key, value );
+ }
+ }
+
+ /**
+ * This will set an item in the dictionary. If value is null then the result
+ * will be the same as removeItem( key ).
+ *
+ * @param key The key to the dictionary object.
+ * @param value The value to the dictionary object.
+ */
+ public void setItem( COSName key, COSObjectable value )
+ {
+ COSBase base = null;
+ if( value != null )
+ {
+ base = value.getCOSObject();
+ }
+ setItem( key, base );
+ }
+
+ /**
+ * This will set an item in the dictionary. If value is null then the result
+ * will be the same as removeItem( key ).
+ *
+ * @param key The key to the dictionary object.
+ * @param value The value to the dictionary object.
+ */
+ public void setItem( String key, COSObjectable value )
+ {
+ setItem( COSName.getPDFName( key ), value );
+ }
+
+ /**
+ * This will set an item in the dictionary.
+ *
+ * @param key The key to the dictionary object.
+ * @param value The value to the dictionary object.
+ */
+ public void setBoolean( String key, boolean value )
+ {
+ setItem( COSName.getPDFName( key ), COSBoolean.getBoolean( value ) );
+ }
+
+ /**
+ * This will set an item in the dictionary.
+ *
+ * @param key The key to the dictionary object.
+ * @param value The value to the dictionary object.
+ */
+ public void setBoolean( COSName key, boolean value )
+ {
+ setItem( key , COSBoolean.getBoolean( value ) );
+ }
+
+ /**
+ * This will set an item in the dictionary. If value is null then the result
+ * will be the same as removeItem( key ).
+ *
+ * @param key The key to the dictionary object.
+ * @param value The value to the dictionary object.
+ */
+ public void setItem( String key, COSBase value )
+ {
+ setItem( COSName.getPDFName( key ), value );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSName
+ * object. If it is null then the object will be removed.
+ *
+ * @param key The key to the object,
+ * @param value The string value for the name.
+ */
+ public void setName( String key, String value )
+ {
+ setName( COSName.getPDFName( key ), value );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSName
+ * object. If it is null then the object will be removed.
+ *
+ * @param key The key to the object,
+ * @param value The string value for the name.
+ */
+ public void setName( COSName key, String value )
+ {
+ COSName name = null;
+ if( value != null )
+ {
+ name = COSName.getPDFName( value );
+ }
+ setItem( key, name );
+ }
+
+ /**
+ * Set the value of a date entry in the dictionary.
+ *
+ * @param key The key to the date value.
+ * @param date The date value.
+ */
+ public void setDate( String key, Calendar date )
+ {
+ setDate( COSName.getPDFName( key ), date );
+ }
+
+ /**
+ * Set the date object.
+ *
+ * @param key The key to the date.
+ * @param date The date to set.
+ */
+ public void setDate( COSName key, Calendar date )
+ {
+ setString( key, DateConverter.toString( date ) );
+ }
+
+ /**
+ * Set the value of a date entry in the dictionary.
+ *
+ * @param embedded The embedded dictionary.
+ * @param key The key to the date value.
+ * @param date The date value.
+ */
+ public void setEmbeddedDate( String embedded, String key, Calendar date )
+ {
+ setEmbeddedDate( embedded, COSName.getPDFName( key ), date );
+ }
+
+ /**
+ * Set the date object.
+ *
+ * @param embedded The embedded dictionary.
+ * @param key The key to the date.
+ * @param date The date to set.
+ */
+ public void setEmbeddedDate( String embedded, COSName key, Calendar date )
+ {
+ COSDictionary dic = (COSDictionary)getDictionaryObject( embedded );
+ if( dic == null && date != null )
+ {
+ dic = new COSDictionary();
+ setItem( embedded, dic );
+ }
+ if( dic != null )
+ {
+ dic.setDate( key, date );
+ }
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSString
+ * object. If it is null then the object will be removed.
+ *
+ * @param key The key to the object,
+ * @param value The string value for the name.
+ */
+ public void setString( String key, String value )
+ {
+ setString( COSName.getPDFName( key ), value );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSString
+ * object. If it is null then the object will be removed.
+ *
+ * @param key The key to the object,
+ * @param value The string value for the name.
+ */
+ public void setString( COSName key, String value )
+ {
+ COSString name = null;
+ if( value != null )
+ {
+ name = new COSString( value );
+ }
+ setItem( key, name );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSString
+ * object. If it is null then the object will be removed.
+ *
+ * @param embedded The embedded dictionary to set the item in.
+ * @param key The key to the object,
+ * @param value The string value for the name.
+ */
+ public void setEmbeddedString( String embedded, String key, String value )
+ {
+ setEmbeddedString( embedded, COSName.getPDFName( key ), value );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSString
+ * object. If it is null then the object will be removed.
+ *
+ * @param embedded The embedded dictionary to set the item in.
+ * @param key The key to the object,
+ * @param value The string value for the name.
+ */
+ public void setEmbeddedString( String embedded, COSName key, String value )
+ {
+ COSDictionary dic = (COSDictionary)getDictionaryObject( embedded );
+ if( dic == null && value != null )
+ {
+ dic = new COSDictionary();
+ setItem( embedded, dic );
+ }
+ if( dic != null )
+ {
+ dic.setString( key, value );
+ }
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSInteger
+ * object.
+ *
+ * @param key The key to the object,
+ * @param value The int value for the name.
+ */
+ public void setInt( String key, int value )
+ {
+ setInt( COSName.getPDFName( key ), value );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSInteger
+ * object.
+ *
+ * @param key The key to the object,
+ * @param value The int value for the name.
+ */
+ public void setInt( COSName key, int value )
+ {
+ setItem( key, COSInteger.get(value) );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSInteger
+ * object.
+ *
+ * @param key The key to the object,
+ * @param value The int value for the name.
+ */
+ public void setLong( String key, long value )
+ {
+ setLong( COSName.getPDFName( key ), value );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSInteger
+ * object.
+ *
+ * @param key The key to the object,
+ * @param value The int value for the name.
+ */
+ public void setLong( COSName key, long value )
+ {
+ COSInteger intVal = null;
+ intVal = COSInteger.get(value);
+ setItem( key, intVal );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSInteger
+ * object.
+ *
+ * @param embeddedDictionary The embedded dictionary.
+ * @param key The key to the object,
+ * @param value The int value for the name.
+ */
+ public void setEmbeddedInt( String embeddedDictionary, String key, int value )
+ {
+ setEmbeddedInt( embeddedDictionary, COSName.getPDFName( key ), value );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSInteger
+ * object.
+ *
+ * @param embeddedDictionary The embedded dictionary.
+ * @param key The key to the object,
+ * @param value The int value for the name.
+ */
+ public void setEmbeddedInt( String embeddedDictionary, COSName key, int value )
+ {
+ COSDictionary embedded = (COSDictionary)getDictionaryObject( embeddedDictionary );
+ if( embedded == null )
+ {
+ embedded = new COSDictionary();
+ setItem( embeddedDictionary, embedded );
+ }
+ embedded.setInt( key, value );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSFloat
+ * object.
+ *
+ * @param key The key to the object,
+ * @param value The int value for the name.
+ */
+ public void setFloat( String key, float value )
+ {
+ setFloat( COSName.getPDFName( key ), value );
+ }
+
+ /**
+ * This is a convenience method that will convert the value to a COSFloat
+ * object.
+ *
+ * @param key The key to the object,
+ * @param value The int value for the name.
+ */
+ public void setFloat( COSName key, float value )
+ {
+ COSFloat fltVal = new COSFloat( value );
+ setItem( key, fltVal );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The name converted to a string.
+ */
+ public String getNameAsString( String key )
+ {
+ return getNameAsString( COSName.getPDFName( key ) );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The name converted to a string.
+ */
+ public String getNameAsString( COSName key )
+ {
+ String retval = null;
+ COSBase name = getDictionaryObject( key );
+ if( name != null )
+ {
+ if ( name instanceof COSName)
+ {
+ retval = ((COSName)name).getName();
+ }
+ else if ( name instanceof COSString)
+ {
+ retval = ((COSString)name).getString();
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The name converted to a string.
+ */
+ public String getNameAsString( String key, String defaultValue )
+ {
+ return getNameAsString( COSName.getPDFName( key ), defaultValue );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The name converted to a string.
+ */
+ public String getNameAsString( COSName key, String defaultValue )
+ {
+ String retval = getNameAsString( key );
+ if( retval == null )
+ {
+ retval = defaultValue;
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The name converted to a string.
+ */
+ public String getString( String key )
+ {
+ return getString( COSName.getPDFName( key ) );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The name converted to a string.
+ */
+ public String getString( COSName key )
+ {
+ String retval = null;
+ COSBase value = getDictionaryObject( key );
+ if( value != null && value instanceof COSString)
+ {
+ retval = ((COSString)value).getString();
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The default value to return.
+ * @return The name converted to a string.
+ */
+ public String getString( String key, String defaultValue )
+ {
+ return getString( COSName.getPDFName( key ), defaultValue );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The default value to return.
+ * @return The name converted to a string.
+ */
+ public String getString( COSName key, String defaultValue )
+ {
+ String retval = getString( key );
+ if( retval == null )
+ {
+ retval = defaultValue;
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param embedded The embedded dictionary.
+ * @param key The key to the item in the dictionary.
+ * @return The name converted to a string.
+ */
+ public String getEmbeddedString( String embedded, String key )
+ {
+ return getEmbeddedString( embedded, COSName.getPDFName( key ), null );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param embedded The embedded dictionary.
+ * @param key The key to the item in the dictionary.
+ * @return The name converted to a string.
+ */
+ public String getEmbeddedString( String embedded, COSName key )
+ {
+ return getEmbeddedString( embedded, key, null );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param embedded The embedded dictionary.
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The default value to return.
+ * @return The name converted to a string.
+ */
+ public String getEmbeddedString( String embedded, String key, String defaultValue )
+ {
+ return getEmbeddedString( embedded, COSName.getPDFName( key ), defaultValue );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param embedded The embedded dictionary.
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The default value to return.
+ * @return The name converted to a string.
+ */
+ public String getEmbeddedString( String embedded, COSName key, String defaultValue )
+ {
+ String retval = defaultValue;
+ COSDictionary dic = (COSDictionary)getDictionaryObject( embedded );
+ if( dic != null )
+ {
+ retval = dic.getString( key, defaultValue );
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The name converted to a string.
+ * @throws IOException If there is an error converting to a date.
+ */
+ public Calendar getDate( String key ) throws IOException
+ {
+ return getDate( COSName.getPDFName( key ) );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The name converted to a string.
+ *
+ * @throws IOException If there is an error converting to a date.
+ */
+ public Calendar getDate( COSName key ) throws IOException
+ {
+ COSString date = (COSString)getDictionaryObject( key );
+ return DateConverter.toCalendar( date );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a date. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The default value to return.
+ * @return The name converted to a string.
+ * @throws IOException If there is an error converting to a date.
+ */
+ public Calendar getDate( String key, Calendar defaultValue ) throws IOException
+ {
+ return getDate( COSName.getPDFName( key ), defaultValue );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a date. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The default value to return.
+ * @return The name converted to a string.
+ * @throws IOException If there is an error converting to a date.
+ */
+ public Calendar getDate( COSName key, Calendar defaultValue ) throws IOException
+ {
+ Calendar retval = getDate( key );
+ if( retval == null )
+ {
+ retval = defaultValue;
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param embedded The embedded dictionary to get.
+ * @param key The key to the item in the dictionary.
+ * @return The name converted to a string.
+ * @throws IOException If there is an error converting to a date.
+ */
+ public Calendar getEmbeddedDate( String embedded, String key ) throws IOException
+ {
+ return getEmbeddedDate( embedded, COSName.getPDFName( key ), null );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a name and convert it to a string. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param embedded The embedded dictionary to get.
+ * @param key The key to the item in the dictionary.
+ * @return The name converted to a string.
+ *
+ * @throws IOException If there is an error converting to a date.
+ */
+ public Calendar getEmbeddedDate( String embedded, COSName key ) throws IOException
+ {
+ return getEmbeddedDate( embedded, key, null );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a date. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param embedded The embedded dictionary to get.
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The default value to return.
+ * @return The name converted to a string.
+ * @throws IOException If there is an error converting to a date.
+ */
+ public Calendar getEmbeddedDate( String embedded, String key, Calendar defaultValue ) throws IOException
+ {
+ return getEmbeddedDate( embedded, COSName.getPDFName( key ), defaultValue );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a date. Null is returned
+ * if the entry does not exist in the dictionary.
+ *
+ * @param embedded The embedded dictionary to get.
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The default value to return.
+ * @return The name converted to a string.
+ * @throws IOException If there is an error converting to a date.
+ */
+ public Calendar getEmbeddedDate( String embedded, COSName key, Calendar defaultValue ) throws IOException
+ {
+ Calendar retval = defaultValue;
+ COSDictionary eDic = (COSDictionary)getDictionaryObject( embedded );
+ if( eDic != null )
+ {
+ retval = eDic.getDate( key, defaultValue );
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a cos boolean and convert it to a primitive boolean.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The value returned if the entry is null.
+ *
+ * @return The value converted to a boolean.
+ */
+ public boolean getBoolean( String key, boolean defaultValue )
+ {
+ return getBoolean( COSName.getPDFName( key ), defaultValue );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a COSBoolean and convert it to a primitive boolean.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The value returned if the entry is null.
+ *
+ * @return The entry converted to a boolean.
+ */
+ public boolean getBoolean( COSName key, boolean defaultValue )
+ {
+ return getBoolean( key, null, defaultValue);
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a COSBoolean and convert it to a primitive boolean.
+ *
+ * @param firstKey The first key to the item in the dictionary.
+ * @param secondKey The second key to the item in the dictionary.
+ * @param defaultValue The value returned if the entry is null.
+ *
+ * @return The entry converted to a boolean.
+ */
+ public boolean getBoolean( COSName firstKey, COSName secondKey, boolean defaultValue )
+ {
+ boolean retval = defaultValue;
+ COSBase bool = getDictionaryObject( firstKey, secondKey );
+ if( bool != null && bool instanceof COSBoolean)
+ {
+ retval = ((COSBoolean)bool).getValue();
+ }
+ return retval;
+ }
+
+ /**
+ * Get an integer from an embedded dictionary. Useful for 1-1 mappings. default:-1
+ *
+ * @param embeddedDictionary The name of the embedded dictionary.
+ * @param key The key in the embedded dictionary.
+ *
+ * @return The value of the embedded integer.
+ */
+ public int getEmbeddedInt( String embeddedDictionary, String key )
+ {
+ return getEmbeddedInt( embeddedDictionary, COSName.getPDFName( key ) );
+ }
+
+ /**
+ * Get an integer from an embedded dictionary. Useful for 1-1 mappings. default:-1
+ *
+ * @param embeddedDictionary The name of the embedded dictionary.
+ * @param key The key in the embedded dictionary.
+ *
+ * @return The value of the embedded integer.
+ */
+ public int getEmbeddedInt( String embeddedDictionary, COSName key )
+ {
+ return getEmbeddedInt( embeddedDictionary, key, -1 );
+ }
+
+ /**
+ * Get an integer from an embedded dictionary. Useful for 1-1 mappings.
+ *
+ * @param embeddedDictionary The name of the embedded dictionary.
+ * @param key The key in the embedded dictionary.
+ * @param defaultValue The value if there is no embedded dictionary or it does not contain the key.
+ *
+ * @return The value of the embedded integer.
+ */
+ public int getEmbeddedInt( String embeddedDictionary, String key, int defaultValue )
+ {
+ return getEmbeddedInt( embeddedDictionary, COSName.getPDFName( key ), defaultValue );
+ }
+
+
+ /**
+ * Get an integer from an embedded dictionary. Useful for 1-1 mappings.
+ *
+ * @param embeddedDictionary The name of the embedded dictionary.
+ * @param key The key in the embedded dictionary.
+ * @param defaultValue The value if there is no embedded dictionary or it does not contain the key.
+ *
+ * @return The value of the embedded integer.
+ */
+ public int getEmbeddedInt( String embeddedDictionary, COSName key, int defaultValue )
+ {
+ int retval = defaultValue;
+ COSDictionary embedded = (COSDictionary)getDictionaryObject( embeddedDictionary );
+ if( embedded != null )
+ {
+ retval = embedded.getInt( key, defaultValue );
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an int. -1 is returned if there is no value.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The integer value.
+ */
+ public int getInt( String key )
+ {
+ return getInt( COSName.getPDFName( key ), -1 );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an int. -1 is returned if there is no value.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The integer value..
+ */
+ public int getInt( COSName key )
+ {
+ return getInt( key, -1 );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an integer. If the dictionary value is null then the
+ * default Value will be returned.
+ *
+ * @param keyList The key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The integer value.
+ */
+ public int getInt( String[] keyList, int defaultValue )
+ {
+ int retval = defaultValue;
+ COSBase obj = getDictionaryObject( keyList );
+ if( obj != null && obj instanceof COSNumber)
+ {
+ retval = ((COSNumber)obj).intValue();
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an integer. If the dictionary value is null then the
+ * default Value will be returned.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The integer value.
+ */
+ public int getInt( String key, int defaultValue )
+ {
+ return getInt( COSName.getPDFName( key ), defaultValue );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an integer. If the dictionary value is null then the
+ * default Value will be returned.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The integer value.
+ */
+ public int getInt( COSName key, int defaultValue )
+ {
+ return getInt( key, null, defaultValue);
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an integer. If the dictionary value is null then the
+ * default Value -1 will be returned.
+ *
+ * @param firstKey The first key to the item in the dictionary.
+ * @param secondKey The second key to the item in the dictionary.
+ * @return The integer value.
+ */
+ public int getInt( COSName firstKey, COSName secondKey )
+ {
+ return getInt( firstKey, secondKey, -1 );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an integer. If the dictionary value is null then the
+ * default Value will be returned.
+ *
+ * @param firstKey The first key to the item in the dictionary.
+ * @param secondKey The second key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The integer value.
+ */
+ public int getInt( COSName firstKey, COSName secondKey, int defaultValue )
+ {
+ int retval = defaultValue;
+ COSBase obj = getDictionaryObject( firstKey, secondKey );
+ if( obj != null && obj instanceof COSNumber)
+ {
+ retval = ((COSNumber)obj).intValue();
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an long. -1 is returned if there is no value.
+ *
+ * @param key The key to the item in the dictionary.
+ *
+ * @return The long value.
+ */
+ public long getLong( String key )
+ {
+ return getLong( COSName.getPDFName( key ), -1L );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an long. -1 is returned if there is no value.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The long value.
+ */
+ public long getLong( COSName key )
+ {
+ return getLong( key, -1L );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an long. If the dictionary value is null then the
+ * default Value will be returned.
+ *
+ * @param keyList The key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The long value.
+ */
+ public long getLong( String[] keyList, long defaultValue )
+ {
+ long retval = defaultValue;
+ COSBase obj = getDictionaryObject( keyList );
+ if( obj != null && obj instanceof COSNumber)
+ {
+ retval = ((COSNumber)obj).longValue();
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an integer. If the dictionary value is null then the
+ * default Value will be returned.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The integer value.
+ */
+ public long getLong( String key, long defaultValue )
+ {
+ return getLong( COSName.getPDFName( key ), defaultValue );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an integer. If the dictionary value is null then the
+ * default Value will be returned.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The integer value.
+ */
+ public long getLong( COSName key, long defaultValue )
+ {
+ long retval = defaultValue;
+ COSBase obj = getDictionaryObject( key );
+ if( obj != null && obj instanceof COSNumber)
+ {
+ retval = ((COSNumber)obj).longValue();
+ }
+ return retval;
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an float. -1 is returned if there is no value.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The float value.
+ */
+ public float getFloat( String key )
+ {
+ return getFloat( COSName.getPDFName( key ), -1 );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an float. -1 is returned if there is no value.
+ *
+ * @param key The key to the item in the dictionary.
+ * @return The float value.
+ */
+ public float getFloat( COSName key )
+ {
+ return getFloat( key, -1 );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be a float. If the dictionary value is null then the
+ * default Value will be returned.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The float value.
+ */
+ public float getFloat( String key, float defaultValue )
+ {
+ return getFloat( COSName.getPDFName( key ), defaultValue );
+ }
+
+ /**
+ * This is a convenience method that will get the dictionary object that
+ * is expected to be an float. If the dictionary value is null then the
+ * default Value will be returned.
+ *
+ * @param key The key to the item in the dictionary.
+ * @param defaultValue The value to return if the dictionary item is null.
+ * @return The float value.
+ */
+ public float getFloat( COSName key, float defaultValue )
+ {
+ float retval = defaultValue;
+ COSBase obj = getDictionaryObject( key );
+ if( obj != null && obj instanceof COSNumber)
+ {
+ retval = ((COSNumber)obj).floatValue();
+ }
+ return retval;
+ }
+
+ /**
+ * This will remove an item for the dictionary. This
+ * will do nothing of the object does not exist.
+ *
+ * @param key The key to the item to remove from the dictionary.
+ */
+ public void removeItem( COSName key )
+ {
+ items.remove( key );
+ }
+
+ /**
+ * This will do a lookup into the dictionary.
+ *
+ * @param key The key to the object.
+ *
+ * @return The item that matches the key.
+ */
+ public COSBase getItem( COSName key )
+ {
+ return items.get( key );
+ }
+
+ /**
+ * This will get the keys for all objects in the dictionary in the sequence that
+ * they were added.
+ *
+ * @deprecated Use the {@link #entrySet()} method instead.
+ * @return a list of the keys in the sequence of insertion
+ */
+ public List<COSName> keyList()
+ {
+ return new ArrayList<COSName>(items.keySet());
+ }
+
+ /**
+ * Returns the names of the entries in this dictionary. The returned
+ * set is in the order the entries were added to the dictionary.
+ *
+ * @since Apache PDFBox 1.1.0
+ * @return names of the entries in this dictionary
+ */
+ public Set<COSName> keySet()
+ {
+ return items.keySet();
+ }
+
+ /**
+ * Returns the name-value entries in this dictionary. The returned
+ * set is in the order the entries were added to the dictionary.
+ *
+ * @since Apache PDFBox 1.1.0
+ * @return name-value entries in this dictionary
+ */
+ public Set<Map.Entry<COSName, COSBase>> entrySet()
+ {
+ return items.entrySet();
+ }
+
+ /**
+ * This will get all of the values for the dictionary.
+ *
+ * @return All the values for the dictionary.
+ */
+ public Collection<COSBase> getValues()
+ {
+ return items.values();
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return The object that the visitor returns.
+ *
+ * @throws COSVisitorException If there is an error visiting this object.
+ */
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return visitor.visitFromDictionary(this);
+ }
+
+ /**
+ * This will add all of the dictionarys keys/values to this dictionary.
+ * Only called when adding keys to a trailer that already exists.
+ *
+ * @param dic The dic to get the keys from.
+ */
+ public void addAll( COSDictionary dic )
+ {
+ for( Map.Entry<COSName, COSBase> entry : dic.entrySet() )
+ {
+ /*
+ * If we're at a second trailer, we have a linearized
+ * pdf file, meaning that the first Size entry represents
+ * all of the objects so we don't need to grab the second.
+ */
+ if(!entry.getKey().getName().equals("Size")
+ || !items.containsKey(COSName.getPDFName("Size")))
+ {
+ setItem( entry.getKey(), entry.getValue() );
+ }
+ }
+ }
+
+ /**
+ * This will add all of the dictionarys keys/values to this dictionary, but only
+ * if they don't already exist. If a key already exists in this dictionary then
+ * nothing is changed.
+ *
+ * @param dic The dic to get the keys from.
+ */
+ public void mergeInto( COSDictionary dic )
+ {
+ for( Map.Entry<COSName, COSBase> entry : dic.entrySet() )
+ {
+ if( getItem( entry.getKey() ) == null )
+ {
+ setItem( entry.getKey(), entry.getValue() );
+ }
+ }
+ }
+
+ /**
+ * Nice method, gives you every object you want
+ * Arrays works properly too. Try "P/Annots/[k]/Rect"
+ * where k means the index of the Annotsarray.
+ *
+ * @param objPath the relative path to the object.
+ * @return the object
+ */
+ public COSBase getObjectFromPath(String objPath)
+ {
+ COSBase retval = null;
+ String[] path = objPath.split(PATH_SEPARATOR);
+ retval = this;
+
+ for (int i = 0; i < path.length; i++)
+ {
+ if(retval instanceof COSArray)
+ {
+ int idx = new Integer(path[i].replaceAll("\\[","").replaceAll("\\]","")).intValue();
+ retval = ((COSArray)retval).getObject(idx);
+ }
+ else if (retval instanceof COSDictionary)
+ {
+ retval = ((COSDictionary)retval).getDictionaryObject( path[i] );
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ StringBuilder retVal = new StringBuilder("COSDictionary{");
+ for( COSName key : items.keySet() )
+ {
+ retVal.append("(" + key + ":" + getDictionaryObject(key).toString() + ") ");
+ }
+ retVal.append("}");
+ return retVal.toString();
+ }
+
+
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.io.RandomAccessBuffer;
+import org.apache.pdfbox.io.RandomAccessFile;
+import org.apache.pdfbox.pdfparser.PDFObjectStreamParser;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This is the in-memory representation of the PDF document. You need to call
+ * close() on this object when you are done using it!!
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.28 $
+ */
+public class COSDocument extends COSBase
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(COSDocument.class);
+
+ private float version;
+
+ /**
+ * Maps ObjectKeys to a COSObject. Note that references to these objects
+ * are also stored in COSDictionary objects that map a name to a specific object.
+ */
+ private final Map<COSObjectKey, COSObject> objectPool =
+ new HashMap<COSObjectKey, COSObject>();
+
+ /**
+ * Maps object and generation ids to object byte offsets.
+ */
+ private final Map<COSObjectKey, Integer> xrefTable =
+ new HashMap<COSObjectKey, Integer>();
+
+ /**
+ * Document trailer dictionary.
+ */
+ private COSDictionary trailer;
+
+ /**
+ * Document signature dictionary
+ */
+ private COSDictionary signDictionary = null;
+
+ /**
+ * Some doc
+ */
+ private SignatureInterface signatureInterface;
+
+ /**
+ * This file will store the streams in order to conserve memory.
+ */
+ private final RandomAccess scratchFile;
+
+ private final File tmpFile;
+
+ private String headerString = "%PDF-1.4";
+
+ private boolean warnMissingClose = true;
+
+ private int startXref;
+
+ private boolean closed = false;
+
+ /**
+ * Flag to skip malformed or otherwise unparseable input where possible.
+ */
+ private final boolean forceParsing;
+
+ /**
+ * Constructor that will use the given random access file for storage
+ * of the PDF streams. The client of this method is responsible for
+ * deleting the storage if necessary that this file will write to. The
+ * close method will close the file though.
+ *
+ * @param scratchFile the random access file to use for storage
+ * @param forceParsing flag to skip malformed or otherwise unparseable
+ * document content where possible
+ */
+ public COSDocument(RandomAccess scratchFile, boolean forceParsing) {
+ this.scratchFile = scratchFile;
+ this.tmpFile = null;
+ this.forceParsing = forceParsing;
+ }
+
+ /**
+ * Constructor that will use a temporary file in the given directory
+ * for storage of the PDF streams. The temporary file is automatically
+ * removed when this document gets closed.
+ *
+ * @param scratchDir directory for the temporary file,
+ * or <code>null</code> to use the system default
+ * @param forceParsing flag to skip malformed or otherwise unparseable
+ * document content where possible
+ */
+ public COSDocument(File scratchDir, boolean forceParsing)
+ throws IOException {
+ this.tmpFile = File.createTempFile("pdfbox-", ".tmp", scratchDir);
+ this.scratchFile = new RandomAccessFile(tmpFile, "rw");
+ this.forceParsing = forceParsing;
+ }
+
+ /**
+ * Constructor. Uses memory to store stream.
+ *
+ * @throws IOException If there is an error creating the tmp file.
+ */
+ public COSDocument() throws IOException {
+ this(new RandomAccessBuffer(), false);
+ }
+
+ /**
+ * Constructor that will create a create a scratch file in the
+ * following directory.
+ *
+ * @param scratchDir The directory to store a scratch file.
+ *
+ * @throws IOException If there is an error creating the tmp file.
+ */
+ public COSDocument(File scratchDir) throws IOException {
+ this(scratchDir, false);
+ }
+
+ /**
+ * Constructor that will use the following random access file for storage
+ * of the PDF streams. The client of this method is responsible for deleting
+ * the storage if necessary that this file will write to. The close method
+ * will close the file though.
+ *
+ * @param file The random access file to use for storage.
+ */
+ public COSDocument(RandomAccess file) {
+ this(file, false);
+ }
+
+ /**
+ * This will get the scratch file for this document.
+ *
+ * @return The scratch file.
+ */
+ public RandomAccess getScratchFile()
+ {
+ return scratchFile;
+ }
+
+ /**
+ * This will get the first dictionary object by type.
+ *
+ * @param type The type of the object.
+ *
+ * @return This will return an object with the specified type.
+ * @throws IOException If there is an error getting the object
+ */
+ public COSObject getObjectByType( String type ) throws IOException
+ {
+ return getObjectByType( COSName.getPDFName( type ) );
+ }
+
+ /**
+ * This will get the first dictionary object by type.
+ *
+ * @param type The type of the object.
+ *
+ * @return This will return an object with the specified type.
+ * @throws IOException If there is an error getting the object
+ */
+ public COSObject getObjectByType( COSName type ) throws IOException
+ {
+ for( COSObject object : objectPool.values() )
+ {
+
+ COSBase realObject = object.getObject();
+ if( realObject instanceof COSDictionary )
+ {
+ try
+ {
+ COSDictionary dic = (COSDictionary)realObject;
+ COSName objectType = (COSName)dic.getItem( COSName.TYPE );
+ if( objectType != null && objectType.equals( type ) )
+ {
+ return object;
+ }
+ }
+ catch (ClassCastException e)
+ {
+ log.warn(e, e);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This will get all dictionary objects by type.
+ *
+ * @param type The type of the object.
+ *
+ * @return This will return an object with the specified type.
+ * @throws IOException If there is an error getting the object
+ */
+ public List<COSObject> getObjectsByType( String type ) throws IOException
+ {
+ return getObjectsByType( COSName.getPDFName( type ) );
+ }
+
+ /**
+ * This will get a dictionary object by type.
+ *
+ * @param type The type of the object.
+ *
+ * @return This will return an object with the specified type.
+ * @throws IOException If there is an error getting the object
+ */
+ public List<COSObject> getObjectsByType( COSName type ) throws IOException
+ {
+ List<COSObject> retval = new ArrayList<COSObject>();
+ for( COSObject object : objectPool.values() )
+ {
+ COSBase realObject = object.getObject();
+ if( realObject instanceof COSDictionary )
+ {
+ try
+ {
+ COSDictionary dic = (COSDictionary)realObject;
+ COSName objectType = (COSName)dic.getItem( COSName.TYPE );
+ if( objectType != null && objectType.equals( type ) )
+ {
+ retval.add( object );
+ }
+ }
+ catch (ClassCastException e)
+ {
+ log.warn(e, e);
+ }
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will print contents to stdout.
+ */
+ public void print()
+ {
+ for( COSObject object : objectPool.values() )
+ {
+ System.out.println( object);
+ }
+ }
+
+ /**
+ * This will set the version of this PDF document.
+ *
+ * @param versionValue The version of the PDF document.
+ */
+ public void setVersion( float versionValue )
+ {
+ version = versionValue;
+ }
+
+ /**
+ * This will get the version of this PDF document.
+ *
+ * @return This documents version.
+ */
+ public float getVersion()
+ {
+ return version;
+ }
+
+ /**
+ * This will tell if this is an encrypted document.
+ *
+ * @return true If this document is encrypted.
+ */
+ public boolean isEncrypted()
+ {
+ boolean encrypted = false;
+ if( trailer != null )
+ {
+ encrypted = trailer.getDictionaryObject( COSName.ENCRYPT ) != null;
+ }
+ return encrypted;
+ }
+
+ /**
+ * This will get the encryption dictionary if the document is encrypted or null
+ * if the document is not encrypted.
+ *
+ * @return The encryption dictionary.
+ */
+ public COSDictionary getEncryptionDictionary()
+ {
+ return (COSDictionary)trailer.getDictionaryObject( COSName.ENCRYPT );
+ }
+
+ public SignatureInterface getSignatureInterface() {
+ return signatureInterface;
+ }
+
+ /**
+ * This will set the encryption dictionary, this should only be called when
+ * encrypting the document.
+ *
+ * @param encDictionary The encryption dictionary.
+ */
+ public void setEncryptionDictionary( COSDictionary encDictionary )
+ {
+ trailer.setItem( COSName.ENCRYPT, encDictionary );
+ }
+
+ public COSDictionary getLastSignatureDictionary() throws IOException {
+ if (signDictionary == null)
+ {
+ COSObject documentCatalog = getCatalog();
+ if (documentCatalog != null)
+ {
+ COSDictionary acroForm = (COSDictionary)documentCatalog.getDictionaryObject(COSName.ACRO_FORM);
+ if (acroForm !=null)
+ {
+ COSArray fields = (COSArray)acroForm.getDictionaryObject(COSName.FIELDS);
+ for ( Object object : fields )
+ {
+ COSObject dict = (COSObject)object;
+ if(dict.getItem(COSName.FT).equals(COSName.SIG))
+ {
+ COSBase dictionaryObject = dict.getDictionaryObject(COSName.V);
+
+ if (dictionaryObject != null)
+ {
+ signDictionary = (COSDictionary)dictionaryObject;
+ }
+ }
+ }
+ }
+ }
+ }
+ return signDictionary;
+ }
+
+ /**
+ * This will get the document ID.
+ *
+ * @return The document id.
+ */
+ public COSArray getDocumentID()
+ {
+ return (COSArray) getTrailer().getItem(COSName.ID);
+ }
+
+ /**
+ * This will set the document ID.
+ *
+ * @param id The document id.
+ */
+ public void setDocumentID( COSArray id )
+ {
+ getTrailer().setItem(COSName.ID, id);
+ }
+
+ public void setSignatureInterface(SignatureInterface signatureInterface) {
+ this.signatureInterface = signatureInterface;
+ }
+
+ /**
+ * This will get the document catalog.
+ *
+ * Maybe this should move to an object at PDFEdit level
+ *
+ * @return catalog is the root of all document activities
+ *
+ * @throws IOException If no catalog can be found.
+ */
+ public COSObject getCatalog() throws IOException
+ {
+ COSObject catalog = getObjectByType( COSName.CATALOG );
+ if( catalog == null )
+ {
+ throw new IOException( "Catalog cannot be found" );
+ }
+ return catalog;
+ }
+
+ /**
+ * This will get a list of all available objects.
+ *
+ * @return A list of all objects.
+ */
+ public List<COSObject> getObjects()
+ {
+ return new ArrayList<COSObject>(objectPool.values());
+ }
+
+ /**
+ * This will get the document trailer.
+ *
+ * @return the document trailer dict
+ */
+ public COSDictionary getTrailer()
+ {
+ return trailer;
+ }
+
+ /**
+ * // MIT added, maybe this should not be supported as trailer is a persistence construct.
+ * This will set the document trailer.
+ *
+ * @param newTrailer the document trailer dictionary
+ */
+ public void setTrailer(COSDictionary newTrailer)
+ {
+ trailer = newTrailer;
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ @Override
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return visitor.visitFromDocument( this );
+ }
+
+ /**
+ * This will close all storage and delete the tmp files.
+ *
+ * @throws IOException If there is an error close resources.
+ */
+ public void close() throws IOException
+ {
+ if (!closed) {
+ scratchFile.close();
+ if (tmpFile != null) {
+ tmpFile.delete();
+ }
+ closed = true;
+ }
+ }
+
+ /**
+ * Warn the user in the finalizer if he didn't close the PDF document. The method also
+ * closes the document just in case, to avoid abandoned temporary files. It's still a good
+ * idea for the user to close the PDF document at the earliest possible to conserve resources.
+ * @throws IOException if an error occurs while closing the temporary files
+ */
+ @Override
+ protected void finalize() throws IOException
+ {
+ if (!closed) {
+ if (warnMissingClose) {
+ log.warn( "Warning: You did not close a PDF Document" );
+ }
+ close();
+ }
+ }
+
+ /**
+ * Controls whether this instance shall issue a warning if the PDF document wasn't closed
+ * properly through a call to the {@link #close()} method. If the PDF document is held in
+ * a cache governed by soft references it is impossible to reliably close the document
+ * before the warning is raised. By default, the warning is enabled.
+ * @param warn true enables the warning, false disables it.
+ */
+ public void setWarnMissingClose(boolean warn)
+ {
+ this.warnMissingClose = warn;
+ }
+
+ /**
+ * @return Returns the headerString.
+ */
+ public String getHeaderString()
+ {
+ return headerString;
+ }
+ /**
+ * @param header The headerString to set.
+ */
+ public void setHeaderString(String header)
+ {
+ headerString = header;
+ }
+
+ /**
+ * This method will search the list of objects for types of ObjStm. If it finds
+ * them then it will parse out all of the objects from the stream that is contains.
+ *
+ * @throws IOException If there is an error parsing the stream.
+ */
+ public void dereferenceObjectStreams() throws IOException
+ {
+ for( COSObject objStream : getObjectsByType( COSName.OBJ_STM ) )
+ {
+ COSStream stream = (COSStream)objStream.getObject();
+ PDFObjectStreamParser parser =
+ new PDFObjectStreamParser(stream, this, forceParsing);
+ parser.parse();
+ for( COSObject next : parser.getObjects() )
+ {
+ COSObjectKey key = new COSObjectKey( next );
+ if(objectPool.get(key) == null || objectPool.get(key).getObject() == null)
+ {
+ COSObject obj = getObjectFromPool(key);
+ obj.setObject(next.getObject());
+ }
+ }
+ }
+ }
+
+ /**
+ * This will get an object from the pool.
+ *
+ * @param key The object key.
+ *
+ * @return The object in the pool or a new one if it has not been parsed yet.
+ *
+ * @throws IOException If there is an error getting the proxy object.
+ */
+ public COSObject getObjectFromPool(COSObjectKey key) throws IOException
+ {
+ COSObject obj = null;
+ if( key != null )
+ {
+ obj = objectPool.get(key);
+ }
+ if (obj == null)
+ {
+ // this was a forward reference, make "proxy" object
+ obj = new COSObject(null);
+ if( key != null )
+ {
+ obj.setObjectNumber( COSInteger.get( key.getNumber() ) );
+ obj.setGenerationNumber( COSInteger.get( key.getGeneration() ) );
+ objectPool.put(key, obj);
+ }
+ }
+ return obj;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Populate XRef HashMap with given values.
+ * Each entry maps ObjectKeys to byte offsets in the file.
+ * @param _xrefTable xref table entries to be added
+ */
+ public void addXRefTable( Map<COSObjectKey, Integer> xrefTable )
+ {
+ this.xrefTable.putAll( xrefTable );
+ }
+
+ /**
+ * Returns the xrefTable which is a mapping of ObjectKeys
+ * to byte offsets in the file.
+ * @return mapping of ObjectsKeys to byte offsets
+ */
+ public Map<COSObjectKey, Integer> getXrefTable()
+ {
+ return xrefTable;
+ }
+
+ /**
+ * This method set the startxref value of the document. This will only
+ * be needed for incremental updates.
+ *
+ * @param readInt
+ */
+ public void setStartXref(int startXref)
+ {
+ this.startXref = startXref;
+ }
+
+ /**
+ * Return the startXref Position of the parsed document. This will only be needed for incremental updates.
+ *
+ * @return a int with the old position of the startxref
+ */
+ public int getStartXref()
+ {
+ return startXref;
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * This class represents a floating point number in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.17 $
+ */
+public class COSFloat extends COSNumber
+{
+ private float value;
+
+ /**
+ * Constructor.
+ *
+ * @param aFloat The primitive float object that this object wraps.
+ */
+ public COSFloat( float aFloat )
+ {
+ value = aFloat;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param aFloat The primitive float object that this object wraps.
+ *
+ * @throws IOException If aFloat is not a float.
+ */
+ public COSFloat( String aFloat ) throws IOException
+ {
+ try
+ {
+ value = Float.parseFloat( aFloat );
+ }
+ catch( NumberFormatException e )
+ {
+ throw new IOException( "Error expected floating point number actual='" +aFloat + "'" );
+ }
+ }
+
+ /**
+ * Set the value of the float object.
+ *
+ * @param floatValue The new float value.
+ */
+ public void setValue( float floatValue )
+ {
+ value = floatValue;
+ }
+
+ /**
+ * The value of the float object that this one wraps.
+ *
+ * @return The value of this object.
+ */
+ public float floatValue()
+ {
+ return value;
+ }
+
+ /**
+ * The value of the double object that this one wraps.
+ *
+ * @return The double of this object.
+ */
+ public double doubleValue()
+ {
+ return value;
+ }
+
+ /**
+ * This will get the integer value of this object.
+ *
+ * @return The int value of this object,
+ */
+ public long longValue()
+ {
+ return (long)value;
+ }
+
+ /**
+ * This will get the integer value of this object.
+ *
+ * @return The int value of this object,
+ */
+ public int intValue()
+ {
+ return (int)value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals( Object o )
+ {
+ return o instanceof COSFloat && Float.floatToIntBits(((COSFloat)o).value) == Float.floatToIntBits(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return Float.floatToIntBits(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return "COSFloat{" + value + "}";
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return visitor.visitFromFloat(this);
+ }
+
+ /**
+ * This will output this string as a PDF object.
+ *
+ * @param output The stream to write to.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void writePDF( OutputStream output ) throws IOException
+ {
+ DecimalFormat formatDecimal = (DecimalFormat)NumberFormat.getNumberInstance();
+ formatDecimal.setMaximumFractionDigits( 10 );
+ formatDecimal.setGroupingUsed( false );
+ DecimalFormatSymbols symbols = formatDecimal.getDecimalFormatSymbols();
+ symbols.setDecimalSeparator( '.' );
+ formatDecimal.setDecimalFormatSymbols( symbols );
+ output.write(formatDecimal.format( value ).getBytes("ISO-8859-1"));
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * This class represents an integer number in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.12 $
+ */
+public class COSInteger extends COSNumber
+{
+
+ /**
+ * The lowest integer to be kept in the {@link #STATIC} array.
+ */
+ private static int LOW = -100;
+
+ /**
+ * The highest integer to be kept in the {@link #STATIC} array.
+ */
+ private static int HIGH = 256;
+
+ /**
+ * Static instances of all COSIntegers in the range from {@link #LOW}
+ * to {@link #HIGH}.
+ */
+ private static final COSInteger[] STATIC = new COSInteger[HIGH - LOW + 1];
+
+ /**
+ * Constant for the number zero.
+ * @since Apache PDFBox 1.1.0
+ */
+ public static final COSInteger ZERO = get(0);
+
+ /**
+ * Constant for the number one.
+ * @since Apache PDFBox 1.1.0
+ */
+ public static final COSInteger ONE = get(1);
+
+ /**
+ * Constant for the number two.
+ * @since Apache PDFBox 1.1.0
+ */
+ public static final COSInteger TWO = get(2);
+
+ /**
+ * Constant for the number three.
+ * @since Apache PDFBox 1.1.0
+ */
+ public static final COSInteger THREE = get(3);
+
+ /**
+ * Returns a COSInteger instance with the given value.
+ *
+ * @param val integer value
+ * @return COSInteger instance
+ */
+ public static COSInteger get(long val) {
+ if (LOW <= val && val <= HIGH) {
+ int index = (int) val - LOW;
+ // no synchronization needed
+ if (STATIC[index] == null) {
+ STATIC[index] = new COSInteger(val);
+ }
+ return STATIC[index];
+ } else {
+ return new COSInteger(val);
+ }
+ }
+
+ private long value;
+
+ /**
+ * constructor.
+ *
+ * @deprecated use the static {@link #get(long)} method instead
+ * @param val The integer value of this object.
+ */
+ public COSInteger( long val )
+ {
+ value = val;
+ }
+
+ /**
+ * constructor.
+ *
+ * @deprecated use the static {@link #get(long)} method instead
+ * @param val The integer value of this object.
+ */
+ public COSInteger( int val )
+ {
+ this( (long)val );
+ }
+
+ /**
+ * This will create a new PDF Int object using a string.
+ *
+ * @param val The string value of the integer.
+ * @deprecated use the static {@link #get(long)} method instead
+ * @throws IOException If the val is not an integer type.
+ */
+ public COSInteger( String val ) throws IOException
+ {
+ try
+ {
+ value = Long.parseLong( val );
+ }
+ catch( NumberFormatException e )
+ {
+ throw new IOException( "Error: value is not an integer type actual='" + val + "'" );
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object o)
+ {
+ return o instanceof COSInteger && ((COSInteger)o).intValue() == intValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ //taken from java.lang.Long
+ return (int)(value ^ (value >> 32));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return "COSInt{" + value + "}";
+ }
+
+ /**
+ * Change the value of this reference.
+ *
+ * @param newValue The new value.
+ */
+ public void setValue( long newValue )
+ {
+ value = newValue;
+ }
+
+ /**
+ * polymorphic access to value as float.
+ *
+ * @return The float value of this object.
+ */
+ public float floatValue()
+ {
+ return value;
+ }
+
+ /**
+ * polymorphic access to value as float.
+ *
+ * @return The double value of this object.
+ */
+ public double doubleValue()
+ {
+ return value;
+ }
+
+ /**
+ * Polymorphic access to value as int
+ * This will get the integer value of this object.
+ *
+ * @return The int value of this object,
+ */
+ public int intValue()
+ {
+ return (int)value;
+ }
+
+ /**
+ * Polymorphic access to value as int
+ * This will get the integer value of this object.
+ *
+ * @return The int value of this object,
+ */
+ public long longValue()
+ {
+ return value;
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return visitor.visitFromInt(this);
+ }
+
+ /**
+ * This will output this string as a PDF object.
+ *
+ * @param output The stream to write to.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void writePDF( OutputStream output ) throws IOException
+ {
+ output.write(String.valueOf(value).getBytes("ISO-8859-1"));
+ }
+
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.persistence.util.COSHEXTable;
+
+
+/**
+ * This class represents a PDF named object.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.42 $
+ */
+public final class COSName extends COSBase implements Comparable<COSName>
+{
+ /**
+ * Note: This is a ConcurrentHashMap because a HashMap must be synchronized if accessed by
+ * multiple threads.
+ */
+ private static Map<String, COSName> nameMap = new ConcurrentHashMap<String, COSName>(8192);
+
+ /**
+ * All common COSName values are stored in a simple HashMap. They are already defined as
+ * static constants and don't need to be synchronized for multithreaded environments.
+ */
+ private static Map<String, COSName> commonNameMap =
+ new HashMap<String, COSName>();
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName A = new COSName( "A" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName AA = new COSName( "AA" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ACRO_FORM = new COSName( "AcroForm" );
+ /**
+ * "ActualText"
+ */
+ public static final COSName ACTUAL_TEXT = new COSName("ActualText");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName AIS = new COSName( "AIS" );
+ /**
+ * "Alt"
+ */
+ public static final COSName ALT = new COSName("Alt");
+ /**
+ * "Alt"
+ */
+ public static final COSName ALTERNATE = new COSName("Alternate");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ANNOT = new COSName( "Annot" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ANNOTS = new COSName( "Annots" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ANTI_ALIAS = new COSName( "AntiAlias" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName AP_REF = new COSName( "APRef" );
+ /**
+ * "Artifact"
+ */
+ public static final COSName ARTIFACT = new COSName("Artifact");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ART_BOX = new COSName("ArtBox" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName AS = new COSName( "AS" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ASCII85_DECODE = new COSName( "ASCII85Decode" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ASCII85_DECODE_ABBREVIATION = new COSName( "A85" );
+ /**
+ * "Attached"
+ */
+ public static final COSName ATTACHED = new COSName("Attached");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ASCENT = new COSName( "Ascent" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ASCII_HEX_DECODE = new COSName( "ASCIIHexDecode" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ASCII_HEX_DECODE_ABBREVIATION = new COSName( "AHx" );
+
+ /** "AP" */
+ public static final COSName AP = new COSName( "AP" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName AUTHOR = new COSName( "Author" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName AVG_WIDTH = new COSName( "AvgWidth" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName B = new COSName( "B" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName BASE_ENCODING = new COSName( "BaseEncoding" );
+ /**
+ * 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.
+ */
+ public static final COSName BBOX = new COSName( "BBox" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName BLACK_IS_1 = new COSName( "BlackIs1" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName BLACK_POINT = new COSName( "BlackPoint" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName BLEED_BOX = new COSName("BleedBox" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName BITS_PER_COMPONENT = new COSName("BitsPerComponent" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName BITS_PER_SAMPLE = new COSName("BitsPerSample" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName BOUNDS = new COSName("Bounds" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName BPC = new COSName("BPC" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CATALOG = new COSName( "Catalog" );
+ /**
+ * "C"
+ */
+ public static final COSName C = new COSName("C");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName C0 = new COSName( "C0" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName C1 = new COSName( "C1" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CA = new COSName( "CA" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CA_NS = new COSName( "ca" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CALGRAY = new COSName( "CalGray" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CALRGB = new COSName( "CalRGB" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CAP_HEIGHT = new COSName( "CapHeight" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CCITTFAX_DECODE = new COSName( "CCITTFaxDecode" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CCITTFAX_DECODE_ABBREVIATION = new COSName( "CCF" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CF = new COSName( "CF" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CFM = new COSName( "CFM" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CHAR_PROCS = new COSName( "CharProcs" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CHAR_SET = new COSName( "CharSet" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CID_FONT_TYPE0 = new COSName( "CIDFontType0" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CID_FONT_TYPE2 = new COSName( "CIDFontType2" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CIDSYSTEMINFO = new COSName( "CIDSystemInfo" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName COLORANTS = new COSName( "Colorants" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName COLORS = new COSName( "Colors" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName COLORSPACE = new COSName( "ColorSpace" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName COLUMNS = new COSName( "Columns" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CONTENTS = new COSName( "Contents" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName COORDS = new COSName( "Coords" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName COUNT = new COSName( "Count" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CLR_F = new COSName( "ClrF" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CLR_FF = new COSName( "ClrFf" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CREATION_DATE = new COSName( "CreationDate" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CREATOR = new COSName( "Creator" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CROP_BOX = new COSName( "CropBox" );
+ /**
+ * The Crypt filter.
+ */
+ public static final COSName CRYPT = new COSName( "Crypt" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName CS = new COSName( "CS" );
+
+ /** "D" */
+ public static final COSName D = new COSName( "D" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DA = new COSName( "DA" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DCT_DECODE = new COSName( "DCTDecode" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DCT_DECODE_ABBREVIATION = new COSName( "DCT" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DECODE = new COSName( "Decode" );
+ /** "DecodeParms" */
+ public static final COSName DECODE_PARMS = new COSName( "DecodeParms" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DESCENT = new COSName( "Descent" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DESCENDANT_FONTS = new COSName( "DescendantFonts" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DEST = new COSName( "Dest" );
+
+ /** "Dests" */
+ public static final COSName DESTS = new COSName( "Dests" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DEVICECMYK = new COSName( "DeviceCMYK" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DEVICEGRAY = new COSName( "DeviceGray" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DEVICEN = new COSName( "DeviceN" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DEVICERGB = new COSName( "DeviceRGB" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DIFFERENCES = new COSName( "Differences" );
+
+ /** "DL" */
+ public static final COSName DL = new COSName( "DL" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DOMAIN = new COSName( "Domain" );
+
+ /** "DP" */
+ public static final COSName DP = new COSName( "DP" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DR = new COSName( "DR" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DV = new COSName( "DV" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName DW = new COSName( "DW" );
+
+ /**
+ * "E"
+ */
+ public static final COSName E = new COSName("E");
+
+ /** "EmbeddedFiles" */
+ public static final COSName EMBEDDED_FILES = new COSName( "EmbeddedFiles" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ENCODE = new COSName( "Encode" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ENCODING = new COSName( "Encoding" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ENCODING_90MS_RKSJ_H = new COSName( "90ms-RKSJ-H" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ENCODING_90MS_RKSJ_V = new COSName( "90ms-RKSJ-V" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ENCODING_ETEN_B5_H = new COSName( "ETen?B5?H" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ENCODING_ETEN_B5_V = new COSName( "ETen?B5?V" );
+
+ /** "Encrypt" */
+ public static final COSName ENCRYPT = new COSName( "Encrypt" );
+
+ /** "EncryptMetaData" */
+ public static final COSName ENCRYPT_META_DATA = new COSName( "EncryptMetadata" );
+
+ /** "ExtGState" */
+ public static final COSName EXT_G_STATE = new COSName( "ExtGState" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName EXTEND = new COSName( "Extend" );
+
+ /** "Extends" */
+ public static final COSName EXTENDS = new COSName( "Extends" );
+
+ /** "F" */
+ public static final COSName F = new COSName( "F" );
+
+ /** "FDecodeParms" */
+ public static final COSName F_DECODE_PARMS = new COSName( "FDecodeParms" );
+
+ /** "FFilter" */
+ public static final COSName F_FILTER = new COSName( "FFilter" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FF = new COSName( "Ff" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FIELDS = new COSName( "Fields" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FILTER = new COSName( "Filter" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FIRST_CHAR = new COSName( "FirstChar" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FL = new COSName( "FL" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FLAGS = new COSName( "Flags" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FLATE_DECODE = new COSName( "FlateDecode" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FLATE_DECODE_ABBREVIATION = new COSName( "Fl" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT = new COSName( "Font" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT_BBOX = new COSName( "FontBBox" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT_FAMILY = new COSName("FontFamily");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT_FILE = new COSName("FontFile");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT_FILE2 = new COSName("FontFile2");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT_FILE3 = new COSName("FontFile3");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT_DESC = new COSName("FontDescriptor");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT_MATRIX = new COSName("FontMatrix" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT_NAME = new COSName("FontName" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT_STRETCH = new COSName("FontStretch" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FONT_WEIGHT = new COSName("FontWeight" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FORM = new COSName( "Form" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FORMTYPE = new COSName( "FormType" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FRM = new COSName( "FRM" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FT = new COSName( "FT" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FUNCTION = new COSName( "Function" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FUNCTION_TYPE = new COSName( "FunctionType" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName FUNCTIONS = new COSName( "Functions" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName GAMMA = new COSName( "Gamma" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName H = new COSName( "H" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName HEIGHT = new COSName( "Height" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ICCBASED = new COSName( "ICCBased" );
+
+ /** "ID" */
+ public static final COSName ID = new COSName("ID");
+
+ /** "IDTree" */
+ public static final COSName ID_TREE = new COSName("IDTree");
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName IDENTITY = new COSName( "Identity" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName IDENTITY_H = new COSName( "Identity-H" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName IMAGE = new COSName( "Image" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName IMAGE_MASK = new COSName( "ImageMask" );
+
+ /** "Index" */
+ public static final COSName INDEX = new COSName( "Index" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName INDEXED = new COSName( "Indexed" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName INFO = new COSName( "Info" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ITALIC_ANGLE = new COSName( "ItalicAngle" );
+
+ /** "JavaScript" */
+ public static final COSName JAVA_SCRIPT = new COSName( "JavaScript" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName JBIG2_DECODE = new COSName( "JBIG2Decode" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName JPX_DECODE = new COSName( "JPXDecode" );
+
+ /** "K" */
+ public static final COSName K = new COSName("K");
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName KEYWORDS = new COSName( "Keywords" );
+
+ /** "Kids" */
+ public static final COSName KIDS = new COSName( "Kids" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LAB = new COSName( "Lab" );
+
+ /** "Lang" */
+ public static final COSName LANG = new COSName("Lang");
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LAST_CHAR = new COSName( "LastChar" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LC = new COSName( "LC" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LEADING = new COSName( "Leading" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LENGTH = new COSName( "Length" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LENGTH1 = new COSName( "Length1" );
+
+ /** "Limits" */
+ public static final COSName LIMITS = new COSName( "Limits" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LJ = new COSName( "LJ" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LW = new COSName( "LW" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LZW_DECODE = new COSName( "LZWDecode" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LZW_DECODE_ABBREVIATION = new COSName( "LZW" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName M = new COSName( "M" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName MAC_ROMAN_ENCODING = new COSName( "MacRomanEncoding" );
+
+ /** "MarkInfo" */
+ public static final COSName MARK_INFO = new COSName("MarkInfo");
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName MASK = new COSName( "Mask" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName MATRIX = new COSName( "Matrix" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName MAX_WIDTH = new COSName( "MaxWidth" );
+ /**
+ * "MCID"
+ */
+ public static final COSName MCID = new COSName("MCID");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName MEDIA_BOX = new COSName( "MediaBox" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName METADATA = new COSName( "Metadata" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName MISSING_WIDTH = new COSName( "MissingWidth" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ML = new COSName( "ML" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName MM_TYPE1 = new COSName( "MMType1" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName MOD_DATE = new COSName( "ModDate" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName N = new COSName( "N" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName NAME = new COSName( "Name" );
+
+ /** "Names" */
+ public static final COSName NAMES = new COSName( "Names" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName NEXT = new COSName( "Next" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName NM = new COSName( "NM" );
+ /** "Nums" */
+ public static final COSName NUMS = new COSName( "Nums" );
+
+ /**
+ * "O"
+ */
+ public static final COSName O = new COSName("O");
+
+ /**
+ * "Obj"
+ */
+ public static final COSName OBJ = new COSName("Obj");
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName OBJ_STM = new COSName( "ObjStm" );
+
+ /** 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.
+ */
+ public static final COSName OP = new COSName( "OP" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName OP_NS = new COSName( "op" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName OPM = new COSName( "OPM" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName OPT = new COSName( "Opt" );
+ /** "Outlines" */
+ public static final COSName OUTLINES = new COSName("Outlines");
+
+ /** "OpenAction" */
+ public static final COSName OPEN_ACTION = new COSName("OpenAction");
+
+ /** A common COSName value. */
+ public static final COSName ORDER = new COSName( "Order" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ORDERING = new COSName( "Ordering" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName P = new COSName( "P" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName PAGE = new COSName( "Page" );
+
+ /** "PageLabels" */
+ public static final COSName PAGE_LABELS = new COSName("PageLabels");
+
+ /** "PageLayout" */
+ public static final COSName PAGE_LAYOUT = new COSName("PageLayout");
+
+ /** "PageMode" */
+ public static final COSName PAGE_MODE = new COSName("PageMode");
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName PAGES = new COSName( "Pages" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName PARENT = new COSName( "Parent" );
+ /**
+ * "ParentTreeNextKey"
+ */
+ public static final COSName PARENT_TREE_NEXT_KEY = new COSName("ParentTreeNextKey");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName PATTERN = new COSName( "Pattern" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName PDF_DOC_ENCODING = new COSName( "PDFDocEncoding" );
+ /**
+ * "Pg"
+ */
+ public static final COSName PG = new COSName("Pg");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName PREDICTOR = new COSName( "Predictor" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName PREV = new COSName( "Prev" );
+
+ /** The COSName value for "ProcSet". */
+ public static final COSName PROC_SET = new COSName( "ProcSet" );
+
+ /**
+ * A common COSName value.
+ */
+ 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.
+ */
+ public static final COSName Q = new COSName( "Q" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName R = new COSName( "R" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName RANGE = new COSName( "Range" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName RECIPIENTS = new COSName( "Recipients" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName RECT = new COSName( "Rect" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName REGISTRY = new COSName( "Registry" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName RESOURCES = new COSName( "Resources" );
+ /**
+ * "RoleMap"
+ */
+ public static final COSName RI = new COSName( "RI" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ROLE_MAP = new COSName("RoleMap");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ROOT = new COSName( "Root" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ROTATE = new COSName( "Rotate" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName ROWS = new COSName( "Rows" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName RUN_LENGTH_DECODE = new COSName( "RunLengthDecode" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName RUN_LENGTH_DECODE_ABBREVIATION = new COSName( "RL" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName RV = new COSName( "RV" );
+ /**
+ * "S"
+ */
+ public static final COSName S = new COSName("S");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SA = new COSName( "SA" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SE = new COSName( "SE" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SEPARATION = new COSName( "Separation" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SET_F = new COSName( "SetF" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SET_FF = new COSName( "SetFf" );
+
+ /** "Shading" */
+ public static final COSName SHADING = new COSName( "Shading" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SHADING_TYPE = new COSName( "ShadingType" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SM = new COSName( "SM" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SMASK = new COSName( "SMask" );
+ /** "Size" */
+ public static final COSName SIZE = new COSName( "Size" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName STANDARD_ENCODING = new COSName( "StandardEncoding" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName STD_CF = new COSName( "StdCF" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName STEM_H = new COSName( "StemH" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName STEM_V = new COSName( "StemV" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName STM_F = new COSName( "StmF" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName STR_F = new COSName( "StrF" );
+
+ /** "StructTreeRoot" */
+ public static final COSName STRUCT_TREE_ROOT = new COSName("StructTreeRoot");
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SUB_FILTER = new COSName( "SubFilter" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SUBJ = new COSName( "Subj" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SUBJECT = new COSName( "Subject" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SUPPLEMENT = new COSName( "Supplement" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SUBTYPE = new COSName( "Subtype" );
+
+ /**
+ * "T"
+ */
+ public static final COSName T = new COSName("T");
+
+ /** "Threads" */
+ public static final COSName THREADS = new COSName("Threads");
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TITLE = new COSName( "Title" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TK = new COSName( "TK" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TRAPPED = new COSName("Trapped" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TRIM_BOX = new COSName("TrimBox" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TRUE_TYPE = new COSName("TrueType" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TO_UNICODE = new COSName( "ToUnicode" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TU = new COSName("TU" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TYPE = new COSName( "Type" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TYPE0 = new COSName( "Type0" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TYPE1 = new COSName( "Type1" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName TYPE3 = new COSName( "Type3" );
+
+ /**
+ * 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");
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName V = new COSName( "V" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName VERSION = new COSName( "Version" );
+
+ /** "ViewerPreferences" */
+ public static final COSName VIEWER_PREFERENCES = new COSName("ViewerPreferences");
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName W = new COSName( "W" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName WIDTH = new COSName( "Width" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName WIDTHS = new COSName( "Widths" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName WIN_ANSI_ENCODING = new COSName( "WinAnsiEncoding" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName WHITE_POINT = new COSName( "WhitePoint" );
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName XHEIGHT = new COSName( "XHeight" );
+
+ /** "XObject" */
+ public static final COSName XOBJECT = new COSName( "XObject" );
+ /**
+ * A common COSName value.
+ */
+ public static final COSName XREF = new COSName( "XRef" );
+ /**
+ * The prefix to a PDF name.
+ */
+ public static final byte[] NAME_PREFIX = new byte[] { 47 }; // The / character
+ /**
+ * The escape character for a name.
+ */
+ public static final byte[] NAME_ESCAPE = new byte[] { 35 }; //The # character
+
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SUBFILTER = new COSName("SubFilter");
+ /**
+ * A signature filter value.
+ */
+ public static final COSName ADOBE_PPKLITE = new COSName("Adobe.PPKLite");
+ /**
+ * A signature filter value.
+ */
+ public static final COSName ENTRUST_PPKEF = new COSName("Entrust.PPKEF");
+ /**
+ * A signature filter value.
+ */
+ public static final COSName CICI_SIGNIT = new COSName("CICI.SignIt");
+ /**
+ * A signature filter value.
+ */
+ public static final COSName VERISIGN_PPKVS = new COSName("VeriSign.PPKVS");
+ /**
+ * A signature subfilter value.
+ */
+ public static final COSName ADBE_X509_RSA_SHA1 = new COSName("adbe.x509.rsa_sha1");
+ /**
+ * A signature subfilter value.
+ */
+ public static final COSName ADBE_PKCS7_DETACHED = new COSName("adbe.pkcs7.detached");
+ /**
+ * A signature subfilter value.www
+ */
+ public static final COSName ADBE_PKCS7_SHA1 = new COSName("adbe.pkcs7.sha1");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName LOCATION = new COSName("Location");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName REASON = new COSName("Reason");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName BYTERANGE = new COSName("ByteRange");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SIG = new COSName("Sig");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName SIG_FLAGS = new COSName("SigFlags");
+
+
+ private String name;
+ private int hashCode;
+
+ /**
+ * This will get a COSName object with that name.
+ *
+ * @param aName The name of the object.
+ *
+ * @return A COSName with the specified name.
+ */
+ public static final COSName getPDFName( String aName )
+ {
+ COSName name = null;
+ if( aName != null )
+ {
+ // Is it a common COSName ??
+ name = commonNameMap.get( aName );
+ if( name == null )
+ {
+ // It seems to be a document specific COSName
+ name = nameMap.get( aName );
+ if( name == null )
+ {
+ //name is added to the synchronized map in the constructor
+ name = new COSName( aName, false );
+ }
+ }
+ }
+ return name;
+ }
+
+ /**
+ * Private constructor. This will limit the number of COSName objects.
+ * that are created.
+ *
+ * @param aName The name of the COSName object.
+ * @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 )
+ {
+ name = aName;
+ if ( staticValue )
+ {
+ commonNameMap.put( aName, this);
+ }
+ else
+ {
+ nameMap.put( aName, this );
+ }
+ hashCode = name.hashCode();
+ }
+
+ /**
+ * Private constructor. This will limit the number of COSName objects.
+ * that are created.
+ *
+ * @param aName The name of the COSName object.
+ */
+ private COSName( String aName )
+ {
+ this( aName, true );
+ }
+
+ /**
+ * This will get the name of this COSName object.
+ *
+ * @return The name of the object.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return "COSName{" + name + "}";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals( Object o )
+ {
+ boolean retval = this == o;
+ if( !retval && o instanceof COSName )
+ {
+ COSName other = (COSName)o;
+ retval = name == other.name || name.equals( other.name );
+ }
+ return retval;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return hashCode;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(COSName other)
+ {
+ return this.name.compareTo( other.name );
+ }
+
+
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return visitor.visitFromName(this);
+ }
+
+ /**
+ * This will output this string as a PDF object.
+ *
+ * @param output The stream to write to.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void writePDF( OutputStream output ) throws IOException
+ {
+ output.write(NAME_PREFIX);
+ byte[] bytes = getName().getBytes("ISO-8859-1");
+ for (int i = 0; i < bytes.length;i++)
+ {
+ int current = ((bytes[i]+256)%256);
+
+ if(current <= 32 || current >= 127 ||
+ current == '(' ||
+ current == ')' ||
+ current == '[' ||
+ current == ']' ||
+ current == '/' ||
+ current == '%' ||
+ current == '<' ||
+ current == '>' ||
+ current == NAME_ESCAPE[0] )
+ {
+ output.write(NAME_ESCAPE);
+ output.write(COSHEXTable.TABLE[current]);
+ }
+ else
+ {
+ output.write(current);
+ }
+ }
+ }
+
+ /**
+ * Not usually needed except if resources need to be reclaimed in a ong
+ * running process.
+ * Patch provided by flester@GMail.com
+ * incorporated 5/23/08, Danielwilson@users.SourceForge.net
+ */
+ public static synchronized void clearResources()
+ {
+ // Clear them all
+ nameMap.clear();
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * This class represents a null PDF object.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.13 $
+ */
+public class COSNull extends COSBase
+{
+ /**
+ * The null token.
+ */
+ public static final byte[] NULL_BYTES = new byte[] {110, 117, 108, 108}; //"null".getBytes( "ISO-8859-1" );
+
+ /**
+ * The one null object in the system.
+ */
+ public static final COSNull NULL = new COSNull();
+
+ /**
+ * Constructor.
+ */
+ private COSNull()
+ {
+ //limit creation to one instance.
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ public Object accept( ICOSVisitor visitor ) throws COSVisitorException
+ {
+ return visitor.visitFromNull( this );
+ }
+
+ /**
+ * This will output this string as a PDF object.
+ *
+ * @param output The stream to write to.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void writePDF( OutputStream output ) throws IOException
+ {
+ output.write(NULL_BYTES);
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import java.io.IOException;
+
+/**
+ * This class represents an abstract number in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public abstract class COSNumber extends COSBase
+{
+
+ /**
+ * @deprecated Use the {@link COSInteger#ZERO} constant instead
+ */
+ public static final COSInteger ZERO = COSInteger.ZERO;
+
+ /**
+ * @deprecated Use the {@link COSInteger#ONE} constant instead
+ */
+ public static final COSInteger ONE = COSInteger.ONE;
+
+ /**
+ * This will get the float value of this number.
+ *
+ * @return The float value of this object.
+ */
+ public abstract float floatValue();
+
+ /**
+ * This will get the double value of this number.
+ *
+ * @return The double value of this number.
+ */
+ public abstract double doubleValue();
+
+ /**
+ * This will get the integer value of this number.
+ *
+ * @return The integer value of this number.
+ */
+ public abstract int intValue();
+
+ /**
+ * This will get the long value of this number.
+ *
+ * @return The long value of this number.
+ */
+ public abstract long longValue();
+
+ /**
+ * This factory method will get the appropriate number object.
+ *
+ * @param number The string representation of the number.
+ *
+ * @return A number object, either float or int.
+ *
+ * @throws IOException If the string is not a number.
+ */
+ public static COSNumber get( String number ) throws IOException
+ {
+ if (number.length() == 1) {
+ char digit = number.charAt(0);
+ if ('0' <= digit && digit <= '9') {
+ return COSInteger.get(digit - '0');
+ } else if (digit == '-' || digit == '.') {
+ // See https://issues.apache.org/jira/browse/PDFBOX-592
+ return COSInteger.ZERO;
+ } else {
+ throw new IOException("Not a number: " + number);
+ }
+ } else if (number.indexOf('.') == -1) {
+ try
+ {
+ return COSInteger.get( Long.parseLong( number ) );
+ }
+ catch( NumberFormatException e )
+ {
+ throw new IOException( "Value is not an integer: " + number );
+ }
+ } else {
+ return new COSFloat(number);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import java.io.IOException;
+
+/**
+ * This class represents a PDF object.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.37 $
+ */
+public class COSObject extends COSBase
+{
+ private COSBase baseObject;
+ private COSInteger objectNumber;
+ private COSInteger generationNumber;
+
+ /**
+ * Constructor.
+ *
+ * @param object The object that this encapsulates.
+ *
+ * @throws IOException If there is an error with the object passed in.
+ */
+ public COSObject( COSBase object ) throws IOException
+ {
+ setObject( object );
+ }
+
+ /**
+ * This will get the dictionary object in this object that has the name key and
+ * if it is a pdfobjref then it will dereference that and return it.
+ *
+ * @param key The key to the value that we are searching for.
+ *
+ * @return The pdf object that matches the key.
+ */
+ public COSBase getDictionaryObject( COSName key )
+ {
+ COSBase retval =null;
+ if( baseObject instanceof COSDictionary )
+ {
+ retval = ((COSDictionary)baseObject).getDictionaryObject( key );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the dictionary object in this object that has the name key.
+ *
+ * @param key The key to the value that we are searching for.
+ *
+ * @return The pdf object that matches the key.
+ */
+ public COSBase getItem( COSName key )
+ {
+ COSBase retval =null;
+ if( baseObject instanceof COSDictionary )
+ {
+ retval = ((COSDictionary)baseObject).getItem( key );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the object that this object encapsulates.
+ *
+ * @return The encapsulated object.
+ */
+ public COSBase getObject()
+ {
+ return baseObject;
+ }
+
+ /**
+ * This will set the object that this object encapsulates.
+ *
+ * @param object The new object to encapsulate.
+ *
+ * @throws IOException If there is an error setting the updated object.
+ */
+ public void setObject( COSBase object ) throws IOException
+ {
+ baseObject = object;
+ /*if( baseObject == null )
+ {
+ baseObject = object;
+ }
+ else
+ {
+ //This is for when an object appears twice in the
+ //pdf file we really want to replace it such that
+ //object references still work correctly.
+ //see owcp-as-received.pdf for an example
+ if( baseObject instanceof COSDictionary )
+ {
+ COSDictionary dic = (COSDictionary)baseObject;
+ COSDictionary dicObject = (COSDictionary)object;
+ dic.clear();
+ dic.addAll( dicObject );
+ }
+ else if( baseObject instanceof COSArray )
+ {
+ COSArray array = (COSArray)baseObject;
+ COSArray arrObject = (COSArray)object;
+ array.clear();
+ for( int i=0; i<arrObject.size(); i++ )
+ {
+ array.add( arrObject.get( i ) );
+ }
+ }
+ else if( baseObject instanceof COSStream )
+ {
+ COSStream oldStream = (COSStream)baseObject;
+ System.out.println( "object:" + object.getClass().getName() );
+ COSStream newStream = (COSStream)object;
+ oldStream.replaceWithStream( newStream );
+ }
+ else if( baseObject instanceof COSInteger )
+ {
+ COSInteger oldInt = (COSInteger)baseObject;
+ COSInteger newInt = (COSInteger)object;
+ oldInt.setValue( newInt.longValue() );
+ }
+ else if( baseObject == null )
+ {
+ baseObject = object;
+ }
+ else
+ {
+ throw new IOException( "Unknown object substitution type:" + baseObject );
+ }
+ }*/
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return "COSObject{" +
+ (objectNumber == null ? "unknown" : "" + objectNumber.intValue() ) + ", " +
+ (generationNumber == null ? "unknown" : "" + generationNumber.intValue() ) +
+ "}";
+ }
+
+ /** Getter for property objectNumber.
+ * @return Value of property objectNumber.
+ */
+ public COSInteger getObjectNumber()
+ {
+ return objectNumber;
+ }
+
+ /** Setter for property objectNumber.
+ * @param objectNum New value of property objectNumber.
+ */
+ public void setObjectNumber(COSInteger objectNum)
+ {
+ objectNumber = objectNum;
+ }
+
+ /** Getter for property generationNumber.
+ * @return Value of property generationNumber.
+ */
+ public COSInteger getGenerationNumber()
+ {
+ return generationNumber;
+ }
+
+ /** Setter for property generationNumber.
+ * @param generationNumberValue New value of property generationNumber.
+ */
+ public void setGenerationNumber(COSInteger generationNumberValue)
+ {
+ generationNumber = generationNumberValue;
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ public Object accept( ICOSVisitor visitor ) throws COSVisitorException
+ {
+ return getObject() != null ? getObject().accept( visitor ) : COSNull.NULL.accept( visitor );
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.util.List;
+
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterManager;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.io.RandomAccessFileInputStream;
+import org.apache.pdfbox.io.RandomAccessFileOutputStream;
+
+/**
+ * This class represents a stream object in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.41 $
+ */
+public class COSStream extends COSDictionary
+{
+ private static final int BUFFER_SIZE=16384;
+
+ private RandomAccess file;
+ /**
+ * The stream with all of the filters applied.
+ */
+ private RandomAccessFileOutputStream filteredStream;
+
+ /**
+ * The stream with no filters, this contains the useful data.
+ */
+ private RandomAccessFileOutputStream unFilteredStream;
+
+ /**
+ * Constructor. Creates a new stream with an empty dictionary.
+ *
+ * @param storage The intermediate storage for the stream.
+ */
+ public COSStream( RandomAccess storage )
+ {
+ super();
+ file = storage;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dictionary The dictionary that is associated with this stream.
+ * @param storage The intermediate storage for the stream.
+ */
+ public COSStream( COSDictionary dictionary, RandomAccess storage )
+ {
+ super( dictionary );
+ file = storage;
+ }
+
+ /**
+ * This will replace this object with the data from the new object. This
+ * is used to easily maintain referential integrity when changing references
+ * to new objects.
+ *
+ * @param stream The stream that have the new values in it.
+ */
+ public void replaceWithStream( COSStream stream )
+ {
+ this.clear();
+ this.addAll( stream );
+ file = stream.file;
+ filteredStream = stream.filteredStream;
+ unFilteredStream = stream.unFilteredStream;
+ }
+
+ /**
+ * This will get the scratch file associated with this stream.
+ *
+ * @return The scratch file where this stream is being stored.
+ */
+ public RandomAccess getScratchFile()
+ {
+ return file;
+ }
+
+ /**
+ * This will get all the tokens in the stream.
+ *
+ * @return All of the tokens in the stream.
+ *
+ * @throws IOException If there is an error parsing the stream.
+ */
+ public List<Object> getStreamTokens() throws IOException
+ {
+ PDFStreamParser parser = new PDFStreamParser( this );
+ parser.parse();
+ return parser.getTokens();
+ }
+
+ /**
+ * This will get the stream with all of the filters applied.
+ *
+ * @return the bytes of the physical (endoced) stream
+ *
+ * @throws IOException when encoding/decoding causes an exception
+ */
+ public InputStream getFilteredStream() throws IOException
+ {
+ if( filteredStream == null )
+ {
+ doEncode();
+ }
+ long position = filteredStream.getPosition();
+ long length = filteredStream.getLength();
+
+ RandomAccessFileInputStream input =
+ new RandomAccessFileInputStream( file, position, length );
+ return new BufferedInputStream( input, BUFFER_SIZE );
+ }
+
+ /**
+ * This will get the logical content stream with none of the filters.
+ *
+ * @return the bytes of the logical (decoded) stream
+ *
+ * @throws IOException when encoding/decoding causes an exception
+ */
+ public InputStream getUnfilteredStream() throws IOException
+ {
+ InputStream retval = null;
+ if( unFilteredStream == null )
+ {
+ doDecode();
+ }
+
+ //if unFilteredStream is still null then this stream has not been
+ //created yet, so we should return null.
+ if( unFilteredStream != null )
+ {
+ long position = unFilteredStream.getPosition();
+ long length = unFilteredStream.getLength();
+ RandomAccessFileInputStream input =
+ new RandomAccessFileInputStream( file, position, length );
+ retval = new BufferedInputStream( input, BUFFER_SIZE );
+ }
+ else
+ {
+ // We should check if the COSStream contains data, maybe it
+ // has been created with a RandomAccessFile - which is not
+ // necessary empty.
+ // In this case, the creation was been done as an input, this should
+ // be the unfiltered file, since no filter has been applied yet.
+// if ( (file != null) &&
+// (file.length() > 0) )
+// {
+// retval = new RandomAccessFileInputStream( file,
+// 0,
+// file.length() );
+// }
+// else
+// {
+ //if there is no stream data then simply return an empty stream.
+ retval = new ByteArrayInputStream( new byte[0] );
+// }
+ }
+ return retval;
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return visitor.visitFromStream(this);
+ }
+
+ /**
+ * This will decode the physical byte stream applying all of the filters to the stream.
+ *
+ * @throws IOException If there is an error applying a filter to the stream.
+ */
+ private void doDecode() throws IOException
+ {
+// FIXME: We shouldn't keep the same reference?
+ unFilteredStream = filteredStream;
+
+ COSBase filters = getFilters();
+ if( filters == null )
+ {
+ //then do nothing
+ }
+ else if( filters instanceof COSName )
+ {
+ doDecode( (COSName)filters, 0 );
+ }
+ else if( filters instanceof COSArray )
+ {
+ COSArray filterArray = (COSArray)filters;
+ for( int i=0; i<filterArray.size(); i++ )
+ {
+ COSName filterName = (COSName)filterArray.get( i );
+ doDecode( filterName, i );
+ }
+ }
+ else
+ {
+ throw new IOException( "Error: Unknown filter type:" + filters );
+ }
+ }
+
+ /**
+ * This will decode applying a single filter on the stream.
+ *
+ * @param filterName The name of the filter.
+ * @param filterIndex The index of the current filter.
+ *
+ * @throws IOException If there is an error parsing the stream.
+ */
+ private void doDecode( COSName filterName, int filterIndex ) throws IOException
+ {
+ FilterManager manager = getFilterManager();
+ Filter filter = manager.getFilter( filterName );
+ InputStream input;
+
+ boolean done = false;
+ IOException exception = null;
+ long position = unFilteredStream.getPosition();
+ long length = unFilteredStream.getLength();
+ // in case we need it later
+ long writtenLength = unFilteredStream.getLengthWritten();
+
+ if( length == 0 )
+ {
+ //if the length is zero then don't bother trying to decode
+ //some filters don't work when attempting to decode
+ //with a zero length stream. See zlib_error_01.pdf
+ unFilteredStream = new RandomAccessFileOutputStream( file );
+ done = true;
+ }
+ else
+ {
+ //ok this is a simple hack, sometimes we read a couple extra
+ //bytes that shouldn't be there, so we encounter an error we will just
+ //try again with one less byte.
+ for( int tryCount=0; !done && tryCount<5; tryCount++ )
+ {
+ try
+ {
+ input = new BufferedInputStream(
+ new RandomAccessFileInputStream( file, position, length ), BUFFER_SIZE );
+ unFilteredStream = new RandomAccessFileOutputStream( file );
+ filter.decode( input, unFilteredStream, this, filterIndex );
+ done = true;
+ }
+ catch( IOException io )
+ {
+ length--;
+ exception = io;
+ }
+ }
+ if( !done )
+ {
+ //if no good stream was found then lets try again but with the
+ //length of data that was actually read and not length
+ //defined in the dictionary
+ length = writtenLength;
+ for( int tryCount=0; !done && tryCount<5; tryCount++ )
+ {
+ try
+ {
+ input = new BufferedInputStream(
+ new RandomAccessFileInputStream( file, position, length ), BUFFER_SIZE );
+ unFilteredStream = new RandomAccessFileOutputStream( file );
+ filter.decode( input, unFilteredStream, this, filterIndex );
+ done = true;
+ }
+ catch( IOException io )
+ {
+ length--;
+ exception = io;
+ }
+ }
+ }
+ }
+ if( !done )
+ {
+ throw exception;
+ }
+ }
+
+ /**
+ * This will encode the logical byte stream applying all of the filters to the stream.
+ *
+ * @throws IOException If there is an error applying a filter to the stream.
+ */
+ private void doEncode() throws IOException
+ {
+ filteredStream = unFilteredStream;
+
+ COSBase filters = getFilters();
+ if( filters == null )
+ {
+ //there is no filter to apply
+ }
+ else if( filters instanceof COSName )
+ {
+ doEncode( (COSName)filters, 0 );
+ }
+ else if( filters instanceof COSArray )
+ {
+ // apply filters in reverse order
+ COSArray filterArray = (COSArray)filters;
+ for( int i=filterArray.size()-1; i>=0; i-- )
+ {
+ COSName filterName = (COSName)filterArray.get( i );
+ doEncode( filterName, i );
+ }
+ }
+ }
+
+ /**
+ * This will encode applying a single filter on the stream.
+ *
+ * @param filterName The name of the filter.
+ * @param filterIndex The index to the filter.
+ *
+ * @throws IOException If there is an error parsing the stream.
+ */
+ private void doEncode( COSName filterName, int filterIndex ) throws IOException
+ {
+ FilterManager manager = getFilterManager();
+ Filter filter = manager.getFilter( filterName );
+ InputStream input;
+
+ input = new BufferedInputStream(
+ new RandomAccessFileInputStream( file, filteredStream.getPosition(),
+ filteredStream.getLength() ), BUFFER_SIZE );
+ filteredStream = new RandomAccessFileOutputStream( file );
+ filter.encode( input, filteredStream, this, filterIndex );
+ }
+
+ /**
+ * This will return the filters to apply to the byte stream.
+ * The method will return
+ * - null if no filters are to be applied
+ * - a COSName if one filter is to be applied
+ * - a COSArray containing COSNames if multiple filters are to be applied
+ *
+ * @return the COSBase object representing the filters
+ */
+ public COSBase getFilters()
+ {
+ return getDictionaryObject(COSName.FILTER);
+ }
+
+ /**
+ * This will create a new stream for which filtered byte should be
+ * written to. You probably don't want this but want to use the
+ * createUnfilteredStream, which is used to write raw bytes to.
+ *
+ * @return A stream that can be written to.
+ *
+ * @throws IOException If there is an error creating the stream.
+ */
+ public OutputStream createFilteredStream() throws IOException
+ {
+ filteredStream = new RandomAccessFileOutputStream( file );
+ unFilteredStream = null;
+ return new BufferedOutputStream( filteredStream, BUFFER_SIZE );
+ }
+
+ /**
+ * This will create a new stream for which filtered byte should be
+ * written to. You probably don't want this but want to use the
+ * createUnfilteredStream, which is used to write raw bytes to.
+ *
+ * @param expectedLength An entry where a length is expected.
+ *
+ * @return A stream that can be written to.
+ *
+ * @throws IOException If there is an error creating the stream.
+ */
+ public OutputStream createFilteredStream( COSBase expectedLength ) throws IOException
+ {
+ filteredStream = new RandomAccessFileOutputStream( file );
+ filteredStream.setExpectedLength( expectedLength );
+ unFilteredStream = null;
+ return new BufferedOutputStream( filteredStream, BUFFER_SIZE );
+ }
+
+ /**
+ * set the filters to be applied to the stream.
+ *
+ * @param filters The filters to set on this stream.
+ *
+ * @throws IOException If there is an error clearing the old filters.
+ */
+ public void setFilters(COSBase filters) throws IOException
+ {
+ setItem(COSName.FILTER, filters);
+ // kill cached filtered streams
+ filteredStream = null;
+ }
+
+ /**
+ * This will create an output stream that can be written to.
+ *
+ * @return An output stream which raw data bytes should be written to.
+ *
+ * @throws IOException If there is an error creating the stream.
+ */
+ public OutputStream createUnfilteredStream() throws IOException
+ {
+ unFilteredStream = new RandomAccessFileOutputStream( file );
+ filteredStream = null;
+ return new BufferedOutputStream( unFilteredStream, BUFFER_SIZE );
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.persistence.util.COSHEXTable;
+
+/**
+ * This represents a string object in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.30 $
+ */
+public class COSString extends COSBase
+{
+ /**
+ * One of the open string tokens.
+ */
+ public static final byte[] STRING_OPEN = new byte[]{ 40 }; //"(".getBytes();
+ /**
+ * One of the close string tokens.
+ */
+ public static final byte[] STRING_CLOSE = new byte[]{ 41 }; //")".getBytes( "ISO-8859-1" );
+ /**
+ * One of the open string tokens.
+ */
+ public static final byte[] HEX_STRING_OPEN = new byte[]{ 60 }; //"<".getBytes( "ISO-8859-1" );
+ /**
+ * One of the close string tokens.
+ */
+ public static final byte[] HEX_STRING_CLOSE = new byte[]{ 62 }; //">".getBytes( "ISO-8859-1" );
+ /**
+ * the escape character in strings.
+ */
+ public static final byte[] ESCAPE = new byte[]{ 92 }; //"\\".getBytes( "ISO-8859-1" );
+
+ /**
+ * CR escape characters.
+ */
+ public static final byte[] CR_ESCAPE = new byte[]{ 92, 114 }; //"\\r".getBytes( "ISO-8859-1" );
+ /**
+ * LF escape characters.
+ */
+ public static final byte[] LF_ESCAPE = new byte[]{ 92, 110 }; //"\\n".getBytes( "ISO-8859-1" );
+ /**
+ * HT escape characters.
+ */
+ public static final byte[] HT_ESCAPE = new byte[]{ 92, 116 }; //"\\t".getBytes( "ISO-8859-1" );
+ /**
+ * BS escape characters.
+ */
+ public static final byte[] BS_ESCAPE = new byte[]{ 92, 98 }; //"\\b".getBytes( "ISO-8859-1" );
+ /**
+ * FF escape characters.
+ */
+ public static final byte[] FF_ESCAPE = new byte[]{ 92, 102 }; //"\\f".getBytes( "ISO-8859-1" );
+
+ private ByteArrayOutputStream out = null;
+ private String str = null;
+
+ /**
+ * Forces the string to be serialized in hex form but not literal form, the default is to stream
+ * in literal form.
+ */
+ private boolean forceHexForm = false;
+
+ /**
+ * Constructor.
+ */
+ public COSString()
+ {
+ out = new ByteArrayOutputStream();
+ }
+
+ /**
+ * Explicit constructor for ease of manual PDF construction.
+ *
+ * @param value The string value of the object.
+ */
+ public COSString( String value )
+ {
+ try
+ {
+ boolean unicode16 = false;
+ char[] chars = value.toCharArray();
+ int length = chars.length;
+ for( int i=0; i<length; i++ )
+ {
+ if( chars[i] > 255 )
+ {
+ unicode16 = true;
+ break;
+ }
+ }
+ if( unicode16 )
+ {
+ byte[] data = value.getBytes( "UTF-16BE" );
+ out = new ByteArrayOutputStream( data.length +2);
+ out.write( 0xFE );
+ out.write( 0xFF );
+ out.write( data );
+ }
+ else
+ {
+ byte[] data = value.getBytes("ISO-8859-1");
+ out = new ByteArrayOutputStream( data.length );
+ out.write( data );
+ }
+ }
+ catch (IOException ignore)
+ {
+ ignore.printStackTrace();
+ //should never happen
+ }
+ }
+
+ /**
+ * Explicit constructor for ease of manual PDF construction.
+ *
+ * @param value The string value of the object.
+ */
+ public COSString( byte[] value )
+ {
+ try
+ {
+ out = new ByteArrayOutputStream( value.length );
+ out.write( value );
+ }
+ catch (IOException ignore)
+ {
+ ignore.printStackTrace();
+ //should never happen
+ }
+ }
+
+ /**
+ * Forces the string to be written in literal form instead of hexadecimal form.
+ *
+ * @param v if v is true the string will be written in literal form, otherwise it will
+ * be written in hexa if necessary.
+ */
+
+ public void setForceLiteralForm(boolean v)
+ {
+ forceHexForm = !v;
+ }
+
+ /**
+ * Forces the string to be written in hexadecimal form instead of literal form.
+ *
+ * @param v if v is true the string will be written in hexadecimal form otherwise it will be written in literal if
+ * necessary.
+ */
+
+ public void setForceHexForm(boolean v)
+ {
+ forceHexForm = v;
+ }
+
+ /**
+ * This will create a COS string from a string of hex characters.
+ *
+ * @param hex A hex string.
+ * @return A cos string with the hex characters converted to their actual bytes.
+ * @throws IOException If there is an error with the hex string.
+ */
+ public static COSString createFromHexString(String hex)
+ throws IOException {
+ return createFromHexString(hex, false);
+ }
+
+ /**
+ * Creates a COS string from a string of hex characters, optionally
+ * ignoring malformed input.
+ *
+ * @param hex A hex string.
+ * @param force flag to ignore malformed input
+ * @return A cos string with the hex characters converted to their actual bytes.
+ * @throws IOException If there is an error with the hex string.
+ */
+ public static COSString createFromHexString(String hex, boolean force)
+ throws IOException {
+ COSString retval = new COSString();
+ StringBuilder hexBuffer = new StringBuilder( hex.trim() );
+ //if odd number then the last hex digit is assumed to be 0
+ if( hexBuffer.length() % 2 != 0 )
+ {
+ hexBuffer.append( '0' );
+ }
+ int length = hexBuffer.length();
+ for (int i = 0; i < length; i += 2) {
+ try {
+ retval.append(
+ Integer.parseInt(hexBuffer.substring(i, i + 2), 16));
+ } catch (NumberFormatException e) {
+ if (force) {
+ retval.append('?');
+ } else {
+ IOException exception =
+ new IOException("Invalid hex string: " + hex);
+ exception.initCause(e);
+ throw exception;
+ }
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will take this string and create a hex representation of the bytes that make the string.
+ *
+ * @return A hex string representing the bytes in this string.
+ */
+ public String getHexString()
+ {
+ StringBuilder retval = new StringBuilder( out.size() * 2 );
+ byte[] data = getBytes();
+ int length = data.length;
+ for( int i=0; i<length; i++ )
+ {
+ retval.append( COSHEXTable.HEX_TABLE[ (data[i]+256)%256 ] );
+ }
+
+ return retval.toString();
+ }
+
+ /**
+ * This will get the string that this object wraps.
+ *
+ * @return The wrapped string.
+ */
+ public String getString()
+ {
+ if (this.str != null)
+ {
+ return this.str;
+ }
+ String retval;
+ String encoding = "ISO-8859-1";
+ byte[] data = getBytes();
+ int start = 0;
+ if( data.length > 2 )
+ {
+ if( data[0] == (byte)0xFF && data[1] == (byte)0xFE )
+ {
+ encoding = "UTF-16LE";
+ start=2;
+ }
+ else if( data[0] == (byte)0xFE && data[1] == (byte)0xFF )
+ {
+ encoding = "UTF-16BE";
+ start=2;
+ }
+ }
+ try
+ {
+ retval = new String( getBytes(), start, data.length-start, encoding );
+ }
+ catch( UnsupportedEncodingException e )
+ {
+ //should never happen
+ e.printStackTrace();
+ retval = new String( getBytes() );
+ }
+ this.str = retval;
+ return retval;
+ }
+
+ /**
+ * This will append a byte[] to the string.
+ *
+ * @param data The byte[] to add to this string.
+ *
+ * @throws IOException If an IO error occurs while writing the byte.
+ */
+ public void append( byte[] data ) throws IOException
+ {
+ out.write( data );
+ this.str = null;
+ }
+
+ /**
+ * This will append a byte to the string.
+ *
+ * @param in The byte to add to this string.
+ *
+ * @throws IOException If an IO error occurs while writing the byte.
+ */
+ public void append( int in ) throws IOException
+ {
+ out.write( in );
+ this.str = null;
+ }
+
+ /**
+ * This will reset the internal buffer.
+ */
+ public void reset()
+ {
+ out.reset();
+ this.str = null;
+ }
+
+ /**
+ * This will get the bytes of the string.
+ *
+ * @return A byte array that represents the string.
+ */
+ public byte[] getBytes()
+ {
+ return out.toByteArray();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "COSString{" + this.getString() + "}";
+ }
+
+ /**
+ * This will output this string as a PDF object.
+ *
+ * @param output The stream to write to.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void writePDF( OutputStream output ) throws IOException
+ {
+ boolean outsideASCII = false;
+ //Lets first check if we need to escape this string.
+ byte[] bytes = getBytes();
+ int length = bytes.length;
+ for( int i=0; i<length && !outsideASCII; i++ )
+ {
+ //if the byte is negative then it is an eight bit byte and is
+ //outside the ASCII range.
+ outsideASCII = bytes[i] <0;
+ }
+ if (!outsideASCII && !forceHexForm)
+ {
+ output.write(STRING_OPEN);
+ for( int i=0; i<length; i++ )
+ {
+ int b = (bytes[i]+256)%256;
+ switch( b )
+ {
+ case '(':
+ case ')':
+ case '\\':
+ {
+ output.write(ESCAPE);
+ output.write((byte)b);
+ break;
+ }
+ case 10: //LF
+ {
+ output.write( LF_ESCAPE );
+ break;
+ }
+ case 13: // CR
+ {
+ output.write( CR_ESCAPE );
+ break;
+ }
+ case '\t':
+ {
+ output.write( HT_ESCAPE );
+ break;
+ }
+ case '\b':
+ {
+ output.write( BS_ESCAPE );
+ break;
+ }
+ case '\f':
+ {
+ output.write( FF_ESCAPE );
+ break;
+ }
+ default:
+ {
+ output.write( (byte)b );
+ }
+ }
+ }
+ output.write(STRING_CLOSE);
+ }
+ else
+ {
+ output.write(HEX_STRING_OPEN);
+ for(int i=0; i<length; i++ )
+ {
+ output.write( COSHEXTable.TABLE[ (bytes[i]+256)%256 ] );
+ }
+ output.write(HEX_STRING_CLOSE);
+ }
+ }
+
+
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ @Override
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return visitor.visitFromString( this );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof COSString)
+ {
+ COSString strObj = (COSString) obj;
+ return this.getString().equals(strObj.getString())
+ && this.forceHexForm == strObj.forceHexForm;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode()
+ {
+ int result = getString().hashCode();
+ return result += forceHexForm ? 17 : 0;
+ }
+}
--- /dev/null
+/*
+ * 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.cos;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * An interface for visiting a PDF document at the type (COS) level.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.6 $
+ */
+public interface ICOSVisitor
+{
+ /**
+ * Notification of visit to Array object.
+ *
+ * @param obj The Object that is being visited.
+ * @return any Object depending on the visitor implementation, or null
+ * @throws COSVisitorException If there is an error while visiting this object.
+ */
+ public Object visitFromArray( COSArray obj ) throws COSVisitorException;
+
+ /**
+ * Notification of visit to boolean object.
+ *
+ * @param obj The Object that is being visited.
+ * @return any Object depending on the visitor implementation, or null
+ * @throws COSVisitorException If there is an error while visiting this object.
+ */
+ public Object visitFromBoolean( COSBoolean obj ) throws COSVisitorException;
+
+ /**
+ * Notification of visit to dictionary object.
+ *
+ * @param obj The Object that is being visited.
+ * @return any Object depending on the visitor implementation, or null
+ * @throws COSVisitorException If there is an error while visiting this object.
+ */
+ public Object visitFromDictionary( COSDictionary obj ) throws COSVisitorException;
+
+ /**
+ * Notification of visit to document object.
+ *
+ * @param obj The Object that is being visited.
+ * @return any Object depending on the visitor implementation, or null
+ * @throws COSVisitorException If there is an error while visiting this object.
+ */
+ public Object visitFromDocument( COSDocument obj ) throws COSVisitorException;
+
+ /**
+ * Notification of visit to float object.
+ *
+ * @param obj The Object that is being visited.
+ * @return any Object depending on the visitor implementation, or null
+ * @throws COSVisitorException If there is an error while visiting this object.
+ */
+ public Object visitFromFloat( COSFloat obj ) throws COSVisitorException;
+
+ /**
+ * Notification of visit to integer object.
+ *
+ * @param obj The Object that is being visited.
+ * @return any Object depending on the visitor implementation, or null
+ * @throws COSVisitorException If there is an error while visiting this object.
+ */
+ public Object visitFromInt( COSInteger obj ) throws COSVisitorException;
+
+ /**
+ * Notification of visit to name object.
+ *
+ * @param obj The Object that is being visited.
+ * @return any Object depending on the visitor implementation, or null
+ * @throws COSVisitorException If there is an error while visiting this object.
+ */
+ public Object visitFromName( COSName obj ) throws COSVisitorException;
+
+ /**
+ * Notification of visit to null object.
+ *
+ * @param obj The Object that is being visited.
+ * @return any Object depending on the visitor implementation, or null
+ * @throws COSVisitorException If there is an error while visiting this object.
+ */
+ public Object visitFromNull( COSNull obj ) throws COSVisitorException;
+
+ /**
+ * Notification of visit to stream object.
+ *
+ * @param obj The Object that is being visited.
+ * @return any Object depending on the visitor implementation, or null
+ * @throws COSVisitorException If there is an error while visiting this object.
+ */
+ public Object visitFromStream( COSStream obj ) throws COSVisitorException;
+
+ /**
+ * Notification of visit to string object.
+ *
+ * @param obj The Object that is being visited.
+ * @return any Object depending on the visitor implementation, or null
+ * @throws COSVisitorException If there is an error while visiting this object.
+ */
+ public Object visitFromString( COSString obj ) throws COSVisitorException;
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These are the low level objects that make up a PDF document.
+<br/><br/>
+
+See the <A href="http://partners.adobe.com/asn/developer/acrosdk/docs/filefmtspecs/PDFReference.pdf">PDF Reference 1.4</A>.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.encoding;
+
+import java.util.Iterator;
+
+import org.apache.fontbox.afm.CharMetric;
+import org.apache.fontbox.afm.FontMetric;
+
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This will handle the encoding from an AFM font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class AFMEncoding extends Encoding
+{
+ private FontMetric metric = null;
+
+ /**
+ * Constructor.
+ *
+ * @param fontInfo The font metric information.
+ */
+ public AFMEncoding( FontMetric fontInfo )
+ {
+ metric = fontInfo;
+ Iterator<CharMetric> characters = metric.getCharMetrics().iterator();
+ while( characters.hasNext() )
+ {
+ CharMetric nextMetric = (CharMetric)characters.next();
+ addCharacterEncoding( nextMetric.getCharacterCode(), nextMetric.getName() );
+ }
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return null;
+ }
+}
--- /dev/null
+/*
+ * 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.encoding;
+
+import java.io.IOException;
+
+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.COSNumber;
+
+/**
+ * This will perform the encoding from a dictionary.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.13 $
+ */
+public class DictionaryEncoding extends Encoding
+{
+ private COSDictionary encoding = null;
+
+ /**
+ * Constructor.
+ *
+ * @param fontEncoding The encoding dictionary.
+ *
+ * @throws IOException If there is a problem getting the base font.
+ */
+ public DictionaryEncoding( COSDictionary fontEncoding ) throws IOException
+ {
+ encoding = fontEncoding;
+
+ //first set up the base encoding
+ //The previious value WinAnsiEncoding() has been changed to StandardEnding
+ //see p 389 of the PDF 1.5 ref�rence table 5.11 entries in a dictionary encoding
+ //"If this entry is absent, the Differences entry describes differences from an implicit
+ //base encoding. For a font program that is embedded in the PDF file, the
+ //implicit base encoding is the font program�s built-in encoding, as described
+ //above and further elaborated in the sections on specific font types below. Otherwise,
+ //for a nonsymbolic font, it is StandardEncoding, and for a symbolic font, it
+ //is the font�s built-in encoding."
+
+ // The default base encoding is standardEncoding
+ Encoding baseEncoding = StandardEncoding.INSTANCE;
+ COSName baseEncodingName =
+ (COSName) encoding.getDictionaryObject(COSName.BASE_ENCODING);
+ if (baseEncodingName != null) {
+ baseEncoding =
+ EncodingManager.INSTANCE.getEncoding(baseEncodingName);
+ }
+
+ nameToCode.putAll( baseEncoding.nameToCode );
+ codeToName.putAll( baseEncoding.codeToName );
+
+
+ //now replace with the differences.
+ COSArray differences = (COSArray)encoding.getDictionaryObject( COSName.DIFFERENCES );
+ int currentIndex = -1;
+ for( int i=0; differences != null && i<differences.size(); i++ )
+ {
+ COSBase next = differences.getObject( i );
+ if( next instanceof COSNumber )
+ {
+ currentIndex = ((COSNumber)next).intValue();
+ }
+ else if( next instanceof COSName )
+ {
+ COSName name = (COSName)next;
+ addCharacterEncoding( currentIndex++, name.getName() );
+ }
+ }
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return encoding;
+ }
+}
--- /dev/null
+/*
+ * 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.encoding;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.StringTokenizer;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.util.ResourceLoader;
+
+/**
+ * This is an interface to a text encoder.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.15 $
+ */
+public abstract class Encoding implements COSObjectable
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(Encoding.class);
+
+ /** Identifies a non-mapped character. */
+ public static final String NOTDEF = ".notdef";
+
+ /**
+ * This is a mapping from a character code to a character name.
+ */
+ protected final Map<Integer, String> codeToName =
+ new HashMap<Integer, String>();
+
+ /**
+ * This is a mapping from a character name to a character code.
+ */
+ protected final Map<String, Integer> nameToCode =
+ new HashMap<String, Integer>();
+
+ private static final Map<String, String> NAME_TO_CHARACTER =
+ new HashMap<String, String>();
+
+ private static final Map<String, String> CHARACTER_TO_NAME =
+ new HashMap<String, String>();
+
+ static
+ {
+ //Loads the official Adobe Glyph List
+ loadGlyphList("org/apache/pdfbox/resources/glyphlist.txt");
+ //Loads some additional glyph mappings
+ loadGlyphList("org/apache/pdfbox/resources/additional_glyphlist.txt");
+
+ // Load an external glyph list file that user can give as JVM property
+ String location = System.getProperty("glyphlist_ext");
+ if(location != null)
+ {
+ File external = new File(location);
+ if(external.exists())
+ {
+ loadGlyphList(location);
+ }
+ }
+
+ NAME_TO_CHARACTER.put( NOTDEF, "" );
+ NAME_TO_CHARACTER.put( "fi", "fi" );
+ NAME_TO_CHARACTER.put( "fl", "fl" );
+ NAME_TO_CHARACTER.put( "ffi", "ffi" );
+ NAME_TO_CHARACTER.put( "ff", "ff" );
+ NAME_TO_CHARACTER.put( "pi", "pi" );
+
+ for( Map.Entry<String, String> entry : NAME_TO_CHARACTER.entrySet() )
+ {
+ CHARACTER_TO_NAME.put( entry.getValue(), entry.getKey() );
+ }
+ }
+
+ /**
+ * Loads a glyph list from a given location and populates the NAME_TO_CHARACTER hashmap
+ * for character lookups.
+ * @param location - The string location of the glyphlist file
+ */
+ private static void loadGlyphList(String location)
+ {
+ BufferedReader glyphStream = null;
+ try
+ {
+ InputStream resource = ResourceLoader.loadResource( location );
+ if (resource == null)
+ {
+ throw new MissingResourceException("Glyphlist not found: " + location,
+ Encoding.class.getName(), location);
+ }
+ glyphStream = new BufferedReader( new InputStreamReader( resource ) );
+ String line = null;
+ while( (line = glyphStream.readLine()) != null )
+ {
+ line = line.trim();
+ //lines starting with # are comments which we can ignore.
+ if( !line.startsWith("#" ) )
+ {
+ int semicolonIndex = line.indexOf( ';' );
+ if( semicolonIndex >= 0 )
+ {
+ try
+ {
+ String characterName = line.substring( 0, semicolonIndex );
+ String unicodeValue = line.substring( semicolonIndex+1, line.length() );
+ StringTokenizer tokenizer = new StringTokenizer( unicodeValue, " ", false );
+ String value = "";
+ while(tokenizer.hasMoreTokens())
+ {
+ int characterCode = Integer.parseInt( tokenizer.nextToken(), 16 );
+ value += (char)characterCode;
+ }
+ if (NAME_TO_CHARACTER.containsKey(characterName))
+ {
+ log.warn("duplicate value for characterName="+characterName+","+value);
+ }
+ else
+ {
+ NAME_TO_CHARACTER.put( characterName, value );
+ }
+ }
+ catch( NumberFormatException nfe )
+ {
+ nfe.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+ catch( IOException io )
+ {
+ io.printStackTrace();
+ }
+ finally
+ {
+ if( glyphStream != null )
+ {
+ try
+ {
+ glyphStream.close();
+ }
+ catch( IOException e )
+ {
+ e.printStackTrace();
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Returns an unmodifiable view of the Code2Name mapping.
+ * @return the Code2Name map
+ */
+ public Map<Integer, String> getCodeToNameMap()
+ {
+ return Collections.unmodifiableMap(codeToName);
+ }
+
+ /**
+ * Returns an unmodifiable view of the Name2Code mapping.
+ * @return the Name2Code map
+ */
+ public Map<String, Integer> getNameToCodeMap()
+ {
+ return Collections.unmodifiableMap(nameToCode);
+ }
+
+ /**
+ * This will add a character encoding.
+ *
+ * @param code The character code that matches the character.
+ * @param name The name of the character.
+ */
+ public void addCharacterEncoding( int code, String name )
+ {
+ codeToName.put( code, name );
+ nameToCode.put( name, code );
+ }
+
+ /**
+ * This will get the character code for the name.
+ *
+ * @param name The name of the character.
+ *
+ * @return The code for the character.
+ *
+ * @throws IOException If there is no character code for the name.
+ */
+ public int getCode( String name ) throws IOException
+ {
+ Integer code = nameToCode.get( name );
+ if( code == null )
+ {
+ throw new IOException( "No character code for character name '" + name + "'" );
+ }
+ return code;
+ }
+
+ /**
+ * This will take a character code and get the name from the code.
+ *
+ * @param code The character code.
+ *
+ * @return The name of the character.
+ *
+ * @throws IOException If there is no name for the code.
+ */
+ public String getName( int code ) throws IOException
+ {
+ return codeToName.get( code );
+ }
+
+ /**
+ * This will take a character code and get the name from the code.
+ *
+ * @param c The character.
+ *
+ * @return The name of the character.
+ *
+ * @throws IOException If there is no name for the character.
+ */
+ public String getNameFromCharacter( char c ) throws IOException
+ {
+ String name = CHARACTER_TO_NAME.get( Character.toString(c) );
+ if( name == null )
+ {
+ throw new IOException( "No name for character '" + c + "'" );
+ }
+ return name;
+ }
+
+ /**
+ * This will get the character from the code.
+ *
+ * @param code The character code.
+ *
+ * @return The printable character for the code.
+ *
+ * @throws IOException If there is not name for the character.
+ */
+ public String getCharacter( int code ) throws IOException
+ {
+ String name = getName( code );
+ if (name != null)
+ {
+ return getCharacter( getName( code ) );
+ }
+ return null;
+ }
+
+ /**
+ * This will get the character from the name.
+ *
+ * @param name The name of the character.
+ *
+ * @return The printable character for the code.
+ */
+ public String getCharacter( String name )
+ {
+ String character = NAME_TO_CHARACTER.get( name );
+ if( character == null )
+ {
+ // test if we have a suffix and if so remove it
+ if ( name.indexOf('.') > 0 )
+ {
+ character = getCharacter(name.substring( 0, name.indexOf('.') ));
+ }
+ // test for Unicode name
+ // (uniXXXX - XXXX must be a multiple of four;
+ // each representing a hexadecimal Unicode code point)
+ else if ( name.startsWith( "uni" ) )
+ {
+ int nameLength = name.length();
+ StringBuilder uniStr = new StringBuilder();
+ try
+ {
+ for ( int chPos = 3; chPos + 4 <= nameLength; chPos += 4 )
+ {
+ int characterCode = Integer.parseInt( name.substring( chPos, chPos + 4), 16 );
+
+ if ( ( characterCode > 0xD7FF ) && ( characterCode < 0xE000 ) )
+ {
+ log.warn( "Unicode character name with not allowed code area: " + name );
+ }
+ else
+ {
+ uniStr.append( (char) characterCode );
+ }
+ }
+ character = uniStr.toString();
+ NAME_TO_CHARACTER.put(name, character);
+ }
+ catch (NumberFormatException nfe)
+ {
+ log.warn( "Not a number in Unicode character name: " + name );
+ character = name;
+ }
+ }
+ else if (nameToCode.containsKey(name))
+ {
+ int code = nameToCode.get(name);
+ character = Character.toString((char)code);
+ }
+ else
+ {
+ character = name;
+ }
+ }
+ return character;
+ }
+
+}
--- /dev/null
+/*
+ * 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.encoding;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This class will handle getting the appropriate encodings.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class EncodingManager
+{
+
+ /**
+ * Default singleton instance of this class.
+ *
+ * @since Apache PDFBox 1.3.0
+ */
+ public static final EncodingManager INSTANCE = new EncodingManager();
+
+ /**
+ * This will get the standard encoding.
+ *
+ * @return The standard encoding.
+ */
+ public Encoding getStandardEncoding() {
+ return StandardEncoding.INSTANCE;
+ }
+
+ /**
+ * This will get an encoding by name.
+ *
+ * @param name The name of the encoding to get.
+ * @return The encoding that matches the name.
+ * @throws IOException if there is no encoding with that name.
+ */
+ public Encoding getEncoding( COSName name ) throws IOException {
+ if (COSName.STANDARD_ENCODING.equals(name)) {
+ return StandardEncoding.INSTANCE;
+ } else if (COSName.WIN_ANSI_ENCODING.equals(name)) {
+ return WinAnsiEncoding.INSTANCE;
+ } else if (COSName.MAC_ROMAN_ENCODING.equals(name)) {
+ return MacRomanEncoding.INSTANCE;
+ } else if (COSName.PDF_DOC_ENCODING.equals(name)) {
+ return PdfDocEncoding.INSTANCE;
+ } else {
+ throw new IOException(
+ "Unknown encoding for '" + name.getName() + "'");
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.encoding;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is an interface to a text encoder.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class MacRomanEncoding extends Encoding
+{
+
+ /**
+ * Singleton instance of this class.
+ *
+ * @since Apache PDFBox 1.3.0
+ */
+ public static final MacRomanEncoding INSTANCE = new MacRomanEncoding();
+
+ /**
+ * Constructor.
+ */
+ public MacRomanEncoding()
+ {
+ addCharacterEncoding( 0101, "A" );
+ addCharacterEncoding( 0256, "AE" );
+ addCharacterEncoding( 0347, "Aacute" );
+ addCharacterEncoding( 0345, "Acircumflex" );
+ addCharacterEncoding( 0200, "Adieresis" );
+ addCharacterEncoding( 0313, "Agrave" );
+ addCharacterEncoding( 0201, "Aring" );
+ addCharacterEncoding( 0314, "Atilde" );
+ addCharacterEncoding( 0102, "B" );
+ addCharacterEncoding( 0103, "C" );
+ addCharacterEncoding( 0202, "Ccedilla" );
+ addCharacterEncoding( 0104, "D" );
+ addCharacterEncoding( 0105, "E" );
+ addCharacterEncoding( 0203, "Eacute" );
+ addCharacterEncoding( 0346, "Ecircumflex" );
+ addCharacterEncoding( 0350, "Edieresis" );
+ addCharacterEncoding( 0351, "Egrave" );
+ addCharacterEncoding( 0106, "F" );
+ addCharacterEncoding( 0107, "G" );
+ addCharacterEncoding( 0110, "H" );
+ addCharacterEncoding( 0111, "I" );
+ addCharacterEncoding( 0352, "Iacute" );
+ addCharacterEncoding( 0353, "Icircumflex" );
+ addCharacterEncoding( 0354, "Idieresis" );
+ addCharacterEncoding( 0355, "Igrave" );
+ addCharacterEncoding( 0112, "J" );
+ addCharacterEncoding( 0113, "K" );
+ addCharacterEncoding( 0114, "L" );
+ addCharacterEncoding( 0115, "M" );
+ addCharacterEncoding( 0116, "N" );
+ addCharacterEncoding( 0204, "Ntilde" );
+ addCharacterEncoding( 0117, "O" );
+ addCharacterEncoding( 0316, "OE" );
+ addCharacterEncoding( 0356, "Oacute" );
+ addCharacterEncoding( 0357, "Ocircumflex" );
+ addCharacterEncoding( 0205, "Odieresis" );
+ addCharacterEncoding( 0361, "Ograve" );
+ addCharacterEncoding( 0257, "Oslash" );
+ addCharacterEncoding( 0315, "Otilde" );
+ addCharacterEncoding( 0120, "P" );
+ addCharacterEncoding( 0121, "Q" );
+ addCharacterEncoding( 0122, "R" );
+ addCharacterEncoding( 0123, "S" );
+ addCharacterEncoding( 0124, "T" );
+ addCharacterEncoding( 0125, "U" );
+ addCharacterEncoding( 0362, "Uacute" );
+ addCharacterEncoding( 0363, "Ucircumflex" );
+ addCharacterEncoding( 0206, "Udieresis" );
+ addCharacterEncoding( 0364, "Ugrave" );
+ addCharacterEncoding( 0126, "V" );
+ addCharacterEncoding( 0127, "W" );
+ addCharacterEncoding( 0130, "X" );
+ addCharacterEncoding( 0131, "Y" );
+ addCharacterEncoding( 0331, "Ydieresis" );
+ addCharacterEncoding( 0132, "Z" );
+ addCharacterEncoding( 0141, "a" );
+ addCharacterEncoding( 0207, "aacute" );
+ addCharacterEncoding( 0211, "acircumflex" );
+ addCharacterEncoding( 0253, "acute" );
+ addCharacterEncoding( 0212, "adieresis" );
+ addCharacterEncoding( 0276, "ae" );
+ addCharacterEncoding( 0210, "agrave" );
+ addCharacterEncoding( 046, "ampersand" );
+ addCharacterEncoding( 0214, "aring" );
+ addCharacterEncoding( 0136, "asciicircum" );
+ addCharacterEncoding( 0176, "asciitilde" );
+ addCharacterEncoding( 052, "asterisk" );
+ addCharacterEncoding( 0100, "at" );
+ addCharacterEncoding( 0213, "atilde" );
+ addCharacterEncoding( 0142, "b" );
+ addCharacterEncoding( 0134, "backslash" );
+ addCharacterEncoding( 0174, "bar" );
+ addCharacterEncoding( 0173, "braceleft" );
+ addCharacterEncoding( 0175, "braceright" );
+ addCharacterEncoding( 0133, "bracketleft" );
+ addCharacterEncoding( 0135, "bracketright" );
+ addCharacterEncoding( 0371, "breve" );
+ addCharacterEncoding( 0245, "bullet" );
+ addCharacterEncoding( 0143, "c" );
+ addCharacterEncoding( 0377, "caron" );
+ addCharacterEncoding( 0215, "ccedilla" );
+ addCharacterEncoding( 0374, "cedilla" );
+ addCharacterEncoding( 0242, "cent" );
+ addCharacterEncoding( 0366, "circumflex" );
+ addCharacterEncoding( 072, "colon" );
+ addCharacterEncoding( 054, "comma" );
+ addCharacterEncoding( 0251, "copyright" );
+ addCharacterEncoding( 0333, "currency" );
+ addCharacterEncoding( 0144, "d" );
+ addCharacterEncoding( 0240, "dagger" );
+ addCharacterEncoding( 0340, "daggerdbl" );
+ addCharacterEncoding( 0241, "degree" );
+ addCharacterEncoding( 0254, "dieresis" );
+ addCharacterEncoding( 0326, "divide" );
+ addCharacterEncoding( 044, "dollar" );
+ addCharacterEncoding( 0372, "dotaccent" );
+ addCharacterEncoding( 0365, "dotlessi" );
+ addCharacterEncoding( 0145, "e" );
+ addCharacterEncoding( 0216, "eacute" );
+ addCharacterEncoding( 0220, "ecircumflex" );
+ addCharacterEncoding( 0221, "edieresis" );
+ addCharacterEncoding( 0217, "egrave" );
+ addCharacterEncoding( 070, "eight" );
+ addCharacterEncoding( 0311, "ellipsis" );
+ addCharacterEncoding( 0321, "emdash" );
+ addCharacterEncoding( 0320, "endash" );
+ addCharacterEncoding( 075, "equal" );
+ addCharacterEncoding( 041, "exclam" );
+ addCharacterEncoding( 0301, "exclamdown" );
+ addCharacterEncoding( 0146, "f" );
+ addCharacterEncoding( 0336, "fi" );
+ addCharacterEncoding( 065, "five" );
+ addCharacterEncoding( 0337, "fl" );
+ addCharacterEncoding( 0304, "florin" );
+ addCharacterEncoding( 064, "four" );
+ addCharacterEncoding( 0332, "fraction" );
+ addCharacterEncoding( 0147, "g" );
+ addCharacterEncoding( 0247, "germandbls" );
+ addCharacterEncoding( 0140, "grave" );
+ addCharacterEncoding( 076, "greater" );
+ addCharacterEncoding( 0307, "guillemotleft" );
+ addCharacterEncoding( 0310, "guillemotright" );
+ addCharacterEncoding( 0334, "guilsinglleft" );
+ addCharacterEncoding( 0335, "guilsinglright" );
+ addCharacterEncoding( 0150, "h" );
+ addCharacterEncoding( 0375, "hungarumlaut" );
+ addCharacterEncoding( 055, "hyphen" );
+ addCharacterEncoding( 0151, "i" );
+ addCharacterEncoding( 0222, "iacute" );
+ addCharacterEncoding( 0224, "icircumflex" );
+ addCharacterEncoding( 0225, "idieresis" );
+ addCharacterEncoding( 0223, "igrave" );
+ addCharacterEncoding( 0152, "j" );
+ addCharacterEncoding( 0153, "k" );
+ addCharacterEncoding( 0154, "l" );
+ addCharacterEncoding( 074, "less" );
+ addCharacterEncoding( 0302, "logicalnot" );
+ addCharacterEncoding( 0155, "m" );
+ addCharacterEncoding( 0370, "macron" );
+ addCharacterEncoding( 0265, "mu" );
+ addCharacterEncoding( 0156, "n" );
+ addCharacterEncoding( 071, "nine" );
+ addCharacterEncoding( 0226, "ntilde" );
+ addCharacterEncoding( 043, "numbersign" );
+ addCharacterEncoding( 0157, "o" );
+ addCharacterEncoding( 0227, "oacute" );
+ addCharacterEncoding( 0231, "ocircumflex" );
+ addCharacterEncoding( 0232, "odieresis" );
+ addCharacterEncoding( 0317, "oe" );
+ addCharacterEncoding( 0376, "ogonek" );
+ addCharacterEncoding( 0230, "ograve" );
+ addCharacterEncoding( 061, "one" );
+ addCharacterEncoding( 0273, "ordfeminine" );
+ addCharacterEncoding( 0274, "ordmasculine" );
+ addCharacterEncoding( 0277, "oslash" );
+ addCharacterEncoding( 0233, "otilde" );
+ addCharacterEncoding( 0160, "p" );
+ addCharacterEncoding( 0246, "paragraph" );
+ addCharacterEncoding( 050, "parenleft" );
+ addCharacterEncoding( 051, "parenright" );
+ addCharacterEncoding( 045, "percent" );
+ addCharacterEncoding( 056, "period" );
+ addCharacterEncoding( 0341, "periodcentered" );
+ addCharacterEncoding( 0344, "perthousand" );
+ addCharacterEncoding( 053, "plus" );
+ addCharacterEncoding( 0261, "plusminus" );
+ addCharacterEncoding( 0161, "q" );
+ addCharacterEncoding( 077, "question" );
+ addCharacterEncoding( 0300, "questiondown" );
+ addCharacterEncoding( 042, "quotedbl" );
+ addCharacterEncoding( 0343, "quotedblbase" );
+ addCharacterEncoding( 0322, "quotedblleft" );
+ addCharacterEncoding( 0323, "quotedblright" );
+ addCharacterEncoding( 0324, "quoteleft" );
+ addCharacterEncoding( 0325, "quoteright" );
+ addCharacterEncoding( 0342, "quotesinglbase" );
+ addCharacterEncoding( 047, "quotesingle" );
+ addCharacterEncoding( 0162, "r" );
+ addCharacterEncoding( 0250, "registered" );
+ addCharacterEncoding( 0373, "ring" );
+ addCharacterEncoding( 0163, "s" );
+ addCharacterEncoding( 0244, "section" );
+ addCharacterEncoding( 073, "semicolon" );
+ addCharacterEncoding( 067, "seven" );
+ addCharacterEncoding( 066, "six" );
+ addCharacterEncoding( 057, "slash" );
+ addCharacterEncoding( 040, "space" );
+ addCharacterEncoding( 0243, "sterling" );
+ addCharacterEncoding( 0164, "t" );
+ addCharacterEncoding( 063, "three" );
+ addCharacterEncoding( 0367, "tilde" );
+ addCharacterEncoding( 0252, "trademark" );
+ addCharacterEncoding( 062, "two" );
+ addCharacterEncoding( 0165, "u" );
+ addCharacterEncoding( 0234, "uacute" );
+ addCharacterEncoding( 0236, "ucircumflex" );
+ addCharacterEncoding( 0237, "udieresis" );
+ addCharacterEncoding( 0235, "ugrave" );
+ addCharacterEncoding( 0137, "underscore" );
+ addCharacterEncoding( 0166, "v" );
+ addCharacterEncoding( 0167, "w" );
+ addCharacterEncoding( 0170, "x" );
+ addCharacterEncoding( 0171, "y" );
+ addCharacterEncoding( 0330, "ydieresis" );
+ addCharacterEncoding( 0264, "yen" );
+ addCharacterEncoding( 0172, "z" );
+ addCharacterEncoding( 060, "zero" );
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return COSName.MAC_ROMAN_ENCODING;
+ }
+}
--- /dev/null
+/*
+ * 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.encoding;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is an interface to a text encoder.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class PdfDocEncoding extends Encoding
+{
+
+ /**
+ * Singleton instance of this class.
+ *
+ * @since Apache PDFBox 1.3.0
+ */
+ public static final PdfDocEncoding INSTANCE = new PdfDocEncoding();
+
+ /**
+ * Constructor.
+ */
+ public PdfDocEncoding()
+ {
+ addCharacterEncoding( 0101, "A" );
+ addCharacterEncoding( 0306, "AE" );
+ addCharacterEncoding( 0301, "Aacute" );
+ addCharacterEncoding( 0302, "Acircumflex" );
+ addCharacterEncoding( 0304, "Adieresis" );
+ addCharacterEncoding( 0300, "Agrave" );
+ addCharacterEncoding( 0305, "Aring" );
+ addCharacterEncoding( 0303, "Atilde" );
+ addCharacterEncoding( 0102, "B" );
+ addCharacterEncoding( 0103, "C" );
+ addCharacterEncoding( 0307, "Ccedilla" );
+ addCharacterEncoding( 0104, "D" );
+ addCharacterEncoding( 0105, "E" );
+ addCharacterEncoding( 0311, "Eacute" );
+ addCharacterEncoding( 0312, "Ecircumflex" );
+ addCharacterEncoding( 0313, "Edieresis" );
+ addCharacterEncoding( 0310, "Egrave" );
+ addCharacterEncoding( 0320, "Eth" );
+ addCharacterEncoding( 0240, "Euro" );
+ addCharacterEncoding( 0106, "F" );
+ addCharacterEncoding( 0107, "G" );
+ addCharacterEncoding( 0110, "H" );
+ addCharacterEncoding( 0111, "I" );
+ addCharacterEncoding( 0315, "Iacute" );
+ addCharacterEncoding( 0316, "Icircumflex" );
+ addCharacterEncoding( 0317, "Idieresis" );
+ addCharacterEncoding( 0314, "Igrave" );
+ addCharacterEncoding( 0112, "J" );
+ addCharacterEncoding( 0113, "K" );
+ addCharacterEncoding( 0114, "L" );
+ addCharacterEncoding( 0225, "Lslash" );
+ addCharacterEncoding( 0115, "M" );
+ addCharacterEncoding( 0116, "N" );
+ addCharacterEncoding( 0321, "Ntilde" );
+ addCharacterEncoding( 0117, "O" );
+ addCharacterEncoding( 0226, "OE" );
+ addCharacterEncoding( 0323, "Oacute" );
+ addCharacterEncoding( 0324, "Ocircumflex" );
+ addCharacterEncoding( 0326, "Odieresis" );
+ addCharacterEncoding( 0322, "Ograve" );
+ addCharacterEncoding( 0330, "Oslash" );
+ addCharacterEncoding( 0325, "Otilde" );
+ addCharacterEncoding( 0120, "P" );
+ addCharacterEncoding( 0121, "Q" );
+ addCharacterEncoding( 0122, "R" );
+ addCharacterEncoding( 0123, "S" );
+ addCharacterEncoding( 0227, "Scaron" );
+ addCharacterEncoding( 0124, "T" );
+ addCharacterEncoding( 0336, "Thorn" );
+ addCharacterEncoding( 0125, "U" );
+ addCharacterEncoding( 0332, "Uacute" );
+ addCharacterEncoding( 0333, "Ucircumflex" );
+ addCharacterEncoding( 0334, "Udieresis" );
+ addCharacterEncoding( 0331, "Ugrave" );
+ addCharacterEncoding( 0126, "V" );
+ addCharacterEncoding( 0127, "W" );
+ addCharacterEncoding( 0130, "X" );
+ addCharacterEncoding( 0131, "Y" );
+ addCharacterEncoding( 0335, "Yacute" );
+ addCharacterEncoding( 0230, "Ydieresis" );
+ addCharacterEncoding( 0132, "Z" );
+ addCharacterEncoding( 0231, "Zcaron" );
+ addCharacterEncoding( 0141, "a" );
+ addCharacterEncoding( 0341, "aacute" );
+ addCharacterEncoding( 0342, "acircumflex" );
+ addCharacterEncoding( 0264, "acute" );
+ addCharacterEncoding( 0344, "adieresis" );
+ addCharacterEncoding( 0346, "ae" );
+ addCharacterEncoding( 0340, "agrave" );
+ addCharacterEncoding( 046, "ampersand" );
+ addCharacterEncoding( 0345, "aring" );
+ addCharacterEncoding( 0136, "asciicircum" );
+ addCharacterEncoding( 0176, "asciitilde" );
+ addCharacterEncoding( 052, "asterisk" );
+ addCharacterEncoding( 0100, "at" );
+ addCharacterEncoding( 0343, "atilde" );
+ addCharacterEncoding( 0142, "b" );
+ addCharacterEncoding( 0134, "backslash" );
+ addCharacterEncoding( 0174, "bar" );
+ addCharacterEncoding( 0173, "braceleft" );
+ addCharacterEncoding( 0175, "braceright" );
+ addCharacterEncoding( 0133, "bracketleft" );
+ addCharacterEncoding( 0135, "bracketright" );
+ addCharacterEncoding( 030, "breve" );
+ addCharacterEncoding( 0246, "brokenbar" );
+ addCharacterEncoding( 0200, "bullet" );
+ addCharacterEncoding( 0143, "c" );
+ addCharacterEncoding( 031, "caron" );
+ addCharacterEncoding( 0347, "ccedilla" );
+ addCharacterEncoding( 0270, "cedilla" );
+ addCharacterEncoding( 0242, "cent" );
+ addCharacterEncoding( 032, "circumflex" );
+ addCharacterEncoding( 072, "colon" );
+ addCharacterEncoding( 054, "comma" );
+ addCharacterEncoding( 0251, "copyright" );
+ addCharacterEncoding( 0244, "currency" );
+ addCharacterEncoding( 0144, "d" );
+ addCharacterEncoding( 0201, "dagger" );
+ addCharacterEncoding( 0202, "daggerdbl" );
+ addCharacterEncoding( 0260, "degree" );
+ addCharacterEncoding( 0250, "dieresis" );
+ addCharacterEncoding( 0367, "divide" );
+ addCharacterEncoding( 044, "dollar" );
+ addCharacterEncoding( 033, "dotaccent" );
+ addCharacterEncoding( 0232, "dotlessi" );
+ addCharacterEncoding( 0145, "e" );
+ addCharacterEncoding( 0351, "eacute" );
+ addCharacterEncoding( 0352, "ecircumflex" );
+ addCharacterEncoding( 0353, "edieresis" );
+ addCharacterEncoding( 0350, "egrave" );
+ addCharacterEncoding( 070, "eight" );
+ addCharacterEncoding( 0203, "ellipsis" );
+ addCharacterEncoding( 0204, "emdash" );
+ addCharacterEncoding( 0205, "endash" );
+ addCharacterEncoding( 075, "equal" );
+ addCharacterEncoding( 0360, "eth" );
+ addCharacterEncoding( 041, "exclam" );
+ addCharacterEncoding( 0241, "exclamdown" );
+ addCharacterEncoding( 0146, "f" );
+ addCharacterEncoding( 0223, "fi" );
+ addCharacterEncoding( 065, "five" );
+ addCharacterEncoding( 0224, "fl" );
+ addCharacterEncoding( 0206, "florin" );
+ addCharacterEncoding( 064, "four" );
+ addCharacterEncoding( 0207, "fraction" );
+ addCharacterEncoding( 0147, "g" );
+ addCharacterEncoding( 0337, "germandbls" );
+ addCharacterEncoding( 0140, "grave" );
+ addCharacterEncoding( 076, "greater" );
+ addCharacterEncoding( 0253, "guillemotleft" );
+ addCharacterEncoding( 0273, "guillemotright" );
+ addCharacterEncoding( 0210, "guilsinglleft" );
+ addCharacterEncoding( 0211, "guilsinglright" );
+ addCharacterEncoding( 0150, "h" );
+ addCharacterEncoding( 034, "hungarumlaut" );
+ addCharacterEncoding( 055, "hyphen" );
+ addCharacterEncoding( 0151, "i" );
+ addCharacterEncoding( 0355, "iacute" );
+ addCharacterEncoding( 0356, "icircumflex" );
+ addCharacterEncoding( 0357, "idieresis" );
+ addCharacterEncoding( 0354, "igrave" );
+ addCharacterEncoding( 0152, "j" );
+ addCharacterEncoding( 0153, "k" );
+ addCharacterEncoding( 0154, "l" );
+ addCharacterEncoding( 074, "less" );
+ addCharacterEncoding( 0254, "logicalnot" );
+ addCharacterEncoding( 0233, "lslash" );
+ addCharacterEncoding( 0155, "m" );
+ addCharacterEncoding( 0257, "macron" );
+ addCharacterEncoding( 0212, "minus" );
+ addCharacterEncoding( 0265, "mu" );
+ addCharacterEncoding( 0327, "multiply" );
+ addCharacterEncoding( 0156, "n" );
+ addCharacterEncoding( 071, "nine" );
+ addCharacterEncoding( 0361, "ntilde" );
+ addCharacterEncoding( 043, "numbersign" );
+ addCharacterEncoding( 0157, "o" );
+ addCharacterEncoding( 0363, "oacute" );
+ addCharacterEncoding( 0364, "ocircumflex" );
+ addCharacterEncoding( 0366, "odieresis" );
+ addCharacterEncoding( 0234, "oe" );
+ addCharacterEncoding( 035, "ogonek" );
+ addCharacterEncoding( 0362, "ograve" );
+ addCharacterEncoding( 061, "one" );
+ addCharacterEncoding( 0275, "onehalf" );
+ addCharacterEncoding( 0274, "onequarter" );
+ addCharacterEncoding( 0271, "onesuperior" );
+ addCharacterEncoding( 0252, "ordfeminine" );
+ addCharacterEncoding( 0272, "ordmasculine" );
+ addCharacterEncoding( 0370, "oslash" );
+ addCharacterEncoding( 0365, "otilde" );
+ addCharacterEncoding( 0160, "p" );
+ addCharacterEncoding( 0266, "paragraph" );
+ addCharacterEncoding( 050, "parenleft" );
+ addCharacterEncoding( 051, "parenright" );
+ addCharacterEncoding( 045, "percent" );
+ addCharacterEncoding( 056, "period" );
+ addCharacterEncoding( 0267, "periodcentered" );
+ addCharacterEncoding( 0213, "perthousand" );
+ addCharacterEncoding( 053, "plus" );
+ addCharacterEncoding( 0261, "plusminus" );
+ addCharacterEncoding( 0161, "q" );
+ addCharacterEncoding( 077, "question" );
+ addCharacterEncoding( 0277, "questiondown" );
+ addCharacterEncoding( 042, "quotedbl" );
+ addCharacterEncoding( 0214, "quotedblbase" );
+ addCharacterEncoding( 0215, "quotedblleft" );
+ addCharacterEncoding( 0216, "quotedblright" );
+ addCharacterEncoding( 0217, "quoteleft" );
+ addCharacterEncoding( 0220, "quoteright" );
+ addCharacterEncoding( 0221, "quotesinglbase" );
+ addCharacterEncoding( 047, "quotesingle" );
+ addCharacterEncoding( 0162, "r" );
+ addCharacterEncoding( 0256, "registered" );
+ addCharacterEncoding( 036, "ring" );
+ addCharacterEncoding( 0163, "s" );
+ addCharacterEncoding( 0235, "scaron" );
+ addCharacterEncoding( 0247, "section" );
+ addCharacterEncoding( 073, "semicolon" );
+ addCharacterEncoding( 067, "seven" );
+ addCharacterEncoding( 066, "six" );
+ addCharacterEncoding( 057, "slash" );
+ addCharacterEncoding( 040, "space" );
+ addCharacterEncoding( 0243, "sterling" );
+ addCharacterEncoding( 0164, "t" );
+ addCharacterEncoding( 0376, "thorn" );
+ addCharacterEncoding( 063, "three" );
+ addCharacterEncoding( 0276, "threequarters" );
+ addCharacterEncoding( 0263, "threesuperior" );
+ addCharacterEncoding( 037, "tilde" );
+ addCharacterEncoding( 0222, "trademark" );
+ addCharacterEncoding( 062, "two" );
+ addCharacterEncoding( 0262, "twosuperior" );
+ addCharacterEncoding( 0165, "u" );
+ addCharacterEncoding( 0372, "uacute" );
+ addCharacterEncoding( 0373, "ucircumflex" );
+ addCharacterEncoding( 0374, "udieresis" );
+ addCharacterEncoding( 0371, "ugrave" );
+ addCharacterEncoding( 0137, "underscore" );
+ addCharacterEncoding( 0166, "v" );
+ addCharacterEncoding( 0167, "w" );
+ addCharacterEncoding( 0170, "x" );
+ addCharacterEncoding( 0171, "y" );
+ addCharacterEncoding( 0375, "yacute" );
+ addCharacterEncoding( 0377, "ydieresis" );
+ addCharacterEncoding( 0245, "yen" );
+ addCharacterEncoding( 0172, "z" );
+ addCharacterEncoding( 0236, "zcaron" );
+ addCharacterEncoding( 060, "zero" );
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return COSName.PDF_DOC_ENCODING;
+ }
+}
--- /dev/null
+/*
+ * 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.encoding;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is an interface to a text encoder.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class StandardEncoding extends Encoding
+{
+
+ /**
+ * Singleton instance of this class.
+ *
+ * @since Apache PDFBox 1.3.0
+ */
+ public static final StandardEncoding INSTANCE = new StandardEncoding();
+
+ /**
+ * Constructor.
+ */
+ public StandardEncoding()
+ {
+ addCharacterEncoding( 0101, "A" );
+ addCharacterEncoding( 0341, "AE" );
+ addCharacterEncoding( 0102, "B" );
+ addCharacterEncoding( 0103, "C" );
+ addCharacterEncoding( 0104, "D" );
+ addCharacterEncoding( 0105, "E" );
+ addCharacterEncoding( 0106, "F" );
+ addCharacterEncoding( 0107, "G" );
+ addCharacterEncoding( 0110, "H" );
+ addCharacterEncoding( 0111, "I" );
+ addCharacterEncoding( 0112, "J" );
+ addCharacterEncoding( 0113, "K" );
+ addCharacterEncoding( 0114, "L" );
+ addCharacterEncoding( 0350, "Lslash" );
+ addCharacterEncoding( 0115, "M" );
+ addCharacterEncoding( 0116, "N" );
+ addCharacterEncoding( 0117, "O" );
+ addCharacterEncoding( 0352, "OE" );
+ addCharacterEncoding( 0351, "Oslash" );
+ addCharacterEncoding( 0120, "P" );
+ addCharacterEncoding( 0121, "Q" );
+ addCharacterEncoding( 0122, "R" );
+ addCharacterEncoding( 0123, "S" );
+ addCharacterEncoding( 0124, "T" );
+ addCharacterEncoding( 0125, "U" );
+ addCharacterEncoding( 0126, "V" );
+ addCharacterEncoding( 0127, "W" );
+ addCharacterEncoding( 0130, "X" );
+ addCharacterEncoding( 0131, "Y" );
+ addCharacterEncoding( 0132, "Z" );
+ addCharacterEncoding( 0141, "a" );
+ addCharacterEncoding( 0302, "acute" );
+ addCharacterEncoding( 0361, "ae" );
+ addCharacterEncoding( 0046, "ampersand" );
+ addCharacterEncoding( 0136, "asciicircum" );
+ addCharacterEncoding( 0176, "asciitilde" );
+ addCharacterEncoding( 0052, "asterisk" );
+ addCharacterEncoding( 0100, "at" );
+ addCharacterEncoding( 0142, "b" );
+ addCharacterEncoding( 0134, "backslash" );
+ addCharacterEncoding( 0174, "bar" );
+ addCharacterEncoding( 0173, "braceleft" );
+ addCharacterEncoding( 0175, "braceright" );
+ addCharacterEncoding( 0133, "bracketleft" );
+ addCharacterEncoding( 0135, "bracketright" );
+ addCharacterEncoding( 0306, "breve" );
+ addCharacterEncoding( 0267, "bullet" );
+ addCharacterEncoding( 0143, "c" );
+ addCharacterEncoding( 0317, "caron" );
+ addCharacterEncoding( 0313, "cedilla" );
+ addCharacterEncoding( 0242, "cent" );
+ addCharacterEncoding( 0303, "circumflex" );
+ addCharacterEncoding( 0072, "colon" );
+ addCharacterEncoding( 0054, "comma" );
+ addCharacterEncoding( 0250, "currency" );
+ addCharacterEncoding( 0144, "d" );
+ addCharacterEncoding( 0262, "dagger" );
+ addCharacterEncoding( 0263, "daggerdbl" );
+ addCharacterEncoding( 0310, "dieresis" );
+ addCharacterEncoding( 0044, "dollar" );
+ addCharacterEncoding( 0307, "dotaccent" );
+ addCharacterEncoding( 0365, "dotlessi" );
+ addCharacterEncoding( 0145, "e" );
+ addCharacterEncoding( 0070, "eight" );
+ addCharacterEncoding( 0274, "ellipsis" );
+ addCharacterEncoding( 0320, "emdash" );
+ addCharacterEncoding( 0261, "endash" );
+ addCharacterEncoding( 0075, "equal" );
+ addCharacterEncoding( 0041, "exclam" );
+ addCharacterEncoding( 0241, "exclamdown" );
+ addCharacterEncoding( 0146, "f" );
+ addCharacterEncoding( 0256, "fi" );
+ addCharacterEncoding( 0065, "five" );
+ addCharacterEncoding( 0257, "fl" );
+ addCharacterEncoding( 0246, "florin" );
+ addCharacterEncoding( 0064, "four" );
+ addCharacterEncoding( 0244, "fraction" );
+ addCharacterEncoding( 0147, "g" );
+ addCharacterEncoding( 0373, "germandbls" );
+ addCharacterEncoding( 0301, "grave" );
+ addCharacterEncoding( 0076, "greater" );
+ addCharacterEncoding( 0253, "guillemotleft" );
+ addCharacterEncoding( 0273, "guillemotright" );
+ addCharacterEncoding( 0254, "guilsinglleft" );
+ addCharacterEncoding( 0255, "guilsinglright" );
+ addCharacterEncoding( 0150, "h" );
+ addCharacterEncoding( 0315, "hungarumlaut" );
+ addCharacterEncoding( 0055, "hyphen" );
+ addCharacterEncoding( 0151, "i" );
+ addCharacterEncoding( 0152, "j" );
+ addCharacterEncoding( 0153, "k" );
+ addCharacterEncoding( 0154, "l" );
+ addCharacterEncoding( 0074, "less" );
+ addCharacterEncoding( 0370, "lslash" );
+ addCharacterEncoding( 0155, "m" );
+ addCharacterEncoding( 0305, "macron" );
+ addCharacterEncoding( 0156, "n" );
+ addCharacterEncoding( 0071, "nine" );
+ addCharacterEncoding( 0043, "numbersign" );
+ addCharacterEncoding( 0157, "o" );
+ addCharacterEncoding( 0372, "oe" );
+ addCharacterEncoding( 0316, "ogonek" );
+ addCharacterEncoding( 0061, "one" );
+ addCharacterEncoding( 0343, "ordfeminine" );
+ addCharacterEncoding( 0353, "ordmasculine" );
+ addCharacterEncoding( 0371, "oslash" );
+ addCharacterEncoding( 0160, "p" );
+ addCharacterEncoding( 0266, "paragraph" );
+ addCharacterEncoding( 0050, "parenleft" );
+ addCharacterEncoding( 0051, "parenright" );
+ addCharacterEncoding( 0045, "percent" );
+ addCharacterEncoding( 0056, "period" );
+ addCharacterEncoding( 0264, "periodcentered" );
+ addCharacterEncoding( 0275, "perthousand" );
+ addCharacterEncoding( 0053, "plus" );
+ addCharacterEncoding( 0161, "q" );
+ addCharacterEncoding( 0077, "question" );
+ addCharacterEncoding( 0277, "questiondown" );
+ addCharacterEncoding( 0042, "quotedbl" );
+ addCharacterEncoding( 0271, "quotedblbase" );
+ addCharacterEncoding( 0252, "quotedblleft" );
+ addCharacterEncoding( 0272, "quotedblright" );
+ addCharacterEncoding( 0140, "quoteleft" );
+ addCharacterEncoding( 0047, "quoteright" );
+ addCharacterEncoding( 0270, "quotesinglbase" );
+ addCharacterEncoding( 0251, "quotesingle" );
+ addCharacterEncoding( 0162, "r" );
+ addCharacterEncoding( 0312, "ring" );
+ addCharacterEncoding( 0163, "s" );
+ addCharacterEncoding( 0247, "section" );
+ addCharacterEncoding( 0073, "semicolon" );
+ addCharacterEncoding( 0067, "seven" );
+ addCharacterEncoding( 0066, "six" );
+ addCharacterEncoding( 0057, "slash" );
+ addCharacterEncoding( 0040, "space" );
+ addCharacterEncoding( 0243, "sterling" );
+ addCharacterEncoding( 0164, "t" );
+ addCharacterEncoding( 0063, "three" );
+ addCharacterEncoding( 0304, "tilde" );
+ addCharacterEncoding( 0062, "two" );
+ addCharacterEncoding( 0165, "u" );
+ addCharacterEncoding( 0137, "underscore" );
+ addCharacterEncoding( 0166, "v" );
+ addCharacterEncoding( 0167, "w" );
+ addCharacterEncoding( 0170, "x" );
+ addCharacterEncoding( 0171, "y" );
+ addCharacterEncoding( 0245, "yen" );
+ addCharacterEncoding( 0172, "z" );
+ addCharacterEncoding( 0060, "zero" );
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return COSName.STANDARD_ENCODING;
+ }
+}
--- /dev/null
+/*
+ * 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.encoding;
+
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This class represents an encoding which was read from a type1 font.
+ *
+ */
+public class Type1Encoding extends Encoding
+{
+ public Type1Encoding(int size)
+ {
+ for (int i=1;i<size;i++)
+ {
+ addCharacterEncoding(i, NOTDEF);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.encoding;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This the win ansi encoding.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class WinAnsiEncoding extends Encoding
+{
+
+ /**
+ * Singleton instance of this class.
+ *
+ * @since Apache PDFBox 1.3.0
+ */
+ public static final WinAnsiEncoding INSTANCE = new WinAnsiEncoding();
+
+ /**
+ * Constructor.
+ */
+ public WinAnsiEncoding()
+ {
+ addCharacterEncoding( 0101, "A" );;
+ addCharacterEncoding( 0306, "AE" );;
+ addCharacterEncoding( 0301, "Aacute" );;
+ addCharacterEncoding( 0302, "Acircumflex" );;
+ addCharacterEncoding( 0304, "Adieresis" );;
+ addCharacterEncoding( 0300, "Agrave" );;
+ addCharacterEncoding( 0305, "Aring" );;
+ addCharacterEncoding( 0303, "Atilde" );;
+ addCharacterEncoding( 0102, "B" );;
+ addCharacterEncoding( 0103, "C" );;
+ addCharacterEncoding( 0307, "Ccedilla" );;
+ addCharacterEncoding( 0104, "D" );;
+ addCharacterEncoding( 0105, "E" );;
+ addCharacterEncoding( 0311, "Eacute" );;
+ addCharacterEncoding( 0312, "Ecircumflex" );;
+ addCharacterEncoding( 0313, "Edieresis" );;
+ addCharacterEncoding( 0310, "Egrave" );;
+ addCharacterEncoding( 0320, "Eth" );;
+ addCharacterEncoding( 0200, "Euro" );;
+ addCharacterEncoding( 0106, "F" );;
+ addCharacterEncoding( 0107, "G" );;
+ addCharacterEncoding( 0110, "H" );;
+ addCharacterEncoding( 0111, "I" );;
+ addCharacterEncoding( 0315, "Iacute" );;
+ addCharacterEncoding( 0316, "Icircumflex" );;
+ addCharacterEncoding( 0317, "Idieresis" );;
+ addCharacterEncoding( 0314, "Igrave" );;
+ addCharacterEncoding( 0112, "J" );;
+ addCharacterEncoding( 0113, "K" );;
+ addCharacterEncoding( 0114, "L" );;
+ addCharacterEncoding( 0115, "M" );;
+ addCharacterEncoding( 0116, "N" );;
+ addCharacterEncoding( 0321, "Ntilde" );;
+ addCharacterEncoding( 0117, "O" );;
+ addCharacterEncoding( 0214, "OE" );;
+ addCharacterEncoding( 0323, "Oacute" );;
+ addCharacterEncoding( 0324, "Ocircumflex" );;
+ addCharacterEncoding( 0326, "Odieresis" );;
+ addCharacterEncoding( 0322, "Ograve" );;
+ addCharacterEncoding( 0330, "Oslash" );;
+ addCharacterEncoding( 0325, "Otilde" );;
+ addCharacterEncoding( 0120, "P" );;
+ addCharacterEncoding( 0121, "Q" );;
+ addCharacterEncoding( 0122, "R" );;
+ addCharacterEncoding( 0123, "S" );;
+ addCharacterEncoding( 0212, "Scaron" );;
+ addCharacterEncoding( 0124, "T" );;
+ addCharacterEncoding( 0336, "Thorn" );;
+ addCharacterEncoding( 0125, "U" );;
+ addCharacterEncoding( 0332, "Uacute" );;
+ addCharacterEncoding( 0333, "Ucircumflex" );;
+ addCharacterEncoding( 0334, "Udieresis" );;
+ addCharacterEncoding( 0331, "Ugrave" );;
+ addCharacterEncoding( 0126, "V" );;
+ addCharacterEncoding( 0127, "W" );;
+ addCharacterEncoding( 0130, "X" );;
+ addCharacterEncoding( 0131, "Y" );;
+ addCharacterEncoding( 0335, "Yacute" );;
+ addCharacterEncoding( 0237, "Ydieresis" );;
+ addCharacterEncoding( 0132, "Z" );;
+ addCharacterEncoding( 0216, "Zcaron" );;
+ addCharacterEncoding( 0141, "a" );;
+ addCharacterEncoding( 0341, "aacute" );;
+ addCharacterEncoding( 0342, "acircumflex" );;
+ addCharacterEncoding( 0264, "acute" );;
+ addCharacterEncoding( 0344, "adieresis" );;
+ addCharacterEncoding( 0346, "ae" );;
+ addCharacterEncoding( 0340, "agrave" );;
+ addCharacterEncoding( 046, "ampersand" );;
+ addCharacterEncoding( 0345, "aring" );;
+ addCharacterEncoding( 0136, "asciicircum" );;
+ addCharacterEncoding( 0176, "asciitilde" );;
+ addCharacterEncoding( 052, "asterisk" );;
+ addCharacterEncoding( 0100, "at" );;
+ addCharacterEncoding( 0343, "atilde" );;
+ addCharacterEncoding( 0142, "b" );;
+ addCharacterEncoding( 0134, "backslash" );;
+ addCharacterEncoding( 0174, "bar" );;
+ addCharacterEncoding( 0173, "braceleft" );;
+ addCharacterEncoding( 0175, "braceright" );;
+ addCharacterEncoding( 0133, "bracketleft" );;
+ addCharacterEncoding( 0135, "bracketright" );;
+ addCharacterEncoding( 0246, "brokenbar" );;
+ addCharacterEncoding( 0225, "bullet" );;
+ addCharacterEncoding( 0143, "c" );;
+ addCharacterEncoding( 0347, "ccedilla" );;
+ addCharacterEncoding( 0270, "cedilla" );;
+ addCharacterEncoding( 0242, "cent" );;
+ addCharacterEncoding( 0210, "circumflex" );;
+ addCharacterEncoding( 072, "colon" );;
+ addCharacterEncoding( 054, "comma" );;
+ addCharacterEncoding( 0251, "copyright" );;
+
+ /**
+ * Added because cweb.pdf uses circlecopyrt
+ */
+ addCharacterEncoding( 0251, "circlecopyrt" );;
+ addCharacterEncoding( 0244, "currency" );;
+ addCharacterEncoding( 0144, "d" );;
+ addCharacterEncoding( 0206, "dagger" );;
+ addCharacterEncoding( 0207, "daggerdbl" );;
+ addCharacterEncoding( 0260, "degree" );;
+ addCharacterEncoding( 0250, "dieresis" );;
+ addCharacterEncoding( 0367, "divide" );;
+ addCharacterEncoding( 044, "dollar" );;
+ addCharacterEncoding( 0145, "e" );;
+ addCharacterEncoding( 0351, "eacute" );;
+ addCharacterEncoding( 0352, "ecircumflex" );;
+ addCharacterEncoding( 0353, "edieresis" );;
+ addCharacterEncoding( 0350, "egrave" );;
+ addCharacterEncoding( 070, "eight" );;
+ addCharacterEncoding( 0205, "ellipsis" );;
+ addCharacterEncoding( 0227, "emdash" );;
+ addCharacterEncoding( 0226, "endash" );;
+ addCharacterEncoding( 075, "equal" );;
+ addCharacterEncoding( 0360, "eth" );;
+ addCharacterEncoding( 041, "exclam" );;
+ addCharacterEncoding( 0241, "exclamdown" );;
+ addCharacterEncoding( 0146, "f" );;
+ addCharacterEncoding( 065, "five" );;
+ addCharacterEncoding( 0203, "florin" );;
+ addCharacterEncoding( 064, "four" );;
+ addCharacterEncoding( 0147, "g" );;
+ addCharacterEncoding( 0337, "germandbls" );;
+ addCharacterEncoding( 0140, "grave" );;
+ addCharacterEncoding( 076, "greater" );;
+ addCharacterEncoding( 0253, "guillemotleft" );;
+ addCharacterEncoding( 0273, "guillemotright" );;
+ addCharacterEncoding( 0213, "guilsinglleft" );;
+ addCharacterEncoding( 0233, "guilsinglright" );;
+ addCharacterEncoding( 0150, "h" );;
+ addCharacterEncoding( 055, "hyphen" );;
+ addCharacterEncoding( 0151, "i" );;
+ addCharacterEncoding( 0355, "iacute" );;
+ addCharacterEncoding( 0356, "icircumflex" );;
+ addCharacterEncoding( 0357, "idieresis" );;
+ addCharacterEncoding( 0354, "igrave" );;
+ addCharacterEncoding( 0152, "j" );;
+ addCharacterEncoding( 0153, "k" );;
+ addCharacterEncoding( 0154, "l" );;
+ addCharacterEncoding( 074, "less" );;
+ addCharacterEncoding( 0254, "logicalnot" );;
+ addCharacterEncoding( 0155, "m" );;
+ addCharacterEncoding( 0257, "macron" );;
+ addCharacterEncoding( 0265, "mu" );;
+ addCharacterEncoding( 0327, "multiply" );;
+ addCharacterEncoding( 0156, "n" );;
+ addCharacterEncoding( 071, "nine" );;
+ addCharacterEncoding( 0361, "ntilde" );;
+ addCharacterEncoding( 043, "numbersign" );;
+ addCharacterEncoding( 0157, "o" );;
+ addCharacterEncoding( 0363, "oacute" );;
+ addCharacterEncoding( 0364, "ocircumflex" );;
+ addCharacterEncoding( 0366, "odieresis" );;
+ addCharacterEncoding( 0234, "oe" );;
+ addCharacterEncoding( 0362, "ograve" );;
+ addCharacterEncoding( 061, "one" );;
+ addCharacterEncoding( 0275, "onehalf" );;
+ addCharacterEncoding( 0274, "onequarter" );;
+ addCharacterEncoding( 0271, "onesuperior" );;
+ addCharacterEncoding( 0252, "ordfeminine" );;
+ addCharacterEncoding( 0272, "ordmasculine" );;
+ addCharacterEncoding( 0370, "oslash" );;
+ addCharacterEncoding( 0365, "otilde" );;
+ addCharacterEncoding( 0160, "p" );;
+ addCharacterEncoding( 0266, "paragraph" );;
+ addCharacterEncoding( 050, "parenleft" );;
+ addCharacterEncoding( 051, "parenright" );;
+ addCharacterEncoding( 045, "percent" );;
+ addCharacterEncoding( 056, "period" );;
+ addCharacterEncoding( 0267, "periodcentered" );;
+ addCharacterEncoding( 0211, "perthousand" );;
+ addCharacterEncoding( 053, "plus" );;
+ addCharacterEncoding( 0261, "plusminus" );;
+ addCharacterEncoding( 0161, "q" );;
+ addCharacterEncoding( 077, "question" );;
+ addCharacterEncoding( 0277, "questiondown" );;
+ addCharacterEncoding( 042, "quotedbl" );;
+ addCharacterEncoding( 0204, "quotedblbase" );;
+ addCharacterEncoding( 0223, "quotedblleft" );;
+ addCharacterEncoding( 0224, "quotedblright" );;
+ addCharacterEncoding( 0221, "quoteleft" );;
+ addCharacterEncoding( 0222, "quoteright" );;
+ addCharacterEncoding( 0202, "quotesinglbase" );;
+ addCharacterEncoding( 047, "quotesingle" );;
+ addCharacterEncoding( 0162, "r" );;
+ addCharacterEncoding( 0256, "registered" );;
+ addCharacterEncoding( 0163, "s" );;
+ addCharacterEncoding( 0232, "scaron" );;
+ addCharacterEncoding( 0247, "section" );;
+ addCharacterEncoding( 073, "semicolon" );;
+ addCharacterEncoding( 067, "seven" );;
+ addCharacterEncoding( 066, "six" );;
+ addCharacterEncoding( 057, "slash" );;
+ addCharacterEncoding( 040, "space" );;
+ addCharacterEncoding( 0243, "sterling" );;
+ addCharacterEncoding( 0164, "t" );;
+ addCharacterEncoding( 0376, "thorn" );;
+ addCharacterEncoding( 063, "three" );;
+ addCharacterEncoding( 0276, "threequarters" );;
+ addCharacterEncoding( 0263, "threesuperior" );;
+ addCharacterEncoding( 0230, "tilde" );;
+ addCharacterEncoding( 0231, "trademark" );;
+ addCharacterEncoding( 062, "two" );;
+ addCharacterEncoding( 0262, "twosuperior" );;
+ addCharacterEncoding( 0165, "u" );;
+ addCharacterEncoding( 0372, "uacute" );;
+ addCharacterEncoding( 0373, "ucircumflex" );;
+ addCharacterEncoding( 0374, "udieresis" );;
+ addCharacterEncoding( 0371, "ugrave" );;
+ addCharacterEncoding( 0137, "underscore" );;
+ addCharacterEncoding( 0166, "v" );;
+ addCharacterEncoding( 0167, "w" );;
+ addCharacterEncoding( 0170, "x" );;
+ addCharacterEncoding( 0171, "y" );;
+ addCharacterEncoding( 0375, "yacute" );;
+ addCharacterEncoding( 0377, "ydieresis" );;
+ addCharacterEncoding( 0245, "yen" );;
+ addCharacterEncoding( 0172, "z" );;
+ addCharacterEncoding( 0236, "zcaron" );;
+ addCharacterEncoding( 060, "zero" );;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return COSName.WIN_ANSI_ENCODING;
+ }
+}
--- /dev/null
+/*
+ * 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.encoding.conversion;
+
+import org.apache.fontbox.cmap.CMap;
+import java.io.UnsupportedEncodingException;
+
+
+/**
+ * CJKConverter converts encodings defined in CJKEncodings.
+ *
+ * @author Pin Xue (http://www.pinxue.net), Holly Lee (holly.lee (at) gmail.com)
+ * @version $Revision: 1.0 $
+ */
+public class CJKConverter implements EncodingConverter
+{
+ // The encoding
+ private String encodingName = null;
+ // The java charset name
+ private String charsetName = null;
+
+
+ /**
+ * Constructs a CJKConverter from a PDF encoding name.
+ *
+ * @param encoding the encoding to be used
+ */
+ public CJKConverter(String encoding)
+ {
+ encodingName = encoding;
+ charsetName = CJKEncodings.getCharset(encoding);
+ }
+
+ /**
+ * Convert a string. It occurs when a cmap lookup returned
+ * converted bytes successfully, but we still need to convert its
+ * encoding. The parameter s is constructs as one byte or a UTF-16BE
+ * encoded string.
+ *
+ * Note: pdfbox set string to UTF-16BE charset before calling into
+ * this.
+ *
+ * {@inheritDoc}
+ */
+ public String convertString(String s)
+ {
+ if ( s.length() == 1 )
+ {
+ return s;
+ }
+
+ if ( charsetName.equalsIgnoreCase("UTF-16BE") )
+ {
+ return s;
+ }
+
+ try
+ {
+ return new String(s.getBytes("UTF-16BE"), charsetName);
+ }
+ catch ( UnsupportedEncodingException uee )
+ {
+ return s;
+ }
+ }
+
+ /**
+ * Convert bytes to a string. We just convert bytes within
+ * coderange defined in CMap.
+ *
+ * {@inheritDoc}
+ */
+ public String convertBytes(byte [] c, int offset, int length, CMap cmap)
+ {
+ if ( cmap != null )
+ {
+ try
+ {
+ if ( cmap.isInCodeSpaceRanges(c, offset, length) )
+ {
+ return new String(c, offset, length, charsetName);
+ }
+ else
+ {
+ return null;
+ }
+
+ }
+ catch ( UnsupportedEncodingException uee )
+ {
+ return new String(c, offset, length);
+ }
+ }
+ // No cmap?
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.encoding.conversion;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * This class represents PDF encoding name to Java charset name mapping.
+ *
+ * @author Pin Xue (http://www.pinxue.net), Holly Lee (holly.lee (at) gmail.com)
+ * @version $Revision: 1.0 $
+ */
+class CJKEncodings
+{
+ // Mapping: PDF encoding name -> Java (IANA) charset name
+ private static HashMap charsetMapping = new HashMap();
+
+ private CJKEncodings()
+ {
+ }
+
+ static
+ {
+ // Chinese (Simplified)
+ // Microsoft Code Page 936 (lfCharSet 0x86), GB 2312-80 character set, EUC-CN encoding
+ charsetMapping.put("GB-EUC-H", "GB2312");
+ // Vertical version of GB-EUC-H
+ charsetMapping.put("GB-EUC-V", "GB2312");
+ // Mac OS, GB 2312-80 character set, EUC-CN encoding, Script Manager code 19
+ charsetMapping.put("GBpc-EUC-H", "GB2312");
+ // Vertical version of GBpc-EUC-H
+ charsetMapping.put("GBpc-EUC-V", "GB2312");
+ // Microsoft Code Page 936 (lfCharSet 0x86), GBK character set, GBK encoding
+ charsetMapping.put("GBK-EUC-H", "GBK");
+ // Vertical version of GBK-EUC-H
+ charsetMapping.put("GBK-EUC-V", "GBK");
+ // Same as GBK-EUC-H but replaces half-width Latin characters with proportional
+ // forms and maps character code 0x24 to a dollar sign ($) instead of a yuan symbol (â\99\80â\88´)
+ charsetMapping.put("GBKp-EUC-H", "GBK");
+ // Vertical version of GBKp-EUC-H
+ charsetMapping.put("GBKp-EUC-V", "GBK");
+ // GB 18030-2000 character set, mixed 1-, 2-, and 4-byte encoding
+ charsetMapping.put("GBK2K-H", "GB18030");
+ // Vertical version of GBK2K-H
+ charsetMapping.put("GBK2K-V", "GB18030");
+ // Unicode (UCS-2) encoding for the Adobe-GB1 character collection
+ charsetMapping.put("UniGB-UCS2-H", "ISO-10646-UCS-2");
+ // Vertical version of UniGB-UCS2-H
+ charsetMapping.put("UniGB-UCS2-V", "ISO-10646-UCS-2");
+ // Unicode (UTF-16BE) encoding for the Adobe-GB1 character collection; contains mappings
+ // for all characters in the GB18030-2000 character set
+ charsetMapping.put("UniGB-UTF16-H", "UTF-16BE");
+ // Vertical version of UniGB-UTF16-H
+ charsetMapping.put("UniGB-UTF16-V", "UTF-16BE");
+
+ // Chinese (Traditional)
+ // Mac OS, Big Five character set, Big Five encoding, Script Manager code 2
+ charsetMapping.put("B5pc-H", "BIG5");
+ // Vertical version of B5pc-H
+ charsetMapping.put("B5pc-V", "BIG5");
+ // Hong Kong SCS, an extension to the Big Five character set and encoding
+ charsetMapping.put("HKscs-B5-H", "Big5-HKSCS");
+ // Vertical version of HKscs-B5-H
+ charsetMapping.put("HKscs-B5-V", "Big5-HKSCS");
+ // Microsoft Code Page 950 (lfCharSet 0x88), Big Five character set with ETen extensions
+ charsetMapping.put("ETen-B5-H", "BIG5");
+ // Vertical version of ETen-B5-H
+ charsetMapping.put("ETen-B5-V", "BIG5");
+ // Same as ETen-B5-H but replaces half-width Latin characters with proportional forms
+ charsetMapping.put("ETenms-B5-H", "BIG5");
+ // Vertical version of ETenms-B5-H
+ charsetMapping.put("ETenms-B5-V", "BIG5");
+ // CNS 11643-1992 character set, EUC-TW encoding
+ charsetMapping.put("CNS-EUC-H", "HZ");
+ // Vertical version of CNS-EUC-H
+ charsetMapping.put("CNS-EUC-V", "HZ");
+ // Unicode (UCS-2) encoding for the Adobe-CNS1 character collection
+ charsetMapping.put("UniCNS-UCS2-H", "ISO-10646-UCS-2");
+ // Vertical version of UniCNS-UCS2-H
+ charsetMapping.put("UniCNS-UCS2-V", "ISO-10646-UCS-2");
+ // Unicode (UTF-16BE) encoding for the Adobe-CNS1 character collection;
+ // contains mappings for all the characters in the HKSCS-2001 character set and
+ // contains both 2- and 4- byte character codes
+ charsetMapping.put("UniCNS-UTF16-H", "UTF-16BE");
+ // Vertical version of UniCNS-UTF16-H
+ charsetMapping.put("UniCNS-UTF16-V", "UTF-16BE");
+
+ //Japanese
+ // Mac OS, JIS X 0208 character set with KanjiTalk6 extensions, Shift-JIS encoding, Script Manager code 1
+ charsetMapping.put("83pv-RKSJ-H", "JIS");
+ // Microsoft Code Page 932 (lfCharSet 0x80), JIS X 0208 character set with NEC and IBM- extensions
+ charsetMapping.put("90ms-RKSJ-H", "JIS");
+ // Vertical version of 90ms-RKSJ-H
+ charsetMapping.put("90ms-RKSJ-V", "JIS");
+ // Same as 90ms-RKSJ-H but replaces half-width Latin characters with proportional forms
+ charsetMapping.put("90msp-RKSJ-H", "JIS");
+ // Vertical version of 90msp-RKSJ-H
+ charsetMapping.put("90msp-RKSJ-V", "JIS");
+ // Mac OS, JIS X 0208 character set with KanjiTalk7 extensions, Shift-JIS encoding, Script Manager code 1
+ charsetMapping.put("90pv-RKSJ-H", "JIS");
+ // JIS X 0208 character set with Fujitsu FMR extensions, Shift-JIS encoding
+ charsetMapping.put("Add-RKSJ-H", "JIS");
+ // Vertical version of Add-RKSJ-H
+ charsetMapping.put("Add-RKSJ-V", "JIS");
+ // JIS X 0208 character set, EUC-JP encoding
+ charsetMapping.put("EUC-H", "JIS");
+ // Vertical version of EUC-H
+ charsetMapping.put("EUC-V", "JIS");
+ // JIS C 6226 (JIS78) character set with NEC extensions, Shift-JIS encoding
+ charsetMapping.put("Ext-RKSJ-H", "JIS");
+ // Vertical version of Ext-RKSJ-H
+ charsetMapping.put("Ext-RKSJ-V", "JIS");
+ // JIS X 0208 character set, ISO-2022-JP encoding
+ charsetMapping.put("H", "JIS");
+ // Vertical version of H
+ charsetMapping.put("V", "JIS");
+ // Unicode (UCS-2) encoding for the Adobe-Japan1 character collection
+ charsetMapping.put("UniJIS-UCS2-H", "ISO-10646-UCS-2");
+ // Vertical version of UniJIS-UCS2-H
+ charsetMapping.put("UniJIS-UCS2-V", "ISO-10646-UCS-2");
+ // Same as UniJIS-UCS2-H but replaces proportional Latin characters with half-width forms
+ charsetMapping.put("UniJIS-UCS2-HW-H", "ISO-10646-UCS-2");
+ // Vertical version of UniJIS-UCS2-HW-H
+ charsetMapping.put("UniJIS-UCS2-HW-V", "ISO-10646-UCS-2");
+ // Unicode (UTF-16BE) encoding for the Adobe-Japan1 character collection;
+ // contains mappings for all characters in the JIS X 0213:1000 character set
+ charsetMapping.put("UniJIS-UTF16-H", "UTF-16BE");
+ // Vertical version of UniJIS-UTF16-H
+ charsetMapping.put("UniJIS-UTF16-V", "UTF-16BE");
+ // JIS X 0208 character set, ISO-2022-JP encoding
+ charsetMapping.put("Identity-H", "JIS");
+ // Vertical version of H
+ charsetMapping.put("Identity-V", "JIS");
+
+ //Korean
+ // KS X 1001:1992 character set, EUC-KR encoding
+ charsetMapping.put("KSC-EUC-H", "KSC");
+ // Vertical version of KSC-EUC-H
+ charsetMapping.put("KSC-EUC-V", "KSC");
+ // Microsoft Code Page 949 (lfCharSet 0x81), KS X 1001:1992 character set
+ // plus 8822.putitional hangul, Unified Hangul Code (UHC) encoding
+ charsetMapping.put("KSCms-UHC-H", "KSC");
+ // Vertical version of KSCms-UHC-H
+ charsetMapping.put("KSCms-UHC-V", "KSC");
+ // Same as KSCms-UHC-H but replaces proportional Latin characters with half-width forms
+ charsetMapping.put("KSCms-UHC-HW-H", "KSC");
+ // Vertical version of KSCms-UHC-HW-H
+ charsetMapping.put("KSCms-UHC-HW-V", "KSC");
+ // Mac OS, KS X 1001:1992 character set with Mac OS KH extensions, Script Manager Code 3
+ charsetMapping.put("KSCpc-EUC-H", "KSC");
+ // Unicode (UCS-2) encoding for the Adobe-Korea1 character collection
+ charsetMapping.put("UniKS-UCS2-H", "ISO-10646-UCS-2");
+ // Vertical version of UniKS-UCS2-H
+ charsetMapping.put("UniKS-UCS2-V", "ISO-10646-UCS-2");
+ // Unicode (UTF-16BE) encoding for the Adobe-Korea1 character collection
+ charsetMapping.put("UniKS-UTF16-H", "UTF-16BE");
+ // Vertical version of UniKS-UTF16-H
+ charsetMapping.put("UniKS-UTF16-V", "UTF-16BE");
+ }
+
+
+ /**
+ * Get respective Java charset name from given PDF encoding name.
+ *
+ * @param encoding PDF encoding name
+ * @return Java charset name, or null if not found
+ */
+ public static final String getCharset( String encoding )
+ {
+ if ( encoding.startsWith("COSName"))
+ {
+ encoding = encoding.substring(8, encoding.length()-1);
+ }
+ return (String)(charsetMapping.get(encoding));
+ }
+
+ /**
+ * Return an iterator to iterate through all encodings.
+ */
+ public static final Iterator getEncodingIterator()
+ {
+ return charsetMapping.keySet().iterator();
+ }
+
+}
--- /dev/null
+/*
+ * 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.encoding.conversion;
+
+import java.util.HashMap;
+
+/**
+ * This class provides a mapping from char code to unicode mapping files used for CJK-encoding.
+ * @author Andreas Lehmkühler
+ * @version $Revision: 1.0 $
+ *
+ */
+
+public class CMapSubstitution
+{
+
+ private static HashMap<String,String> cmapSubstitutions = new HashMap<String,String>();
+
+ private CMapSubstitution()
+ {
+ }
+
+ static
+ {
+ // I don't know if these mappings are complete. Perhaps there
+ // has to be added still one or more
+
+ // chinese simplified
+ cmapSubstitutions.put( "Adobe-GB1-4", "Adobe-GB1-UCS2" );
+ cmapSubstitutions.put( "GBK-EUC-H", "GBK-EUC-UCS2" );
+ cmapSubstitutions.put( "GBK-EUC-V", "GBK-EUC-UCS2" );
+ cmapSubstitutions.put( "GBpc-EUC-H", "GBpc-EUC-UCS2C" );
+ cmapSubstitutions.put( "GBpc-EUC-V", "GBpc-EUC-UCS2C" );
+
+ // chinese traditional
+ cmapSubstitutions.put( "Adobe-CNS1-4", "Adobe-CNS1-UCS2" );
+ cmapSubstitutions.put( "B5pc-H", "B5pc-UCS2" );
+ cmapSubstitutions.put( "B5pc-V", "B5pc-UCS2" );
+ cmapSubstitutions.put( "ETen-B5-H", "ETen-B5-UCS2" );
+ cmapSubstitutions.put( "ETen-B5-V", "ETen-B5-UCS2" );
+ cmapSubstitutions.put( "ETenms-B5-H", "ETen-B5-UCS2" );
+ cmapSubstitutions.put( "ETenms-B5-V", "ETen-B5-UCS2" );
+
+ // japanese
+ cmapSubstitutions.put( "90ms-RKSJ-H", "90ms-RKSJ-UCS2" );
+ cmapSubstitutions.put( "90ms-RKSJ-V", "90ms-RKSJ-UCS2" );
+ cmapSubstitutions.put( "90msp-RKSJ-H", "90ms-RKSJ-UCS2" );
+ cmapSubstitutions.put( "90msp-RKSJ-V", "90ms-RKSJ-UCS2" );
+ cmapSubstitutions.put( "90pv-RKSJ-H", "90pv-RKSJ-UCS2");
+ cmapSubstitutions.put( "UniJIS-UCS2-HW-H", "UniJIS-UCS2-H" );
+ cmapSubstitutions.put( "Adobe-Japan1-4", "Adobe-Japan1-UCS2");
+
+ cmapSubstitutions.put( "Adobe-Identity-0", "Identity-H");
+ cmapSubstitutions.put( "Adobe-Identity-1", "Identity-H");
+ }
+
+ /**
+ *
+ * @param cmapName The name of a cmap for which we have to find a possible substitution
+ * @return the substitution for the given cmap name
+ */
+ public static String substituteCMap(String cmapName)
+ {
+ if (cmapSubstitutions.containsKey(cmapName))
+ {
+ return (String)cmapSubstitutions.get(cmapName);
+ }
+ return cmapName;
+ }
+}
--- /dev/null
+/*
+ * 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.encoding.conversion;
+
+import java.util.Iterator;
+import java.util.HashMap;
+
+/**
+ * EncodingConversionManager maintains relationship between PDF encoding name
+ * and respective EncodingConverter instance. Those PDF encoding name like
+ * GBK-EUC-H should be converted to java charset name before constructing a
+ * java string instance
+ *
+ * @author Pin Xue (http://www.pinxue.net), Holly Lee (holly.lee (at) gmail.com)
+ * @version $Revision: 1.0 $
+ */
+public class EncodingConversionManager
+{
+ /**
+ * Mapping from PDF encoding name to EncodingConverter instance.
+ */
+ private static HashMap encodingMap = new HashMap();
+
+ private EncodingConversionManager()
+ {
+ }
+
+ /**
+ * Initialize the encodingMap before anything calls us.
+ */
+ static {
+
+ // Add CJK encodings to map
+ Iterator it = CJKEncodings.getEncodingIterator();
+
+ while ( it.hasNext() )
+ {
+ String encodingName = (String)(it.next());
+ encodingMap.put(encodingName, new CJKConverter(encodingName));
+ }
+ // If there is any other encoding conversions, please add it here.
+
+ }
+
+ /**
+ * Get converter from given encoding name. If no converted defined,
+ * a null is returned.
+ *
+ * @param encoding search for a converter for the given encoding name
+ * @return the converter for the given encoding name
+ */
+ public static final EncodingConverter getConverter(String encoding)
+ {
+ return (EncodingConverter)(encodingMap.get(encoding));
+ }
+
+
+}
--- /dev/null
+/*
+ * 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.encoding.conversion;
+
+import org.apache.fontbox.cmap.CMap;
+
+/**
+ * EncodingConverter converts string or characters in one encoding, which is specified in PDF
+ * file, to another string with respective java charset. The mapping from
+ * PDF encoding name to java charset name is maintained by EncodingConversionManager
+
+ * @author Pin Xue (http://www.pinxue.net), Holly Lee (holly.lee (at) gmail.com)
+ * @version $Revision: 1.0 $
+ */
+public interface EncodingConverter
+{
+ /**
+ * Convert a string.
+ *
+ * @param s the string to be converted
+ * @return the converted string
+ */
+ public String convertString(String s);
+
+ /**
+ * Convert bytes to a string.
+ *
+ * @param c the byte array to be converted
+ * @param offset the starting offset of the array
+ * @param length the number of bytes
+ * @param cmap the cmap to be used for conversion
+ * @return the converted string
+ */
+ public String convertBytes(byte [] c, int offset, int length, CMap cmap);
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains the implementations for conversions of encodings and CMaps that are used in PDF documents.
+</body>
+</html>
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains the implementations for all of the encodings that are used in PDF documents.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * This class is an implementation of the alleged RC4 algorithm.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class ARCFour
+{
+ private int[] salt;
+ private int b;
+ private int c;
+
+ /**
+ * Constructor.
+ *
+ */
+ public ARCFour()
+ {
+ salt = new int[256];
+ }
+
+ /**
+ * This will reset the key to be used.
+ *
+ * @param key The RC4 key used during encryption.
+ */
+ public void setKey( byte[] key )
+ {
+ b = 0;
+ c = 0;
+
+ if(key.length < 1 || key.length > 32)
+ {
+ throw new IllegalArgumentException("number of bytes must be between 1 and 32");
+ }
+ for(int i = 0; i < salt.length; i++)
+ {
+ salt[i] = i;
+ }
+
+ int keyIndex = 0;
+ int saltIndex = 0;
+ for( int i = 0; i < salt.length; i++)
+ {
+ saltIndex = (fixByte(key[keyIndex]) + salt[i] + saltIndex) % 256;
+ swap( salt, i, saltIndex );
+ keyIndex = (keyIndex + 1) % key.length;
+ }
+
+ }
+
+ /**
+ * Thie will ensure that the value for a byte >=0.
+ *
+ * @param aByte The byte to test against.
+ *
+ * @return A value >=0 and < 256
+ */
+ private static final int fixByte( byte aByte )
+ {
+ return aByte < 0 ? 256 + aByte : aByte;
+ }
+
+ /**
+ * This will swap two values in an array.
+ *
+ * @param data The array to swap from.
+ * @param firstIndex The index of the first element to swap.
+ * @param secondIndex The index of the second element to swap.
+ */
+ private static final void swap( int[] data, int firstIndex, int secondIndex )
+ {
+ int tmp = data[ firstIndex ];
+ data[ firstIndex ] = data[ secondIndex ];
+ data[ secondIndex ] = tmp;
+ }
+
+ /**
+ * This will encrypt and write the next byte.
+ *
+ * @param aByte The byte to encrypt.
+ * @param output The stream to write to.
+ *
+ * @throws IOException If there is an error writing to the output stream.
+ */
+ public void write( byte aByte, OutputStream output ) throws IOException
+ {
+ b = (b + 1) % 256;
+ c = (salt[b] + c) % 256;
+ swap( salt, b, c );
+ int saltIndex = (salt[b] + salt[c]) % 256;
+ output.write(aByte ^ (byte)salt[saltIndex]);
+ }
+
+ /**
+ * This will encrypt and write the data.
+ *
+ * @param data The data to encrypt.
+ * @param output The stream to write to.
+ *
+ * @throws IOException If there is an error writing to the output stream.
+ */
+ public void write( byte[] data, OutputStream output ) throws IOException
+ {
+ for( int i = 0; i < data.length; i++ )
+ {
+ write( data[i], output );
+ }
+ }
+
+ /**
+ * This will encrypt and write the data.
+ *
+ * @param data The data to encrypt.
+ * @param output The stream to write to.
+ *
+ * @throws IOException If there is an error writing to the output stream.
+ */
+ public void write( InputStream data, OutputStream output ) throws IOException
+ {
+ byte[] buffer = new byte[1024];
+ int amountRead = 0;
+ while( (amountRead = data.read( buffer )) != -1 )
+ {
+ write( buffer, 0, amountRead, output );
+ }
+ }
+
+ /**
+ * This will encrypt and write the data.
+ *
+ * @param data The data to encrypt.
+ * @param offset The offset into the array to start reading data from.
+ * @param len The number of bytes to attempt to read.
+ * @param output The stream to write to.
+ *
+ * @throws IOException If there is an error writing to the output stream.
+ */
+ public void write( byte[] data, int offset, int len, OutputStream output) throws IOException
+ {
+ for( int i = offset; i < offset + len; i++ )
+ {
+ write( data[i], output );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.PDStandardEncryption;
+
+/**
+ * This class will deal with encrypting/decrypting a document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.13 $
+ *
+ * @deprecated use the new security API instead.
+ *
+ * @see org.apache.pdfbox.pdmodel.encryption.StandardSecurityHandler
+ */
+public class DocumentEncryption
+{
+ private PDDocument pdDocument = null;
+ private COSDocument document = null;
+
+ private byte[] encryptionKey = null;
+ private PDFEncryption encryption = new PDFEncryption();
+
+ private Set objects = new HashSet();
+
+ /**
+ * A set that contains potential signature dictionaries. This is used
+ * because the Contents entry of the signature is not encrypted.
+ */
+ private Set potentialSignatures = new HashSet();
+
+ /**
+ * Constructor.
+ *
+ * @param doc The document to decrypt.
+ */
+ public DocumentEncryption( PDDocument doc )
+ {
+ pdDocument = doc;
+ document = doc.getDocument();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param doc The document to decrypt.
+ */
+ public DocumentEncryption( COSDocument doc )
+ {
+ pdDocument = new PDDocument( doc );
+ document = doc;
+ }
+
+ /**
+ * This will encrypt the given document, given the owner password and user password.
+ * The encryption method used is the standard filter.
+ *
+ * @throws CryptographyException If an error occurs during encryption.
+ * @throws IOException If there is an error accessing the data.
+ */
+ public void initForEncryption()
+ throws CryptographyException, IOException
+ {
+ String ownerPassword = pdDocument.getOwnerPasswordForEncryption();
+ String userPassword = pdDocument.getUserPasswordForEncryption();
+ if( ownerPassword == null )
+ {
+ ownerPassword = "";
+ }
+ if( userPassword == null )
+ {
+ userPassword = "";
+ }
+ PDStandardEncryption encParameters = (PDStandardEncryption)pdDocument.getEncryptionDictionary();
+ int permissionInt = encParameters.getPermissions();
+ int revision = encParameters.getRevision();
+ int length = encParameters.getLength()/8;
+ COSArray idArray = document.getDocumentID();
+
+ //check if the document has an id yet. If it does not then
+ //generate one
+ if( idArray == null || idArray.size() < 2 )
+ {
+ idArray = new COSArray();
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance( "MD5" );
+ BigInteger time = BigInteger.valueOf( System.currentTimeMillis() );
+ md.update( time.toByteArray() );
+ md.update( ownerPassword.getBytes("ISO-8859-1") );
+ md.update( userPassword.getBytes("ISO-8859-1") );
+ md.update( document.toString().getBytes() );
+ byte[] id = md.digest( this.toString().getBytes("ISO-8859-1") );
+ COSString idString = new COSString();
+ idString.append( id );
+ idArray.add( idString );
+ idArray.add( idString );
+ document.setDocumentID( idArray );
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e );
+ }
+
+ }
+ COSString id = (COSString)idArray.getObject( 0 );
+ encryption = new PDFEncryption();
+
+ byte[] o = encryption.computeOwnerPassword(
+ ownerPassword.getBytes("ISO-8859-1"),
+ userPassword.getBytes("ISO-8859-1"), revision, length);
+
+ byte[] u = encryption.computeUserPassword(
+ userPassword.getBytes("ISO-8859-1"),
+ o, permissionInt, id.getBytes(), revision, length);
+
+ encryptionKey = encryption.computeEncryptedKey(
+ userPassword.getBytes("ISO-8859-1"), o, permissionInt, id.getBytes(), revision, length);
+
+ encParameters.setOwnerKey( o );
+ encParameters.setUserKey( u );
+
+ document.setEncryptionDictionary( encParameters.getCOSDictionary() );
+ }
+
+
+
+ /**
+ * This will decrypt the document.
+ *
+ * @param password The password for the document.
+ *
+ * @throws CryptographyException If there is an error decrypting the document.
+ * @throws IOException If there is an error getting the stream data.
+ * @throws InvalidPasswordException If the password is not a user or owner password.
+ */
+ public void decryptDocument( String password )
+ throws CryptographyException, IOException, InvalidPasswordException
+ {
+ if( password == null )
+ {
+ password = "";
+ }
+
+ PDStandardEncryption encParameters = (PDStandardEncryption)pdDocument.getEncryptionDictionary();
+
+
+ int permissions = encParameters.getPermissions();
+ int revision = encParameters.getRevision();
+ int length = encParameters.getLength()/8;
+
+ COSString id = (COSString)document.getDocumentID().getObject( 0 );
+ byte[] u = encParameters.getUserKey();
+ byte[] o = encParameters.getOwnerKey();
+
+ boolean isUserPassword =
+ encryption.isUserPassword( password.getBytes("ISO-8859-1"), u,
+ o, permissions, id.getBytes(), revision, length );
+ boolean isOwnerPassword =
+ encryption.isOwnerPassword( password.getBytes("ISO-8859-1"), u,
+ o, permissions, id.getBytes(), revision, length );
+
+ if( isUserPassword )
+ {
+ encryptionKey =
+ encryption.computeEncryptedKey(
+ password.getBytes("ISO-8859-1"), o,
+ permissions, id.getBytes(), revision, length );
+ }
+ else if( isOwnerPassword )
+ {
+ byte[] computedUserPassword =
+ encryption.getUserPassword(
+ password.getBytes("ISO-8859-1"),
+ o,
+ revision,
+ length );
+ encryptionKey =
+ encryption.computeEncryptedKey(
+ computedUserPassword, o,
+ permissions, id.getBytes(), revision, length );
+ }
+ else
+ {
+ throw new InvalidPasswordException( "Error: The supplied password does not match " +
+ "either the owner or user password in the document." );
+ }
+
+ COSDictionary trailer = document.getTrailer();
+ COSArray fields = (COSArray)trailer.getObjectFromPath( "Root/AcroForm/Fields" );
+
+ //We need to collect all the signature dictionaries, for some
+ //reason the 'Contents' entry of signatures is not really encrypted
+ if( fields != null )
+ {
+ for( int i=0; i<fields.size(); i++ )
+ {
+ COSDictionary field = (COSDictionary)fields.getObject( i );
+ addDictionaryAndSubDictionary( potentialSignatures, field );
+ }
+ }
+
+ List allObjects = document.getObjects();
+ Iterator objectIter = allObjects.iterator();
+ while( objectIter.hasNext() )
+ {
+ decryptObject( (COSObject)objectIter.next() );
+ }
+ document.setEncryptionDictionary( null );
+ }
+
+ private void addDictionaryAndSubDictionary( Set set, COSDictionary dic )
+ {
+ set.add( dic );
+ COSArray kids = (COSArray)dic.getDictionaryObject( COSName.KIDS );
+ for( int i=0; kids != null && i<kids.size(); i++ )
+ {
+ addDictionaryAndSubDictionary( set, (COSDictionary)kids.getObject( i ) );
+ }
+ COSBase value = dic.getDictionaryObject( COSName.V );
+ if( value instanceof COSDictionary )
+ {
+ addDictionaryAndSubDictionary( set, (COSDictionary)value );
+ }
+ }
+
+ /**
+ * This will decrypt an object in the document.
+ *
+ * @param object The object to decrypt.
+ *
+ * @throws CryptographyException If there is an error decrypting the stream.
+ * @throws IOException If there is an error getting the stream data.
+ */
+ private void decryptObject( COSObject object )
+ throws CryptographyException, IOException
+ {
+ long objNum = object.getObjectNumber().intValue();
+ long genNum = object.getGenerationNumber().intValue();
+ COSBase base = object.getObject();
+ decrypt( base, objNum, genNum );
+ }
+
+ /**
+ * This will dispatch to the correct method.
+ *
+ * @param obj The object to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation Number.
+ *
+ * @throws CryptographyException If there is an error decrypting the stream.
+ * @throws IOException If there is an error getting the stream data.
+ */
+ public void decrypt( Object obj, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ if( !objects.contains( obj ) )
+ {
+ objects.add( obj );
+
+ if( obj instanceof COSString )
+ {
+ decryptString( (COSString)obj, objNum, genNum );
+ }
+ else if( obj instanceof COSStream )
+ {
+ decryptStream( (COSStream)obj, objNum, genNum );
+ }
+ else if( obj instanceof COSDictionary )
+ {
+ decryptDictionary( (COSDictionary)obj, objNum, genNum );
+ }
+ else if( obj instanceof COSArray )
+ {
+ decryptArray( (COSArray)obj, objNum, genNum );
+ }
+ }
+ }
+
+ /**
+ * This will decrypt a stream.
+ *
+ * @param stream The stream to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation number.
+ *
+ * @throws CryptographyException If there is an error getting the stream.
+ * @throws IOException If there is an error getting the stream data.
+ */
+ private void decryptStream( COSStream stream, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ decryptDictionary( stream, objNum, genNum );
+ InputStream encryptedStream = stream.getFilteredStream();
+ encryption.encryptData( objNum,
+ genNum,
+ encryptionKey,
+ encryptedStream,
+ stream.createFilteredStream() );
+ }
+
+ /**
+ * This will decrypt a dictionary.
+ *
+ * @param dictionary The dictionary to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation number.
+ *
+ * @throws CryptographyException If there is an error decrypting the document.
+ * @throws IOException If there is an error creating a new string.
+ */
+ private void decryptDictionary( COSDictionary dictionary, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ for( Map.Entry<COSName, COSBase> entry : dictionary.entrySet() )
+ {
+ //if we are a signature dictionary and contain a Contents entry then
+ //we don't decrypt it.
+ if( !(entry.getKey().getName().equals( "Contents" ) &&
+ entry.getValue() instanceof COSString &&
+ potentialSignatures.contains( dictionary )))
+ {
+ decrypt( entry.getValue(), objNum, genNum );
+ }
+ }
+ }
+
+ /**
+ * This will decrypt a string.
+ *
+ * @param string the string to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation number.
+ *
+ * @throws CryptographyException If an error occurs during decryption.
+ * @throws IOException If an error occurs writing the new string.
+ */
+ private void decryptString( COSString string, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ ByteArrayInputStream data = new ByteArrayInputStream( string.getBytes() );
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ encryption.encryptData( objNum,
+ genNum,
+ encryptionKey,
+ data,
+ buffer );
+ string.reset();
+ string.append( buffer.toByteArray() );
+ }
+
+ /**
+ * This will decrypt an array.
+ *
+ * @param array The array to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation number.
+ *
+ * @throws CryptographyException If an error occurs during decryption.
+ * @throws IOException If there is an error accessing the data.
+ */
+ private void decryptArray( COSArray array, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ for( int i=0; i<array.size(); i++ )
+ {
+ decrypt( array.get( i ), objNum, genNum );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.pdfbox.exceptions.CryptographyException;
+
+/**
+ * This class will deal with PDF encryption algorithms.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.15 $
+ *
+ * @deprecated use the new security layer instead
+ *
+ * @see org.apache.pdfbox.pdmodel.encryption.StandardSecurityHandler
+ */
+public final class PDFEncryption
+{
+ private ARCFour rc4 = new ARCFour();
+ /**
+ * The encryption padding defined in the PDF 1.4 Spec algorithm 3.2.
+ */
+ public static final byte[] ENCRYPT_PADDING =
+ {
+ (byte)0x28, (byte)0xBF, (byte)0x4E, (byte)0x5E, (byte)0x4E,
+ (byte)0x75, (byte)0x8A, (byte)0x41, (byte)0x64, (byte)0x00,
+ (byte)0x4E, (byte)0x56, (byte)0xFF, (byte)0xFA, (byte)0x01,
+ (byte)0x08, (byte)0x2E, (byte)0x2E, (byte)0x00, (byte)0xB6,
+ (byte)0xD0, (byte)0x68, (byte)0x3E, (byte)0x80, (byte)0x2F,
+ (byte)0x0C, (byte)0xA9, (byte)0xFE, (byte)0x64, (byte)0x53,
+ (byte)0x69, (byte)0x7A
+ };
+
+ /**
+ * This will encrypt a piece of data.
+ *
+ * @param objectNumber The id for the object.
+ * @param genNumber The generation id for the object.
+ * @param key The key used to encrypt the data.
+ * @param data The data to encrypt/decrypt.
+ * @param output The stream to write to.
+ *
+ * @throws CryptographyException If there is an error encrypting the data.
+ * @throws IOException If there is an io error.
+ */
+ public final void encryptData(
+ long objectNumber,
+ long genNumber,
+ byte[] key,
+ InputStream data,
+ OutputStream output )
+ throws CryptographyException, IOException
+ {
+ byte[] newKey = new byte[ key.length + 5 ];
+ System.arraycopy( key, 0, newKey, 0, key.length );
+ //PDF 1.4 reference pg 73
+ //step 1
+ //we have the reference
+
+ //step 2
+ newKey[newKey.length -5] = (byte)(objectNumber & 0xff);
+ newKey[newKey.length -4] = (byte)((objectNumber >> 8) & 0xff);
+ newKey[newKey.length -3] = (byte)((objectNumber >> 16) & 0xff);
+ newKey[newKey.length -2] = (byte)(genNumber & 0xff);
+ newKey[newKey.length -1] = (byte)((genNumber >> 8) & 0xff);
+
+
+ //step 3
+ byte[] digestedKey = null;
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance( "MD5" );
+ digestedKey = md.digest( newKey );
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e );
+ }
+
+ //step 4
+ int length = Math.min( newKey.length, 16 );
+ byte[] finalKey = new byte[ length ];
+ System.arraycopy( digestedKey, 0, finalKey, 0, length );
+
+ rc4.setKey( finalKey );
+ rc4.write( data, output );
+ output.flush();
+ }
+
+ /**
+ * This will get the user password from the owner password and the documents o value.
+ *
+ * @param ownerPassword The plaintext owner password.
+ * @param o The document's o entry.
+ * @param revision The document revision number.
+ * @param length The length of the encryption.
+ *
+ * @return The plaintext padded user password.
+ *
+ * @throws CryptographyException If there is an error getting the user password.
+ * @throws IOException If there is an error reading data.
+ */
+ public final byte[] getUserPassword(
+ byte[] ownerPassword,
+ byte[] o,
+ int revision,
+ long length )
+ throws CryptographyException, IOException
+ {
+ try
+ {
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+ //3.3 STEP 1
+ byte[] ownerPadded = truncateOrPad( ownerPassword );
+
+ //3.3 STEP 2
+ MessageDigest md = MessageDigest.getInstance( "MD5" );
+ md.update( ownerPadded );
+ byte[] digest = md.digest();
+
+ //3.3 STEP 3
+ if( revision == 3 || revision == 4 )
+ {
+ for( int i=0; i<50; i++ )
+ {
+ md.reset();
+ md.update( digest );
+ digest = md.digest();
+ }
+ }
+ if( revision == 2 && length != 5 )
+ {
+ throw new CryptographyException(
+ "Error: Expected length=5 actual=" + length );
+ }
+
+ //3.3 STEP 4
+ byte[] rc4Key = new byte[ (int)length ];
+ System.arraycopy( digest, 0, rc4Key, 0, (int)length );
+
+ //3.7 step 2
+ if( revision == 2 )
+ {
+ rc4.setKey( rc4Key );
+ rc4.write( o, result );
+ }
+ else if( revision == 3 || revision == 4)
+ {
+ /**
+ byte[] iterationKey = new byte[ rc4Key.length ];
+ byte[] dataToEncrypt = o;
+ for( int i=19; i>=0; i-- )
+ {
+ System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+ for( int j=0; j< iterationKey.length; j++ )
+ {
+ iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+ }
+ rc4.setKey( iterationKey );
+ rc4.write( dataToEncrypt, result );
+ dataToEncrypt = result.toByteArray();
+ result.reset();
+ }
+ result.write( dataToEncrypt, 0, dataToEncrypt.length );
+ */
+ byte[] iterationKey = new byte[ rc4Key.length ];
+
+
+ byte[] otemp = new byte[ o.length ]; //sm
+ System.arraycopy( o, 0, otemp, 0, o.length ); //sm
+ rc4.write( o, result);//sm
+
+ for( int i=19; i>=0; i-- )
+ {
+ System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+ for( int j=0; j< iterationKey.length; j++ )
+ {
+ iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+ }
+ rc4.setKey( iterationKey );
+ result.reset(); //sm
+ rc4.write( otemp, result ); //sm
+ otemp = result.toByteArray(); //sm
+ }
+ }
+
+
+ return result.toByteArray();
+
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e );
+ }
+ }
+
+ /**
+ * This will tell if this is the owner password or not.
+ *
+ * @param ownerPassword The plaintext owner password.
+ * @param u The U value from the PDF Document.
+ * @param o The owner password hash.
+ * @param permissions The document permissions.
+ * @param id The document id.
+ * @param revision The revision of the encryption.
+ * @param length The length of the encryption key.
+ *
+ * @return true if the owner password matches the one from the document.
+ *
+ * @throws CryptographyException If there is an error while executing crypt functions.
+ * @throws IOException If there is an error while checking owner password.
+ */
+ public final boolean isOwnerPassword(
+ byte[] ownerPassword,
+ byte[] u,
+ byte[] o,
+ int permissions,
+ byte[] id,
+ int revision,
+ int length)
+ throws CryptographyException, IOException
+ {
+ byte[] userPassword = getUserPassword( ownerPassword, o, revision, length );
+ return isUserPassword( userPassword, u, o, permissions, id, revision, length );
+ }
+
+ /**
+ * This will tell if this is a valid user password.
+ *
+ * Algorithm 3.6 pg 80
+ *
+ * @param password The password to test.
+ * @param u The U value from the PDF Document.
+ * @param o The owner password hash.
+ * @param permissions The document permissions.
+ * @param id The document id.
+ * @param revision The revision of the encryption.
+ * @param length The length of the encryption key.
+ *
+ * @return true If this is the correct user password.
+ *
+ * @throws CryptographyException If there is an error computing the value.
+ * @throws IOException If there is an IO error while computing the owners password.
+ */
+ public final boolean isUserPassword(
+ byte[] password,
+ byte[] u,
+ byte[] o,
+ int permissions,
+ byte[] id,
+ int revision,
+ int length)
+ throws CryptographyException, IOException
+ {
+ boolean matches = false;
+ //STEP 1
+ byte[] computedValue = computeUserPassword( password, o, permissions, id, revision, length );
+ if( revision == 2 )
+ {
+ //STEP 2
+ matches = arraysEqual( u, computedValue );
+ }
+ else if( revision == 3 || revision == 4 )
+ {
+ //STEP 2
+ matches = arraysEqual( u, computedValue, 16 );
+ }
+ return matches;
+ }
+
+ /**
+ * This will compare two byte[] for equality for count number of bytes.
+ *
+ * @param first The first byte array.
+ * @param second The second byte array.
+ * @param count The number of bytes to compare.
+ *
+ * @return true If the arrays contain the exact same data.
+ */
+ private final boolean arraysEqual( byte[] first, byte[] second, int count )
+ {
+ boolean equal = first.length >= count && second.length >= count;
+ for( int i=0; i<count && equal; i++ )
+ {
+ equal = first[i] == second[i];
+ }
+ return equal;
+ }
+
+ /**
+ * This will compare two byte[] for equality.
+ *
+ * @param first The first byte array.
+ * @param second The second byte array.
+ *
+ * @return true If the arrays contain the exact same data.
+ */
+ private final boolean arraysEqual( byte[] first, byte[] second )
+ {
+ boolean equal = first.length == second.length;
+ for( int i=0; i<first.length && equal; i++ )
+ {
+ equal = first[i] == second[i];
+ }
+ return equal;
+ }
+
+ /**
+ * This will compute the user password hash.
+ *
+ * @param password The plain text password.
+ * @param o The owner password hash.
+ * @param permissions The document permissions.
+ * @param id The document id.
+ * @param revision The revision of the encryption.
+ * @param length The length of the encryption key.
+ *
+ * @return The user password.
+ *
+ * @throws CryptographyException If there is an error computing the user password.
+ * @throws IOException If there is an IO error.
+ */
+ public final byte[] computeUserPassword(
+ byte[] password,
+ byte[] o,
+ int permissions,
+ byte[] id,
+ int revision,
+ int length )
+ throws CryptographyException, IOException
+ {
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+ //STEP 1
+ byte[] encryptionKey = computeEncryptedKey( password, o, permissions, id, revision, length );
+
+ if( revision == 2 )
+ {
+ //STEP 2
+ rc4.setKey( encryptionKey );
+ rc4.write( ENCRYPT_PADDING, result );
+ }
+ else if( revision == 3 || revision == 4 )
+ {
+ try
+ {
+ //STEP 2
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ //md.update( truncateOrPad( password ) );
+ md.update( ENCRYPT_PADDING );
+
+ //STEP 3
+ md.update( id );
+ result.write( md.digest() );
+
+ //STEP 4 and 5
+ byte[] iterationKey = new byte[ encryptionKey.length ];
+ for( int i=0; i<20; i++ )
+ {
+ System.arraycopy( encryptionKey, 0, iterationKey, 0, iterationKey.length );
+ for( int j=0; j< iterationKey.length; j++ )
+ {
+ iterationKey[j] = (byte)(iterationKey[j] ^ i);
+ }
+ rc4.setKey( iterationKey );
+ ByteArrayInputStream input = new ByteArrayInputStream( result.toByteArray() );
+ result.reset();
+ rc4.write( input, result );
+ }
+
+ //step 6
+ byte[] finalResult = new byte[32];
+ System.arraycopy( result.toByteArray(), 0, finalResult, 0, 16 );
+ System.arraycopy( ENCRYPT_PADDING, 0, finalResult, 16, 16 );
+ result.reset();
+ result.write( finalResult );
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e );
+ }
+ }
+ return result.toByteArray();
+ }
+
+ /**
+ * This will compute the encrypted key.
+ *
+ * @param password The password used to compute the encrypted key.
+ * @param o The owner password hash.
+ * @param permissions The permissions for the document.
+ * @param id The document id.
+ * @param revision The security revision.
+ * @param length The length of the encryption key.
+ *
+ * @return The encryption key.
+ *
+ * @throws CryptographyException If there is an error computing the key.
+ */
+ public final byte[] computeEncryptedKey(
+ byte[] password,
+ byte[] o,
+ int permissions,
+ byte[] id,
+ int revision,
+ int length )
+ throws CryptographyException
+ {
+ byte[] result = new byte[ length ];
+ try
+ {
+ //PDFReference 1.4 pg 78
+ //step1
+ byte[] padded = truncateOrPad( password );
+
+ //step 2
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ md.update( padded );
+
+ //step 3
+ md.update( o );
+
+ //step 4
+ byte zero = (byte)(permissions >>> 0);
+ byte one = (byte)(permissions >>> 8);
+ byte two = (byte)(permissions >>> 16);
+ byte three = (byte)(permissions >>> 24);
+
+ md.update( zero );
+ md.update( one );
+ md.update( two );
+ md.update( three );
+
+ //step 5
+ md.update( id );
+ byte[] digest = md.digest();
+
+ //step 6
+ if( revision == 3 || revision == 4)
+ {
+ for( int i=0; i<50; i++ )
+ {
+ md.reset();
+ md.update( digest, 0, length );
+ digest = md.digest();
+ }
+ }
+
+ //step 7
+ if( revision == 2 && length != 5 )
+ {
+ throw new CryptographyException(
+ "Error: length should be 5 when revision is two actual=" + length );
+ }
+ System.arraycopy( digest, 0, result, 0, length );
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e );
+ }
+ return result;
+ }
+
+ /**
+ * This algorithm is taked from PDF Reference 1.4 Algorithm 3.3 Page 79.
+ *
+ * @param ownerPassword The plain owner password.
+ * @param userPassword The plain user password.
+ * @param revision The version of the security.
+ * @param length The length of the document.
+ *
+ * @return The computed owner password.
+ *
+ * @throws CryptographyException If there is an error computing O.
+ * @throws IOException If there is an error computing O.
+ */
+ public final byte[] computeOwnerPassword(
+ byte[] ownerPassword,
+ byte[] userPassword,
+ int revision,
+ int length )
+ throws CryptographyException, IOException
+ {
+ try
+ {
+ //STEP 1
+ byte[] ownerPadded = truncateOrPad( ownerPassword );
+
+ //STEP 2
+ MessageDigest md = MessageDigest.getInstance( "MD5" );
+ md.update( ownerPadded );
+ byte[] digest = md.digest();
+
+ //STEP 3
+ if( revision == 3 || revision == 4)
+ {
+ for( int i=0; i<50; i++ )
+ {
+ md.reset();
+ md.update( digest, 0, length );
+ digest = md.digest();
+ }
+ }
+ if( revision == 2 && length != 5 )
+ {
+ throw new CryptographyException(
+ "Error: Expected length=5 actual=" + length );
+ }
+
+ //STEP 4
+ byte[] rc4Key = new byte[ length ];
+ System.arraycopy( digest, 0, rc4Key, 0, length );
+
+ //STEP 5
+ byte[] paddedUser = truncateOrPad( userPassword );
+
+
+ //STEP 6
+ rc4.setKey( rc4Key );
+ ByteArrayOutputStream crypted = new ByteArrayOutputStream();
+ rc4.write( new ByteArrayInputStream( paddedUser ), crypted );
+
+
+ //STEP 7
+ if( revision == 3 || revision == 4 )
+ {
+ byte[] iterationKey = new byte[ rc4Key.length ];
+ for( int i=1; i<20; i++ )
+ {
+ System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+ for( int j=0; j< iterationKey.length; j++ )
+ {
+ iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+ }
+ rc4.setKey( iterationKey );
+ ByteArrayInputStream input = new ByteArrayInputStream( crypted.toByteArray() );
+ crypted.reset();
+ rc4.write( input, crypted );
+ }
+ }
+
+ //STEP 8
+ return crypted.toByteArray();
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e.getMessage() );
+ }
+ }
+
+ /**
+ * This will take the password and truncate or pad it as necessary.
+ *
+ * @param password The password to pad or truncate.
+ *
+ * @return The padded or truncated password.
+ */
+ private final byte[] truncateOrPad( byte[] password )
+ {
+ byte[] padded = new byte[ ENCRYPT_PADDING.length ];
+ int bytesBeforePad = Math.min( password.length, padded.length );
+ System.arraycopy( password, 0, padded, 0, bytesBeforePad );
+ System.arraycopy( ENCRYPT_PADDING, 0, padded, bytesBeforePad, ENCRYPT_PADDING.length-bytesBeforePad );
+ return padded;
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These classes deal with encryption algorithms that are used in the PDF Document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.examples;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.cos.COSDocument;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+
+/**
+ * A simple class which has some methods used by all examples.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public abstract class AbstractExample
+{
+ /**
+ * Close the stream.
+ *
+ * @param stream The stream to close.
+ *
+ * @throws IOException If there is an error closing the stream.
+ */
+ public void close( InputStream stream ) throws IOException
+ {
+ if( stream != null )
+ {
+ stream.close();
+ }
+ }
+
+ /**
+ * Close the stream.
+ *
+ * @param stream The stream to close.
+ *
+ * @throws IOException If there is an error closing the stream.
+ */
+ public void close( OutputStream stream ) throws IOException
+ {
+ if( stream != null )
+ {
+ stream.close();
+ }
+ }
+
+ /**
+ * Close the document.
+ *
+ * @param doc The doc to close.
+ *
+ * @throws IOException If there is an error closing the document.
+ */
+ public void close( COSDocument doc ) throws IOException
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+
+ /**
+ * Close the document.
+ *
+ * @param doc The doc to close.
+ *
+ * @throws IOException If there is an error closing the document.
+ */
+ public void close( PDDocument doc ) throws IOException
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+
+ /**
+ * Close the writer.
+ *
+ * @param writer The writer to close.
+ *
+ * @throws IOException If there is an error closing the writer.
+ */
+ public static void close( COSWriter writer ) throws IOException
+ {
+ if( writer != null )
+ {
+ writer.close();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.examples.fdf;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDField;
+
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+
+/**
+ * This example will take a PDF document and print all the fields from the file.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.16 $
+ */
+public class PrintFields
+{
+
+ /**
+ * This will print all the fields from the document.
+ *
+ * @param pdfDocument The PDF to get the fields from.
+ *
+ * @throws IOException If there is an error getting the fields.
+ */
+ public void printFields( PDDocument pdfDocument ) throws IOException
+ {
+ PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
+ PDAcroForm acroForm = docCatalog.getAcroForm();
+ List fields = acroForm.getFields();
+ Iterator fieldsIter = fields.iterator();
+
+ System.out.println(new Integer(fields.size()).toString() + " top-level fields were found on the form");
+
+ while( fieldsIter.hasNext())
+ {
+ PDField field = (PDField)fieldsIter.next();
+ processField(field, "|--", field.getPartialName());
+ }
+ }
+
+ private void processField(PDField field, String sLevel, String sParent) throws IOException
+ {
+ List kids = field.getKids();
+ if(kids != null)
+ {
+ Iterator kidsIter = kids.iterator();
+ if(!sParent.equals(field.getPartialName()))
+ {
+ sParent = sParent + "." + field.getPartialName();
+ }
+ System.out.println(sLevel + sParent);
+ //System.out.println(sParent + " is of type " + field.getClass().getName());
+ while(kidsIter.hasNext())
+ {
+ Object pdfObj = kidsIter.next();
+ if(pdfObj instanceof PDField)
+ {
+ PDField kid = (PDField)pdfObj;
+ processField(kid, "| " + sLevel, sParent);
+ }
+ }
+ }
+ else
+ {
+ String outputString = sLevel + sParent + "." + field.getPartialName() + " = " + field.getValue() +
+ ", type=" + field.getClass().getName();
+
+ System.out.println(outputString);
+ }
+ }
+
+ /**
+ * This will read a PDF file and print out the form elements.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args command line arguments
+ *
+ * @throws IOException If there is an error importing the FDF document.
+ * @throws CryptographyException If there is an error decrypting the document.
+ */
+ public static void main(String[] args) throws IOException, CryptographyException
+ {
+ PDDocument pdf = null;
+ try
+ {
+ if( args.length != 1 )
+ {
+ usage();
+ }
+ else
+ {
+ pdf = PDDocument.load( args[0] );
+ PrintFields exporter = new PrintFields();
+ if( pdf.isEncrypted() )
+ {
+ try
+ {
+ pdf.decrypt( "" );
+ }
+ catch( InvalidPasswordException e )
+ {
+ System.err.println( "Error: The document is encrypted." );
+ usage();
+ }
+ }
+ exporter.printFields( pdf );
+ }
+ }
+ finally
+ {
+ if( pdf != null )
+ {
+ pdf.close();
+ }
+ }
+ }
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: org.apache.pdfbox.examples.fdf.PrintFields <pdf-file>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDField;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.examples.AbstractExample;
+
+/**
+ * This example will take a PDF document and set a FDF field in it.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class SetField extends AbstractExample
+{
+
+ /**
+ * This will set a single field in the document.
+ *
+ * @param pdfDocument The PDF to set the field in.
+ * @param name The name of the field to set.
+ * @param value The new value of the field.
+ *
+ * @throws IOException If there is an error setting the field.
+ */
+ public void setField( PDDocument pdfDocument, String name, String value ) throws IOException
+ {
+ PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog();
+ PDAcroForm acroForm = docCatalog.getAcroForm();
+ PDField field = acroForm.getField( name );
+ if( field != null )
+ {
+ field.setValue( value );
+ }
+ else
+ {
+ System.err.println( "No field found with name:" + name );
+ }
+
+ }
+
+ /**
+ * This will read a PDF file and set a field and then write it the pdf out again.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args command line arguments
+ *
+ * @throws IOException If there is an error importing the FDF document.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public static void main(String[] args) throws IOException, COSVisitorException
+ {
+ SetField setter = new SetField();
+ setter.setField( args );
+ }
+
+ private void setField( String[] args ) throws IOException, COSVisitorException
+ {
+ PDDocument pdf = null;
+ try
+ {
+ if( args.length != 3 )
+ {
+ usage();
+ }
+ else
+ {
+ SetField example = new SetField();
+
+ pdf = PDDocument.load( args[0] );
+ example.setField( pdf, args[1], args[2] );
+ pdf.save( args[0] );
+ }
+ }
+ finally
+ {
+ if( pdf != null )
+ {
+ pdf.close();
+ }
+ }
+ }
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: org.apache.pdfbox.examples.fdf.SetField <pdf-file> <field-name> <field-value>" );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These are examples that use the FDF features of a PDF document.
+</body>
+</html>
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The packages in this package will show how to use the PDFBox API.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.io.RandomAccessFile;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDCcitt;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+
+
+/**
+ * This is an example that creates a reads a document and adds an image to it..
+ *
+ * The example is taken from the pdf file format specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class AddImageToPDF
+{
+
+ /**
+ * Add an image to an existing PDF document.
+ *
+ * @param inputFile The input PDF to add the image to.
+ * @param image The filename of the image to put in the PDF.
+ * @param outputFile The file to write to the pdf to.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void createPDFFromImage( String inputFile, String image, String outputFile )
+ throws IOException, COSVisitorException
+ {
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = PDDocument.load( inputFile );
+
+ //we will add the image to the first page.
+ PDPage page = (PDPage)doc.getDocumentCatalog().getAllPages().get( 0 );
+
+ PDXObjectImage ximage = null;
+ if( image.toLowerCase().endsWith( ".jpg" ) )
+ {
+ ximage = new PDJpeg(doc, new FileInputStream( image ) );
+ }
+ else if (image.toLowerCase().endsWith(".tif") || image.toLowerCase().endsWith(".tiff"))
+ {
+ ximage = new PDCcitt(doc, new RandomAccessFile(new File(image),"r"));
+ }
+ else
+ {
+ //BufferedImage awtImage = ImageIO.read( new File( image ) );
+ //ximage = new PDPixelMap(doc, awtImage);
+ throw new IOException( "Image type not supported:" + image );
+ }
+ PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true);
+
+ contentStream.drawImage( ximage, 20, 20 );
+
+ contentStream.close();
+ doc.save( outputFile );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will load a PDF document and add a single image on it.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ AddImageToPDF app = new AddImageToPDF();
+ try
+ {
+ if( args.length != 3 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.createPDFFromImage( args[0], args[1], args[2] );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: " + this.getClass().getName() + " <input-pdf> <image> <output-pdf>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionJavaScript;
+
+import java.io.IOException;
+
+/**
+ * This is an example of how to set some javascript in the document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class AddJavascript
+{
+ private AddJavascript()
+ {
+ //static class, should not be instantiated.
+ }
+
+ /**
+ * This will print the documents data.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( args[0] );
+ PDActionJavaScript javascript = new PDActionJavaScript(
+ "app.alert( {cMsg: 'PDFBox rocks!', nIcon: 3, nType: 0, cTitle: 'PDFBox Javascript example' } );");
+ document.getDocumentCatalog().setOpenAction( javascript );
+ if( document.isEncrypted() )
+ {
+ throw new IOException( "Encrypted documents are not supported for this example" );
+ }
+ document.save( args[1] );
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.AddJavascript <input-pdf> <output-pdf>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+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;
+
+
+/**
+ * This is an example of how to add a message to every page
+ * in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class AddMessageToEachPage
+{
+ /**
+ * Constructor.
+ */
+ public AddMessageToEachPage()
+ {
+ super();
+ }
+
+ /**
+ * create the second sample document from the PDF file format specification.
+ *
+ * @param file The file to write the PDF to.
+ * @param message The message to write in the file.
+ * @param outfile The resulting PDF.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void doIt( String file, String message, String outfile ) throws IOException, COSVisitorException
+ {
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = PDDocument.load( file );
+
+ List allPages = doc.getDocumentCatalog().getAllPages();
+ PDFont font = PDType1Font.HELVETICA_BOLD;
+ float fontSize = 36.0f;
+
+ for( int i=0; i<allPages.size(); i++ )
+ {
+ PDPage page = (PDPage)allPages.get( i );
+ PDRectangle pageSize = page.findMediaBox();
+ float stringWidth = font.getStringWidth( message )*fontSize/1000f;
+ // calculate to center of the page
+ int rotation = page.findRotation();
+ boolean rotate = rotation == 90 || rotation == 270;
+ float pageWidth = rotate ? pageSize.getHeight() : pageSize.getWidth();
+ float pageHeight = rotate ? pageSize.getWidth() : pageSize.getHeight();
+ double centeredXPosition = rotate ? pageHeight/2f : (pageWidth - stringWidth)/2f;
+ double centeredYPosition = rotate ? (pageWidth - stringWidth)/2f : pageHeight/2f;
+ // append the content to the existing stream
+ PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true,true);
+ contentStream.beginText();
+ // set font and font size
+ contentStream.setFont( font, fontSize );
+ // set text color to red
+ contentStream.setNonStrokingColor(255, 0, 0);
+ if (rotate)
+ {
+ // rotate the text according to the page rotation
+ contentStream.setTextRotation(Math.PI/2, centeredXPosition, centeredYPosition);
+ }
+ else
+ {
+ contentStream.setTextTranslation(centeredXPosition, centeredYPosition);
+ }
+ contentStream.drawString( message );
+ contentStream.endText();
+ contentStream.close();
+ }
+
+ doc.save( outfile );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will create a hello world PDF document.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ AddMessageToEachPage app = new AddMessageToEachPage();
+ try
+ {
+ if( args.length != 3 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt( args[0], args[1], args[2] );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: " + this.getClass().getName() + " <input-file> <Message> <output-file>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.jempbox.xmp.XMPMetadata;
+import org.apache.jempbox.xmp.XMPSchemaBasic;
+import org.apache.jempbox.xmp.XMPSchemaDublinCore;
+import org.apache.jempbox.xmp.XMPSchemaPDF;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+
+import java.util.GregorianCalendar;
+
+/**
+ * This is an example on how to add metadata to a document.
+ *
+ * Usage: java org.apache.pdfbox.examples.pdmodel.AddMetadataToDocument <input-pdf> <output-pdf>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class AddMetadataFromDocInfo
+{
+ private AddMetadataFromDocInfo()
+ {
+ //utility class
+ }
+
+ /**
+ * This will print the documents data.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if( document.isEncrypted() )
+ {
+ System.err.println( "Error: Cannot add metadata to encrypted document." );
+ System.exit( 1 );
+ }
+ PDDocumentCatalog catalog = document.getDocumentCatalog();
+ PDDocumentInformation info = document.getDocumentInformation();
+
+ XMPMetadata metadata = new XMPMetadata();
+
+ XMPSchemaPDF pdfSchema = metadata.addPDFSchema();
+ pdfSchema.setKeywords( info.getKeywords() );
+ pdfSchema.setProducer( info.getProducer() );
+
+ XMPSchemaBasic basicSchema = metadata.addBasicSchema();
+ basicSchema.setModifyDate( info.getModificationDate() );
+ basicSchema.setCreateDate( info.getCreationDate() );
+ basicSchema.setCreatorTool( info.getCreator() );
+ basicSchema.setMetadataDate( new GregorianCalendar() );
+
+ XMPSchemaDublinCore dcSchema = metadata.addDublinCoreSchema();
+ dcSchema.setTitle( info.getTitle() );
+ dcSchema.addCreator( "PDFBox" );
+ dcSchema.setDescription( info.getSubject() );
+
+ PDMetadata metadataStream = new PDMetadata(document);
+ metadataStream.importXMPMetadata( metadata );
+ catalog.setMetadata( metadataStream );
+
+ document.save( args[1] );
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.AddMetadataFromDocInfo " +
+ "<input-pdf> <output-pdf>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+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.color.PDGamma;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionURI;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLine;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationSquareCircle;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
+
+
+import java.util.List;
+
+/**
+ * This is an example on how to add annotations to pages of a PDF document.
+ *
+ * @author Paul King
+ * @version $Revision: 1.2 $
+ */
+public class Annotation
+{
+ private Annotation()
+ {
+ //utility class, should not be instantiated.
+ }
+
+ /**
+ * This will create a doucument showing various annotations.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 1 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = new PDDocument();
+
+ try
+ {
+ PDPage page = new PDPage();
+ document.addPage(page);
+ List annotations = page.getAnnotations();
+
+ // Setup some basic reusable objects/constants
+ // Annotations themselves can only be used once!
+
+ float inch = 72;
+ PDGamma colourRed = new PDGamma();
+ colourRed.setR(1);
+ PDGamma colourBlue = new PDGamma();
+ colourBlue.setB(1);
+ PDGamma colourBlack = new PDGamma();
+
+ PDBorderStyleDictionary borderThick = new PDBorderStyleDictionary();
+ borderThick.setWidth(inch/12); // 12th inch
+ PDBorderStyleDictionary borderThin = new PDBorderStyleDictionary();
+ borderThin.setWidth(inch/72); // 1 point
+ PDBorderStyleDictionary borderULine = new PDBorderStyleDictionary();
+ borderULine.setStyle(PDBorderStyleDictionary.STYLE_UNDERLINE);
+ borderULine.setWidth(inch/72); // 1 point
+
+
+ float pw = page.getMediaBox().getUpperRightX();
+ float ph = page.getMediaBox().getUpperRightY();
+
+
+ // First add some text, two lines we'll add some annotations to this later
+
+
+ PDFont font = PDType1Font.HELVETICA_BOLD;
+
+ PDPageContentStream contentStream = new PDPageContentStream(document, page);
+ contentStream.beginText();
+ contentStream.setFont( font, 18 );
+ contentStream.moveTextPositionByAmount( inch, ph-inch-18);
+ contentStream.drawString( "PDFBox" );
+ contentStream.moveTextPositionByAmount( 0,-(inch/2));
+ contentStream.drawString( "Click Here" );
+ contentStream.endText();
+
+ contentStream.close();
+
+ // Now add the markup annotation, a highlight to PDFBox text
+ PDAnnotationTextMarkup txtMark = new PDAnnotationTextMarkup(PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT);
+ txtMark.setColour(colourBlue);
+ txtMark.setConstantOpacity((float)0.2); // Make the highlight 20% transparent
+
+ // Set the rectangle containing the markup
+
+ float textWidth = (font.getStringWidth( "PDFBox" )/1000) * 18;
+ PDRectangle position = new PDRectangle();
+ position.setLowerLeftX(inch);
+ position.setLowerLeftY( ph-inch-18 );
+ position.setUpperRightX(72 + textWidth);
+ position.setUpperRightY(ph-inch);
+ txtMark.setRectangle(position);
+
+ // work out the points forming the four corners of the annotations
+ // set out in anti clockwise form (Completely wraps the text)
+ // OK, the below doesn't match that description.
+ // It's what acrobat 7 does and displays properly!
+ float[] quads = new float[8];
+
+ quads[0] = position.getLowerLeftX(); // x1
+ quads[1] = position.getUpperRightY()-2; // y1
+ quads[2] = position.getUpperRightX(); // x2
+ quads[3] = quads[1]; // y2
+ quads[4] = quads[0]; // x3
+ quads[5] = position.getLowerLeftY()-2; // y3
+ quads[6] = quads[2]; // x4
+ quads[7] = quads[5]; // y5
+
+ txtMark.setQuadPoints(quads);
+ txtMark.setContents("Highlighted since it's important");
+
+ annotations.add(txtMark);
+
+ // Now add the link annotation, so the clickme works
+ PDAnnotationLink txtLink = new PDAnnotationLink();
+ txtLink.setBorderStyle(borderULine);
+
+ // Set the rectangle containing the link
+
+ textWidth = (font.getStringWidth( "Click Here" )/1000) * 18;
+ position = new PDRectangle();
+ position.setLowerLeftX(inch);
+ position.setLowerLeftY( ph-(float)(1.5*inch)-20); // down a couple of points
+ position.setUpperRightX(72 + textWidth);
+ position.setUpperRightY(ph-(float)(1.5*inch));
+ txtLink.setRectangle(position);
+
+ // add an action
+ PDActionURI action = new PDActionURI();
+ action.setURI("http://www.pdfbox.org");
+ txtLink.setAction(action);
+
+ annotations.add(txtLink);
+
+
+ // Now draw a few more annotations
+
+ PDAnnotationSquareCircle aCircle =
+ new PDAnnotationSquareCircle( PDAnnotationSquareCircle.SUB_TYPE_CIRCLE);
+ aCircle.setContents("Circle Annotation");
+ aCircle.setInteriorColour(colourRed); // Fill in circle in red
+ aCircle.setColour(colourBlue); // The border itself will be blue
+ aCircle.setBorderStyle(borderThin);
+
+ // Place the annotation on the page, we'll make this 1" round
+ // 3" down, 1" in on the page
+
+ position = new PDRectangle();
+ position.setLowerLeftX(inch);
+ position.setLowerLeftY(ph-(3*inch)-inch); // 1" height, 3" down
+ position.setUpperRightX(2*inch); // 1" in, 1" width
+ position.setUpperRightY(ph-(3*inch)); // 3" down
+ aCircle.setRectangle(position);
+
+ // add to the annotations on the page
+ annotations.add(aCircle);
+
+ // Now a square annotation
+
+ PDAnnotationSquareCircle aSquare =
+ new PDAnnotationSquareCircle( PDAnnotationSquareCircle.SUB_TYPE_SQUARE);
+ aSquare.setContents("Square Annotation");
+ aSquare.setColour(colourRed); // Outline in red, not setting a fill
+ aSquare.setBorderStyle(borderThick);
+
+ // Place the annotation on the page, we'll make this 1" (72points) square
+ // 3.5" down, 1" in from the right on the page
+
+ position = new PDRectangle(); // Reuse the variable, but note it's a new object!
+ position.setLowerLeftX(pw-(2*inch)); // 1" in from right, 1" wide
+ position.setLowerLeftY(ph-(float)(3.5*inch) - inch); // 1" height, 3.5" down
+ position.setUpperRightX(pw-inch); // 1" in from right
+ position.setUpperRightY(ph-(float)(3.5*inch)); // 3.5" down
+ aSquare.setRectangle(position);
+
+ // add to the annotations on the page
+ annotations.add(aSquare);
+
+ // Now we want to draw a line between the two, one end with an open arrow
+
+ PDAnnotationLine aLine = new PDAnnotationLine();
+
+ aLine.setEndPointEndingStyle( PDAnnotationLine.LE_OPEN_ARROW );
+ aLine.setContents("Circle->Square");
+ aLine.setCaption(true); // Make the contents a caption on the line
+
+ // Set the rectangle containing the line
+
+ position = new PDRectangle(); // Reuse the variable, but note it's a new object!
+ position.setLowerLeftX(2*inch); // 1" in + width of circle
+ position.setLowerLeftY(ph-(float)(3.5*inch)-inch); // 1" height, 3.5" down
+ position.setUpperRightX(pw-inch-inch); // 1" in from right, and width of square
+ position.setUpperRightY(ph-(3*inch)); // 3" down (top of circle)
+ aLine.setRectangle(position);
+
+ // Now set the line position itself
+ float[] linepos = new float[4];
+ linepos[0] = 2*inch; // x1 = rhs of circle
+ linepos[1] = ph-(float)(3.5*inch); // y1 halfway down circle
+ linepos[2] = pw-(2*inch); // x2 = lhs of square
+ linepos[3] = ph-(4*inch); // y2 halfway down square
+ aLine.setLine(linepos);
+
+ aLine.setBorderStyle(borderThick);
+ aLine.setColour(colourBlack);
+
+ // add to the annotations on the page
+ annotations.add(aLine);
+
+
+ // Finally all done
+
+
+ document.save( args[0] );
+ }
+ finally
+ {
+ document.close();
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.Annotation <output-pdf>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+/**
+ * This will create a blank PDF and write the contents to a file.
+ *
+ * usage: java org.apache.pdfbox.examples.pdmodel.CreateBlankPDF <outputfile.pdf>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class CreateBlankPDF
+{
+
+ /**
+ * This will create a blank PDF and write the contents to a file.
+ *
+ * @param file The name of the file to write to.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error while generating the document.
+ */
+ public void create( String file ) throws IOException, COSVisitorException
+ {
+ PDDocument document = null;
+ try
+ {
+ document = new PDDocument();
+ //Every document requires at least one page, so we will add one
+ //blank page.
+ PDPage blankPage = new PDPage();
+ document.addPage( blankPage );
+ document.save( file );
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+
+ /**
+ * This will create a blank document.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws IOException If there is an error writing the document data.
+ * @throws COSVisitorException If there is an error generating the data.
+ */
+ public static void main( String[] args ) throws IOException, COSVisitorException
+ {
+ if( args.length != 1 )
+ {
+ usage();
+ }
+ else
+ {
+ CreateBlankPDF creator = new CreateBlankPDF();
+ creator.create( args[0] );
+ }
+ }
+
+ /**
+ * This will print the usage of this class.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: java org.apache.pdfbox.examples.pdmodel.CreateBlankPDF <outputfile.pdf>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageFitWidthDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
+
+import java.util.List;
+
+/**
+ * This is an example on how to add bookmarks to a PDF document. It simply
+ * adds 1 bookmark for every page.
+ *
+ * Usage: java org.apache.pdfbox.examples.pdmodel.CreateBookmarks <input-pdf> <output-pdf>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class CreateBookmarks
+{
+ private CreateBookmarks()
+ {
+ //utility class
+ }
+
+ /**
+ * This will print the documents data.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if( document.isEncrypted() )
+ {
+ System.err.println( "Error: Cannot add bookmarks to encrypted document." );
+ System.exit( 1 );
+ }
+ PDDocumentOutline outline = new PDDocumentOutline();
+ document.getDocumentCatalog().setDocumentOutline( outline );
+ PDOutlineItem pagesOutline = new PDOutlineItem();
+ pagesOutline.setTitle( "All Pages" );
+ outline.appendChild( pagesOutline );
+ List pages = document.getDocumentCatalog().getAllPages();
+ for( int i=0; i<pages.size(); i++ )
+ {
+ PDPage page = (PDPage)pages.get( i );
+ PDPageFitWidthDestination dest = new PDPageFitWidthDestination();
+ dest.setPage( page );
+ PDOutlineItem bookmark = new PDOutlineItem();
+ bookmark.setDestination( dest );
+ bookmark.setTitle( "Page " + (i+1) );
+ pagesOutline.appendChild( bookmark );
+ }
+ pagesOutline.openNode();
+ outline.openNode();
+
+ document.save( args[1] );
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.CreateBookmarks <input-pdf> <output-pdf>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+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;
+
+
+/**
+ * This is an example of how to create a page with a landscape orientation.
+ * @version $Revision: 1.0 $
+ */
+public class CreateLandscapePDF
+{
+ /**
+ * Constructor.
+ */
+ public CreateLandscapePDF()
+ {
+ super();
+ }
+
+ /**
+ * creates a sample document with a landscape orientation and some text surrounded by a box.
+ *
+ * @param message The message to write in the file.
+ * @param outfile The resulting PDF.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void doIt( String message, String outfile ) throws IOException, COSVisitorException
+ {
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = new PDDocument();
+
+ PDFont font = PDType1Font.HELVETICA;
+ PDPage page = new PDPage();
+ page.setMediaBox(PDPage.PAGE_SIZE_A4);
+ page.setRotation(90);
+ doc.addPage(page);
+ PDRectangle pageSize = page.findMediaBox();
+ float pageWidth = pageSize.getWidth();
+ float fontSize = 12;
+ float stringWidth = font.getStringWidth( message )*fontSize/1000f;
+ float startX = 100;
+ float startY = 100;
+ PDPageContentStream contentStream = new PDPageContentStream(doc, page, false, false);
+ // add the rotation using the current transformation matrix
+ // including a translation of pageWidth to use the lower left corner as 0,0 reference
+ contentStream.concatenate2CTM(0, 1, -1, 0, pageWidth, 0);
+ contentStream.setFont( font, fontSize );
+ contentStream.beginText();
+ contentStream.moveTextPositionByAmount(startX, startY);
+ contentStream.drawString( message);
+ contentStream.moveTextPositionByAmount(0, 100);
+ contentStream.drawString( message);
+ contentStream.moveTextPositionByAmount(100, 100);
+ contentStream.drawString( message);
+ contentStream.endText();
+
+ contentStream.drawLine(startX-2, startY-2, startX-2, startY+200+fontSize);
+ contentStream.drawLine(startX-2, startY+200+fontSize, startX+100+stringWidth+2, startY+200+fontSize);
+ contentStream.drawLine(startX+100+stringWidth+2, startY+200+fontSize, startX+100+stringWidth+2, startY-2);
+ contentStream.drawLine(startX+100+stringWidth+2, startY-2, startX-2, startY-2);
+ contentStream.close();
+
+ doc.save( outfile );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will create a PDF document with a landscape orientation and some text surrounded by a box.
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ CreateLandscapePDF app = new CreateLandscapePDF();
+ try
+ {
+ if( args.length != 2 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt( args[0], args[1] );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: " + this.getClass().getName() + " <Message> <output-file>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
+import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+
+/**
+ * This is an example that creates a simple document and embeds a file into it..
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class EmbeddedFiles
+{
+ /**
+ * Constructor.
+ */
+ public EmbeddedFiles()
+ {
+ super();
+ }
+
+ /**
+ * create the second sample document from the PDF file format specification.
+ *
+ * @param file The file to write the PDF to.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void doIt( String file) throws IOException, COSVisitorException
+ {
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = new PDDocument();
+
+ PDPage page = new PDPage();
+ doc.addPage( page );
+ PDFont font = PDType1Font.HELVETICA_BOLD;
+
+ PDPageContentStream contentStream = new PDPageContentStream(doc, page);
+ contentStream.beginText();
+ contentStream.setFont( font, 12 );
+ contentStream.moveTextPositionByAmount( 100, 700 );
+ contentStream.drawString( "Go to Document->File Attachments to View Embedded Files" );
+ contentStream.endText();
+ contentStream.close();
+
+ //embedded files are stored in a named tree
+ PDEmbeddedFilesNameTreeNode efTree = new PDEmbeddedFilesNameTreeNode();
+
+
+ //first create the file specification, which holds the embedded file
+ PDComplexFileSpecification fs = new PDComplexFileSpecification();
+ fs.setFile( "Test.txt" );
+ //create a dummy file stream, this would probably normally be a FileInputStream
+ byte[] data = "This is the contents of the embedded file".getBytes("ISO-8859-1");
+ ByteArrayInputStream fakeFile =
+ new ByteArrayInputStream( data );
+ PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile );
+ //now lets some of the optional parameters
+ ef.setSubtype( "test/plain" );
+ ef.setSize( data.length );
+ ef.setCreationDate( new GregorianCalendar() );
+ fs.setEmbeddedFile( ef );
+
+ //now add the entry to the embedded file tree and set in the document.
+ efTree.setNames( Collections.singletonMap( "My first attachment", fs ) );
+ PDDocumentNameDictionary names = new PDDocumentNameDictionary( doc.getDocumentCatalog() );
+ names.setEmbeddedFiles( efTree );
+ doc.getDocumentCatalog().setNames( names );
+
+
+ doc.save( file );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will create a hello world PDF document with an embedded file.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ EmbeddedFiles app = new EmbeddedFiles();
+ try
+ {
+ if( args.length != 1 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt( args[0] );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: " + this.getClass().getName() + " <output-file>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.jempbox.xmp.XMPMetadata;
+import org.apache.jempbox.xmp.XMPSchemaBasic;
+import org.apache.jempbox.xmp.XMPSchemaDublinCore;
+import org.apache.jempbox.xmp.XMPSchemaPDF;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This is an example on how to extract metadata from a PDF document.
+ * <p>
+ * Usage: java org.apache.pdfbox.examples.pdmodel.ExtractDocument <input-pdf>
+ *
+ * @version $Revision$
+ */
+public class ExtractMetadata
+{
+ private ExtractMetadata()
+ {
+ //utility class
+ }
+
+ /**
+ * This is the main method.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 1 )
+ {
+ usage();
+ System.exit(1);
+ }
+ else
+ {
+ PDDocument document = null;
+
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if (document.isEncrypted())
+ {
+ try
+ {
+ document.decrypt("");
+ }
+ catch( InvalidPasswordException e )
+ {
+ System.err.println( "Error: The document is encrypted." );
+ }
+ catch( org.apache.pdfbox.exceptions.CryptographyException e )
+ {
+ e.printStackTrace();
+ }
+ }
+ PDDocumentCatalog catalog = document.getDocumentCatalog();
+ PDMetadata meta = catalog.getMetadata();
+ if ( meta != null)
+ {
+ XMPMetadata metadata = meta.exportXMPMetadata();
+
+ XMPSchemaDublinCore dc = metadata.getDublinCoreSchema();
+ if (dc != null)
+ {
+ display("Title:", dc.getTitle());
+ display("Description:", dc.getDescription());
+ list("Creators: ", dc.getCreators());
+ list("Dates:", dc.getDates());
+ }
+
+ XMPSchemaPDF pdf = metadata.getPDFSchema();
+ if (pdf != null)
+ {
+ display("Keywords:", pdf.getKeywords());
+ display("PDF Version:", pdf.getPDFVersion());
+ display("PDF Producer:", pdf.getProducer());
+ }
+
+ XMPSchemaBasic basic = metadata.getBasicSchema();
+ if (basic != null)
+ {
+ display("Create Date:", basic.getCreateDate());
+ display("Modify Date:", basic.getModifyDate());
+ display("Creator Tool:", basic.getCreatorTool());
+ }
+ }
+ else
+ {
+ // The pdf doesn't contain any metadata, try to use the document information instead
+ PDDocumentInformation information = document.getDocumentInformation();
+ if ( information != null)
+ {
+ showDocumentInformation(information);
+ }
+ }
+
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ private static void showDocumentInformation(PDDocumentInformation information)
+ {
+ display("Title:", information.getTitle());
+ display("Subject:", information.getSubject());
+ display("Author:", information.getAuthor());
+ display("Creator:", information.getCreator());
+ display("Producer:", information.getProducer());
+ }
+
+ private static void list(String title, List list)
+ {
+ if (list == null)
+ {
+ return;
+ }
+ System.out.println(title);
+ Iterator iter = list.iterator();
+ while (iter.hasNext())
+ {
+ Object o = iter.next();
+ System.out.println(" " + format(o));
+ }
+ }
+
+ private static String format(Object o)
+ {
+ if (o instanceof Calendar)
+ {
+ Calendar cal = (Calendar)o;
+ return DateFormat.getDateInstance().format(cal.getTime());
+ }
+ else
+ {
+ return o.toString();
+ }
+ }
+
+ private static void display(String title, Object value)
+ {
+ if (value != null)
+ {
+ System.out.println(title + " " + format(value));
+ }
+ }
+
+ /**
+ * This will print the usage for this program.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java " + ExtractMetadata.class.getName() + " <input-pdf>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionGoTo;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * This is an example on how to an action to go to the second page when the PDF is opened.
+ *
+ * Usage: java org.apache.pdfbox.examples.pdmodel.GoToSecondPageOnOpen <input-pdf> <output-pdf>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class GoToSecondBookmarkOnOpen
+{
+ private GoToSecondBookmarkOnOpen()
+ {
+ //utility class
+ }
+
+ /**
+ * This will print the documents data.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if( document.isEncrypted() )
+ {
+ System.err.println( "Error: Cannot add bookmark destination to encrypted documents." );
+ System.exit( 1 );
+ }
+
+ List pages = document.getDocumentCatalog().getAllPages();
+ if( pages.size() < 2 )
+ {
+ throw new IOException( "Error: The PDF must have at least 2 pages.");
+ }
+ PDDocumentOutline bookmarks = document.getDocumentCatalog().getDocumentOutline();
+ if( bookmarks == null )
+ {
+ throw new IOException( "Error: The PDF does not contain any bookmarks" );
+ }
+ PDOutlineItem item = bookmarks.getFirstChild().getNextSibling();
+ PDDestination dest = item.getDestination();
+ PDActionGoTo action = new PDActionGoTo();
+ action.setDestination(dest);
+ document.getDocumentCatalog().setOpenAction(action);
+
+ document.save( args[1] );
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.GoToSecondBookmarkOnOpen" +
+ "<input-pdf> <output-pdf>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+
+/**
+ * This is an example that creates a simple document.
+ *
+ * The example is taken from the pdf file format specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class HelloWorld
+{
+ /**
+ * Constructor.
+ */
+ public HelloWorld()
+ {
+ super();
+ }
+
+ /**
+ * create the second sample document from the PDF file format specification.
+ *
+ * @param file The file to write the PDF to.
+ * @param message The message to write in the file.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void doIt( String file, String message) throws IOException, COSVisitorException
+ {
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = new PDDocument();
+
+ PDPage page = new PDPage();
+ doc.addPage( page );
+ PDFont font = PDType1Font.HELVETICA_BOLD;
+
+ PDPageContentStream contentStream = new PDPageContentStream(doc, page);
+ contentStream.beginText();
+ contentStream.setFont( font, 12 );
+ contentStream.moveTextPositionByAmount( 100, 700 );
+ contentStream.drawString( message );
+ contentStream.endText();
+ contentStream.close();
+ doc.save( file );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will create a hello world PDF document.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ HelloWorld app = new HelloWorld();
+ try
+ {
+ if( args.length != 2 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt( args[0], args[1] );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: " + this.getClass().getName() + " <output-file> <Message>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
+
+/**
+ * This is an example that creates a simple document
+ * with a ttf-font.
+ *
+ * @author <a href="mailto:m.g.n@gmx.de">Michael Niedermair</a>
+ * @version $Revision: 1.2 $
+ */
+public class HelloWorldTTF
+{
+
+ /**
+ * create the second sample document from the PDF file format specification.
+ *
+ * @param file The file to write the PDF to.
+ * @param message The message to write in the file.
+ * @param fontfile The ttf-font file.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void doIt(final String file, final String message,
+ final String fontfile) throws IOException, COSVisitorException
+ {
+
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = new PDDocument();
+
+ PDPage page = new PDPage();
+ doc.addPage(page);
+ PDFont font = PDTrueTypeFont.loadTTF(doc, fontfile);
+
+ PDPageContentStream contentStream = new PDPageContentStream(doc,
+ page);
+ contentStream.beginText();
+ contentStream.setFont(font, 12);
+ contentStream.moveTextPositionByAmount(100, 700);
+ contentStream.drawString(message);
+ contentStream.endText();
+ contentStream.close();
+ doc.save(file);
+ System.out.println(file + " created!");
+ }
+ finally
+ {
+ if (doc != null)
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will create a hello world PDF document
+ * with a ttf-font.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+
+ HelloWorldTTF app = new HelloWorldTTF();
+ try
+ {
+ if (args.length != 3)
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt(args[0], args[1], args[2]);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println("usage: " + this.getClass().getName()
+ + " <output-file> <Message> <ttf-file>");
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1AfmPfbFont;
+
+/**
+ * This is an example that creates a simple document
+ * with a Type 1 font (afm + pfb).
+ *
+ * @author <a href="mailto:m.g.n@gmx.de">Michael Niedermair</a>
+ * @version $Revision: 1.2 $
+ */
+public class HelloWorldType1AfmPfb
+{
+
+ /**
+ * create the second sample document from the PDF file format specification.
+ *
+ * @param file The file to write the PDF to.
+ * @param message The message to write in the file.
+ * @param fontfile The ttf-font file.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void doIt(final String file, final String message,
+ final String fontfile) throws IOException, COSVisitorException
+ {
+
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = new PDDocument();
+
+ PDPage page = new PDPage();
+ doc.addPage(page);
+ PDFont font = new PDType1AfmPfbFont(doc,fontfile);
+
+ PDPageContentStream contentStream = new PDPageContentStream(doc,
+ page);
+ contentStream.beginText();
+ contentStream.setFont(font, 12);
+ contentStream.moveTextPositionByAmount(100, 700);
+ contentStream.drawString(message);
+ contentStream.endText();
+ contentStream.close();
+ doc.save(file);
+ System.out.println(file + " created!");
+ }
+ finally
+ {
+ if (doc != null)
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will create a hello world PDF document
+ * with a ttf-font.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+
+ HelloWorldType1AfmPfb app = new HelloWorldType1AfmPfb();
+ try
+ {
+ if (args.length != 3)
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt(args[0], args[1], args[2]);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println("usage: " + this.getClass().getName()
+ + " <output-file> <Message> <afm-file>");
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.io.RandomAccessFile;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDCcitt;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+
+
+/**
+ * This is an example that creates a simple document.
+ *
+ * The example is taken from the pdf file format specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class ImageToPDF
+{
+
+ /**
+ * create the second sample document from the PDF file format specification.
+ *
+ * @param file The file to write the PDF to.
+ * @param image The filename of the image to put in the PDF.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void createPDFFromImage( String file, String image) throws IOException, COSVisitorException
+ {
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = new PDDocument();
+
+ PDPage page = new PDPage();
+ doc.addPage( page );
+
+ PDXObjectImage ximage = null;
+ if( image.toLowerCase().endsWith( ".jpg" ) )
+ {
+ ximage = new PDJpeg(doc, new FileInputStream( image ) );
+ }
+ else if (image.toLowerCase().endsWith(".tif") || image.toLowerCase().endsWith(".tiff"))
+ {
+ ximage = new PDCcitt(doc, new RandomAccessFile(new File(image),"r"));
+ }
+ else
+ {
+ //BufferedImage awtImage = ImageIO.read( new File( image ) );
+ //ximage = new PDPixelMap(doc, awtImage);
+ throw new IOException( "Image type not supported:" + image );
+ }
+ PDPageContentStream contentStream = new PDPageContentStream(doc, page);
+
+ contentStream.drawImage( ximage, 20, 20 );
+
+ contentStream.close();
+ doc.save( file );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will create a PDF document with a single image on it.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ ImageToPDF app = new ImageToPDF();
+ try
+ {
+ if( args.length != 2 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.createPDFFromImage( args[0], args[1] );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: " + this.getClass().getName() + " <output-file> <image>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+import org.apache.pdfbox.pdfparser.PDFParser;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * This is an example on how to access the bookmarks that are part of a pdf document.
+ *
+ * Usage: java org.apache.pdfbox.examples.pdmodel.PrintBookmarks <input-pdf>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PrintBookmarks
+{
+ /**
+ * This will print the documents data.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 1 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ FileInputStream file = null;
+ try
+ {
+ file = new FileInputStream( args[0] );
+ PDFParser parser = new PDFParser( file );
+ parser.parse();
+ document = parser.getPDDocument();
+ if( document.isEncrypted() )
+ {
+ try
+ {
+ document.decrypt( "" );
+ }
+ catch( InvalidPasswordException e )
+ {
+ System.err.println( "Error: Document is encrypted with a password." );
+ System.exit( 1 );
+ }
+ }
+ PrintBookmarks meta = new PrintBookmarks();
+ PDDocumentOutline outline = document.getDocumentCatalog().getDocumentOutline();
+ if( outline != null )
+ {
+ meta.printBookmark( outline, "" );
+ }
+ else
+ {
+ System.out.println( "This document does not contain any bookmarks" );
+ }
+ }
+ finally
+ {
+ if( file != null )
+ {
+ file.close();
+ }
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.PrintBookmarks <input-pdf>" );
+ }
+
+ /**
+ * This will print the documents bookmarks to System.out.
+ *
+ * @param bookmark The bookmark to print out.
+ * @param indentation A pretty printing parameter
+ *
+ * @throws IOException If there is an error getting the page count.
+ */
+ public void printBookmark( PDOutlineNode bookmark, String indentation ) throws IOException
+ {
+ PDOutlineItem current = bookmark.getFirstChild();
+ while( current != null )
+ {
+ System.out.println( indentation + current.getTitle() );
+ printBookmark( current, indentation + " " );
+ current = current.getNextSibling();
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+import org.apache.pdfbox.pdfparser.PDFParser;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import java.text.SimpleDateFormat;
+
+import java.util.Calendar;
+
+/**
+ * This is an example on how to get a documents metadata information.
+ *
+ * Usage: java org.apache.pdfbox.examples.pdmodel.PrintDocumentMetaData <input-pdf>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.11 $
+ */
+public class PrintDocumentMetaData
+{
+ /**
+ * This will print the documents data.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 1 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ FileInputStream file = null;
+ try
+ {
+ file = new FileInputStream( args[0] );
+ PDFParser parser = new PDFParser( file );
+ parser.parse();
+ document = parser.getPDDocument();
+ if( document.isEncrypted() )
+ {
+ try
+ {
+ document.decrypt( "" );
+ }
+ catch( InvalidPasswordException e )
+ {
+ System.err.println( "Error: Document is encrypted with a password." );
+ System.exit( 1 );
+ }
+ }
+ PrintDocumentMetaData meta = new PrintDocumentMetaData();
+ meta.printMetadata( document );
+ }
+ finally
+ {
+ if( file != null )
+ {
+ file.close();
+ }
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.PrintDocumentMetaData <input-pdf>" );
+ }
+
+ /**
+ * This will print the documents data to System.out.
+ *
+ * @param document The document to get the metadata from.
+ *
+ * @throws IOException If there is an error getting the page count.
+ */
+ public void printMetadata( PDDocument document ) throws IOException
+ {
+ PDDocumentInformation info = document.getDocumentInformation();
+ PDDocumentCatalog cat = document.getDocumentCatalog();
+ PDMetadata metadata = cat.getMetadata();
+ System.out.println( "Page Count=" + document.getNumberOfPages() );
+ System.out.println( "Title=" + info.getTitle() );
+ System.out.println( "Author=" + info.getAuthor() );
+ System.out.println( "Subject=" + info.getSubject() );
+ System.out.println( "Keywords=" + info.getKeywords() );
+ System.out.println( "Creator=" + info.getCreator() );
+ System.out.println( "Producer=" + info.getProducer() );
+ System.out.println( "Creation Date=" + formatDate( info.getCreationDate() ) );
+ System.out.println( "Modification Date=" + formatDate( info.getModificationDate() ) );
+ System.out.println( "Trapped=" + info.getTrapped() );
+ if( metadata != null )
+ {
+ System.out.println( "Metadata=" + metadata.getInputStreamAsString() );
+ }
+ }
+
+ /**
+ * This will format a date object.
+ *
+ * @param date The date to format.
+ *
+ * @return A string representation of the date.
+ */
+ private String formatDate( Calendar date )
+ {
+ String retval = null;
+ if( date != null )
+ {
+ SimpleDateFormat formatter = new SimpleDateFormat();
+ retval = formatter.format( date.getTime() );
+ }
+
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.awt.geom.Rectangle2D;
+import java.util.List;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionURI;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
+import org.apache.pdfbox.util.PDFTextStripperByArea;
+
+
+/**
+ * This is an example of how to access a URL in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PrintURLs
+{
+ /**
+ * Constructor.
+ */
+ private PrintURLs()
+ {
+ //utility class
+ }
+
+ /**
+ * This will create a hello world PDF document.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ *
+ * @throws Exception If there is an error extracting the URLs.
+ */
+ public static void main(String[] args) throws Exception
+ {
+ PDDocument doc = null;
+ try
+ {
+ if( args.length != 1 )
+ {
+ usage();
+ }
+ else
+ {
+ doc = PDDocument.load( args[0] );
+ List allPages = doc.getDocumentCatalog().getAllPages();
+ for( int i=0; i<allPages.size(); i++ )
+ {
+ PDFTextStripperByArea stripper = new PDFTextStripperByArea();
+ PDPage page = (PDPage)allPages.get( i );
+ List annotations = page.getAnnotations();
+ //first setup text extraction regions
+ for( int j=0; j<annotations.size(); j++ )
+ {
+ PDAnnotation annot = (PDAnnotation)annotations.get( j );
+ if( annot instanceof PDAnnotationLink )
+ {
+ PDAnnotationLink link = (PDAnnotationLink)annot;
+ PDRectangle rect = link.getRectangle();
+ //need to reposition link rectangle to match text space
+ float x = rect.getLowerLeftX();
+ float y = rect.getUpperRightY();
+ float width = rect.getWidth();
+ float height = rect.getHeight();
+ int rotation = page.findRotation();
+ if( rotation == 0 )
+ {
+ PDRectangle pageSize = page.findMediaBox();
+ y = pageSize.getHeight() - y;
+ }
+ else if( rotation == 90 )
+ {
+ //do nothing
+ }
+
+ Rectangle2D.Float awtRect = new Rectangle2D.Float( x,y,width,height );
+ stripper.addRegion( "" + j, awtRect );
+ }
+ }
+
+ stripper.extractRegions( page );
+
+ for( int j=0; j<annotations.size(); j++ )
+ {
+ PDAnnotation annot = (PDAnnotation)annotations.get( j );
+ if( annot instanceof PDAnnotationLink )
+ {
+ PDAnnotationLink link = (PDAnnotationLink)annot;
+ PDAction action = link.getAction();
+ String urlText = stripper.getTextForRegion( "" + j );
+ if( action instanceof PDActionURI )
+ {
+ PDActionURI uri = (PDActionURI)action;
+ System.out.println( "Page " + (i+1) +":'" + urlText + "'=" + uri.getURI() );
+ }
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: " + PrintURLs.class.getName() + " <input-file>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import java.io.IOException;
+
+/**
+ * This is an example on how to remove pages from a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class RemoveFirstPage
+{
+ private RemoveFirstPage()
+ {
+ //utility class, should not be instantiated.
+ }
+
+ /**
+ * This will print the documents data.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if( document.isEncrypted() )
+ {
+ throw new IOException( "Encrypted documents are not supported for this example" );
+ }
+ if( document.getNumberOfPages() <= 1 )
+ {
+ throw new IOException( "Error: A PDF document must have at least one page, " +
+ "cannot remove the last page!");
+ }
+ document.removePage( 0 );
+ document.save( args[1] );
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.RemoveFirstPage <input-pdf> <output-pdf>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+import org.apache.pdfbox.util.PDFOperator;
+
+
+/**
+ * This is an example that will replace a string in a PDF with a new one.
+ *
+ * The example is taken from the pdf file format specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ReplaceString
+{
+ /**
+ * Constructor.
+ */
+ public ReplaceString()
+ {
+ super();
+ }
+
+ /**
+ * Locate a string in a PDF and replace it with a new string.
+ *
+ * @param inputFile The PDF to open.
+ * @param outputFile The PDF to write to.
+ * @param strToFind The string to find in the PDF document.
+ * @param message The message to write in the file.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void doIt( String inputFile, String outputFile, String strToFind, String message)
+ throws IOException, COSVisitorException
+ {
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = PDDocument.load( inputFile );
+ List pages = doc.getDocumentCatalog().getAllPages();
+ for( int i=0; i<pages.size(); i++ )
+ {
+ PDPage page = (PDPage)pages.get( i );
+ PDStream contents = page.getContents();
+ PDFStreamParser parser = new PDFStreamParser(contents.getStream() );
+ parser.parse();
+ List tokens = parser.getTokens();
+ for( int j=0; j<tokens.size(); j++ )
+ {
+ Object next = tokens.get( j );
+ if( next instanceof PDFOperator )
+ {
+ PDFOperator op = (PDFOperator)next;
+ //Tj and TJ are the two operators that display
+ //strings in a PDF
+ if( op.getOperation().equals( "Tj" ) )
+ {
+ //Tj takes one operator and that is the string
+ //to display so lets update that operator
+ COSString previous = (COSString)tokens.get( j-1 );
+ String string = previous.getString();
+ string = string.replaceFirst( strToFind, message );
+ previous.reset();
+ previous.append( string.getBytes("ISO-8859-1") );
+ }
+ else if( op.getOperation().equals( "TJ" ) )
+ {
+ COSArray previous = (COSArray)tokens.get( j-1 );
+ for( int k=0; k<previous.size(); k++ )
+ {
+ Object arrElement = previous.getObject( k );
+ if( arrElement instanceof COSString )
+ {
+ COSString cosString = (COSString)arrElement;
+ String string = cosString.getString();
+ string = string.replaceFirst( strToFind, message );
+ cosString.reset();
+ cosString.append( string.getBytes("ISO-8859-1") );
+ }
+ }
+ }
+ }
+ }
+ //now that the tokens are updated we will replace the
+ //page content stream.
+ PDStream updatedStream = new PDStream(doc);
+ OutputStream out = updatedStream.createOutputStream();
+ ContentStreamWriter tokenWriter = new ContentStreamWriter(out);
+ tokenWriter.writeTokens( tokens );
+ page.setContents( updatedStream );
+ }
+ doc.save( outputFile );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will open a PDF and replace a string if it finds it.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ ReplaceString app = new ReplaceString();
+ try
+ {
+ if( args.length != 4 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt( args[0], args[1], args[2], args[3] );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: " + this.getClass().getName() +
+ " <input-file> <output-file> <search-string> <Message>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.util.List;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionURI;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
+
+
+/**
+ * This is an example of how to replace a URL in a PDF document. This
+ * will only replace the URL that the text refers to and not the text
+ * itself.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ReplaceURLs
+{
+ /**
+ * Constructor.
+ */
+ private ReplaceURLs()
+ {
+ //utility class
+ }
+
+ /**
+ * This will read in a document and replace all of the urls with
+ * http://www.pdfbox.org.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ *
+ * @throws Exception If there is an error during the process.
+ */
+ public static void main(String[] args) throws Exception
+ {
+ PDDocument doc = null;
+ try
+ {
+ if( args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ doc = PDDocument.load( args[0] );
+ List allPages = doc.getDocumentCatalog().getAllPages();
+ for( int i=0; i<allPages.size(); i++ )
+ {
+ PDPage page = (PDPage)allPages.get( i );
+ List annotations = page.getAnnotations();
+
+ for( int j=0; j<annotations.size(); j++ )
+ {
+ PDAnnotation annot = (PDAnnotation)annotations.get( j );
+ if( annot instanceof PDAnnotationLink )
+ {
+ PDAnnotationLink link = (PDAnnotationLink)annot;
+ PDAction action = link.getAction();
+ if( action instanceof PDActionURI )
+ {
+ PDActionURI uri = (PDActionURI)action;
+ String oldURI = uri.getURI();
+ String newURI = "http://www.pdfbox.org";
+ System.out.println( "Page " + (i+1) +": Replacing " + oldURI + " with " + newURI );
+ uri.setURI( newURI );
+ }
+ }
+ }
+ }
+ doc.save( args[1] );
+ }
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: " + ReplaceURLs.class.getName() + " <input-file> <output-file>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationRubberStamp;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an example on how to add annotations to pages of a PDF document.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class RubberStamp
+{
+ private RubberStamp()
+ {
+ //utility class, should not be instantiated.
+ }
+
+ /**
+ * This will print the documents data.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if( document.isEncrypted() )
+ {
+ throw new IOException( "Encrypted documents are not supported for this example" );
+ }
+ List allpages = new ArrayList();
+ document.getDocumentCatalog().getPages().getAllKids(allpages);
+
+ for (int i=0; i < allpages.size(); i++)
+ {
+ PDPage apage = (PDPage) allpages.get(i);
+ List annotations = apage.getAnnotations();
+
+ PDAnnotationRubberStamp rs = new PDAnnotationRubberStamp();
+ rs.setName(PDAnnotationRubberStamp.NAME_TOP_SECRET);
+ rs.setRectangle(new PDRectangle(100,100));
+ rs.setContents("A top secret note");
+
+ annotations.add(rs);
+ }
+
+ document.save( args[1] );
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.RubberStamp <input-pdf> <output-pdf>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+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.graphics.xobject.PDJpeg;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationRubberStamp;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.util.MapUtil;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This is an example on how to add a rubber stamp with an image to pages of a PDF document.
+ *
+ * @version $Revision: 1.0 $
+ */
+public class RubberStampWithImage
+{
+
+ 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 SPACE = " ";
+
+ private static NumberFormat formatDecimal = NumberFormat.getNumberInstance( Locale.US );
+
+ /**
+ * Add a rubber stamp with an jpg image to every page of the given document.
+ * @param args the command line arguments
+ * @throws IOException an exception is thrown if something went wrong
+ */
+ public void doIt( String[] args ) throws IOException
+ {
+ if( args.length != 3 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if( document.isEncrypted() )
+ {
+ throw new IOException( "Encrypted documents are not supported for this example" );
+ }
+ List allpages = new ArrayList();
+ document.getDocumentCatalog().getPages().getAllKids(allpages);
+ int numberOfPages = allpages.size();
+
+ for (int i=0; i < numberOfPages; i++)
+ {
+ PDPage apage = (PDPage) allpages.get(i);
+ List annotations = apage.getAnnotations();
+ PDAnnotationRubberStamp rubberStamp = new PDAnnotationRubberStamp();
+ rubberStamp.setName(PDAnnotationRubberStamp.NAME_TOP_SECRET);
+ rubberStamp.setRectangle(new PDRectangle(100,100));
+ rubberStamp.setContents("A top secret note");
+
+ // Create a PDXObjectImage with the given jpg
+ FileInputStream fin = new FileInputStream( args[2] );
+ PDJpeg mypic = new PDJpeg(document,fin);
+
+ //Define and set the target rectangle
+ PDRectangle myrect = new PDRectangle();
+ myrect.setUpperRightX(275);
+ myrect.setUpperRightY(575);
+ myrect.setLowerLeftX(250);
+ myrect.setLowerLeftY(550);
+
+ // Create a PDXObjectForm
+ PDStream formstream = new PDStream(document);
+ OutputStream os = formstream.createOutputStream();
+ PDXObjectForm form = new PDXObjectForm(formstream);
+ form.setResources(new PDResources());
+ form.setBBox(myrect);
+ form.setFormType(1);
+ // adjust the image to the target rectangle and add it to the stream
+ drawXObject(mypic, form.getResources(), os, 250, 550, 25, 25);
+ os.close();
+
+ PDAppearanceStream myDic = new PDAppearanceStream(form.getCOSStream());
+ PDAppearanceDictionary appearance = new PDAppearanceDictionary(new COSDictionary());
+ appearance.setNormalAppearance(myDic);
+ rubberStamp.setAppearance(appearance);
+ rubberStamp.setRectangle(myrect);
+
+ //Add the new RubberStamp to the document
+ annotations.add(rubberStamp);
+
+ }
+ document.save( args[1] );
+ }
+ catch(COSVisitorException exception)
+ {
+ System.err.println("An error occured during saving the document.");
+ System.err.println("Exception:"+exception);
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ private void drawXObject( PDXObjectImage xobject, PDResources resources, OutputStream os,
+ float x, float y, float width, float height ) throws IOException
+ {
+ // This is similar to PDPageContentStream.drawXObject()
+ String xObjectPrefix = "Im";
+ String objMapping = MapUtil.getNextUniqueKey( resources.getImages(), xObjectPrefix );
+ resources.getXObjects().put( objMapping, xobject );
+
+ appendRawCommands( os, SAVE_GRAPHICS_STATE );
+ appendRawCommands( os, formatDecimal.format( width ) );
+ appendRawCommands( os, SPACE );
+ appendRawCommands( os, formatDecimal.format( 0 ) );
+ appendRawCommands( os, SPACE );
+ appendRawCommands( os, formatDecimal.format( 0 ) );
+ appendRawCommands( os, SPACE );
+ appendRawCommands( os, formatDecimal.format( height ) );
+ appendRawCommands( os, SPACE );
+ appendRawCommands( os, formatDecimal.format( x ) );
+ appendRawCommands( os, SPACE );
+ appendRawCommands( os, formatDecimal.format( y ) );
+ appendRawCommands( os, SPACE );
+ appendRawCommands( os, CONCATENATE_MATRIX );
+ appendRawCommands( os, SPACE );
+ appendRawCommands( os, "/" );
+ appendRawCommands( os, objMapping );
+ appendRawCommands( os, SPACE );
+ appendRawCommands( os, XOBJECT_DO );
+ appendRawCommands( os, SPACE );
+ appendRawCommands( os, RESTORE_GRAPHICS_STATE );
+ }
+
+ private void appendRawCommands(OutputStream os, String commands) throws IOException
+ {
+ os.write( commands.getBytes("ISO-8859-1"));
+ }
+
+ /**
+ * This creates an instance of RubberStampWithImage.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ RubberStampWithImage rubberStamp = new RubberStampWithImage();
+ rubberStamp.doIt(args);
+ }
+
+ /**
+ * This will print the usage for this example.
+ */
+ private void usage()
+ {
+ System.err.println( "Usage: java "+getClass().getName()+" <input-pdf> <output-pdf> <jpeg-filename>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.awt.Color;
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+
+/**
+ * This is an example that creates a simple document.
+ *
+ * The example is taken from the pdf file format specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ShowColorBoxes
+{
+ /**
+ * Constructor.
+ */
+ public ShowColorBoxes()
+ {
+ super();
+ }
+
+ /**
+ * create the second sample document from the PDF file format specification.
+ *
+ * @param file The file to write the PDF to.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void doIt( String file) throws IOException, COSVisitorException
+ {
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = new PDDocument();
+
+ PDPage page = new PDPage();
+ doc.addPage( page );
+
+ PDPageContentStream contentStream = new PDPageContentStream(doc, page);
+ //first fill the entire background with cyan
+ contentStream.setNonStrokingColor( Color.CYAN );
+ contentStream.fillRect( 0,0, page.findMediaBox().getWidth(), page.findMediaBox().getHeight() );
+
+ //then draw a red box in the lower left hand corner
+ contentStream.setNonStrokingColor( Color.RED );
+ contentStream.fillRect( 10, 10, 100, 100 );
+
+ contentStream.close();
+ doc.save( file );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will create a hello world PDF document.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ ShowColorBoxes app = new ShowColorBoxes();
+ try
+ {
+ if( args.length != 1 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt( args[0] );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: " + this.getClass().getName() + " <output-file>" );
+ }
+}
--- /dev/null
+/*
+ * 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.examples.pdmodel;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+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;
+
+
+/**
+ * This is an example of how to use a text matrix.
+ * @version $Revision: 1.0 $
+ */
+public class UsingTextMatrix
+{
+ /**
+ * Constructor.
+ */
+ public UsingTextMatrix()
+ {
+ super();
+ }
+
+ /**
+ * creates a sample document with some text using a text matrix.
+ *
+ * @param message The message to write in the file.
+ * @param outfile The resulting PDF.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error writing the PDF.
+ */
+ public void doIt( String message, String outfile ) throws IOException, COSVisitorException
+ {
+ // the document
+ PDDocument doc = null;
+ try
+ {
+ doc = new PDDocument();
+
+ // Page 1
+ PDFont font = PDType1Font.HELVETICA;
+ PDPage page = new PDPage();
+ page.setMediaBox(PDPage.PAGE_SIZE_A4);
+ doc.addPage(page);
+ float fontSize = 12.0f;
+
+ PDRectangle pageSize = page.findMediaBox();
+ float centeredXPosition = (pageSize.getWidth() - fontSize/1000f)/2f;
+ float stringWidth = font.getStringWidth( message );
+ float centeredYPosition = (pageSize.getHeight() - (stringWidth*fontSize)/1000f)/3f;
+
+ PDPageContentStream contentStream = new PDPageContentStream(doc, page, false, false);
+ contentStream.setFont( font, fontSize );
+ contentStream.beginText();
+ // counterclockwise rotation
+ for (int i=0;i<8;i++)
+ {
+ contentStream.setTextRotation(i*Math.PI*0.25, centeredXPosition,
+ pageSize.getHeight()-centeredYPosition);
+ contentStream.drawString( message + " " + i);
+ }
+ // clockwise rotation
+ for (int i=0;i<8;i++)
+ {
+ contentStream.setTextRotation(-i*Math.PI*0.25, centeredXPosition, centeredYPosition);
+ contentStream.drawString( message + " " + i);
+ }
+
+ contentStream.endText();
+ contentStream.close();
+
+ // Page 2
+ page = new PDPage();
+ page.setMediaBox(PDPage.PAGE_SIZE_A4);
+ doc.addPage(page);
+ fontSize = 1.0f;
+
+ contentStream = new PDPageContentStream(doc, page, false, false);
+ contentStream.setFont( font, fontSize );
+ contentStream.beginText();
+
+ // text scaling
+ for (int i=0;i<10;i++)
+ {
+ contentStream.setTextScaling(12+(i*6), 12+(i*6), 100, 100+i*50);
+ contentStream.drawString( message + " " +i);
+ }
+ contentStream.endText();
+ contentStream.close();
+
+ // Page 3
+ page = new PDPage();
+ page.setMediaBox(PDPage.PAGE_SIZE_A4);
+ doc.addPage(page);
+ fontSize = 1.0f;
+
+ contentStream = new PDPageContentStream(doc, page, false, false);
+ contentStream.setFont( font, fontSize );
+ contentStream.beginText();
+
+ int i = 0;
+ // text scaling combined with rotation
+ contentStream.setTextMatrix(12, 0, 0, 12, centeredXPosition, centeredYPosition*1.5);
+ contentStream.drawString( message + " " +i++);
+
+ contentStream.setTextMatrix(0, 18, -18, 0, centeredXPosition, centeredYPosition*1.5);
+ contentStream.drawString( message + " " +i++);
+
+ contentStream.setTextMatrix(-24, 0, 0, -24, centeredXPosition, centeredYPosition*1.5);
+ contentStream.drawString( message + " " +i++);
+
+ contentStream.setTextMatrix(0, -30, 30, 0, centeredXPosition, centeredYPosition*1.5);
+ contentStream.drawString( message + " " +i++);
+
+ contentStream.endText();
+ contentStream.close();
+
+ doc.save( outfile );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ /**
+ * This will create a PDF document with some examples how to use a text matrix.
+ *
+ * @param args Command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ UsingTextMatrix app = new UsingTextMatrix();
+ try
+ {
+ if( args.length != 2 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt( args[0], args[1] );
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: " + this.getClass().getName() + " <Message> <output-file>" );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These examples show how to use the classes in the PDModel package.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.examples.persistence;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDocument;
+
+
+
+import org.apache.pdfbox.pdfparser.PDFParser;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+/**
+ * This is an example used to copy a documents contents from a source doc to destination doc
+ * via an in-memory document representation.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.7 $
+ */
+public class CopyDoc
+{
+ /**
+ * Constructor.
+ */
+ public CopyDoc()
+ {
+ super();
+ }
+
+ /**
+ * This will perform the document copy.
+ *
+ * @param in The filename used for input.
+ * @param out The filename used for output.
+ *
+ * @throws IOException If there is an error parsing the document.
+ * @throws COSVisitorException If there is an error while copying the document.
+ */
+ public void doIt(String in, String out) throws IOException, COSVisitorException
+ {
+ java.io.InputStream is = null;
+ java.io.OutputStream os = null;
+ COSWriter writer = null;
+ try
+ {
+ is = new java.io.FileInputStream(in);
+ PDFParser parser = new PDFParser(is);
+ parser.parse();
+
+ COSDocument doc = parser.getDocument();
+
+ os = new java.io.FileOutputStream(out);
+ writer = new COSWriter(os);
+
+ writer.write(doc);
+
+ }
+ finally
+ {
+ if( is != null )
+ {
+ is.close();
+ }
+ if( os != null )
+ {
+ os.close();
+ }
+ if( writer != null )
+ {
+ writer.close();
+ }
+ }
+ }
+
+ /**
+ * This will copy a PDF document.
+ * <br />
+ * see usage() for commandline
+ *
+ * @param args command line arguments
+ */
+ public static void main(String[] args)
+ {
+ CopyDoc app = new CopyDoc();
+ try
+ {
+ if( args.length != 2 )
+ {
+ app.usage();
+ }
+ else
+ {
+ app.doIt( args[0], args[1]);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * This will print out a message telling how to use this example.
+ */
+ private void usage()
+ {
+ System.err.println( "usage: " + this.getClass().getName() + " <input-file> <output-file>" );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These examples will show how to use the persistence features of the PDFBox.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.examples.signature;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import java.security.cert.CertificateFactory;
+
+import java.util.Collection;
+
+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;
+
+/**
+ * This will read a document from the filesystem, decrypt it and do something with the signature.
+ *
+ * usage: java org.apache.pdfbox.examples.signature.ShowSignature <password> <inputfile>
+ *
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class ShowSignature
+{
+
+ private ShowSignature()
+ {
+ }
+ /**
+ * This is the entry point for the application.
+ *
+ * @param args The command-line arguments.
+ *
+ * @throws Exception If there is an error reading the file.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ ShowSignature show = new ShowSignature();
+ show.showSignature( args );
+ }
+
+ private void showSignature( String[] args ) throws Exception
+ {
+ if( args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ String password = args[0];
+ String infile = args[1];
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( infile );
+
+ if( document.isEncrypted() )
+ {
+ document.decrypt( password );
+ }
+ else
+ {
+ System.err.println( "Warning: Document is not encrypted." );
+ }
+
+ COSDictionary trailer = document.getDocument().getTrailer();
+ COSDictionary root = (COSDictionary)trailer.getDictionaryObject( COSName.ROOT );
+ COSDictionary acroForm = (COSDictionary)root.getDictionaryObject( COSName.ACRO_FORM );
+ COSArray fields = (COSArray)acroForm.getDictionaryObject( COSName.FIELDS );
+ for( int i=0; i<fields.size(); i++ )
+ {
+ COSDictionary field = (COSDictionary)fields.getObject( i );
+ String type = field.getNameAsString( "FT" );
+ if( "Sig".equals( type ) )
+ {
+ COSDictionary cert = (COSDictionary)field.getDictionaryObject( COSName.V );
+ if( cert != null )
+ {
+ System.out.println( "Certificate found" );
+ System.out.println( "Name=" + cert.getDictionaryObject( COSName.NAME ) );
+ System.out.println( "Modified=" + cert.getDictionaryObject( COSName.getPDFName( "M" ) ) );
+ COSName subFilter = (COSName)cert.getDictionaryObject( COSName.getPDFName( "SubFilter" ) );
+ if( subFilter != null )
+ {
+ if( subFilter.getName().equals( "adbe.x509.rsa_sha1" ) )
+ {
+ COSString certString = (COSString)cert.getDictionaryObject(
+ COSName.getPDFName( "Cert" ) );
+ byte[] certData = certString.getBytes();
+ CertificateFactory factory = CertificateFactory.getInstance( "X.509" );
+ ByteArrayInputStream certStream = new ByteArrayInputStream( certData );
+ Collection certs = factory.generateCertificates( certStream );
+ System.out.println( "certs=" + certs );
+ }
+ else if( subFilter.getName().equals( "adbe.pkcs7.sha1" ) )
+ {
+ COSString certString = (COSString)cert.getDictionaryObject(
+ COSName.CONTENTS );
+ byte[] certData = certString.getBytes();
+ CertificateFactory factory = CertificateFactory.getInstance( "X.509" );
+ ByteArrayInputStream certStream = new ByteArrayInputStream( certData );
+ Collection certs = factory.generateCertificates( certStream );
+ System.out.println( "certs=" + certs );
+ }
+ else
+ {
+ System.err.println( "Unknown certificate type:" + subFilter );
+ }
+ }
+ else
+ {
+ throw new IOException( "Missing subfilter for cert dictionary" );
+ }
+ }
+ else
+ {
+ System.out.println( "Signature found, but no certificate" );
+ }
+ }
+ }
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print a usage message.
+ */
+ private static void usage()
+ {
+ System.err.println( "usage: java org.apache.pdfbox.examples.signature.ShowSignature " +
+ "<password> <inputfile>" );
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These examples will show how to gain access to the PDF signature.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.examples.util;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.util.PDFTextStripperByArea;
+
+import java.awt.Rectangle;
+
+import java.util.List;
+
+/**
+ * This is an example on how to extract text from a specific area on the PDF document.
+ *
+ * Usage: java org.apache.pdfbox.examples.util.ExtractTextByArea <input-pdf>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ExtractTextByArea
+{
+ private ExtractTextByArea()
+ {
+ //utility class and should not be constructed.
+ }
+
+
+ /**
+ * This will print the documents text in a certain area.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 1 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if( document.isEncrypted() )
+ {
+ try
+ {
+ document.decrypt( "" );
+ }
+ catch( InvalidPasswordException e )
+ {
+ System.err.println( "Error: Document is encrypted with a password." );
+ System.exit( 1 );
+ }
+ }
+ PDFTextStripperByArea stripper = new PDFTextStripperByArea();
+ stripper.setSortByPosition( true );
+ Rectangle rect = new Rectangle( 10, 280, 275, 60 );
+ stripper.addRegion( "class1", rect );
+ List allPages = document.getDocumentCatalog().getAllPages();
+ PDPage firstPage = (PDPage)allPages.get( 0 );
+ stripper.extractRegions( firstPage );
+ System.out.println( "Text in the area:" + rect );
+ System.out.println( stripper.getTextForRegion( "class1" ) );
+
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.util.ExtractTextByArea <input-pdf>" );
+ }
+
+}
--- /dev/null
+/*
+ * 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.examples.util;
+
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.PDFStreamEngine;
+import org.apache.pdfbox.util.ResourceLoader;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.io.IOException;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This is an example on how to get the x/y coordinates of image locations.
+ *
+ * Usage: java org.apache.pdfbox.examples.util.PrintImageLocations <input-pdf>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PrintImageLocations extends PDFStreamEngine
+{
+ /**
+ * Default constructor.
+ *
+ * @throws IOException If there is an error loading text stripper properties.
+ */
+ public PrintImageLocations() throws IOException
+ {
+ super( ResourceLoader.loadProperties(
+ "org/apache/pdfbox/resources/PDFTextStripper.properties", true ) );
+ }
+
+ /**
+ * This will print the documents data.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 1 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if( document.isEncrypted() )
+ {
+ try
+ {
+ document.decrypt( "" );
+ }
+ catch( InvalidPasswordException e )
+ {
+ System.err.println( "Error: Document is encrypted with a password." );
+ System.exit( 1 );
+ }
+ }
+ PrintImageLocations printer = new PrintImageLocations();
+ List allPages = document.getDocumentCatalog().getAllPages();
+ for( int i=0; i<allPages.size(); i++ )
+ {
+ PDPage page = (PDPage)allPages.get( i );
+ System.out.println( "Processing page: " + i );
+ printer.processStream( page, page.findResources(), page.getContents().getStream() );
+ }
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This is used to handle an operation.
+ *
+ * @param operator The operation to perform.
+ * @param arguments The list of arguments.
+ *
+ * @throws IOException If there is an error processing the operation.
+ */
+ protected void processOperator( PDFOperator operator, List arguments ) throws IOException
+ {
+ String operation = operator.getOperation();
+ if( operation.equals( "Do" ) )
+ {
+ COSName objectName = (COSName)arguments.get( 0 );
+ Map xobjects = getResources().getXObjects();
+ PDXObject xobject = (PDXObject)xobjects.get( objectName.getName() );
+ if( xobject instanceof PDXObjectImage )
+ {
+ try
+ {
+ PDXObjectImage image = (PDXObjectImage)xobject;
+ PDPage page = getCurrentPage();
+ Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
+ double rotationInRadians =(page.findRotation() * Math.PI)/180;
+
+
+ AffineTransform rotation = new AffineTransform();
+ rotation.setToRotation( rotationInRadians );
+ AffineTransform rotationInverse = rotation.createInverse();
+ Matrix rotationInverseMatrix = new Matrix();
+ rotationInverseMatrix.setFromAffineTransform( rotationInverse );
+ Matrix rotationMatrix = new Matrix();
+ rotationMatrix.setFromAffineTransform( rotation );
+
+ Matrix unrotatedCTM = ctm.multiply( rotationInverseMatrix );
+ float xScale = unrotatedCTM.getXScale();
+ float yScale = unrotatedCTM.getYScale();
+
+ System.out.println( "Found image[" + objectName.getName() + "] " +
+ "at " + unrotatedCTM.getXPosition() + "," + unrotatedCTM.getYPosition() +
+ " size=" + (xScale/100f*image.getWidth()) + "," + (yScale/100f*image.getHeight() ));
+ }
+ catch( NoninvertibleTransformException e )
+ {
+ throw new WrappedIOException( e );
+ }
+ }
+ }
+ else
+ {
+ super.processOperator( operator, arguments );
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.PrintImageLocations <input-pdf>" );
+ }
+
+}
--- /dev/null
+/*
+ * 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.examples.util;
+
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.util.PDFTextStripper;
+import org.apache.pdfbox.util.TextPosition;
+
+import java.io.IOException;
+
+import java.util.List;
+
+/**
+ * This is an example on how to get some x/y coordinates of text.
+ *
+ * Usage: java org.apache.pdfbox.examples.util.PrintTextLocations <input-pdf>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class PrintTextLocations extends PDFTextStripper
+{
+ /**
+ * Default constructor.
+ *
+ * @throws IOException If there is an error loading text stripper properties.
+ */
+ public PrintTextLocations() throws IOException
+ {
+ super.setSortByPosition( true );
+ }
+
+ /**
+ * This will print the documents data.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 1 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if( document.isEncrypted() )
+ {
+ try
+ {
+ document.decrypt( "" );
+ }
+ catch( InvalidPasswordException e )
+ {
+ System.err.println( "Error: Document is encrypted with a password." );
+ System.exit( 1 );
+ }
+ }
+ PrintTextLocations printer = new PrintTextLocations();
+ List allPages = document.getDocumentCatalog().getAllPages();
+ for( int i=0; i<allPages.size(); i++ )
+ {
+ PDPage page = (PDPage)allPages.get( i );
+ System.out.println( "Processing page: " + i );
+ PDStream contents = page.getContents();
+ if( contents != null )
+ {
+ printer.processStream( page, page.findResources(), page.getContents().getStream() );
+ }
+ }
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * A method provided as an event interface to allow a subclass to perform
+ * some specific functionality when text needs to be processed.
+ *
+ * @param text The text to be processed
+ */
+ protected void processTextPosition( TextPosition text )
+ {
+ System.out.println( "String[" + text.getXDirAdj() + "," +
+ text.getYDirAdj() + " fs=" + text.getFontSize() + " xscale=" +
+ text.getXScale() + " height=" + text.getHeightDir() + " space=" +
+ text.getWidthOfSpace() + " width=" +
+ text.getWidthDirAdj() + "]" + text.getCharacter() );
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.PrintTextLocations <input-pdf>" );
+ }
+
+}
--- /dev/null
+/*
+ * 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.examples.util;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an example on how to remove all text from PDF document.
+ *
+ * Usage: java org.apache.pdfbox.examples.util.RemoveAllText <input-pdf> <output-pdf>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class RemoveAllText
+{
+ /**
+ * Default constructor.
+ */
+ private RemoveAllText()
+ {
+ //example class should not be instantiated
+ }
+
+ /**
+ * This will remove all text from a PDF document.
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception If there is an error parsing the document.
+ */
+ public static void main( String[] args ) throws Exception
+ {
+ if( args.length != 2 )
+ {
+ usage();
+ }
+ else
+ {
+ PDDocument document = null;
+ try
+ {
+ document = PDDocument.load( args[0] );
+ if( document.isEncrypted() )
+ {
+ System.err.println( "Error: Encrypted documents are not supported for this example." );
+ System.exit( 1 );
+ }
+ List allPages = document.getDocumentCatalog().getAllPages();
+ for( int i=0; i<allPages.size(); i++ )
+ {
+ PDPage page = (PDPage)allPages.get( i );
+ PDFStreamParser parser = new PDFStreamParser(page.getContents());
+ parser.parse();
+ List tokens = parser.getTokens();
+ List newTokens = new ArrayList();
+ for( int j=0; j<tokens.size(); j++)
+ {
+ Object token = tokens.get( j );
+ if( token instanceof PDFOperator )
+ {
+ PDFOperator op = (PDFOperator)token;
+ if( op.getOperation().equals( "TJ") || op.getOperation().equals( "Tj" ))
+ {
+ //remove the one argument to this operator
+ newTokens.remove( newTokens.size() -1 );
+ continue;
+ }
+ }
+ newTokens.add( token );
+
+ }
+ PDStream newContents = new PDStream( document );
+ ContentStreamWriter writer = new ContentStreamWriter( newContents.createOutputStream() );
+ writer.writeTokens( newTokens );
+ newContents.addCompression();
+ page.setContents( newContents );
+ }
+ document.save( args[1] );
+ }
+ finally
+ {
+ if( document != null )
+ {
+ document.close();
+ }
+ }
+ }
+ }
+
+ /**
+ * This will print the usage for this document.
+ */
+ private static void usage()
+ {
+ System.err.println( "Usage: java org.apache.pdfbox.examples.pdmodel.RemoveAllText <input-pdf> <output-pdf>" );
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The packages in this package will show how to use the PDFBox util API.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.exceptions;
+
+/**
+ * An exception that represents something gone wrong when visiting a PDF object.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.6 $
+ */
+public class COSVisitorException extends WrappedException
+{
+
+ /**
+ * COSVisitorException constructor comment.
+ *
+ * @param e The root exception that caused this exception.
+ */
+ public COSVisitorException( Exception e )
+ {
+ super( e );
+ }
+
+
+}
--- /dev/null
+/*
+ * 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.exceptions;
+
+/**
+ * An exception that indicates that something has gone wrong during a
+ * cryptography operation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class CryptographyException extends Exception
+{
+ private Exception embedded;
+
+ /**
+ * Constructor.
+ *
+ * @param msg A msg to go with this exception.
+ */
+ public CryptographyException( String msg )
+ {
+ super( msg );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param e The root exception that caused this exception.
+ */
+ public CryptographyException( Exception e )
+ {
+ super( e.getMessage() );
+ setEmbedded( e );
+ }
+ /**
+ * This will get the exception that caused this exception.
+ *
+ * @return The embedded exception if one exists.
+ */
+ public Exception getEmbedded()
+ {
+ return embedded;
+ }
+ /**
+ * This will set the exception that caused this exception.
+ *
+ * @param e The sub exception.
+ */
+ private void setEmbedded( Exception e )
+ {
+ embedded = e;
+ }
+}
--- /dev/null
+/*
+ * 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.exceptions;
+
+/**
+ * An exception that indicates an invalid password was supplied.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class InvalidPasswordException extends Exception
+{
+
+ /**
+ * Constructor.
+ *
+ * @param msg A msg to go with this exception.
+ */
+ public InvalidPasswordException( String msg )
+ {
+ super( msg );
+ }
+}
--- /dev/null
+/*
+ * 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.exceptions;
+
+import java.io.IOException;
+
+/**
+ * This exception will be thrown when a local destination(page within the same PDF) is required
+ * but the bookmark(PDOutlineItem) refers to an external destination or an action that does not
+ * point to a page.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class OutlineNotLocalException extends IOException
+{
+
+ /**
+ * Constructor.
+ *
+ * @param msg An error message.
+ */
+ public OutlineNotLocalException( String msg )
+ {
+ super( msg );
+ }
+}
--- /dev/null
+package org.apache.pdfbox.exceptions;
+
+/*
+ * 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.
+ */
+
+/**
+ * An exception that indicates a problem during the signing process.
+ *
+ * @author Thomas Chojecki
+ * @version $Revision: $
+ */
+public class SignatureException extends Exception
+{
+
+ public final static int WRONG_PASSWORD = 1;
+
+ public final static int UNSUPPORTED_OPERATION = 2;
+
+ public final static int CERT_PATH_CHECK_INVALID = 3;
+
+ public final static int NO_SUCH_ALGORITHM = 4;
+
+ public final static int INVALID_PAGE_FOR_SIGNATURE = 5;
+
+ public final static int VISUAL_SIGNATURE_INVALID = 6;
+
+ private int no;
+
+ /**
+ * Constructor.
+ *
+ * @param msg A msg to go with this exception.
+ */
+ public SignatureException( String msg )
+ {
+ super( msg );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param errno A error number to fulfill this exception
+ * @param msg A msg to go with this exception.
+ */
+ public SignatureException( int errno , String msg )
+ {
+ super( msg );
+ no = errno;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param e The exception that should be encapsulate.
+ */
+ public SignatureException(Throwable e)
+ {
+ super(e);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param errno A error number to fulfill this exception
+ * @param e The exception that should be encapsulate.
+ */
+ public SignatureException( int errno, Throwable e)
+ {
+ super(e);
+ }
+
+ /**
+ * A error number to fulfill this exception
+ *
+ * @return the error number if available, otherwise 0
+ */
+ public int getErrNo()
+ {
+ return no;
+ }
+}
--- /dev/null
+/*
+ * 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.exceptions;
+
+import java.io.PrintStream;
+
+/**
+ * An exception that that holds a sub exception.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class WrappedException extends Exception
+{
+ private Exception wrapped = null;
+
+ /**
+ * constructor comment.
+ *
+ * @param e The root exception that caused this exception.
+ */
+ public WrappedException( Exception e )
+ {
+ wrapped = e;
+ }
+
+ /**
+ * Gets the wrapped exception message.
+ *
+ * @return A message indicating the exception.
+ */
+ public String getMessage()
+ {
+ return wrapped.getMessage();
+ }
+
+ /**
+ * Prints this throwable and its backtrace to the specified print stream.
+ *
+ * @param s <code>PrintStream</code> to use for output
+ */
+ public void printStackTrace(PrintStream s)
+ {
+ super.printStackTrace( s );
+ wrapped.printStackTrace( s );
+ }
+}
--- /dev/null
+/*
+ * 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.exceptions;
+
+import java.io.IOException;
+
+/**
+ * An simple class that allows a sub exception to be stored.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class WrappedIOException extends IOException
+{
+ /**
+ * constructor comment.
+ *
+ * @param e The root exception that caused this exception.
+ */
+ public WrappedIOException( Throwable e )
+ {
+ initCause( e );
+ }
+
+ /**
+ * constructor comment.
+ *
+ * @param message Descriptive text for the exception.
+ * @param e The root exception that caused this exception.
+ */
+ public WrappedIOException( String message, Throwable e )
+ {
+ super( message );
+ initCause( e );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package is a place holder for exceptions that are used in the PDFBox project.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.io.ASCII85InputStream;
+import org.apache.pdfbox.io.ASCII85OutputStream;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is the used for the ASCIIHexDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class ASCII85Filter implements Filter
+{
+ /**
+ * {@inheritDoc}
+ */
+ public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ ASCII85InputStream is = null;
+ try
+ {
+ is = new ASCII85InputStream(compressedData);
+ byte[] buffer = new byte[1024];
+ int amountRead = 0;
+ while( (amountRead = is.read( buffer, 0, 1024) ) != -1 )
+ {
+ result.write(buffer, 0, amountRead);
+ }
+ result.flush();
+ }
+ finally
+ {
+ if( is != null )
+ {
+ is.close();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ ASCII85OutputStream os = new ASCII85OutputStream(result);
+ byte[] buffer = new byte[1024];
+ int amountRead = 0;
+ while( (amountRead = rawData.read( buffer, 0, 1024 )) != -1 )
+ {
+ os.write( buffer, 0, amountRead );
+ }
+ os.close();
+ result.flush();
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.persistence.util.COSHEXTable;
+
+/**
+ * This is the used for the ASCIIHexDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class ASCIIHexFilter implements Filter
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(ASCIIHexFilter.class);
+ /**
+ * Whitespace.
+ * 0 0x00 Null (NUL)
+ * 9 0x09 Tab (HT)
+ * 10 0x0A Line feed (LF)
+ * 12 0x0C Form feed (FF)
+ * 13 0x0D Carriage return (CR)
+ * 32 0x20 Space (SP)
+ */
+
+ private boolean isWhitespace(int c)
+ {
+ return c == 0 || c == 9 || c == 10 || c == 12 || c == 13 || c == 32;
+ }
+
+ private boolean isEOD(int c)
+ {
+ return (c == 62); // '>' - EOD
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ int value = 0;
+ int firstByte = 0;
+ int secondByte = 0;
+ while ((firstByte = compressedData.read()) != -1)
+ {
+ // always after first char
+ while(isWhitespace(firstByte))
+ {
+ firstByte = compressedData.read();
+ }
+ if(isEOD(firstByte))
+ {
+ break;
+ }
+
+ if(REVERSE_HEX[firstByte] == -1)
+ {
+ log.error("Invalid Hex Code; int: " + firstByte + " char: " + (char) firstByte);
+ }
+ value = REVERSE_HEX[firstByte] * 16;
+ secondByte = compressedData.read();
+
+ if(isEOD(secondByte))
+ {
+ // second value behaves like 0 in case of EOD
+ result.write( value );
+ break;
+ }
+ if(secondByte >= 0)
+ {
+ if(REVERSE_HEX[secondByte] == -1)
+ {
+ log.error("Invalid Hex Code; int: " + secondByte + " char: " + (char) secondByte);
+ }
+ value += REVERSE_HEX[secondByte];
+ }
+ result.write( value );
+ }
+ result.flush();
+ }
+
+ private static final int[] REVERSE_HEX =
+ {
+ -1, //0
+ -1, //1
+ -1, //2
+ -1, //3
+ -1, //4
+ -1, //5
+ -1, //6
+ -1, //7
+ -1, //8
+ -1, //9
+ -1, //10
+ -1, //11
+ -1, //12
+ -1, //13
+ -1, //14
+ -1, //15
+ -1, //16
+ -1, //17
+ -1, //18
+ -1, //19
+ -1, //20
+ -1, //21
+ -1, //22
+ -1, //23
+ -1, //24
+ -1, //25
+ -1, //26
+ -1, //27
+ -1, //28
+ -1, //29
+ -1, //30
+ -1, //31
+ -1, //32
+ -1, //33
+ -1, //34
+ -1, //35
+ -1, //36
+ -1, //37
+ -1, //38
+ -1, //39
+ -1, //40
+ -1, //41
+ -1, //42
+ -1, //43
+ -1, //44
+ -1, //45
+ -1, //46
+ -1, //47
+ 0, //48
+ 1, //49
+ 2, //50
+ 3, //51
+ 4, //52
+ 5, //53
+ 6, //54
+ 7, //55
+ 8, //56
+ 9, //57
+ -1, //58
+ -1, //59
+ -1, //60
+ -1, //61
+ -1, //62
+ -1, //63
+ -1, //64
+ 10, //65
+ 11, //66
+ 12, //67
+ 13, //68
+ 14, //69
+ 15, //70
+ -1, //71
+ -1, //72
+ -1, //73
+ -1, //74
+ -1, //75
+ -1, //76
+ -1, //77
+ -1, //78
+ -1, //79
+ -1, //80
+ -1, //81
+ -1, //82
+ -1, //83
+ -1, //84
+ -1, //85
+ -1, //86
+ -1, //87
+ -1, //88
+ -1, //89
+ -1, //90
+ -1, //91
+ -1, //92
+ -1, //93
+ -1, //94
+ -1, //95
+ -1, //96
+ 10, //97
+ 11, //98
+ 12, //99
+ 13, //100
+ 14, //101
+ 15, //102
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ int byteRead = 0;
+ while( (byteRead = rawData.read()) != -1 )
+ {
+ int value = (byteRead+256)%256;
+ result.write( COSHEXTable.TABLE[value] );
+ }
+ result.flush();
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is a filter for the CCITTFax Decoder.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Marcel Kammer
+ * @author Paul King
+ * @version $Revision: 1.13 $
+ */
+public class CCITTFaxDecodeFilter implements Filter
+{
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(CCITTFaxDecodeFilter.class);
+
+ /**
+ * Constructor.
+ */
+ public CCITTFaxDecodeFilter()
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decode(InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex)
+ throws IOException
+ {
+
+ COSBase decodeP = options.getDictionaryObject(COSName.DECODE_PARMS);
+ COSDictionary decodeParms = null;
+ if (decodeP instanceof COSDictionary)
+ {
+ decodeParms = (COSDictionary)decodeP;
+ }
+ else if (decodeP instanceof COSArray)
+ {
+ decodeParms = (COSDictionary)((COSArray)decodeP).get(0);
+ }
+ int length = options.getInt(COSName.LENGTH);
+ byte[] compressed = new byte[length];
+ compressedData.read(compressed, 0, length);
+ int cols = decodeParms.getInt(COSName.COLUMNS, 1728);
+ int rows = decodeParms.getInt(COSName.ROWS, 0);
+ int height = options.getInt(COSName.HEIGHT, 0);
+ if (rows > 0 && height > 0)
+ {
+ // ensure that rows doesn't contain implausible data, see PDFBOX-771
+ rows = Math.min(rows, height);
+ }
+ else
+ {
+ // at least one of the values has to have a valid value
+ rows = Math.max(rows, height);
+ }
+ int k = decodeParms.getInt(COSName.K);
+ int arraySize = (cols + 7) / 8 * rows;
+ byte[] decompressed = new byte[arraySize];
+ TIFFFaxDecoder faxDecoder = new TIFFFaxDecoder(1, cols, rows);
+ // TODO possible options??
+ long tiffOptions = 0;
+ if (k == 0)
+ {
+ faxDecoder.decode1D(decompressed, compressed, 0, rows);
+ }
+ else if (k > 0)
+ {
+ faxDecoder.decode2D(decompressed, compressed, 0, rows, tiffOptions);
+ }
+ else if (k < 0)
+ {
+ faxDecoder.decodeT6(decompressed, compressed, 0, rows, tiffOptions);
+ }
+ result.write(decompressed);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode(InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ log.warn("CCITTFaxDecode.encode is not implemented yet, skipping this stream.");
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ *
+ * @author adam.nichols
+ */
+public class CryptFilter implements Filter
+{
+ /**
+ * {@inheritDoc}
+ */
+ public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ COSName encryptionName = (COSName)options.getDictionaryObject(COSName.NAME);
+ if(encryptionName == null || encryptionName.equals(COSName.IDENTITY))
+ {
+ // currently the only supported implementation is the Identity crypt filter
+ Filter identityFilter = new IdentityFilter();
+ identityFilter.decode(compressedData, result, options, filterIndex);
+ }
+ else
+ {
+ throw new IOException("Unsupported crypt filter "+encryptionName.getName());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ COSName encryptionName = (COSName)options.getDictionaryObject(COSName.NAME);
+ if(encryptionName == null || encryptionName.equals(COSName.IDENTITY))
+ {
+ // currently the only supported implementation is the Identity crypt filter
+ Filter identityFilter = new IdentityFilter();
+ identityFilter.encode(rawData, result, options, filterIndex);
+ }
+ else
+ {
+ throw new IOException("Unsupported crypt filter "+encryptionName.getName());
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is the used for the DCTDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class DCTFilter implements Filter
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(DCTFilter.class);
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ log.warn( "DCTFilter.decode is not implemented yet, skipping this stream." );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ log.warn( "DCTFilter.encode is not implemented yet, skipping this stream." );
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is the interface that will be used to apply filters to a byte stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public interface Filter
+{
+ /**
+ * This will decode some compressed data.
+ *
+ * @param compressedData The compressed byte stream.
+ * @param result The place to write the uncompressed byte stream.
+ * @param options The options to use to encode the data.
+ * @param filterIndex The index to the filter being decoded.
+ *
+ * @throws IOException If there is an error decompressing the stream.
+ */
+ public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException;
+
+ /**
+ * This will encode some data.
+ *
+ * @param rawData The raw data to encode.
+ * @param result The place to write to encoded results to.
+ * @param options The options to use to encode the data.
+ * @param filterIndex The index to the filter being encoded.
+ *
+ * @throws IOException If there is an error compressing the stream.
+ */
+ public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException;
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.IOException;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This will contain manage all the different types of filters that are available.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.13 $
+ */
+public class FilterManager
+{
+ private Map<COSName, Filter> filters = new HashMap<COSName, Filter>();
+
+ /**
+ * Constructor.
+ */
+ public FilterManager()
+ {
+ Filter flateFilter = new FlateFilter();
+ Filter dctFilter = new DCTFilter();
+ Filter ccittFaxFilter = new CCITTFaxDecodeFilter();
+ Filter lzwFilter = new LZWFilter();
+ Filter asciiHexFilter = new ASCIIHexFilter();
+ Filter ascii85Filter = new ASCII85Filter();
+ Filter runLengthFilter = new RunLengthDecodeFilter();
+ Filter cryptFilter = new CryptFilter();
+ Filter jpxFilter = new JPXFilter();
+ Filter jbig2Filter = new JBIG2Filter();
+
+ addFilter( COSName.FLATE_DECODE, flateFilter );
+ addFilter( COSName.FLATE_DECODE_ABBREVIATION, flateFilter );
+ addFilter( COSName.DCT_DECODE, dctFilter );
+ addFilter( COSName.DCT_DECODE_ABBREVIATION, dctFilter );
+ addFilter( COSName.CCITTFAX_DECODE, ccittFaxFilter );
+ addFilter( COSName.CCITTFAX_DECODE_ABBREVIATION, ccittFaxFilter );
+ addFilter( COSName.LZW_DECODE, lzwFilter );
+ addFilter( COSName.LZW_DECODE_ABBREVIATION, lzwFilter );
+ addFilter( COSName.ASCII_HEX_DECODE, asciiHexFilter );
+ addFilter( COSName.ASCII_HEX_DECODE_ABBREVIATION, asciiHexFilter );
+ addFilter( COSName.ASCII85_DECODE, ascii85Filter );
+ addFilter( COSName.ASCII85_DECODE_ABBREVIATION, ascii85Filter );
+ addFilter( COSName.RUN_LENGTH_DECODE, runLengthFilter );
+ addFilter( COSName.RUN_LENGTH_DECODE_ABBREVIATION, runLengthFilter );
+ addFilter( COSName.CRYPT, cryptFilter );
+ addFilter( COSName.JPX_DECODE, jpxFilter );
+ addFilter( COSName.JBIG2_DECODE, jbig2Filter );
+
+ }
+
+ /**
+ * This will get all of the filters that are available in the system.
+ *
+ * @return All available filters in the system.
+ */
+ public Collection<Filter> getFilters()
+ {
+ return filters.values();
+ }
+
+ /**
+ * This will add an available filter.
+ *
+ * @param filterName The name of the filter.
+ * @param filter The filter to use.
+ */
+ public void addFilter( COSName filterName, Filter filter )
+ {
+ filters.put( filterName, filter );
+ }
+
+ /**
+ * This will get a filter by name.
+ *
+ * @param filterName The name of the filter to retrieve.
+ *
+ * @return The filter that matches the name.
+ *
+ * @throws IOException If the filter could not be found.
+ */
+ public Filter getFilter( COSName filterName ) throws IOException
+ {
+ Filter filter = (Filter)filters.get( filterName );
+ if( filter == null )
+ {
+ throw new IOException( "Unknown stream filter:" + filterName );
+ }
+
+ return filter;
+ }
+
+ /**
+ * This will get a filter by name.
+ *
+ * @param filterName The name of the filter to retrieve.
+ *
+ * @return The filter that matches the name.
+ *
+ * @throws IOException If the filter could not be found.
+ */
+ public Filter getFilter( String filterName ) throws IOException
+ {
+ return getFilter( COSName.getPDFName( filterName ) );
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.EOFException;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is the used for the FlateDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Marcel Kammer
+ * @version $Revision: 1.12 $
+ */
+public class FlateFilter implements Filter
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(FlateFilter.class);
+
+ private static final int BUFFER_SIZE = 2048;
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decode(InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ COSBase baseObj = options.getDictionaryObject(COSName.DECODE_PARMS, COSName.DP);
+ COSDictionary dict = null;
+ if( baseObj instanceof COSDictionary )
+ {
+ dict = (COSDictionary)baseObj;
+ }
+ else if( baseObj instanceof COSArray )
+ {
+ COSArray paramArray = (COSArray)baseObj;
+ if( filterIndex < paramArray.size() )
+ {
+ dict = (COSDictionary)paramArray.getObject( filterIndex );
+ }
+ }
+ else if( baseObj == null )
+ {
+ //do nothing
+ }
+ else
+ {
+ throw new IOException( "Error: Expected COSArray or COSDictionary and not "
+ + baseObj.getClass().getName() );
+ }
+
+
+ int predictor = -1;
+ int colors = -1;
+ int bitsPerPixel = -1;
+ int columns = -1;
+ InflaterInputStream decompressor = null;
+ ByteArrayInputStream bais = null;
+ ByteArrayOutputStream baos = null;
+ if (dict!=null)
+ {
+ predictor = dict.getInt(COSName.PREDICTOR);
+ if(predictor > 1)
+ {
+ colors = dict.getInt(COSName.COLORS);
+ bitsPerPixel = options.getInt(COSName.BITS_PER_COMPONENT);
+ columns = dict.getInt(COSName.COLUMNS);
+ }
+ }
+
+ try
+ {
+ // Decompress data to temporary ByteArrayOutputStream
+ decompressor = new InflaterInputStream(compressedData);
+ int amountRead;
+ int mayRead = compressedData.available();
+
+ if (mayRead > 0)
+ {
+ byte[] buffer = new byte[Math.min(mayRead,BUFFER_SIZE)];
+
+ // Decode data using given predictor
+ if (predictor==-1 || predictor == 1 )
+ {
+ try
+ {
+ // decoding not needed
+ while ((amountRead = decompressor.read(buffer, 0, Math.min(mayRead,BUFFER_SIZE))) != -1)
+ {
+ result.write(buffer, 0, amountRead);
+ }
+ }
+ catch (OutOfMemoryError exception)
+ {
+ // if the stream is corrupt an OutOfMemoryError may occur
+ log.error("Stop reading corrupt stream");
+ }
+ catch (ZipException exception)
+ {
+ // if the stream is corrupt an OutOfMemoryError may occur
+ log.error("Stop reading corrupt stream");
+ }
+ catch (EOFException exception)
+ {
+ // if the stream is corrupt an OutOfMemoryError may occur
+ log.error("Stop reading corrupt stream");
+ }
+ }
+ else
+ {
+ /*
+ * Reverting back to default values
+ */
+ if( colors == -1 )
+ {
+ colors = 1;
+ }
+ if( bitsPerPixel == -1 )
+ {
+ bitsPerPixel = 8;
+ }
+ if( columns == -1 )
+ {
+ columns = 1;
+ }
+
+ baos = new ByteArrayOutputStream();
+ while ((amountRead = decompressor.read(buffer, 0, Math.min(mayRead,BUFFER_SIZE))) != -1)
+ {
+ baos.write(buffer, 0, amountRead);
+ }
+ baos.flush();
+
+ // Copy data to ByteArrayInputStream for reading
+ bais = new ByteArrayInputStream(baos.toByteArray());
+ baos.close();
+ baos = null;
+
+ byte[] decodedData = decodePredictor(predictor, colors, bitsPerPixel, columns, bais);
+ bais.close();
+ bais = null;
+
+ result.write(decodedData);
+ }
+ }
+
+ result.flush();
+ }
+ finally
+ {
+ if (decompressor != null)
+ {
+ decompressor.close();
+ }
+ if (bais != null)
+ {
+ bais.close();
+ }
+ if (baos != null)
+ {
+ baos.close();
+ }
+ }
+ }
+
+ private byte[] decodePredictor(int predictor, int colors, int bitsPerComponent, int columns, InputStream data)
+ throws IOException
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[2048];
+ if (predictor == 1 )
+ {
+ // No prediction
+ int i = 0;
+ while ((i = data.read(buffer)) != -1)
+ {
+ baos.write(buffer, 0, i);
+ }
+ }
+ else
+ {
+ // calculate sizes
+ int bpp = colors * (bitsPerComponent % 8 +1);
+ int rowlength = columns * bpp;
+ byte[] actline = new byte[rowlength];
+ // Initialize lastline with Zeros according to PNG-specification
+ byte[] lastline = new byte[rowlength];
+
+ boolean done = false;
+ int linepredictor = predictor;
+
+ while (!done && data.available() > 0)
+ {
+ // test for PNG predictor; each value >= 10 (not only 15) indicates usage of PNG predictor
+ if (predictor >= 10)
+ {
+ // PNG predictor; each row starts with predictor type (0, 1, 2, 3, 4)
+ linepredictor = data.read();// read per line predictor
+ if (linepredictor == -1)
+ {
+ done = true;// reached EOF
+ break;
+ }
+ else
+ {
+ linepredictor += 10; // add 10 to tread value 0 as 10, 1 as 11, ...
+ }
+ }
+
+ // read line
+ int i = 0;
+ int offset = 0;
+ while (offset < rowlength && ((i = data.read(actline, offset, rowlength - offset)) != -1))
+ {
+ offset += i;
+ }
+
+ // Do prediction as specified in PNG-Specification 1.2
+ switch (linepredictor)
+ {
+ case 2:// PRED TIFF SUB
+ /**
+ * @TODO decode tiff with bitsPerComponent != 8; e.g. for 4 bpc each nibble must be subtracted separately
+ */
+ if ( bitsPerComponent != 8 )
+ {
+ throw new IOException("TIFF-Predictor with " + bitsPerComponent + " bits per component not supported");
+ }
+ // for 8 bits per component it is the same algorithm as PRED SUB of PNG format
+ for (int p = 0; p < rowlength; p++)
+ {
+ int sub = actline[p] & 0xff;
+ int left = p - bpp >= 0 ? actline[p - bpp] & 0xff : 0;
+ actline[p] = (byte) (sub + left);
+ }
+ break;
+ case 10:// PRED NONE
+ // do nothing
+ break;
+ case 11:// PRED SUB
+ for (int p = 0; p < rowlength; p++)
+ {
+ int sub = actline[p];
+ int left = p - bpp >= 0 ? actline[p - bpp]: 0;
+ actline[p] = (byte) (sub + left);
+ }
+ break;
+ case 12:// PRED UP
+ for (int p = 0; p < rowlength; p++)
+ {
+ int up = actline[p] & 0xff;
+ int prior = lastline[p] & 0xff;
+ actline[p] = (byte) ((up + prior) & 0xff);
+ }
+ break;
+ case 13:// PRED AVG
+ for (int p = 0; p < rowlength; p++)
+ {
+ int avg = actline[p] & 0xff;
+ int left = p - bpp >= 0 ? actline[p - bpp] & 0xff: 0;
+ int up = lastline[p] & 0xff;
+ actline[p] = (byte) ((avg + (int)Math.floor( (left + up)/2 ) ) & 0xff);
+ }
+ break;
+ case 14:// PRED PAETH
+ for (int p = 0; p < rowlength; p++)
+ {
+ int paeth = actline[p] & 0xff;
+ int a = p - bpp >= 0 ? actline[p - bpp] & 0xff : 0;// left
+ int b = lastline[p] & 0xff;// upper
+ int c = p - bpp >= 0 ? lastline[p - bpp] & 0xff : 0;// upperleft
+ int value = a + b - c;
+ int absa = Math.abs(value - a);
+ int absb = Math.abs(value - b);
+ int absc = Math.abs(value - c);
+
+ if (absa <= absb && absa <= absc)
+ {
+ actline[p] = (byte) ((paeth + a) & 0xff);
+ }
+ else if (absb <= absc)
+ {
+ actline[p] = (byte) ((paeth + b) & 0xff);
+ }
+ else
+ {
+ actline[p] = (byte) ((paeth + c) & 0xff);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ lastline = (byte[])actline.clone();
+ baos.write(actline, 0, actline.length);
+ }
+ }
+ return baos.toByteArray();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode(InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ DeflaterOutputStream out = new DeflaterOutputStream(result);
+ int amountRead = 0;
+ int mayRead = rawData.available();
+ if (mayRead > 0)
+ {
+ byte[] buffer = new byte[Math.min(mayRead,BUFFER_SIZE)];
+ while ((amountRead = rawData.read(buffer, 0, Math.min(mayRead,BUFFER_SIZE))) != -1)
+ {
+ out.write(buffer, 0, amountRead);
+ }
+ }
+ out.close();
+ result.flush();
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * The IdentityFilter filter just passes the data through without any modifications.
+ * This is defined in section 7.6.5 of the PDF 1.7 spec and also stated in table
+ * 26.
+ *
+ * @author adam.nichols
+ */
+public class IdentityFilter implements Filter
+{
+ private static final int BUFFER_SIZE = 1024;
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int amountRead = 0;
+ while( (amountRead = compressedData.read( buffer, 0, BUFFER_SIZE )) != -1 )
+ {
+ result.write( buffer, 0, amountRead );
+ }
+ result.flush();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int amountRead = 0;
+ while( (amountRead = rawData.read( buffer, 0, BUFFER_SIZE )) != -1 )
+ {
+ result.write( buffer, 0, amountRead );
+ }
+ result.flush();
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.imageio.ImageIO;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * Modeled on the JBIG2Decode filter.
+ *
+ * thanks to Timo Böhme <timo.boehme@ontochem.com>
+ */
+
+public class JBIG2Filter implements Filter
+{
+
+ /** Log instance */
+ private static final Log log = LogFactory.getLog(JBIG2Filter.class);
+
+ /**
+ * Decode JBIG2 data using Java ImageIO library.
+ *
+ * {@inheritDoc}
+ *
+ */
+ public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ BufferedImage bi = ImageIO.read( compressedData );
+ if ( bi != null )
+ {
+ DataBuffer dBuf = bi.getData().getDataBuffer();
+ if ( dBuf.getDataType() == DataBuffer.TYPE_BYTE )
+ {
+ result.write( ( ( DataBufferByte ) dBuf ).getData() );
+ }
+ else
+ {
+ log.error( "Image data buffer not of type byte but type " + dBuf.getDataType() );
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ System.err.println( "Warning: JBIG2.encode is not implemented yet, skipping this stream." );
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.imageio.ImageIO;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is used for the JPXDecode filter.
+ *
+ * @author <a href="mailto:timo.boehme@ontochem.com">Timo Böhme</a>
+ *
+ */
+public class JPXFilter implements Filter
+{
+
+ /** Log instance */
+ private static final Log log = LogFactory.getLog(JPXFilter.class);
+
+ /**
+ * Decode JPEG2000 data using Java ImageIO library.
+ *
+ * {@inheritDoc}
+ *
+ */
+ public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ BufferedImage bi = ImageIO.read( compressedData );
+ if ( bi != null )
+ {
+ DataBuffer dBuf = bi.getData().getDataBuffer();
+ if ( dBuf.getDataType() == DataBuffer.TYPE_BYTE )
+ {
+ result.write( ( ( DataBufferByte ) dBuf ).getData() );
+ }
+ else
+ {
+ log.error( "Image data buffer not of type byte but type " + dBuf.getDataType() );
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ System.err.println( "Warning: JPXFilter.encode is not implemented yet, skipping this stream." );
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This is the used for the LZWDecode filter. This represents the dictionary mappings
+ * between codes and their values.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+class LZWDictionary
+{
+ private Map codeToData = new HashMap();
+ private LZWNode root = new LZWNode();
+
+ private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ private long nextCode = 258;
+ private int codeSize = 9;
+
+ /**
+ * constructor.
+ */
+ public LZWDictionary()
+ {
+ for( long i=0; i<256; i++ )
+ {
+ LZWNode node = new LZWNode();
+ node.setCode( i );
+ root.setNode( (byte)i, node );
+ codeToData.put( new Long( i ), new byte[]{ (byte)i } );
+ }
+ }
+
+ /**
+ * This will get the value for the code. It will return null if the code is not
+ * defined.
+ *
+ * @param code The key to the data.
+ *
+ * @return The data that is mapped to the code.
+ */
+ public byte[] getData( long code )
+ {
+ return (byte[])codeToData.get( new Long( code ) );
+ }
+
+ /**
+ * This will take a visit from a byte[]. This will create new code entries as
+ * necessary.
+ *
+ * @param data The byte to get a visit from.
+ *
+ * @throws IOException If there is an error visiting this data.
+ */
+ public void visit( byte[] data ) throws IOException
+ {
+ for( int i=0; i<data.length; i++ )
+ {
+ visit( data[i] );
+ }
+ }
+
+ /**
+ * This will take a visit from a byte. This will create new code entries as
+ * necessary.
+ *
+ * @param data The byte to get a visit from.
+ *
+ * @throws IOException If there is an error visiting this data.
+ */
+ public void visit( byte data ) throws IOException
+ {
+ buffer.write( data );
+ byte[] curBuffer = buffer.toByteArray();
+ LZWNode previous = null;
+ LZWNode current = root;
+ boolean createNewCode = false;
+ for( int i=0; i<curBuffer.length && current != null; i++ )
+ {
+ previous = current;
+ current = current.getNode( curBuffer[i] );
+ if( current == null )
+ {
+ createNewCode = true;
+ current = new LZWNode();
+ previous.setNode( curBuffer[i], current );
+ }
+ }
+ if( createNewCode )
+ {
+ long code = nextCode++;
+ current.setCode( code );
+ codeToData.put( new Long( code ), curBuffer );
+
+ /**
+ System.out.print( "Adding " + code + "='" );
+ for( int i=0; i<curBuffer.length; i++ )
+ {
+ String hex = Integer.toHexString( ((curBuffer[i]+256)%256) );
+ if( hex.length() <=1 )
+ {
+ hex = "0" + hex;
+ }
+ if( i != curBuffer.length -1 )
+ {
+ hex += " ";
+ }
+ System.out.print( hex.toUpperCase() );
+ }
+ System.out.println( "'" );
+ **/
+ buffer.reset();
+ buffer.write( data );
+ resetCodeSize();
+ }
+ }
+
+ /**
+ * This will get the next code that will be created.
+ *
+ * @return The next code to be created.
+ */
+ public long getNextCode()
+ {
+ return nextCode;
+ }
+
+ /**
+ * This will get the size of the code in bits, 9, 10, or 11.
+ *
+ * @return The size of the code in bits.
+ */
+ public int getCodeSize()
+ {
+ return codeSize;
+ }
+
+ /**
+ * This will determine the code size.
+ */
+ private void resetCodeSize()
+ {
+ if( nextCode >= 2048 )
+ {
+ codeSize = 12;
+ }
+ else if( nextCode >= 1024 )
+ {
+ codeSize = 11;
+ }
+ else if( nextCode >= 512 )
+ {
+ codeSize = 10;
+ }
+ else
+ {
+ codeSize = 9;
+ }
+ }
+
+ /**
+ * This will crear the internal buffer that the dictionary uses.
+ */
+ public void clear()
+ {
+ buffer.reset();
+ }
+
+ /**
+ * This will folow the path to the data node.
+ *
+ * @param data The path to the node.
+ *
+ * @return The node that resides at that path.
+ */
+ public LZWNode getNode( byte[] data )
+ {
+ return root.getNode( data );
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PushbackInputStream;
+import java.io.StreamCorruptedException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.io.NBitInputStream;
+import org.apache.pdfbox.io.NBitOutputStream;
+
+/**
+ * This is the used for the LZWDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.15 $
+ */
+public class LZWFilter implements Filter
+{
+
+ /**
+ * The LZW clear table code.
+ */
+ public static final long CLEAR_TABLE = 256;
+ /**
+ * The LZW end of data code.
+ */
+ public static final long EOD = 257;
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ //log.debug("decode( )");
+ NBitInputStream in = null;
+ in = new NBitInputStream( compressedData );
+ in.setBitsInChunk( 9 );
+ LZWDictionary dic = new LZWDictionary();
+ byte firstByte = 0;
+ long nextCommand = 0;
+ while( (nextCommand = in.read() ) != EOD )
+ {
+ // log.debug( "decode - nextCommand=" + nextCommand + ", bitsInChunk: " + in.getBitsInChunk());
+
+ if( nextCommand == CLEAR_TABLE )
+ {
+ in.setBitsInChunk( 9 );
+ dic = new LZWDictionary();
+ }
+ else
+ {
+ byte[] data = dic.getData( nextCommand );
+ if( data == null )
+ {
+ dic.visit( firstByte );
+ data = dic.getData( nextCommand );
+ dic.clear();
+ }
+ if( data == null )
+ {
+ throw new StreamCorruptedException( "Error: data is null" );
+ }
+ dic.visit(data);
+
+ //log.debug( "decode - dic.getNextCode(): " + dic.getNextCode());
+
+ if( dic.getNextCode() >= 2047 )
+ {
+ in.setBitsInChunk( 12 );
+ }
+ else if( dic.getNextCode() >= 1023 )
+ {
+ in.setBitsInChunk( 11 );
+ }
+ else if( dic.getNextCode() >= 511 )
+ {
+ in.setBitsInChunk( 10 );
+ }
+ else
+ {
+ in.setBitsInChunk( 9 );
+ }
+ /**
+ if( in.getBitsInChunk() != dic.getCodeSize() )
+ {
+ in.unread( nextCommand );
+ in.setBitsInChunk( dic.getCodeSize() );
+ System.out.print( "Switching " + nextCommand + " to " );
+ nextCommand = in.read();
+ System.out.println( "" + nextCommand );
+ data = dic.getData( nextCommand );
+ }**/
+ firstByte = data[0];
+ result.write( data );
+ }
+ }
+ result.flush();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ //log.debug("encode( )");
+ PushbackInputStream input = new PushbackInputStream( rawData, 4096 );
+ LZWDictionary dic = new LZWDictionary();
+ NBitOutputStream out = new NBitOutputStream( result );
+ out.setBitsInChunk( 9 ); //initially nine
+ out.write( CLEAR_TABLE );
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ int byteRead = 0;
+ for( int i=0; (byteRead = input.read()) != -1; i++ )
+ {
+ //log.debug( "byteRead = '" + (char)byteRead + "' (0x" + Integer.toHexString(byteRead) + "), i=" + i);
+ buffer.write( byteRead );
+ dic.visit( (byte)byteRead );
+ out.setBitsInChunk( dic.getCodeSize() );
+
+ //log.debug( "Getting node '" + new String( buffer.toByteArray() ) + "', buffer.size = " + buffer.size() );
+ LZWNode node = dic.getNode( buffer.toByteArray() );
+ int nextByte = input.read();
+ if( nextByte != -1 )
+ {
+ //log.debug( "nextByte = '" + (char)nextByte + "' (0x" + Integer.toHexString(nextByte) + ")");
+ LZWNode next = node.getNode( (byte)nextByte );
+ if( next == null )
+ {
+ //log.debug("encode - No next node, writing node and resetting buffer (" +
+ // " node.getCode: " + node.getCode() + ")" +
+ // " bitsInChunk: " + out.getBitsInChunk() +
+ // ")");
+ out.write( node.getCode() );
+ buffer.reset();
+ }
+
+ input.unread( nextByte );
+ }
+ else
+ {
+ //log.debug("encode - EOF on lookahead: writing node, resetting buffer, and terminating read loop (" +
+ // " node.getCode: " + node.getCode() + ")" +
+ // " bitsInChunk: " + out.getBitsInChunk() +
+ // ")");
+ out.write( node.getCode() );
+ buffer.reset();
+ break;
+ }
+
+ if( dic.getNextCode() == 4096 )
+ {
+ //log.debug("encode - Clearing dictionary and unreading pending buffer data (" +
+ // " bitsInChunk: " + out.getBitsInChunk() +
+ // ")");
+ out.write( CLEAR_TABLE );
+ dic = new LZWDictionary();
+ input.unread( buffer.toByteArray() );
+ buffer.reset();
+ }
+ }
+
+ // Fix the code size based on the fact that we are writing the EOD
+ //
+ if( dic.getNextCode() >= 2047 )
+ {
+ out.setBitsInChunk( 12 );
+ }
+ else if( dic.getNextCode() >= 1023 )
+ {
+ out.setBitsInChunk( 11 );
+ }
+ else if( dic.getNextCode() >= 511 )
+ {
+ out.setBitsInChunk( 10 );
+ }
+ else
+ {
+ out.setBitsInChunk( 9 );
+ }
+
+ //log.debug("encode - Writing EOD (" +
+ // " bitsInChunk: " + out.getBitsInChunk() +
+ // ")");
+ out.write( EOD );
+ out.close();
+ result.flush();
+ }
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This is the used for the LZWDecode filter.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+class LZWNode
+{
+ private long code;
+ private Map subNodes = new HashMap();
+
+ /**
+ * This will get the number of children.
+ *
+ * @return The number of children.
+ */
+ public int childCount()
+ {
+ return subNodes.size();
+ }
+
+ /**
+ * This will set the node for a particular byte.
+ *
+ * @param b The byte for that node.
+ * @param node The node to add.
+ */
+ public void setNode( byte b, LZWNode node )
+ {
+ subNodes.put( new Byte( b ), node );
+ }
+
+ /**
+ * This will get the node that is a direct sub node of this node.
+ *
+ * @param data The byte code to the node.
+ *
+ * @return The node at that value if it exists.
+ */
+ public LZWNode getNode( byte data )
+ {
+ return (LZWNode)subNodes.get( new Byte( data ) );
+ }
+
+
+ /**
+ * This will traverse the tree until it gets to the sub node.
+ * This will return null if the node does not exist.
+ *
+ * @param data The path to the node.
+ *
+ * @return The node that resides at the data path.
+ */
+ public LZWNode getNode( byte[] data )
+ {
+ LZWNode current = this;
+ for( int i=0; i<data.length && current != null; i++ )
+ {
+ current = current.getNode( data[i] );
+ }
+ return current;
+ }
+
+ /** Getter for property code.
+ * @return Value of property code.
+ */
+ public long getCode()
+ {
+ return code;
+ }
+
+ /** Setter for property code.
+ * @param codeValue New value of property code.
+ */
+ public void setCode(long codeValue)
+ {
+ code = codeValue;
+ }
+
+}
--- /dev/null
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is a filter for the RunLength Decoder.
+ *
+ * From the PDF Reference
+ * <pre>
+ * The RunLengthDecode filter decodes data that has been encoded in a simple
+ * byte-oriented format based on run length. The encoded data is a sequence of
+ * runs, where each run consists of a length byte followed by 1 to 128 bytes of data. If
+ * the length byte is in the range 0 to 127, the following length + 1 (1 to 128) bytes
+ * are copied literally during decompression. If length is in the range 129 to 255, the
+ * following single byte is to be copied 257 ? length (2 to 128) times during decompression.
+ * A length value of 128 denotes EOD.
+ *
+ * The compression achieved by run-length encoding depends on the input data. In
+ * the best case (all zeros), a compression of approximately 64:1 is achieved for long
+ * files. The worst case (the hexadecimal sequence 00 alternating with FF) results in
+ * an expansion of 127:128.
+ * </pre>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class RunLengthDecodeFilter implements Filter
+{
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(RunLengthDecodeFilter.class);
+
+ private static final int RUN_LENGTH_EOD = 128;
+
+ /**
+ * Constructor.
+ */
+ public RunLengthDecodeFilter()
+ {
+ //default constructor
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ int dupAmount = -1;
+ byte[] buffer = new byte[128];
+ while( (dupAmount = compressedData.read()) != -1 && dupAmount != RUN_LENGTH_EOD )
+ {
+ if( dupAmount <= 127 )
+ {
+ int amountToCopy = dupAmount+1;
+ int compressedRead = 0;
+ while( amountToCopy > 0 )
+ {
+ compressedRead = compressedData.read( buffer, 0, amountToCopy );
+ result.write( buffer, 0, compressedRead );
+ amountToCopy -= compressedRead;
+ }
+ }
+ else
+ {
+ int dupByte = compressedData.read();
+ for( int i=0; i<257-dupAmount; i++ )
+ {
+ result.write( dupByte );
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex )
+ throws IOException
+ {
+ log.warn( "RunLengthDecodeFilter.encode is not implemented yet, skipping this stream." );
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.pdfbox.filter;
+
+class TIFFFaxDecoder {
+
+ private int bitPointer, bytePointer;
+ private byte[] data;
+ private int w, h;
+ private int fillOrder;
+
+ // Data structures needed to store changing elements for the previous
+ // and the current scanline
+ private int changingElemSize = 0;
+ private int[] prevChangingElems;
+ private int[] currChangingElems;
+
+ // Element at which to start search in getNextChangingElement
+ private int lastChangingElement = 0;
+
+ private int compression = 2;
+
+ // Variables set by T4Options
+ private int uncompressedMode = 0;
+ private int fillBits = 0;
+ private int oneD;
+
+ static int[] table1 = {
+ 0x00, // 0 bits are left in first byte - SHOULD NOT HAPPEN
+ 0x01, // 1 bits are left in first byte
+ 0x03, // 2 bits are left in first byte
+ 0x07, // 3 bits are left in first byte
+ 0x0f, // 4 bits are left in first byte
+ 0x1f, // 5 bits are left in first byte
+ 0x3f, // 6 bits are left in first byte
+ 0x7f, // 7 bits are left in first byte
+ 0xff // 8 bits are left in first byte
+ };
+
+ static int[] table2 = {
+ 0x00, // 0
+ 0x80, // 1
+ 0xc0, // 2
+ 0xe0, // 3
+ 0xf0, // 4
+ 0xf8, // 5
+ 0xfc, // 6
+ 0xfe, // 7
+ 0xff // 8
+ };
+
+ // Table to be used when fillOrder = 2, for flipping bytes.
+ static byte[] flipTable = {
+ 0, -128, 64, -64, 32, -96, 96, -32,
+ 16, -112, 80, -48, 48, -80, 112, -16,
+ 8, -120, 72, -56, 40, -88, 104, -24,
+ 24, -104, 88, -40, 56, -72, 120, -8,
+ 4, -124, 68, -60, 36, -92, 100, -28,
+ 20, -108, 84, -44, 52, -76, 116, -12,
+ 12, -116, 76, -52, 44, -84, 108, -20,
+ 28, -100, 92, -36, 60, -68, 124, -4,
+ 2, -126, 66, -62, 34, -94, 98, -30,
+ 18, -110, 82, -46, 50, -78, 114, -14,
+ 10, -118, 74, -54, 42, -86, 106, -22,
+ 26, -102, 90, -38, 58, -70, 122, -6,
+ 6, -122, 70, -58, 38, -90, 102, -26,
+ 22, -106, 86, -42, 54, -74, 118, -10,
+ 14, -114, 78, -50, 46, -82, 110, -18,
+ 30, -98, 94, -34, 62, -66, 126, -2,
+ 1, -127, 65, -63, 33, -95, 97, -31,
+ 17, -111, 81, -47, 49, -79, 113, -15,
+ 9, -119, 73, -55, 41, -87, 105, -23,
+ 25, -103, 89, -39, 57, -71, 121, -7,
+ 5, -123, 69, -59, 37, -91, 101, -27,
+ 21, -107, 85, -43, 53, -75, 117, -11,
+ 13, -115, 77, -51, 45, -83, 109, -19,
+ 29, -99, 93, -35, 61, -67, 125, -3,
+ 3, -125, 67, -61, 35, -93, 99, -29,
+ 19, -109, 83, -45, 51, -77, 115, -13,
+ 11, -117, 75, -53, 43, -85, 107, -21,
+ 27, -101, 91, -37, 59, -69, 123, -5,
+ 7, -121, 71, -57, 39, -89, 103, -25,
+ 23, -105, 87, -41, 55, -73, 119, -9,
+ 15, -113, 79, -49, 47, -81, 111, -17,
+ 31, -97, 95, -33, 63, -65, 127, -1,
+ };
+
+ // The main 10 bit white runs lookup table
+ static short[] white = {
+ // 0 - 7
+ 6430, 6400, 6400, 6400, 3225, 3225, 3225, 3225,
+ // 8 - 15
+ 944, 944, 944, 944, 976, 976, 976, 976,
+ // 16 - 23
+ 1456, 1456, 1456, 1456, 1488, 1488, 1488, 1488,
+ // 24 - 31
+ 718, 718, 718, 718, 718, 718, 718, 718,
+ // 32 - 39
+ 750, 750, 750, 750, 750, 750, 750, 750,
+ // 40 - 47
+ 1520, 1520, 1520, 1520, 1552, 1552, 1552, 1552,
+ // 48 - 55
+ 428, 428, 428, 428, 428, 428, 428, 428,
+ // 56 - 63
+ 428, 428, 428, 428, 428, 428, 428, 428,
+ // 64 - 71
+ 654, 654, 654, 654, 654, 654, 654, 654,
+ // 72 - 79
+ 1072, 1072, 1072, 1072, 1104, 1104, 1104, 1104,
+ // 80 - 87
+ 1136, 1136, 1136, 1136, 1168, 1168, 1168, 1168,
+ // 88 - 95
+ 1200, 1200, 1200, 1200, 1232, 1232, 1232, 1232,
+ // 96 - 103
+ 622, 622, 622, 622, 622, 622, 622, 622,
+ // 104 - 111
+ 1008, 1008, 1008, 1008, 1040, 1040, 1040, 1040,
+ // 112 - 119
+ 44, 44, 44, 44, 44, 44, 44, 44,
+ // 120 - 127
+ 44, 44, 44, 44, 44, 44, 44, 44,
+ // 128 - 135
+ 396, 396, 396, 396, 396, 396, 396, 396,
+ // 136 - 143
+ 396, 396, 396, 396, 396, 396, 396, 396,
+ // 144 - 151
+ 1712, 1712, 1712, 1712, 1744, 1744, 1744, 1744,
+ // 152 - 159
+ 846, 846, 846, 846, 846, 846, 846, 846,
+ // 160 - 167
+ 1264, 1264, 1264, 1264, 1296, 1296, 1296, 1296,
+ // 168 - 175
+ 1328, 1328, 1328, 1328, 1360, 1360, 1360, 1360,
+ // 176 - 183
+ 1392, 1392, 1392, 1392, 1424, 1424, 1424, 1424,
+ // 184 - 191
+ 686, 686, 686, 686, 686, 686, 686, 686,
+ // 192 - 199
+ 910, 910, 910, 910, 910, 910, 910, 910,
+ // 200 - 207
+ 1968, 1968, 1968, 1968, 2000, 2000, 2000, 2000,
+ // 208 - 215
+ 2032, 2032, 2032, 2032, 16, 16, 16, 16,
+ // 216 - 223
+ 10257, 10257, 10257, 10257, 12305, 12305, 12305, 12305,
+ // 224 - 231
+ 330, 330, 330, 330, 330, 330, 330, 330,
+ // 232 - 239
+ 330, 330, 330, 330, 330, 330, 330, 330,
+ // 240 - 247
+ 330, 330, 330, 330, 330, 330, 330, 330,
+ // 248 - 255
+ 330, 330, 330, 330, 330, 330, 330, 330,
+ // 256 - 263
+ 362, 362, 362, 362, 362, 362, 362, 362,
+ // 264 - 271
+ 362, 362, 362, 362, 362, 362, 362, 362,
+ // 272 - 279
+ 362, 362, 362, 362, 362, 362, 362, 362,
+ // 280 - 287
+ 362, 362, 362, 362, 362, 362, 362, 362,
+ // 288 - 295
+ 878, 878, 878, 878, 878, 878, 878, 878,
+ // 296 - 303
+ 1904, 1904, 1904, 1904, 1936, 1936, 1936, 1936,
+ // 304 - 311
+ -18413, -18413, -16365, -16365, -14317, -14317, -10221, -10221,
+ // 312 - 319
+ 590, 590, 590, 590, 590, 590, 590, 590,
+ // 320 - 327
+ 782, 782, 782, 782, 782, 782, 782, 782,
+ // 328 - 335
+ 1584, 1584, 1584, 1584, 1616, 1616, 1616, 1616,
+ // 336 - 343
+ 1648, 1648, 1648, 1648, 1680, 1680, 1680, 1680,
+ // 344 - 351
+ 814, 814, 814, 814, 814, 814, 814, 814,
+ // 352 - 359
+ 1776, 1776, 1776, 1776, 1808, 1808, 1808, 1808,
+ // 360 - 367
+ 1840, 1840, 1840, 1840, 1872, 1872, 1872, 1872,
+ // 368 - 375
+ 6157, 6157, 6157, 6157, 6157, 6157, 6157, 6157,
+ // 376 - 383
+ 6157, 6157, 6157, 6157, 6157, 6157, 6157, 6157,
+ // 384 - 391
+ -12275, -12275, -12275, -12275, -12275, -12275, -12275, -12275,
+ // 392 - 399
+ -12275, -12275, -12275, -12275, -12275, -12275, -12275, -12275,
+ // 400 - 407
+ 14353, 14353, 14353, 14353, 16401, 16401, 16401, 16401,
+ // 408 - 415
+ 22547, 22547, 24595, 24595, 20497, 20497, 20497, 20497,
+ // 416 - 423
+ 18449, 18449, 18449, 18449, 26643, 26643, 28691, 28691,
+ // 424 - 431
+ 30739, 30739, -32749, -32749, -30701, -30701, -28653, -28653,
+ // 432 - 439
+ -26605, -26605, -24557, -24557, -22509, -22509, -20461, -20461,
+ // 440 - 447
+ 8207, 8207, 8207, 8207, 8207, 8207, 8207, 8207,
+ // 448 - 455
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 456 - 463
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 464 - 471
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 472 - 479
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 480 - 487
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 488 - 495
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 496 - 503
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 504 - 511
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 512 - 519
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 520 - 527
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 528 - 535
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 536 - 543
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 544 - 551
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 552 - 559
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 560 - 567
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 568 - 575
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 576 - 583
+ 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107,
+ // 584 - 591
+ 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107,
+ // 592 - 599
+ 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107,
+ // 600 - 607
+ 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107,
+ // 608 - 615
+ 266, 266, 266, 266, 266, 266, 266, 266,
+ // 616 - 623
+ 266, 266, 266, 266, 266, 266, 266, 266,
+ // 624 - 631
+ 266, 266, 266, 266, 266, 266, 266, 266,
+ // 632 - 639
+ 266, 266, 266, 266, 266, 266, 266, 266,
+ // 640 - 647
+ 298, 298, 298, 298, 298, 298, 298, 298,
+ // 648 - 655
+ 298, 298, 298, 298, 298, 298, 298, 298,
+ // 656 - 663
+ 298, 298, 298, 298, 298, 298, 298, 298,
+ // 664 - 671
+ 298, 298, 298, 298, 298, 298, 298, 298,
+ // 672 - 679
+ 524, 524, 524, 524, 524, 524, 524, 524,
+ // 680 - 687
+ 524, 524, 524, 524, 524, 524, 524, 524,
+ // 688 - 695
+ 556, 556, 556, 556, 556, 556, 556, 556,
+ // 696 - 703
+ 556, 556, 556, 556, 556, 556, 556, 556,
+ // 704 - 711
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 712 - 719
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 720 - 727
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 728 - 735
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 736 - 743
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 744 - 751
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 752 - 759
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 760 - 767
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 768 - 775
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 776 - 783
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 784 - 791
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 792 - 799
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 800 - 807
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 808 - 815
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 816 - 823
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 824 - 831
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 832 - 839
+ 460, 460, 460, 460, 460, 460, 460, 460,
+ // 840 - 847
+ 460, 460, 460, 460, 460, 460, 460, 460,
+ // 848 - 855
+ 492, 492, 492, 492, 492, 492, 492, 492,
+ // 856 - 863
+ 492, 492, 492, 492, 492, 492, 492, 492,
+ // 864 - 871
+ 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059,
+ // 872 - 879
+ 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059,
+ // 880 - 887
+ 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059,
+ // 888 - 895
+ 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059,
+ // 896 - 903
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 904 - 911
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 912 - 919
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 920 - 927
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 928 - 935
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 936 - 943
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 944 - 951
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 952 - 959
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 960 - 967
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 968 - 975
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 976 - 983
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 984 - 991
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 992 - 999
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 1000 - 1007
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 1008 - 1015
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 1016 - 1023
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ };
+
+ // Additional make up codes for both White and Black runs
+ static short[] additionalMakeup = {
+ 28679, 28679, 31752, (short)32777,
+ (short)33801, (short)34825, (short)35849, (short)36873,
+ (short)29703, (short)29703, (short)30727, (short)30727,
+ (short)37897, (short)38921, (short)39945, (short)40969
+ };
+
+ // Initial black run look up table, uses the first 4 bits of a code
+ static short[] initBlack = {
+ // 0 - 7
+ 3226, 6412, 200, 168, 38, 38, 134, 134,
+ // 8 - 15
+ 100, 100, 100, 100, 68, 68, 68, 68
+ };
+
+ //
+ static short[] twoBitBlack = {292, 260, 226, 226}; // 0 - 3
+
+ // Main black run table, using the last 9 bits of possible 13 bit code
+ static short[] black = {
+ // 0 - 7
+ 62, 62, 30, 30, 0, 0, 0, 0,
+ // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ // 24 - 31
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ // 32 - 39
+ 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225,
+ // 40 - 47
+ 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225,
+ // 48 - 55
+ 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225,
+ // 56 - 63
+ 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225,
+ // 64 - 71
+ 588, 588, 588, 588, 588, 588, 588, 588,
+ // 72 - 79
+ 1680, 1680, 20499, 22547, 24595, 26643, 1776, 1776,
+ // 80 - 87
+ 1808, 1808, -24557, -22509, -20461, -18413, 1904, 1904,
+ // 88 - 95
+ 1936, 1936, -16365, -14317, 782, 782, 782, 782,
+ // 96 - 103
+ 814, 814, 814, 814, -12269, -10221, 10257, 10257,
+ // 104 - 111
+ 12305, 12305, 14353, 14353, 16403, 18451, 1712, 1712,
+ // 112 - 119
+ 1744, 1744, 28691, 30739, -32749, -30701, -28653, -26605,
+ // 120 - 127
+ 2061, 2061, 2061, 2061, 2061, 2061, 2061, 2061,
+ // 128 - 135
+ 424, 424, 424, 424, 424, 424, 424, 424,
+ // 136 - 143
+ 424, 424, 424, 424, 424, 424, 424, 424,
+ // 144 - 151
+ 424, 424, 424, 424, 424, 424, 424, 424,
+ // 152 - 159
+ 424, 424, 424, 424, 424, 424, 424, 424,
+ // 160 - 167
+ 750, 750, 750, 750, 1616, 1616, 1648, 1648,
+ // 168 - 175
+ 1424, 1424, 1456, 1456, 1488, 1488, 1520, 1520,
+ // 176 - 183
+ 1840, 1840, 1872, 1872, 1968, 1968, 8209, 8209,
+ // 184 - 191
+ 524, 524, 524, 524, 524, 524, 524, 524,
+ // 192 - 199
+ 556, 556, 556, 556, 556, 556, 556, 556,
+ // 200 - 207
+ 1552, 1552, 1584, 1584, 2000, 2000, 2032, 2032,
+ // 208 - 215
+ 976, 976, 1008, 1008, 1040, 1040, 1072, 1072,
+ // 216 - 223
+ 1296, 1296, 1328, 1328, 718, 718, 718, 718,
+ // 224 - 231
+ 456, 456, 456, 456, 456, 456, 456, 456,
+ // 232 - 239
+ 456, 456, 456, 456, 456, 456, 456, 456,
+ // 240 - 247
+ 456, 456, 456, 456, 456, 456, 456, 456,
+ // 248 - 255
+ 456, 456, 456, 456, 456, 456, 456, 456,
+ // 256 - 263
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 264 - 271
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 272 - 279
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 280 - 287
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 288 - 295
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 296 - 303
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 304 - 311
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 312 - 319
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 320 - 327
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 328 - 335
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 336 - 343
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 344 - 351
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 352 - 359
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 360 - 367
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 368 - 375
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 376 - 383
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 384 - 391
+ 490, 490, 490, 490, 490, 490, 490, 490,
+ // 392 - 399
+ 490, 490, 490, 490, 490, 490, 490, 490,
+ // 400 - 407
+ 4113, 4113, 6161, 6161, 848, 848, 880, 880,
+ // 408 - 415
+ 912, 912, 944, 944, 622, 622, 622, 622,
+ // 416 - 423
+ 654, 654, 654, 654, 1104, 1104, 1136, 1136,
+ // 424 - 431
+ 1168, 1168, 1200, 1200, 1232, 1232, 1264, 1264,
+ // 432 - 439
+ 686, 686, 686, 686, 1360, 1360, 1392, 1392,
+ // 440 - 447
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ // 448 - 455
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 456 - 463
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 464 - 471
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 472 - 479
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 480 - 487
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 488 - 495
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 496 - 503
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 504 - 511
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ };
+
+ static byte[] twoDCodes = {
+ // 0 - 7
+ 80, 88, 23, 71, 30, 30, 62, 62,
+ // 8 - 15
+ 4, 4, 4, 4, 4, 4, 4, 4,
+ // 16 - 23
+ 11, 11, 11, 11, 11, 11, 11, 11,
+ // 24 - 31
+ 11, 11, 11, 11, 11, 11, 11, 11,
+ // 32 - 39
+ 35, 35, 35, 35, 35, 35, 35, 35,
+ // 40 - 47
+ 35, 35, 35, 35, 35, 35, 35, 35,
+ // 48 - 55
+ 51, 51, 51, 51, 51, 51, 51, 51,
+ // 56 - 63
+ 51, 51, 51, 51, 51, 51, 51, 51,
+ // 64 - 71
+ 41, 41, 41, 41, 41, 41, 41, 41,
+ // 72 - 79
+ 41, 41, 41, 41, 41, 41, 41, 41,
+ // 80 - 87
+ 41, 41, 41, 41, 41, 41, 41, 41,
+ // 88 - 95
+ 41, 41, 41, 41, 41, 41, 41, 41,
+ // 96 - 103
+ 41, 41, 41, 41, 41, 41, 41, 41,
+ // 104 - 111
+ 41, 41, 41, 41, 41, 41, 41, 41,
+ // 112 - 119
+ 41, 41, 41, 41, 41, 41, 41, 41,
+ // 120 - 127
+ 41, 41, 41, 41, 41, 41, 41, 41,
+ };
+
+ /**
+ * @param fillOrder The fill order of the compressed data bytes.
+ * @param w The width of the image in pixels
+ * @param h The height of the image in pixels
+ */
+ public TIFFFaxDecoder(int fillOrder, int w, int h) {
+ this.fillOrder = fillOrder;
+ this.w = w;
+ this.h = h;
+
+ this.bitPointer = 0;
+ this.bytePointer = 0;
+ this.prevChangingElems = new int[w];
+ this.currChangingElems = new int[w];
+ }
+
+ // One-dimensional decoding methods
+
+ public void decode1D(byte[] buffer, byte[] compData,
+ int startX, int height) {
+ this.data = compData;
+
+ int lineOffset = 0;
+ int scanlineStride = (w + 7)/8;
+
+ bitPointer = 0;
+ bytePointer = 0;
+
+ for (int i = 0; i < height; i++) {
+ decodeNextScanline(buffer, lineOffset, startX);
+ lineOffset += scanlineStride;
+ }
+ }
+
+ public void decodeNextScanline(byte[] buffer,
+ int lineOffset, int bitOffset) {
+ int bits = 0, code = 0, isT = 0;
+ int current, entry, twoBits;
+ boolean isWhite = true;
+
+ // Initialize starting of the changing elements array
+ changingElemSize = 0;
+
+ // While scanline not complete
+ while (bitOffset < w) {
+ while (isWhite) {
+ // White run
+ current = nextNBits(10);
+ entry = white[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x0f;
+
+ if (bits == 12) { // Additional Make up code
+ // Get the next 2 bits
+ twoBits = nextLesserThan8Bits(2);
+ // Consolidate the 2 new bits and last 2 bits into 4 bits
+ current = ((current << 2) & 0x000c) | twoBits;
+ entry = additionalMakeup[current];
+ bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111
+ code = (entry >>> 4) & 0x0fff; // 12 bits
+ bitOffset += code; // Skip white run
+
+ updatePointer(4 - bits);
+ } else if (bits == 0) { // ERROR
+ throw new Error("TIFFFaxDecoder0");
+ } else if (bits == 15) { // EOL
+ throw new Error("TIFFFaxDecoder1");
+ } else {
+ // 11 bits - 0000 0111 1111 1111 = 0x07ff
+ code = (entry >>> 5) & 0x07ff;
+ bitOffset += code;
+
+ updatePointer(10 - bits);
+ if (isT == 0) {
+ isWhite = false;
+ currChangingElems[changingElemSize++] = bitOffset;
+ }
+ }
+ }
+
+ // Check whether this run completed one width, if so
+ // advance to next byte boundary for compression = 2.
+ if (bitOffset == w) {
+ if (compression == 2) {
+ advancePointer();
+ }
+ break;
+ }
+
+ while (!isWhite) {
+ // Black run
+ current = nextLesserThan8Bits(4);
+ entry = initBlack[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x000f;
+ code = (entry >>> 5) & 0x07ff;
+
+ if (code == 100) {
+ current = nextNBits(9);
+ entry = black[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x000f;
+ code = (entry >>> 5) & 0x07ff;
+
+ if (bits == 12) {
+ // Additional makeup codes
+ updatePointer(5);
+ current = nextLesserThan8Bits(4);
+ entry = additionalMakeup[current];
+ bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111
+ code = (entry >>> 4) & 0x0fff; // 12 bits
+
+ setToBlack(buffer, lineOffset, bitOffset, code);
+ bitOffset += code;
+
+ updatePointer(4 - bits);
+ } else if (bits == 15) {
+ // EOL code
+ throw new Error("TIFFFaxDecoder2");
+ } else {
+ setToBlack(buffer, lineOffset, bitOffset, code);
+ bitOffset += code;
+
+ updatePointer(9 - bits);
+ if (isT == 0) {
+ isWhite = true;
+ currChangingElems[changingElemSize++] = bitOffset;
+ }
+ }
+ } else if (code == 200) {
+ // Is a Terminating code
+ current = nextLesserThan8Bits(2);
+ entry = twoBitBlack[current];
+ code = (entry >>> 5) & 0x07ff;
+ bits = (entry >>> 1) & 0x0f;
+
+ setToBlack(buffer, lineOffset, bitOffset, code);
+ bitOffset += code;
+
+ updatePointer(2 - bits);
+ isWhite = true;
+ currChangingElems[changingElemSize++] = bitOffset;
+ } else {
+ // Is a Terminating code
+ setToBlack(buffer, lineOffset, bitOffset, code);
+ bitOffset += code;
+
+ updatePointer(4 - bits);
+ isWhite = true;
+ currChangingElems[changingElemSize++] = bitOffset;
+ }
+ }
+
+ // Check whether this run completed one width
+ if (bitOffset == w) {
+ if (compression == 2) {
+ advancePointer();
+ }
+ break;
+ }
+ }
+
+ currChangingElems[changingElemSize++] = bitOffset;
+ }
+
+ // Two-dimensional decoding methods
+
+ public void decode2D(byte[] buffer,
+ byte[] compData,
+ int startX,
+ int height,
+ long tiffT4Options) {
+ this.data = compData;
+ compression = 3;
+
+ bitPointer = 0;
+ bytePointer = 0;
+
+ int scanlineStride = (w + 7)/8;
+
+ int a0, a1, b1, b2;
+ int[] b = new int[2];
+ int entry, code, bits;
+ boolean isWhite;
+ int currIndex = 0;
+ int[] temp;
+
+ // fillBits - dealt with this in readEOL
+ // 1D/2D encoding - dealt with this in readEOL
+
+ // uncompressedMode - haven't dealt with this yet.
+
+
+ oneD = (int)(tiffT4Options & 0x01);
+ uncompressedMode = (int)((tiffT4Options & 0x02) >> 1);
+ fillBits = (int)((tiffT4Options & 0x04) >> 2);
+
+ // The data must start with an EOL code
+ if (readEOL() != 1) {
+ throw new Error("TIFFFaxDecoder3");
+ }
+
+ int lineOffset = 0;
+ int bitOffset;
+
+ // Then the 1D encoded scanline data will occur, changing elements
+ // array gets set.
+ decodeNextScanline(buffer, lineOffset, startX);
+ lineOffset += scanlineStride;
+
+ for (int lines = 1; lines < height; lines++) {
+
+ // Every line must begin with an EOL followed by a bit which
+ // indicates whether the following scanline is 1D or 2D encoded.
+ if (readEOL() == 0) {
+ // 2D encoded scanline follows
+
+ // Initialize previous scanlines changing elements, and
+ // initialize current scanline's changing elements array
+ temp = prevChangingElems;
+ prevChangingElems = currChangingElems;
+ currChangingElems = temp;
+ currIndex = 0;
+
+ // a0 has to be set just before the start of this scanline.
+ a0 = -1;
+ isWhite = true;
+ bitOffset = startX;
+
+ lastChangingElement = 0;
+
+ while (bitOffset < w) {
+ // Get the next changing element
+ getNextChangingElement(a0, isWhite, b);
+
+ b1 = b[0];
+ b2 = b[1];
+
+ // Get the next seven bits
+ entry = nextLesserThan8Bits(7);
+
+ // Run these through the 2DCodes table
+ entry = (int)(twoDCodes[entry] & 0xff);
+
+ // Get the code and the number of bits used up
+ code = (entry & 0x78) >>> 3;
+ bits = entry & 0x07;
+
+ if (code == 0) {
+ if (!isWhite) {
+ setToBlack(buffer, lineOffset, bitOffset,
+ b2 - bitOffset);
+ }
+ bitOffset = a0 = b2;
+
+ // Set pointer to consume the correct number of bits.
+ updatePointer(7 - bits);
+ } else if (code == 1) {
+ // Horizontal
+ updatePointer(7 - bits);
+
+ // identify the next 2 codes.
+ int number;
+ if (isWhite) {
+ number = decodeWhiteCodeWord();
+ bitOffset += number;
+ currChangingElems[currIndex++] = bitOffset;
+
+ number = decodeBlackCodeWord();
+ setToBlack(buffer, lineOffset, bitOffset, number);
+ bitOffset += number;
+ currChangingElems[currIndex++] = bitOffset;
+ } else {
+ number = decodeBlackCodeWord();
+ setToBlack(buffer, lineOffset, bitOffset, number);
+ bitOffset += number;
+ currChangingElems[currIndex++] = bitOffset;
+
+ number = decodeWhiteCodeWord();
+ bitOffset += number;
+ currChangingElems[currIndex++] = bitOffset;
+ }
+
+ a0 = bitOffset;
+ } else if (code <= 8) {
+ // Vertical
+ a1 = b1 + (code - 5);
+
+ currChangingElems[currIndex++] = a1;
+
+ // We write the current color till a1 - 1 pos,
+ // since a1 is where the next color starts
+ if (!isWhite) {
+ setToBlack(buffer, lineOffset, bitOffset,
+ a1 - bitOffset);
+ }
+ bitOffset = a0 = a1;
+ isWhite = !isWhite;
+
+ updatePointer(7 - bits);
+ } else {
+ throw new Error("TIFFFaxDecoder4");
+ }
+ }
+
+ // Add the changing element beyond the current scanline for the
+ // other color too
+ currChangingElems[currIndex++] = bitOffset;
+ changingElemSize = currIndex;
+ } else {
+ // 1D encoded scanline follows
+ decodeNextScanline(buffer, lineOffset, startX);
+ }
+
+ lineOffset += scanlineStride;
+ }
+ }
+
+ public synchronized void decodeT6(byte[] buffer,
+ byte[] compData,
+ int startX,
+ int height,
+ long tiffT6Options) {
+ this.data = compData;
+ compression = 4;
+
+ bitPointer = 0;
+ bytePointer = 0;
+
+ int scanlineStride = (w + 7)/8;
+
+ int a0, a1, b1, b2;
+ int entry, code, bits;
+ boolean isWhite;
+ int currIndex;
+ int[] temp;
+
+ // Return values from getNextChangingElement
+ int[] b = new int[2];
+
+ // uncompressedMode - have written some code for this, but this
+ // has not been tested due to lack of test images using this optional
+
+ uncompressedMode = (int)((tiffT6Options & 0x02) >> 1);
+
+ // Local cached reference
+ int[] cce = currChangingElems;
+
+ // Assume invisible preceding row of all white pixels and insert
+ // both black and white changing elements beyond the end of this
+ // imaginary scanline.
+ changingElemSize = 0;
+ cce[changingElemSize++] = w;
+ cce[changingElemSize++] = w;
+
+ int lineOffset = 0;
+ int bitOffset;
+
+ for (int lines = 0; lines < height; lines++) {
+ // a0 has to be set just before the start of the scanline.
+ a0 = -1;
+ isWhite = true;
+
+ // Assign the changing elements of the previous scanline to
+ // prevChangingElems and start putting this new scanline's
+ // changing elements into the currChangingElems.
+ temp = prevChangingElems;
+ prevChangingElems = currChangingElems;
+ cce = currChangingElems = temp;
+ currIndex = 0;
+
+ // Start decoding the scanline at startX in the raster
+ bitOffset = startX;
+
+ // Reset search start position for getNextChangingElement
+ lastChangingElement = 0;
+
+ // Till one whole scanline is decoded
+ while (bitOffset < w) {
+ // Get the next changing element
+ getNextChangingElement(a0, isWhite, b);
+ b1 = b[0];
+ b2 = b[1];
+
+ // Get the next seven bits
+ entry = nextLesserThan8Bits(7);
+ // Run these through the 2DCodes table
+ entry = (int)(twoDCodes[entry] & 0xff);
+
+ // Get the code and the number of bits used up
+ code = (entry & 0x78) >>> 3;
+ bits = entry & 0x07;
+
+ if (code == 0) { // Pass
+ // We always assume WhiteIsZero format for fax.
+ if (!isWhite) {
+ setToBlack(buffer, lineOffset, bitOffset,
+ b2 - bitOffset);
+ }
+ bitOffset = a0 = b2;
+
+ // Set pointer to only consume the correct number of bits.
+ updatePointer(7 - bits);
+ } else if (code == 1) { // Horizontal
+ // Set pointer to only consume the correct number of bits.
+ updatePointer(7 - bits);
+
+ // identify the next 2 alternating color codes.
+ int number;
+ if (isWhite) {
+ // Following are white and black runs
+ number = decodeWhiteCodeWord();
+ bitOffset += number;
+ cce[currIndex++] = bitOffset;
+
+ number = decodeBlackCodeWord();
+ setToBlack(buffer, lineOffset, bitOffset, number);
+ bitOffset += number;
+ cce[currIndex++] = bitOffset;
+ } else {
+ // First a black run and then a white run follows
+ number = decodeBlackCodeWord();
+ setToBlack(buffer, lineOffset, bitOffset, number);
+ bitOffset += number;
+ cce[currIndex++] = bitOffset;
+
+ number = decodeWhiteCodeWord();
+ bitOffset += number;
+ cce[currIndex++] = bitOffset;
+ }
+
+ a0 = bitOffset;
+ } else if (code <= 8) { // Vertical
+ a1 = b1 + (code - 5);
+ cce[currIndex++] = a1;
+
+ // We write the current color till a1 - 1 pos,
+ // since a1 is where the next color starts
+ if (!isWhite) {
+ setToBlack(buffer, lineOffset, bitOffset,
+ a1 - bitOffset);
+ }
+ bitOffset = a0 = a1;
+ isWhite = !isWhite;
+
+ updatePointer(7 - bits);
+ } else if (code == 11) {
+ if (nextLesserThan8Bits(3) != 7) {
+ throw new Error("TIFFFaxDecoder5");
+ }
+
+ int zeros = 0;
+ boolean exit = false;
+
+ while (!exit) {
+ while (nextLesserThan8Bits(1) != 1) {
+ zeros++;
+ }
+
+ if (zeros > 5) {
+ // Exit code
+
+ // Zeros before exit code
+ zeros = zeros - 6;
+
+ if (!isWhite && (zeros > 0)) {
+ cce[currIndex++] = bitOffset;
+ }
+
+ // Zeros before the exit code
+ bitOffset += zeros;
+ if (zeros > 0) {
+ // Some zeros have been written
+ isWhite = true;
+ }
+
+ // Read in the bit which specifies the color of
+ // the following run
+ if (nextLesserThan8Bits(1) == 0) {
+ if (!isWhite) {
+ cce[currIndex++] = bitOffset;
+ }
+ isWhite = true;
+ } else {
+ if (isWhite) {
+ cce[currIndex++] = bitOffset;
+ }
+ isWhite = false;
+ }
+
+ exit = true;
+ }
+
+ if (zeros == 5) {
+ if (!isWhite) {
+ cce[currIndex++] = bitOffset;
+ }
+ bitOffset += zeros;
+
+ // Last thing written was white
+ isWhite = true;
+ } else {
+ bitOffset += zeros;
+
+ cce[currIndex++] = bitOffset;
+ setToBlack(buffer, lineOffset, bitOffset, 1);
+ ++bitOffset;
+
+ // Last thing written was black
+ isWhite = false;
+ }
+
+ }
+ } else {
+ throw new Error("TIFFFaxDecoder5");
+ }
+ }
+
+ // Add the changing element beyond the current scanline for the
+ // other color too
+ cce[currIndex++] = bitOffset;
+
+ // Number of changing elements in this scanline.
+ changingElemSize = currIndex;
+
+ lineOffset += scanlineStride;
+ }
+ }
+
+ private void setToBlack(byte[] buffer,
+ int lineOffset, int bitOffset,
+ int numBits) {
+ int bitNum = 8*lineOffset + bitOffset;
+ int lastBit = bitNum + numBits;
+
+ int byteNum = bitNum >> 3;
+
+ // Handle bits in first byte
+ int shift = bitNum & 0x7;
+ if (shift > 0) {
+ int maskVal = 1 << (7 - shift);
+ byte val = buffer[byteNum];
+ while (maskVal > 0 && bitNum < lastBit) {
+ val |= maskVal;
+ maskVal >>= 1;
+ ++bitNum;
+ }
+ buffer[byteNum] = val;
+ }
+
+ // Fill in 8 bits at a time
+ byteNum = bitNum >> 3;
+ while (bitNum < lastBit - 7) {
+ buffer[byteNum++] = (byte)255;
+ bitNum += 8;
+ }
+
+ // Fill in remaining bits
+ while (bitNum < lastBit) {
+ byteNum = bitNum >> 3;
+ buffer[byteNum] |= 1 << (7 - (bitNum & 0x7));
+ ++bitNum;
+ }
+ }
+
+ // Returns run length
+ private int decodeWhiteCodeWord() {
+ int current, entry, bits, isT, twoBits, code = -1;
+ int runLength = 0;
+ boolean isWhite = true;
+
+ while (isWhite) {
+ current = nextNBits(10);
+ entry = white[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x0f;
+
+ if (bits == 12) { // Additional Make up code
+ // Get the next 2 bits
+ twoBits = nextLesserThan8Bits(2);
+ // Consolidate the 2 new bits and last 2 bits into 4 bits
+ current = ((current << 2) & 0x000c) | twoBits;
+ entry = additionalMakeup[current];
+ bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111
+ code = (entry >>> 4) & 0x0fff; // 12 bits
+ runLength += code;
+ updatePointer(4 - bits);
+ } else if (bits == 0) { // ERROR
+ throw new Error("TIFFFaxDecoder0");
+ } else if (bits == 15) { // EOL
+ throw new Error("TIFFFaxDecoder1");
+ } else {
+ // 11 bits - 0000 0111 1111 1111 = 0x07ff
+ code = (entry >>> 5) & 0x07ff;
+ runLength += code;
+ updatePointer(10 - bits);
+ if (isT == 0) {
+ isWhite = false;
+ }
+ }
+ }
+
+ return runLength;
+ }
+
+ // Returns run length
+ private int decodeBlackCodeWord() {
+ int current, entry, bits, isT, code = -1;
+ int runLength = 0;
+ boolean isWhite = false;
+
+ while (!isWhite) {
+ current = nextLesserThan8Bits(4);
+ entry = initBlack[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x000f;
+ code = (entry >>> 5) & 0x07ff;
+
+ if (code == 100) {
+ current = nextNBits(9);
+ entry = black[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x000f;
+ code = (entry >>> 5) & 0x07ff;
+
+ if (bits == 12) {
+ // Additional makeup codes
+ updatePointer(5);
+ current = nextLesserThan8Bits(4);
+ entry = additionalMakeup[current];
+ bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111
+ code = (entry >>> 4) & 0x0fff; // 12 bits
+ runLength += code;
+
+ updatePointer(4 - bits);
+ } else if (bits == 15) {
+ // EOL code
+ throw new Error("TIFFFaxDecoder2");
+ } else {
+ runLength += code;
+ updatePointer(9 - bits);
+ if (isT == 0) {
+ isWhite = true;
+ }
+ }
+ } else if (code == 200) {
+ // Is a Terminating code
+ current = nextLesserThan8Bits(2);
+ entry = twoBitBlack[current];
+ code = (entry >>> 5) & 0x07ff;
+ runLength += code;
+ bits = (entry >>> 1) & 0x0f;
+ updatePointer(2 - bits);
+ isWhite = true;
+ } else {
+ // Is a Terminating code
+ runLength += code;
+ updatePointer(4 - bits);
+ isWhite = true;
+ }
+ }
+
+ return runLength;
+ }
+
+ private int readEOL() {
+ if (fillBits == 0) {
+ if (nextNBits(12) != 1) {
+ throw new Error("TIFFFaxDecoder6");
+ }
+ } else if (fillBits == 1) {
+
+ // First EOL code word xxxx 0000 0000 0001 will occur
+ // As many fill bits will be present as required to make
+ // the EOL code of 12 bits end on a byte boundary.
+
+ int bitsLeft = 8 - bitPointer;
+
+ if (nextNBits(bitsLeft) != 0) {
+ throw new Error("TIFFFaxDecoder8");
+ }
+
+ // If the number of bitsLeft is less than 8, then to have a 12
+ // bit EOL sequence, two more bytes are certainly going to be
+ // required. The first of them has to be all zeros, so ensure
+ // that.
+ if (bitsLeft < 4) {
+ if (nextNBits(8) != 0) {
+ throw new Error("TIFFFaxDecoder8");
+ }
+ }
+
+ // There might be a random number of fill bytes with 0s, so
+ // loop till the EOL of 0000 0001 is found, as long as all
+ // the bytes preceding it are 0's.
+ int n;
+ while ((n = nextNBits(8)) != 1) {
+
+ // If not all zeros
+ if (n != 0) {
+ throw new Error("TIFFFaxDecoder8");
+ }
+ }
+ }
+
+ // If one dimensional encoding mode, then always return 1
+ if (oneD == 0) {
+ return 1;
+ } else {
+ // Otherwise for 2D encoding mode,
+ // The next one bit signifies 1D/2D encoding of next line.
+ return nextLesserThan8Bits(1);
+ }
+ }
+
+ private void getNextChangingElement(int a0, boolean isWhite, int[] ret) {
+ // Local copies of instance variables
+ int[] pce = this.prevChangingElems;
+ int ces = this.changingElemSize;
+
+ // If the previous match was at an odd element, we still
+ // have to search the preceeding element.
+ // int start = lastChangingElement & ~0x1;
+ int start = lastChangingElement > 0 ? lastChangingElement - 1 : 0;
+ if (isWhite) {
+ start &= ~0x1; // Search even numbered elements
+ } else {
+ start |= 0x1; // Search odd numbered elements
+ }
+
+ int i = start;
+ for (; i < ces; i += 2) {
+ int temp = pce[i];
+ if (temp > a0) {
+ lastChangingElement = i;
+ ret[0] = temp;
+ break;
+ }
+ }
+
+ if (i + 1 < ces) {
+ ret[1] = pce[i + 1];
+ }
+ }
+
+ private int nextNBits(int bitsToGet) {
+ byte b, next, next2next;
+ int l = data.length - 1;
+ int bp = this.bytePointer;
+
+ if (fillOrder == 1) {
+ b = data[bp];
+
+ if (bp == l) {
+ next = 0x00;
+ next2next = 0x00;
+ } else if ((bp + 1) == l) {
+ next = data[bp + 1];
+ next2next = 0x00;
+ } else {
+ next = data[bp + 1];
+ next2next = data[bp + 2];
+ }
+ } else if (fillOrder == 2) {
+ b = flipTable[data[bp] & 0xff];
+
+ if (bp == l) {
+ next = 0x00;
+ next2next = 0x00;
+ } else if ((bp + 1) == l) {
+ next = flipTable[data[bp + 1] & 0xff];
+ next2next = 0x00;
+ } else {
+ next = flipTable[data[bp + 1] & 0xff];
+ next2next = flipTable[data[bp + 2] & 0xff];
+ }
+ } else {
+ throw new Error("TIFFFaxDecoder7");
+ }
+
+ int bitsLeft = 8 - bitPointer;
+ int bitsFromNextByte = bitsToGet - bitsLeft;
+ int bitsFromNext2NextByte = 0;
+ if (bitsFromNextByte > 8) {
+ bitsFromNext2NextByte = bitsFromNextByte - 8;
+ bitsFromNextByte = 8;
+ }
+
+ bytePointer++;
+
+ int i1 = (b & table1[bitsLeft]) << (bitsToGet - bitsLeft);
+ int i2 = (next & table2[bitsFromNextByte]) >>> (8 - bitsFromNextByte);
+
+ int i3 = 0;
+ if (bitsFromNext2NextByte != 0) {
+ i2 <<= bitsFromNext2NextByte;
+ i3 = (next2next & table2[bitsFromNext2NextByte]) >>>
+ (8 - bitsFromNext2NextByte);
+ i2 |= i3;
+ bytePointer++;
+ bitPointer = bitsFromNext2NextByte;
+ } else {
+ if (bitsFromNextByte == 8) {
+ bitPointer = 0;
+ bytePointer++;
+ } else {
+ bitPointer = bitsFromNextByte;
+ }
+ }
+
+ int i = i1 | i2;
+ return i;
+ }
+
+ private int nextLesserThan8Bits(int bitsToGet) {
+ byte b, next;
+ int l = data.length - 1;
+ int bp = this.bytePointer;
+
+ if (fillOrder == 1) {
+ b = data[bp];
+ if (bp == l) {
+ next = 0x00;
+ } else {
+ next = data[bp + 1];
+ }
+ } else if (fillOrder == 2) {
+ b = flipTable[data[bp] & 0xff];
+ if (bp == l) {
+ next = 0x00;
+ } else {
+ next = flipTable[data[bp + 1] & 0xff];
+ }
+ } else {
+ throw new Error("TIFFFaxDecoder7");
+ }
+
+ int bitsLeft = 8 - bitPointer;
+ int bitsFromNextByte = bitsToGet - bitsLeft;
+
+ int shift = bitsLeft - bitsToGet;
+ int i1, i2;
+ if (shift >= 0) {
+ i1 = (b & table1[bitsLeft]) >>> shift;
+ bitPointer += bitsToGet;
+ if (bitPointer == 8) {
+ bitPointer = 0;
+ bytePointer++;
+ }
+ } else {
+ i1 = (b & table1[bitsLeft]) << (-shift);
+ i2 = (next & table2[bitsFromNextByte]) >>> (8 - bitsFromNextByte);
+
+ i1 |= i2;
+ bytePointer++;
+ bitPointer = bitsFromNextByte;
+ }
+
+ return i1;
+ }
+
+ // Move pointer backwards by given amount of bits
+ private void updatePointer(int bitsToMoveBack) {
+ int i = bitPointer - bitsToMoveBack;
+
+ if (i < 0) {
+ bytePointer--;
+ bitPointer = 8 + i;
+ } else {
+ bitPointer = i;
+ }
+ }
+
+ // Move to the next byte boundary
+ private boolean advancePointer() {
+ if (bitPointer != 0) {
+ bytePointer++;
+ bitPointer = 0;
+ }
+
+ return true;
+ }
+}
+
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package will hold the PDFBox implementations of the filters that are used in PDF documents.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.FilterInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * This class represents an ASCII85 stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class ASCII85InputStream extends FilterInputStream
+{
+ private int index;
+ private int n;
+ private boolean eof;
+
+ private byte[] ascii;
+ private byte[] b;
+
+ /**
+ * Constructor.
+ *
+ * @param is The input stream to actually read from.
+ */
+ public ASCII85InputStream( InputStream is )
+ {
+ super(is);
+ index = 0;
+ n = 0;
+ eof = false;
+ ascii = new byte[5];
+ b = new byte[4];
+ }
+
+ /**
+ * This will read the next byte from the stream.
+ *
+ * @return The next byte read from the stream.
+ *
+ * @throws IOException If there is an error reading from the wrapped stream.
+ */
+ public final int read() throws IOException
+ {
+ if( index >= n )
+ {
+ if(eof)
+ {
+ return -1;
+ }
+ index = 0;
+ int k;
+ byte z;
+ do
+ {
+ int zz=(byte)in.read();
+ if(zz==-1)
+ {
+ eof=true;
+ return -1;
+ }
+ z = (byte)zz;
+ } while( z=='\n' || z=='\r' || z==' ');
+
+ if (z == '~' || z=='x')
+ {
+ eof=true;
+ ascii=b=null;
+ n = 0;
+ return -1;
+ }
+ else if (z == 'z')
+ {
+ b[0]=b[1]=b[2]=b[3]=0;
+ n = 4;
+ }
+ else
+ {
+ ascii[0]=z; // may be EOF here....
+ for (k=1;k<5;++k)
+ {
+ do
+ {
+ int zz=(byte)in.read();
+ if(zz==-1)
+ {
+ eof=true;
+ return -1;
+ }
+ z=(byte)zz;
+ } while ( z=='\n' || z=='\r' || z==' ' );
+ ascii[k]=z;
+ if (z == '~' || z=='x')
+ {
+ break;
+ }
+ }
+ n = k - 1;
+ if ( n==0 )
+ {
+ eof = true;
+ ascii = null;
+ b = null;
+ return -1;
+ }
+ if ( k < 5 )
+ {
+ for (++k; k < 5; ++k )
+ {
+ ascii[k] = 0x21;
+ }
+ eof=true;
+ }
+ // decode stream
+ long t=0;
+ for ( k=0; k<5; ++k)
+ {
+ z=(byte)(ascii[k] - 0x21);
+ if (z < 0 || z > 93)
+ {
+ n = 0;
+ eof = true;
+ ascii = null;
+ b = null;
+ throw new IOException("Invalid data in Ascii85 stream");
+ }
+ t = (t * 85L) + z;
+ }
+ for ( k = 3; k>=0; --k)
+ {
+ b[k] = (byte)(t & 0xFFL);
+ t>>>=8;
+ }
+ }
+ }
+ return b[index++] & 0xFF;
+ }
+
+ /**
+ * This will read a chunk of data.
+ *
+ * @param data The buffer to write data to.
+ * @param offset The offset into the data stream.
+ * @param len The number of byte to attempt to read.
+ *
+ * @return The number of bytes actually read.
+ *
+ * @throws IOException If there is an error reading data from the underlying stream.
+ */
+ public final int read(byte[] data, int offset, int len) throws IOException
+ {
+ if(eof && index>=n)
+ {
+ return -1;
+ }
+ for(int i=0;i<len;i++)
+ {
+ if(index<n)
+ {
+ data[i+offset]=b[index++];
+ }
+ else
+ {
+ int t = read();
+ if ( t == -1 )
+ {
+ return i;
+ }
+ data[i+offset]=(byte)t;
+ }
+ }
+ return len;
+ }
+
+ /**
+ * This will close the underlying stream and release any resources.
+ *
+ * @throws IOException If there is an error closing the underlying stream.
+ */
+ public void close() throws IOException
+ {
+ ascii = null;
+ eof = true;
+ b = null;
+ super.close();
+ }
+
+ /**
+ * non supported interface methods.
+ *
+ * @return False always.
+ */
+ public boolean markSupported()
+ {
+ return false;
+ }
+
+ /**
+ * Unsupported.
+ *
+ * @param nValue ignored.
+ *
+ * @return Always zero.
+ */
+ public long skip(long nValue)
+ {
+ return 0;
+ }
+
+ /**
+ * Unsupported.
+ *
+ * @return Always zero.
+ */
+ public int available()
+ {
+ return 0;
+ }
+
+ /**
+ * Unsupported.
+ *
+ * @param readlimit ignored.
+ */
+ public void mark(int readlimit)
+ {
+ }
+
+ /**
+ * Unsupported.
+ *
+ * @throws IOException telling that this is an unsupported action.
+ */
+ public void reset() throws IOException
+ {
+ throw new IOException("Reset is not supported");
+ }
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class represents an ASCII85 output stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class ASCII85OutputStream extends FilterOutputStream
+{
+
+ private int lineBreak;
+ private int count;
+
+ private byte[] indata;
+ private byte[] outdata;
+
+ /**
+ * Function produces five ASCII printing characters from
+ * four bytes of binary data.
+ */
+ private int maxline;
+ private boolean flushed;
+ private char terminator;
+
+ /**
+ * Constructor.
+ *
+ * @param out The output stream to write to.
+ */
+ public ASCII85OutputStream( OutputStream out )
+ {
+ super( out );
+ lineBreak = 36*2;
+ maxline = 36*2;
+ count=0;
+ indata=new byte[4];
+ outdata=new byte[5];
+ flushed=true;
+ terminator='~';
+ }
+
+ /**
+ * This will set the terminating character.
+ *
+ * @param term The terminating character.
+ */
+ public void setTerminator(char term)
+ {
+ if(term<118 || term>126 || term=='z')
+ {
+ throw new IllegalArgumentException("Terminator must be 118-126 excluding z");
+ }
+ terminator=term;
+ }
+
+ /**
+ * This will get the terminating character.
+ *
+ * @return The terminating character.
+ */
+ public char getTerminator()
+ {
+ return terminator;
+ }
+
+ /**
+ * This will set the line length that will be used.
+ *
+ * @param l The length of the line to use.
+ */
+ public void setLineLength(int l)
+ {
+ if( lineBreak > l )
+ {
+ lineBreak = l;
+ }
+ maxline=l;
+ }
+
+ /**
+ * This will get the length of the line.
+ *
+ * @return The line length attribute.
+ */
+ public int getLineLength()
+ {
+ return maxline;
+ }
+
+ /**
+ * This will transform the next four ascii bytes.
+ */
+ private final void transformASCII85()
+ {
+ long word;
+ word=( (((indata[0] << 8) | (indata[1] &0xFF)) << 16) |
+ ( (indata[2] & 0xFF) << 8) | (indata[3] & 0xFF)
+ ) & 0xFFFFFFFFL;
+ // System.out.println("word=0x"+Long.toString(word,16)+" "+word);
+
+ if (word == 0 )
+ {
+ outdata[0]=(byte)'z';
+ outdata[1]=0;
+ return;
+ }
+ long x;
+ x=word/(85L*85L*85L*85L);
+ // System.out.println("x0="+x);
+ outdata[0]=(byte)(x+'!');
+ word-=x*85L*85L*85L*85L;
+
+ x=word/(85L*85L*85L);
+ // System.out.println("x1="+x);
+ outdata[1]=(byte)(x+'!');
+ word-=x*85L*85L*85L;
+
+ x=word/(85L*85L);
+ // System.out.println("x2="+x);
+ outdata[2]=(byte)(x+'!');
+ word-=x*85L*85L;
+
+ x=word/85L;
+ // System.out.println("x3="+x);
+ outdata[3]=(byte)(x+'!');
+
+ // word-=x*85L;
+
+ // System.out.println("x4="+(word % 85L));
+ outdata[4]=(byte)((word%85L)+'!');
+ }
+
+ /**
+ * This will write a single byte.
+ *
+ * @param b The byte to write.
+ *
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public final void write(int b) throws IOException
+ {
+ flushed=false;
+ indata[count++]=(byte)b;
+ if(count < 4 )
+ {
+ return;
+ }
+ transformASCII85();
+ for(int i=0;i<5;i++)
+ {
+ if(outdata[i]==0)
+ {
+ break;
+ }
+ out.write(outdata[i]);
+ if(--lineBreak==0)
+ {
+ out.write('\n');
+ lineBreak=maxline;
+ }
+ }
+ count = 0;
+ }
+
+ /**
+ * This will write a chunk of data to the stream.
+ *
+ * @param b The byte buffer to read from.
+ * @param off The offset into the buffer.
+ * @param sz The number of bytes to read from the buffer.
+ *
+ * @throws IOException If there is an error writing to the underlying stream.
+ */
+ public final void write(byte[] b,int off, int sz) throws IOException
+ {
+ for(int i=0;i<sz;i++)
+ {
+ if(count < 3)
+ {
+ indata[count++]=b[off+i];
+ }
+ else
+ {
+ write(b[off+i]);
+ }
+ }
+ }
+
+ /**
+ * This will flush the data to the stream.
+ *
+ * @throws IOException If there is an error writing the data to the stream.
+ */
+ public final void flush() throws IOException
+ {
+ if(flushed)
+ {
+ return;
+ }
+ if(count > 0 )
+ {
+ for( int i=count; i<4; i++ )
+ {
+ indata[i]=0;
+ }
+ transformASCII85();
+ if(outdata[0]=='z')
+ {
+ for(int i=0;i<5;i++) // expand 'z',
+ {
+ outdata[i]=(byte)'!';
+ }
+ }
+ for(int i=0;i<count+1;i++)
+ {
+ out.write(outdata[i]);
+ if(--lineBreak==0)
+ {
+ out.write('\n');
+ lineBreak=maxline;
+ }
+ }
+ }
+ if(--lineBreak==0)
+ {
+ out.write('\n');
+ }
+ out.write(terminator);
+ out.write('\n');
+ count = 0;
+ lineBreak=maxline;
+ flushed=true;
+ super.flush();
+ }
+
+ /**
+ * This will close the stream.
+ *
+ * @throws IOException If there is an error closing the wrapped stream.
+ */
+ public void close() throws IOException
+ {
+ try
+ {
+ super.close();
+ }
+ finally
+ {
+ indata=outdata=null;
+ }
+ }
+
+ /**
+ * This will flush the stream.
+ *
+ * @throws Throwable If there is an error.
+ */
+ protected void finalize() throws Throwable
+ {
+ try
+ {
+ flush();
+ }
+ finally
+ {
+ super.finalize();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * PushBackInputStream for byte arrays.
+ *
+ * The inheritance from PushBackInputStream is only to avoid the
+ * introduction of an interface with all PushBackInputStream
+ * methods. The parent PushBackInputStream is not used in any way and
+ * all methods are overridden. (Thus when adding new methods to PushBackInputStream
+ * override them in this class as well!)
+ * unread() is limited to the number of bytes already read from this stream (i.e.
+ * the current position in the array). This limitation usually poses no problem
+ * to a parser, but allows for some optimization since only one array has to
+ * be dealt with.
+ *
+ * Note: This class is not thread safe. Clients must provide synchronization
+ * if needed.
+ *
+ * Note: Calling unread() after mark() will cause (part of) the unread data to be
+ * read again after reset(). Thus do not call unread() between mark() and reset().
+ *
+ * @author Andreas Weiss (andreas.weiss@switzerland.org)
+ * @version $Revision: 1.2 $
+ */
+public class ByteArrayPushBackInputStream extends PushBackInputStream
+{
+ private byte[] data;
+ private int datapos;
+ private int datalen;
+ private int save;
+
+ // dummy for base class constructor
+ private static final InputStream DUMMY = new ByteArrayInputStream("".getBytes());
+
+ /**
+ * Constructor.
+ * @param input Data to read from. Note that calls to unread() will
+ * modify this array! If this is not desired, pass a copy.
+ *
+ * @throws IOException If there is an IO error.
+ */
+ public ByteArrayPushBackInputStream(byte[] input) throws IOException
+ {
+ super(DUMMY, 1);
+ data = input;
+ datapos = 0;
+ save = datapos;
+ datalen = input != null ? input.length : 0;
+ }
+
+ /**
+ * This will peek at the next byte.
+ *
+ * @return The next byte on the stream, leaving it as available to read.
+ */
+ public int peek()
+ {
+ try
+ {
+ // convert negative values to 128..255
+ return (data[datapos] + 0x100) & 0xff;
+ }
+ catch (ArrayIndexOutOfBoundsException ex)
+ {
+ // could check this before, but this is a rare case
+ // and this method is called sufficiently often to justify this
+ // optimization
+ return -1;
+ }
+ }
+
+ /**
+ * A simple test to see if we are at the end of the stream.
+ *
+ * @return true if we are at the end of the stream.
+ */
+ public boolean isEOF()
+ {
+ return datapos >= datalen;
+ }
+
+ /**
+ * Save the state of this stream.
+ * @param readlimit Has no effect.
+ * @see InputStream#mark(int)
+ */
+ public void mark(int readlimit)
+ {
+ if (false)
+ {
+ ++readlimit; // avoid unused param warning
+ }
+ save = datapos;
+ }
+
+ /**
+ * Check if mark is supported.
+ * @return Always true.
+ * @see InputStream#markSupported()
+ */
+ public boolean markSupported()
+ {
+ return true;
+ }
+
+ /**
+ * Restore the state of this stream to the last saveState call.
+ * @see InputStream#reset()
+ */
+ public void reset()
+ {
+ datapos = save;
+ }
+
+ /** Available bytes.
+ * @see InputStream#available()
+ * @return Available bytes.
+ */
+ public int available()
+ {
+ int av = datalen - datapos;
+ return av > 0 ? av : 0;
+ }
+
+ /** Totally available bytes in the underlying array.
+ * @return Available bytes.
+ */
+ public int size()
+ {
+ return datalen;
+ }
+
+ /**
+ * Pushes back a byte.
+ * After this method returns, the next byte to be read will have the value (byte)by.
+ * @param by the int value whose low-order byte is to be pushed back.
+ * @throws IOException - If there is not enough room in the buffer for the byte.
+ * @see java.io.PushbackInputStream#unread(int)
+ */
+ public void unread(int by) throws IOException
+ {
+ if (datapos == 0)
+ {
+ throw new IOException("ByteArrayParserInputStream.unread(int): " +
+ "cannot unread 1 byte at buffer position " + datapos);
+ }
+ --datapos;
+ data[datapos] = (byte)by;
+ }
+
+ /**
+ * Pushes back a portion of an array of bytes by copying it to the
+ * front of the pushback buffer. After this method returns, the next byte
+ * to be read will have the value b[off], the byte after that will have
+ * the value b[off+1], and so forth.
+ * @param buffer the byte array to push back.
+ * @param off the start offset of the data.
+ * @param len the number of bytes to push back.
+ * @throws IOException If there is not enough room in the pushback buffer
+ * for the specified number of bytes.
+ * @see java.io.PushbackInputStream#unread(byte[], int, int)
+ */
+ public void unread(byte[] buffer, int off, int len) throws IOException
+ {
+ if (len <= 0 || off >= buffer.length)
+ {
+ return;
+ }
+ if (off < 0)
+ {
+ off = 0;
+ }
+ if (len > buffer.length)
+ {
+ len = buffer.length;
+ }
+ localUnread(buffer, off, len);
+ }
+
+ /**
+ * Pushes back a portion of an array of bytes by copying it to the
+ * front of the pushback buffer. After this method returns, the next byte
+ * to be read will have the value buffer[0], the byte after that will have
+ * the value buffer[1], and so forth.
+ * @param buffer the byte array to push back.
+ * @throws IOException If there is not enough room in the pushback buffer
+ * for the specified number of bytes.
+ * @see java.io.PushbackInputStream#unread(byte[])
+ */
+ public void unread(byte[] buffer) throws IOException
+ {
+ localUnread(buffer, 0, buffer.length);
+ }
+
+ /**
+ * Pushes back a portion of an array of bytes by copying it to the
+ * front of the pushback buffer. After this method returns, the next byte
+ * to be read will have the value buffer[off], the byte after that will have
+ * the value buffer[off+1], and so forth.
+ * Internal method that assumes off and len to be valid.
+ * @param buffer the byte array to push back.
+ * @param off the start offset of the data.
+ * @param len the number of bytes to push back.
+ * @throws IOException If there is not enough room in the pushback buffer
+ * for the specified number of bytes.
+ * @see java.io.PushbackInputStream#unread(byte[], int, int)
+ */
+ private void localUnread(byte[] buffer, int off, int len) throws IOException
+ {
+ if (datapos < len)
+ {
+ throw new IOException("ByteArrayParserInputStream.unread(int): " +
+ "cannot unread " + len +
+ " bytes at buffer position " + datapos);
+ }
+ datapos -= len;
+ System.arraycopy(buffer, off, data, datapos, len);
+ }
+
+ /**
+ * Read a byte.
+ * @see InputStream#read()
+ * @return Byte read or -1 if no more bytes are available.
+ */
+ public int read()
+ {
+ try
+ {
+ // convert negative values to 128..255
+ return (data[datapos++] + 0x100) & 0xff;
+ }
+ catch (ArrayIndexOutOfBoundsException ex)
+ {
+ // could check this before, but this is a rare case
+ // and this method is called sufficiently often to justify this
+ // optimization
+ datapos = datalen;
+ return -1;
+ }
+ }
+
+ /**
+ * Read a number of bytes.
+ * @see InputStream#read(byte[])
+ * @param buffer the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or -1 if there
+ * is no more data because the end of the stream has been reached.
+ */
+ public int read(byte[] buffer)
+ {
+ return localRead(buffer, 0, buffer.length);
+ }
+
+ /**
+ * Read a number of bytes.
+ * @see InputStream#read(byte[], int, int)
+ * @param buffer the buffer into which the data is read.
+ * @param off the start offset in array buffer at which the data is written.
+ * @param len the maximum number of bytes to read.
+ * @return the total number of bytes read into the buffer, or -1 if there
+ * is no more data because the end of the stream has been reached.
+ */
+ public int read(byte[] buffer, int off, int len)
+ {
+ if (len <= 0 || off >= buffer.length)
+ {
+ return 0;
+ }
+ if (off < 0)
+ {
+ off = 0;
+ }
+ if (len > buffer.length)
+ {
+ len = buffer.length;
+ }
+ return localRead(buffer, off, len);
+ }
+
+
+ /**
+ * Read a number of bytes. Internal method that assumes off and len to be
+ * valid.
+ * @see InputStream#read(byte[], int, int)
+ * @param buffer the buffer into which the data is read.
+ * @param off the start offset in array buffer at which the data is written.
+ * @param len the maximum number of bytes to read.
+ * @return the total number of bytes read into the buffer, or -1 if there
+ * is no more data because the end of the stream has been reached.
+ */
+ public int localRead(byte[] buffer, int off, int len)
+ {
+ if (len == 0)
+ {
+ return 0; // must return 0 even if at end!
+ }
+ else if (datapos >= datalen)
+ {
+ return -1;
+ }
+ else
+ {
+ int newpos = datapos + len;
+ if (newpos > datalen)
+ {
+ newpos = datalen;
+ len = newpos - datapos;
+ }
+ System.arraycopy(data, datapos, buffer, off, len);
+ datapos = newpos;
+ return len;
+ }
+ }
+
+ /**
+ * Skips over and discards n bytes of data from this input stream.
+ * The skip method may, for a variety of reasons, end up skipping over some
+ * smaller number of bytes, possibly 0. This may result from any of a number
+ * of conditions; reaching end of file before n bytes have been skipped is
+ * only one possibility. The actual number of bytes skipped is returned.
+ * If n is negative, no bytes are skipped.
+ * @param num the number of bytes to be skipped.
+ * @return the actual number of bytes skipped.
+ * @see InputStream#skip(long)
+ */
+ public long skip(long num)
+ {
+ if (num <= 0)
+ {
+ return 0;
+ }
+ else
+ {
+ long newpos = datapos + num;
+ if (newpos >= datalen)
+ {
+ num = datalen - datapos;
+ datapos = datalen;
+ }
+ else
+ {
+ datapos = (int)newpos;
+ }
+ return num;
+ }
+ }
+
+ /** Position the stream at a given index. Positioning the stream
+ * at position size() will cause the next call to read() to return -1.
+ *
+ * @param newpos Position in the underlying array. A negative value will be
+ * interpreted as 0, a value greater than size() as size().
+ * @return old position.
+ */
+ public int seek(int newpos)
+ {
+ if (newpos < 0)
+ {
+ newpos = 0;
+ }
+ else if (newpos > datalen)
+ {
+ newpos = datalen;
+ }
+ int oldpos = pos;
+ pos = newpos;
+ return oldpos;
+ }
+
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * An byte array output stream that allows direct access to the byte array.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class FastByteArrayOutputStream extends ByteArrayOutputStream
+{
+ /**
+ * Constructor.
+ *
+ * @param size An initial size of the stream.
+ */
+ public FastByteArrayOutputStream( int size )
+ {
+ super( size );
+ }
+
+ /**
+ * This will get the underlying byte array.
+ *
+ * @return The underlying byte array at this moment in time.
+ */
+ public byte[] getByteArray()
+ {
+ return buf;
+ }
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * This is an n-bit input stream. This means that you can read chunks of data
+ * as any number of bits, not just 8 bits like the regular input stream. Just set the
+ * number of bits that you would like to read on each call. The default is 8.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class NBitInputStream
+{
+ private int bitsInChunk;
+ private InputStream in;
+
+ private int currentByte;
+ private int bitsLeftInCurrentByte;
+
+ /**
+ * Constructor.
+ *
+ * @param is The input stream to read from.
+ */
+ public NBitInputStream( InputStream is )
+ {
+ in = is;
+ bitsLeftInCurrentByte = 0;
+ bitsInChunk = 8;
+ }
+
+ /**
+ * This will unread some data.
+ *
+ * @param data The data to put back into the stream.
+ */
+ public void unread( long data )
+ {
+ data <<= bitsLeftInCurrentByte;
+ currentByte |= data;
+ bitsLeftInCurrentByte += bitsInChunk;
+ }
+
+ /**
+ * This will read the next n bits from the stream and return the unsigned
+ * value of those bits.
+ *
+ * @return The next n bits from the stream.
+ *
+ * @throws IOException If there is an error reading from the underlying stream.
+ */
+ public long read() throws IOException
+ {
+ long retval = 0;
+ for( int i=0; i<bitsInChunk && retval != -1; i++ )
+ {
+ if( bitsLeftInCurrentByte == 0 )
+ {
+ currentByte = in.read();
+ bitsLeftInCurrentByte = 8;
+ }
+ if( currentByte == -1 )
+ {
+ retval = -1;
+ }
+ else
+ {
+ retval <<= 1;
+ retval |= ((currentByte >> (bitsLeftInCurrentByte-1))&0x1);
+ bitsLeftInCurrentByte--;
+ }
+ }
+ return retval;
+ }
+
+ /** Getter for property bitsToRead.
+ * @return Value of property bitsToRead.
+ */
+ public int getBitsInChunk()
+ {
+ return bitsInChunk;
+ }
+
+ /** Setter for property bitsToRead.
+ * @param bitsInChunkValue New value of property bitsToRead.
+ */
+ public void setBitsInChunk(int bitsInChunkValue)
+ {
+ bitsInChunk = bitsInChunkValue;
+ }
+
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * This is an n-bit output stream. This means that you write data in n-bit chunks.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class NBitOutputStream
+{
+ private int bitsInChunk;
+ private OutputStream out;
+
+ private int currentByte;
+ private int positionInCurrentByte;
+
+ /**
+ * Constructor.
+ *
+ * @param os The output stream to write to.
+ */
+ public NBitOutputStream( OutputStream os )
+ {
+ out = os;
+ currentByte = 0;
+ positionInCurrentByte = 7;
+ }
+
+ /**
+ * This will write the next n-bits to the stream.
+ *
+ * @param chunk The next chunk of data to write.
+ *
+ * @throws IOException If there is an error writing the chunk.
+ */
+ public void write( long chunk ) throws IOException
+ {
+ long bitToWrite;
+ for( int i=(bitsInChunk-1); i>=0; i-- )
+ {
+ bitToWrite = (chunk >> i) & 0x1;
+ bitToWrite <<= positionInCurrentByte;
+ currentByte |= bitToWrite;
+ positionInCurrentByte--;
+ if( positionInCurrentByte < 0 )
+ {
+ out.write( currentByte );
+ currentByte = 0;
+ positionInCurrentByte = 7;
+ }
+ }
+ }
+
+ /**
+ * This will close the stream.
+ *
+ * @throws IOException if there is an error closing the stream.
+ */
+ public void close() throws IOException
+ {
+ if( positionInCurrentByte < 7 )
+ {
+ out.write( currentByte );
+ }
+ }
+
+ /** Getter for property bitsToRead.
+ * @return Value of property bitsToRead.
+ */
+ public int getBitsInChunk()
+ {
+ return bitsInChunk;
+ }
+
+ /** Setter for property bitsToRead.
+ * @param bitsInChunkValue New value of property bitsToRead.
+ */
+ public void setBitsInChunk(int bitsInChunkValue)
+ {
+ bitsInChunk = bitsInChunkValue;
+ }
+
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.EOFException;
+
+/**
+ * A simple subclass that adds a few convience methods.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PushBackInputStream extends java.io.PushbackInputStream
+{
+ /*
+ * The current position in the file.
+ */
+ private int offset = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param input The input stream.
+ * @param size The size of the push back buffer.
+ *
+ * @throws IOException If there is an error with the stream.
+ */
+ public PushBackInputStream( InputStream input, int size ) throws IOException
+ {
+ super( input, size );
+ if( input == null )
+ {
+ throw new IOException( "Error: input was null" );
+ }
+ }
+
+ /**
+ * This will peek at the next byte.
+ *
+ * @return The next byte on the stream, leaving it as available to read.
+ *
+ * @throws IOException If there is an error reading the next byte.
+ */
+ public int peek() throws IOException
+ {
+ int result = read();
+ if( result != -1 )
+ {
+ unread( result );
+ }
+ return result;
+ }
+
+ /**
+ * Returns the current byte offset in the file.
+ * @return the int byte offset
+ */
+ public int getOffset()
+ {
+ return offset;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int read() throws IOException
+ {
+ int retval = super.read();
+ if (retval != -1)
+ {
+ offset++;
+ }
+ return retval;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int read(byte[] b) throws IOException
+ {
+ return this.read(b, 0, b.length);
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ int retval = super.read(b, off, len);
+ if (retval != -1)
+ {
+ offset += retval;
+ }
+ return retval;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void unread(int b) throws IOException
+ {
+ offset--;
+ super.unread(b);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void unread(byte[] b) throws IOException
+ {
+ this.unread(b, 0, b.length);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void unread(byte[] b, int off, int len) throws IOException
+ {
+ if (len > 0)
+ {
+ offset -= len;
+ super.unread(b, off, len);
+ }
+ }
+
+ /**
+ * A simple test to see if we are at the end of the stream.
+ *
+ * @return true if we are at the end of the stream.
+ *
+ * @throws IOException If there is an error reading the next byte.
+ */
+ public boolean isEOF() throws IOException
+ {
+ int peek = peek();
+ return peek == -1;
+ }
+
+ /**
+ * This is a method used to fix PDFBox issue 974661, the PDF parsing code needs
+ * to know if there is at least x amount of data left in the stream, but the available()
+ * method returns how much data will be available without blocking. PDFBox is willing to
+ * block to read the data, so we will first fill the internal buffer.
+ *
+ * @throws IOException If there is an error filling the buffer.
+ */
+ public void fillBuffer() throws IOException
+ {
+ int bufferLength = buf.length;
+ byte[] tmpBuffer = new byte[bufferLength];
+ int amountRead = 0;
+ int totalAmountRead = 0;
+ while( amountRead != -1 && totalAmountRead < bufferLength )
+ {
+ amountRead = this.read( tmpBuffer, totalAmountRead, bufferLength - totalAmountRead );
+ if( amountRead != -1 )
+ {
+ totalAmountRead += amountRead;
+ }
+ }
+ this.unread( tmpBuffer, 0, totalAmountRead );
+ }
+
+ /**
+ * Reads a given number of bytes from the underlying stream.
+ * @param length the number of bytes to be read
+ * @return a byte array containing the bytes just read
+ * @throws IOException if an I/O error occurs while reading data
+ */
+ public byte[] readFully(int length) throws IOException
+ {
+ byte[] data = new byte[length];
+ int pos = 0;
+ while (pos < length)
+ {
+ int amountRead = read( data, pos, length - pos );
+ if (amountRead < 0)
+ {
+ throw new EOFException("Premature end of file");
+ }
+ pos += amountRead;
+ }
+ return data;
+ }
+
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.IOException;
+
+/**
+ * An interface to allow PDF files to be stored completely in memory or
+ * to use a scratch file on the disk.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public interface RandomAccess
+{
+
+ /**
+ * Release resources that are being held.
+ *
+ * @throws IOException If there is an error closing this resource.
+ */
+ public void close() throws IOException;
+
+ /**
+ * Seek to a position in the data.
+ *
+ * @param position The position to seek to.
+ * @throws IOException If there is an error while seeking.
+ */
+ public void seek(long position) throws IOException;
+
+ /**
+ * Read a single byte of data.
+ *
+ * @return The byte of data that is being read.
+ *
+ * @throws IOException If there is an error while reading the data.
+ */
+ public int read() throws IOException;
+
+ /**
+ * Read a buffer of data.
+ *
+ * @param b The buffer to write the data to.
+ * @param offset Offset into the buffer to start writing.
+ * @param length The amount of data to attempt to read.
+ * @return The number of bytes that were actually read.
+ * @throws IOException If there was an error while reading the data.
+ */
+ public int read(byte[] b, int offset, int length) throws IOException;
+
+ /**
+ * The total number of bytes that are available.
+ *
+ * @return The number of bytes available.
+ *
+ * @throws IOException If there is an IO error while determining the
+ * length of the data stream.
+ */
+ public long length() throws IOException;
+
+ /**
+ * Write a byte to the stream.
+ *
+ * @param b The byte to write.
+ * @throws IOException If there is an IO error while writing.
+ */
+ public void write(int b) throws IOException;
+
+ /**
+ * Write a buffer of data to the stream.
+ *
+ * @param b The buffer to get the data from.
+ * @param offset An offset into the buffer to get the data from.
+ * @param length The length of data to write.
+ * @throws IOException If there is an error while writing the data.
+ */
+ public void write(byte[] b, int offset, int length) throws IOException;
+
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.IOException;
+
+/**
+ * An interface to allow PDF files to be stored completely in memory.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class RandomAccessBuffer implements RandomAccess
+{
+
+ private byte[] buffer;
+ private long pointer;
+ private long size;
+
+ /**
+ * Default constructor.
+ */
+ public RandomAccessBuffer()
+ {
+ // starting with a 16kb buffer
+ buffer = new byte[16384];
+ pointer = 0;
+ size = 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ buffer = null;
+ pointer = 0;
+ size = 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void seek(long position) throws IOException
+ {
+ this.pointer = position;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int read() throws IOException
+ {
+ if (pointer >= this.size)
+ {
+ return -1;
+ }
+ return buffer[(int)pointer++] & 0xff;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int read(byte[] b, int offset, int length) throws IOException
+ {
+ if (pointer >= this.size)
+ {
+ return 0;
+ }
+ int maxLength = (int) Math.min(length, this.size-pointer);
+ System.arraycopy(buffer, (int) pointer, b, offset, maxLength);
+ pointer += maxLength;
+ return maxLength;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public long length() throws IOException
+ {
+ return size;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void write(int b) throws IOException
+ {
+ if (pointer >= buffer.length)
+ {
+ if (pointer >= Integer.MAX_VALUE)
+ {
+ throw new IOException("RandomAccessBuffer overflow");
+ }
+ buffer = expandBuffer(buffer, (int)Math.min(2L * buffer.length, Integer.MAX_VALUE));
+ }
+ buffer[(int)pointer++] = (byte)b;
+ if (pointer > this.size)
+ {
+ this.size = pointer;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void write(byte[] b, int offset, int length) throws IOException
+ {
+ long newSize = pointer+length;
+ if (newSize >= buffer.length)
+ {
+ if (newSize > Integer.MAX_VALUE)
+ {
+ throw new IOException("RandomAccessBuffer overflow");
+ }
+ newSize = Math.min(Math.max(2L * buffer.length, newSize), Integer.MAX_VALUE);
+ buffer = expandBuffer(buffer, (int)newSize);
+ }
+ System.arraycopy(b, offset, buffer, (int)pointer, length);
+ pointer += length;
+ if (pointer > this.size)
+ {
+ this.size = pointer;
+ }
+ }
+
+ /**
+ * expand the given buffer to the new size.
+ *
+ * @param buffer the given buffer
+ * @param newSize the new size
+ * @return the expanded buffer
+ *
+ */
+ private byte[] expandBuffer(byte[] buffer, int newSize)
+ {
+ byte[] expandedBuffer = new byte[newSize];
+ System.arraycopy(buffer, 0, expandedBuffer, 0, buffer.length);
+ return expandedBuffer;
+ }
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * An interface to allow temp PDF data to be stored in a scratch
+ * file on the disk to reduce memory consumption.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class RandomAccessFile implements RandomAccess
+{
+ private java.io.RandomAccessFile ras;
+
+ /**
+ * Constructor.
+ *
+ * @param file The file to write the data to.
+ * @param mode The writing mode.
+ * @throws FileNotFoundException If the file cannot be created.
+ */
+ public RandomAccessFile(File file, String mode) throws FileNotFoundException
+ {
+ ras = new java.io.RandomAccessFile(file, mode);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close() throws IOException
+ {
+ ras.close();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void seek(long position) throws IOException
+ {
+ ras.seek(position);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int read() throws IOException
+ {
+ return ras.read();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int read(byte[] b, int offset, int length) throws IOException
+ {
+ return ras.read(b, offset, length);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public long length() throws IOException
+ {
+ return ras.length();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void write(byte[] b, int offset, int length) throws IOException
+ {
+ ras.write(b, offset, length);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void write(int b) throws IOException
+ {
+ ras.write(b);
+ }
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * This class allows a section of a RandomAccessFile to be accessed as an
+ * input stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class RandomAccessFileInputStream extends InputStream
+{
+ private RandomAccess file;
+ private long currentPosition;
+ private long endPosition;
+
+ /**
+ * Constructor.
+ *
+ * @param raFile The file to read the data from.
+ * @param startPosition The position in the file that this stream starts.
+ * @param length The length of the input stream.
+ */
+ public RandomAccessFileInputStream( RandomAccess raFile, long startPosition, long length )
+ {
+ file = raFile;
+ currentPosition = startPosition;
+ endPosition = currentPosition+length;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public int available()
+ {
+ return (int)(endPosition - currentPosition);
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public void close()
+ {
+ //do nothing because we want to leave the random access file open.
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public int read() throws IOException
+ {
+ synchronized(file)
+ {
+ int retval = -1;
+ if( currentPosition < endPosition )
+ {
+ file.seek( currentPosition );
+ currentPosition++;
+ retval = file.read();
+ }
+ return retval;
+ }
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public int read( byte[] b, int offset, int length ) throws IOException
+ {
+ //only allow a read of the amount available.
+ if( length > available() )
+ {
+ length = available();
+ }
+ int amountRead = -1;
+ //only read if there are bytes actually available, otherwise
+ //return -1 if the EOF has been reached.
+ if( available() > 0 )
+ {
+ synchronized(file)
+ {
+ file.seek( currentPosition );
+ amountRead = file.read( b, offset, length );
+ }
+ }
+ //update the current cursor position.
+ if( amountRead > 0 )
+ {
+ currentPosition += amountRead;
+ }
+ return amountRead;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public long skip( long amountToSkip )
+ {
+ long amountSkipped = Math.min( amountToSkip, available() );
+ currentPosition+= amountSkipped;
+ return amountSkipped;
+ }
+}
--- /dev/null
+/*
+ * 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.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSNumber;
+
+/**
+ * This will write to a RandomAccessFile in the filesystem and keep track
+ * of the position it is writing to and the length of the stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class RandomAccessFileOutputStream extends OutputStream
+{
+ private RandomAccess file;
+ private long position;
+ private long lengthWritten = 0;
+ private COSBase expectedLength = null;
+
+ /**
+ * Constructor to create an output stream that will write to the end of a
+ * random access file.
+ *
+ * @param raf The file to write to.
+ *
+ * @throws IOException If there is a problem accessing the raf.
+ */
+ public RandomAccessFileOutputStream( RandomAccess raf ) throws IOException
+ {
+ file = raf;
+ //first get the position that we will be writing to
+ position = raf.length();
+ }
+
+ /**
+ * This will get the position in the RAF that the stream was written
+ * to.
+ *
+ * @return The position in the raf where the file can be obtained.
+ */
+ public long getPosition()
+ {
+ return position;
+ }
+
+ /**
+ * Get the amount of data that was actually written to the stream, in theory this
+ * should be the same as the length specified but in some cases it doesn't match.
+ *
+ * @return The number of bytes actually written to this stream.
+ */
+ public long getLengthWritten()
+ {
+ return lengthWritten;
+ }
+
+ /**
+ * The number of bytes written to the stream.
+ *
+ * @return The number of bytes read to the stream.
+ */
+ public long getLength()
+ {
+ long length = -1;
+ if( expectedLength instanceof COSNumber )
+ {
+ length = ((COSNumber)expectedLength).intValue();
+ }
+ else if( expectedLength instanceof COSObject &&
+ ((COSObject)expectedLength).getObject() instanceof COSNumber )
+ {
+ length = ((COSNumber)((COSObject)expectedLength).getObject()).intValue();
+ }
+ if( length == -1 )
+ {
+ length = lengthWritten;
+ }
+ return length;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void write( byte[] b, int offset, int length ) throws IOException
+ {
+ file.seek( position+lengthWritten );
+ lengthWritten += length;
+ file.write( b, offset, length );
+
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public void write( int b ) throws IOException
+ {
+ file.seek( position+lengthWritten );
+ lengthWritten++;
+ file.write( b );
+ }
+
+ /**
+ * This will get the length that the PDF document specified this stream
+ * should be. This may not match the number of bytes read.
+ *
+ * @return The expected length.
+ */
+ public COSBase getExpectedLength()
+ {
+ return expectedLength;
+ }
+
+ /**
+ * This will set the expected length of this stream.
+ *
+ * @param value The expected value.
+ */
+ public void setExpectedLength(COSBase value)
+ {
+ expectedLength = value;
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains IO streams.
+</body>
+</html>
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package holds executable classes that interact with the PDFBox application.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.pdfparser;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.io.PushBackInputStream;
+import org.apache.pdfbox.io.RandomAccess;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This class is used to contain parsing logic that will be used by both the
+ * PDFParser and the COSStreamParser.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.61 $
+ */
+public abstract class BaseParser
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(BaseParser.class);
+
+ private static final int E = 'e';
+ private static final int N = 'n';
+ private static final int D = 'd';
+
+ private static final int S = 's';
+ private static final int T = 't';
+ private static final int R = 'r';
+ //private static final int E = 'e';
+ private static final int A = 'a';
+ private static final int M = 'm';
+
+ private static final int O = 'o';
+ private static final int B = 'b';
+ private static final int J = 'j';
+
+ /**
+ * This is a byte array that will be used for comparisons.
+ */
+ public static final byte[] ENDSTREAM =
+ new byte[] { E, N, D, S, T, R, E, A, M };
+
+ /**
+ * This is a byte array that will be used for comparisons.
+ */
+ public static final byte[] ENDOBJ =
+ new byte[] { E, N, D, O, B, J };
+
+ /**
+ * This is a byte array that will be used for comparisons.
+ */
+ public static final String DEF = "def";
+
+ /**
+ * Default value of the {@link #forceParsing} flag.
+ */
+ protected static final boolean FORCE_PARSING =
+ Boolean.getBoolean("org.apache.pdfbox.forceParsing");
+
+ /**
+ * This is the stream that will be read from.
+ */
+ protected PushBackInputStream pdfSource;
+
+ /**
+ * This is the document that will be parsed.
+ */
+ protected COSDocument document;
+
+ /**
+ * Flag to skip malformed or otherwise unparseable input where possible.
+ */
+ protected final boolean forceParsing;
+
+ /**
+ * Constructor.
+ *
+ * @since Apache PDFBox 1.3.0
+ * @param input The input stream to read the data from.
+ * @param forceParcing flag to skip malformed or otherwise unparseable
+ * input where possible
+ * @throws IOException If there is an error reading the input stream.
+ */
+ public BaseParser(InputStream input, boolean forceParsing)
+ throws IOException {
+ this.pdfSource = new PushBackInputStream(
+ new BufferedInputStream(input, 16384), 4096);
+ this.forceParsing = forceParsing;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param input The input stream to read the data from.
+ * @throws IOException If there is an error reading the input stream.
+ */
+ public BaseParser(InputStream input) throws IOException {
+ this(input, FORCE_PARSING);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param input The array to read the data from.
+ * @throws IOException If there is an error reading the byte data.
+ */
+ protected BaseParser(byte[] input) throws IOException {
+ this(new ByteArrayInputStream(input));
+ }
+
+ /**
+ * Set the document for this stream.
+ *
+ * @param doc The current document.
+ */
+ public void setDocument( COSDocument doc )
+ {
+ document = doc;
+ }
+
+ private static boolean isHexDigit(char ch)
+ {
+ return (ch >= '0' && ch <= '9') ||
+ (ch >= 'a' && ch <= 'f') ||
+ (ch >= 'A' && ch <= 'F');
+ // the line below can lead to problems with certain versions of the IBM JIT compiler
+ // (and is slower anyway)
+ //return (HEXDIGITS.indexOf(ch) != -1);
+ }
+
+ /**
+ * This will parse a PDF dictionary value.
+ *
+ * @return The parsed Dictionary object.
+ *
+ * @throws IOException If there is an error parsing the dictionary object.
+ */
+ private COSBase parseCOSDictionaryValue() throws IOException
+ {
+ COSBase retval = null;
+ COSBase number = parseDirObject();
+ skipSpaces();
+ char next = (char)pdfSource.peek();
+ if( next >= '0' && next <= '9' )
+ {
+ COSBase generationNumber = parseDirObject();
+ skipSpaces();
+ char r = (char)pdfSource.read();
+ if( r != 'R' )
+ {
+ throw new IOException( "expected='R' actual='" + r + "' " + pdfSource );
+ }
+ COSObjectKey key = new COSObjectKey(((COSInteger) number).intValue(),
+ ((COSInteger) generationNumber).intValue());
+ retval = document.getObjectFromPool(key);
+ }
+ else
+ {
+ retval = number;
+ }
+ return retval;
+ }
+
+ /**
+ * This will parse a PDF dictionary.
+ *
+ * @return The parsed dictionary.
+ *
+ * @throws IOException IF there is an error reading the stream.
+ */
+ protected COSDictionary parseCOSDictionary() throws IOException
+ {
+ char c = (char)pdfSource.read();
+ if( c != '<')
+ {
+ throw new IOException( "expected='<' actual='" + c + "'" );
+ }
+ c = (char)pdfSource.read();
+ if( c != '<')
+ {
+ throw new IOException( "expected='<' actual='" + c + "' " + pdfSource );
+ }
+ skipSpaces();
+ COSDictionary obj = new COSDictionary();
+ boolean done = false;
+ while( !done )
+ {
+ skipSpaces();
+ c = (char)pdfSource.peek();
+ if( c == '>')
+ {
+ done = true;
+ }
+ else
+ if(c != '/')
+ {
+ //an invalid dictionary, we are expecting
+ //the key, read until we can recover
+ log.warn("Invalid dictionary, found: '" + (char)c + "' but expected: '/'");
+ int read = pdfSource.read();
+ while(read != -1 && read != '/' && read != '>')
+ {
+ // in addition to stopping when we find / or >, we also want
+ // to stop when we find endstream or endobj.
+ if(read==E) {
+ read = pdfSource.read();
+ if(read==N) {
+ read = pdfSource.read();
+ if(read==D) {
+ read = pdfSource.read();
+ if(read==S) {
+ read = pdfSource.read();
+ if(read==T) {
+ read = pdfSource.read();
+ if(read==R) {
+ read = pdfSource.read();
+ if(read==E) {
+ read = pdfSource.read();
+ if(read==A) {
+ read = pdfSource.read();
+ if(read==M) {
+ return obj; // we're done reading this object!
+ }
+ }
+ }
+ }
+ }
+ } else if(read==O) {
+ read = pdfSource.read();
+ if(read==B) {
+ read = pdfSource.read();
+ if(read==J) {
+ return obj; // we're done reading this object!
+ }
+ }
+ }
+ }
+ }
+ }
+ read = pdfSource.read();
+ }
+ if(read != -1)
+ {
+ pdfSource.unread(read);
+ }
+ else
+ {
+ return obj;
+ }
+ }
+ else
+ {
+ COSName key = parseCOSName();
+ COSBase value = parseCOSDictionaryValue();
+ skipSpaces();
+ if( ((char)pdfSource.peek()) == 'd' )
+ {
+ //if the next string is 'def' then we are parsing a cmap stream
+ //and want to ignore it, otherwise throw an exception.
+ String potentialDEF = readString();
+ if( !potentialDEF.equals( DEF ) )
+ {
+ pdfSource.unread( potentialDEF.getBytes("ISO-8859-1") );
+ }
+ else
+ {
+ skipSpaces();
+ }
+ }
+
+ if( value == null )
+ {
+ log.warn("Bad Dictionary Declaration " + pdfSource );
+ }
+ else
+ {
+ obj.setItem( key, value );
+ }
+ }
+ }
+ char ch = (char)pdfSource.read();
+ if( ch != '>' )
+ {
+ throw new IOException( "expected='>' actual='" + ch + "'" );
+ }
+ ch = (char)pdfSource.read();
+ if( ch != '>' )
+ {
+ throw new IOException( "expected='>' actual='" + ch + "'" );
+ }
+ return obj;
+ }
+
+ /**
+ * This will read a COSStream from the input stream.
+ *
+ * @param file The file to write the stream to when reading.
+ * @param dic The dictionary that goes with this stream.
+ *
+ * @return The parsed pdf stream.
+ *
+ * @throws IOException If there is an error reading the stream.
+ */
+ protected COSStream parseCOSStream( COSDictionary dic, RandomAccess file ) throws IOException
+ {
+ COSStream stream = new COSStream( dic, file );
+ OutputStream out = null;
+ try
+ {
+ String streamString = readString();
+ //long streamLength;
+
+ if (!streamString.equals("stream"))
+ {
+ throw new IOException("expected='stream' actual='" + streamString + "'");
+ }
+
+ //PDF Ref 3.2.7 A stream must be followed by either
+ //a CRLF or LF but nothing else.
+
+ int whitespace = pdfSource.read();
+
+ //see brother_scan_cover.pdf, it adds whitespaces
+ //after the stream but before the start of the
+ //data, so just read those first
+ while (whitespace == 0x20)
+ {
+ whitespace = pdfSource.read();
+ }
+
+ if( whitespace == 0x0D )
+ {
+ whitespace = pdfSource.read();
+ if( whitespace != 0x0A )
+ {
+ pdfSource.unread( whitespace );
+ //The spec says this is invalid but it happens in the real
+ //world so we must support it.
+ }
+ }
+ else if (whitespace == 0x0A)
+ {
+ //that is fine
+ }
+ else
+ {
+ //we are in an error.
+ //but again we will do a lenient parsing and just assume that everything
+ //is fine
+ pdfSource.unread( whitespace );
+ }
+
+ /*This needs to be dic.getItem because when we are parsing, the underlying object
+ * might still be null.
+ */
+ COSBase streamLength = dic.getItem(COSName.LENGTH);
+
+ //Need to keep track of the
+ out = stream.createFilteredStream( streamLength );
+
+ String endStream = null;
+ readUntilEndStream(out);
+ skipSpaces();
+ endStream = readString();
+
+ if (!endStream.equals("endstream"))
+ {
+ /*
+ * Sometimes stream objects don't have an endstream tag so readUntilEndStream(out)
+ * also can stop on endobj tags. If that's the case we need to make sure to unread
+ * the endobj so parseObject() can handle that case normally.
+ */
+ if (endStream.startsWith("endobj"))
+ {
+ byte[] endobjarray = endStream.getBytes("ISO-8859-1");
+ pdfSource.unread(endobjarray);
+ }
+ /*
+ * Some PDF files don't contain a new line after endstream so we
+ * need to make sure that the next object number is getting read separately
+ * and not part of the endstream keyword. Ex. Some files would have "endstream8"
+ * instead of "endstream"
+ */
+ else if(endStream.startsWith("endstream"))
+ {
+ String extra = endStream.substring(9, endStream.length());
+ endStream = endStream.substring(0, 9);
+ byte[] array = extra.getBytes("ISO-8859-1");
+ pdfSource.unread(array);
+ }
+ else
+ {
+ /*
+ * If for some reason we get something else here, Read until we find the next
+ * "endstream"
+ */
+ readUntilEndStream( out );
+ endStream = readString();
+ if( !endStream.equals( "endstream" ) )
+ {
+ throw new IOException("expected='endstream' actual='" + endStream + "' " + pdfSource);
+ }
+ }
+ }
+ }
+ finally
+ {
+ if( out != null )
+ {
+ out.close();
+ }
+ }
+ return stream;
+ }
+
+ /**
+ * This method will read through the current stream object until
+ * we find the keyword "endstream" meaning we're at the end of this
+ * object. Some pdf files, however, forget to write some endstream tags
+ * and just close off objects with an "endobj" tag so we have to handle
+ * this case as well.
+ * @param out The stream we write out to.
+ * @throws IOException
+ */
+ private void readUntilEndStream( OutputStream out ) throws IOException{
+ int byteRead;
+ do{ //use a fail fast test for end of stream markers
+ byteRead = pdfSource.read();
+ if(byteRead==E){//only branch if "e"
+ byteRead = pdfSource.read();
+ if(byteRead==N){ //only continue branch if "en"
+ byteRead = pdfSource.read();
+ if(byteRead==D){//up to "end" now
+ byteRead = pdfSource.read();
+ if(byteRead==S){
+ byteRead = pdfSource.read();
+ if(byteRead==T){
+ byteRead = pdfSource.read();
+ if(byteRead==R){
+ byteRead = pdfSource.read();
+ if(byteRead==E){
+ byteRead = pdfSource.read();
+ if(byteRead==A){
+ byteRead = pdfSource.read();
+ if(byteRead==M){
+ //found the whole marker
+ pdfSource.unread( ENDSTREAM );
+ return;
+ }else{
+ out.write(ENDSTREAM, 0, 8);
+ }
+ }else{
+ out.write(ENDSTREAM, 0, 7);
+ }
+ }else{
+ out.write(ENDSTREAM, 0, 6);
+ }
+ }else{
+ out.write(ENDSTREAM, 0, 5);
+ }
+ }else{
+ out.write(ENDSTREAM, 0, 4);
+ }
+ }else if(byteRead==O){
+ byteRead = pdfSource.read();
+ if(byteRead==B){
+ byteRead = pdfSource.read();
+ if(byteRead==J){
+ //found whole marker
+ pdfSource.unread( ENDOBJ );
+ return;
+ }else{
+ out.write(ENDOBJ, 0, 5);
+ }
+ }else{
+ out.write(ENDOBJ, 0, 4);
+ }
+ }else{
+ out.write(E);
+ out.write(N);
+ out.write(D);
+ }
+ }else{
+ out.write(E);
+ out.write(N);
+ }
+ }else{
+ out.write(E);
+ }
+ }
+ if(byteRead!=-1)out.write(byteRead);
+
+ }while(byteRead!=-1);
+ }
+
+ /**
+ * This is really a bug in the Document creators code, but it caused a crash
+ * in PDFBox, the first bug was in this format:
+ * /Title ( (5)
+ * /Creator which was patched in 1 place.
+ * However it missed the case where the Close Paren was escaped
+ *
+ * The second bug was in this format
+ * /Title (c:\)
+ * /Producer
+ *
+ * This patch moves this code out of the parseCOSString method, so it can be used twice.
+ *
+ *
+ * @param bracesParameter the number of braces currently open.
+ *
+ * @return the corrected value of the brace counter
+ * @throws IOException
+ */
+ private int checkForMissingCloseParen(final int bracesParameter) throws IOException {
+ int braces=bracesParameter;
+ byte[] nextThreeBytes = new byte[3];
+ int amountRead = pdfSource.read(nextThreeBytes);
+
+ //lets handle the special case seen in Bull River Rules and Regulations.pdf
+ //The dictionary looks like this
+ // 2 0 obj
+ // <<
+ // /Type /Info
+ // /Creator (PaperPort http://www.scansoft.com)
+ // /Producer (sspdflib 1.0 http://www.scansoft.com)
+ // /Title ( (5)
+ // /Author ()
+ // /Subject ()
+ //
+ // Notice the /Title, the braces are not even but they should
+ // be. So lets assume that if we encounter an this scenario
+ // <end_brace><new_line><opening_slash> then that
+ // means that there is an error in the pdf and assume that
+ // was the end of the document.
+ //
+ if( amountRead == 3 )
+ {
+ if(( nextThreeBytes[0] == 0x0d && // Look for a carriage return
+ nextThreeBytes[1] == 0x0a && // Look for a new line
+ nextThreeBytes[2] == 0x2f ) || // Look for a slash /
+ // Add a second case without a new line
+ (nextThreeBytes[0] == 0x0d && // Look for a carriage return
+ nextThreeBytes[1] == 0x2f )) // Look for a slash /
+ {
+ braces = 0;
+ }
+ }
+ if (amountRead > 0)
+ {
+ pdfSource.unread( nextThreeBytes, 0, amountRead );
+ }
+ return braces;
+ }
+ /**
+ * This will parse a PDF string.
+ *
+ * @return The parsed PDF string.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ protected COSString parseCOSString() throws IOException
+ {
+ char nextChar = (char)pdfSource.read();
+ COSString retval = new COSString();
+ char openBrace;
+ char closeBrace;
+ if( nextChar == '(' )
+ {
+ openBrace = '(';
+ closeBrace = ')';
+ }
+ else if( nextChar == '<' )
+ {
+ openBrace = '<';
+ closeBrace = '>';
+ }
+ else
+ {
+ throw new IOException( "parseCOSString string should start with '(' or '<' and not '" +
+ nextChar + "' " + pdfSource );
+ }
+
+ //This is the number of braces read
+ //
+ int braces = 1;
+ int c = pdfSource.read();
+ while( braces > 0 && c != -1)
+ {
+ char ch = (char)c;
+ int nextc = -2; // not yet read
+
+ if(ch == closeBrace)
+ {
+
+ braces--;
+ braces=checkForMissingCloseParen(braces);
+ if( braces != 0 )
+ {
+ retval.append( ch );
+ }
+ }
+ else if( ch == openBrace )
+ {
+ braces++;
+ retval.append( ch );
+ }
+ else if( ch == '\\' )
+ {
+ //patched by ram
+ char next = (char)pdfSource.read();
+ switch(next)
+ {
+ case 'n':
+ retval.append( '\n' );
+ break;
+ case 'r':
+ retval.append( '\r' );
+ break;
+ case 't':
+ retval.append( '\t' );
+ break;
+ case 'b':
+ retval.append( '\b' );
+ break;
+ case 'f':
+ retval.append( '\f' );
+ break;
+ case ')':
+ // PDFBox 276 /Title (c:\)
+ braces=checkForMissingCloseParen(braces);
+ if( braces != 0 )
+ {
+ retval.append( next );
+ }
+ else {
+ retval.append('\\');
+ }
+ break;
+ case '(':
+ case '\\':
+ retval.append( next );
+ break;
+ case 10:
+ case 13:
+ //this is a break in the line so ignore it and the newline and continue
+ c = pdfSource.read();
+ while( isEOL(c) && c != -1)
+ {
+ c = pdfSource.read();
+ }
+ nextc = c;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ {
+ StringBuffer octal = new StringBuffer();
+ octal.append( next );
+ c = pdfSource.read();
+ char digit = (char)c;
+ if( digit >= '0' && digit <= '7' )
+ {
+ octal.append( digit );
+ c = pdfSource.read();
+ digit = (char)c;
+ if( digit >= '0' && digit <= '7' )
+ {
+ octal.append( digit );
+ }
+ else
+ {
+ nextc = c;
+ }
+ }
+ else
+ {
+ nextc = c;
+ }
+
+ int character = 0;
+ try
+ {
+ character = Integer.parseInt( octal.toString(), 8 );
+ }
+ catch( NumberFormatException e )
+ {
+ throw new IOException( "Error: Expected octal character, actual='" + octal + "'" );
+ }
+ retval.append( character );
+ break;
+ }
+ default:
+ {
+ retval.append( '\\' );
+ retval.append( next );
+ //another ficken problem with PDF's, sometimes the \ doesn't really
+ //mean escape like the PDF spec says it does, sometimes is should be literal
+ //which is what we will assume here.
+ //throw new IOException( "Unexpected break sequence '" + next + "' " + pdfSource );
+ }
+ }
+ }
+ else
+ {
+ if( openBrace == '<' )
+ {
+ if( isHexDigit(ch) )
+ {
+ retval.append( ch );
+ }
+ }
+ else
+ {
+ retval.append( ch );
+ }
+ }
+ if (nextc != -2)
+ {
+ c = nextc;
+ }
+ else
+ {
+ c = pdfSource.read();
+ }
+ }
+ if (c != -1)
+ {
+ pdfSource.unread(c);
+ }
+ if( openBrace == '<' )
+ {
+ retval = COSString.createFromHexString(
+ retval.getString(), forceParsing);
+ }
+ return retval;
+ }
+
+ /**
+ * This will parse a PDF array object.
+ *
+ * @return The parsed PDF array.
+ *
+ * @throws IOException If there is an error parsing the stream.
+ */
+ protected COSArray parseCOSArray() throws IOException
+ {
+ char ch = (char)pdfSource.read();
+ if( ch != '[')
+ {
+ throw new IOException( "expected='[' actual='" + ch + "'" );
+ }
+ COSArray po = new COSArray();
+ COSBase pbo = null;
+ skipSpaces();
+ int i = 0;
+ while( ((i = pdfSource.peek()) > 0) && ((char)i != ']') )
+ {
+ pbo = parseDirObject();
+ if( pbo instanceof COSObject )
+ {
+ // We have to check if the expected values are there or not PDFBOX-385
+ if (po.get(po.size()-1) instanceof COSInteger)
+ {
+ COSInteger genNumber = (COSInteger)po.remove( po.size() -1 );
+ if (po.get(po.size()-1) instanceof COSInteger)
+ {
+ COSInteger number = (COSInteger)po.remove( po.size() -1 );
+ COSObjectKey key = new COSObjectKey(number.intValue(), genNumber.intValue());
+ pbo = document.getObjectFromPool(key);
+ }
+ else
+ {
+ // the object reference is somehow wrong
+ pbo = null;
+ }
+ }
+ else
+ {
+ pbo = null;
+ }
+ }
+ if( pbo != null )
+ {
+ po.add( pbo );
+ }
+ else
+ {
+ //it could be a bad object in the array which is just skipped
+ log.warn("Corrupt object reference" );
+
+ // This could also be an "endobj" or "endstream" which means we can assume that
+ // the array has ended.
+ String isThisTheEnd = readString();
+ pdfSource.unread(isThisTheEnd.getBytes("ISO-8859-1"));
+ if("endobj".equals(isThisTheEnd) || "endstream".equals(isThisTheEnd))
+ return po;
+ }
+ skipSpaces();
+ }
+ pdfSource.read(); //read ']'
+ skipSpaces();
+ return po;
+ }
+
+ /**
+ * Determine if a character terminates a PDF name.
+ *
+ * @param ch The character
+ * @return <code>true</code> if the character terminates a PDF name, otherwise <code>false</code>.
+ */
+ protected boolean isEndOfName(char ch)
+ {
+ return (ch == ' ' || ch == 13 || ch == 10 || ch == 9 || ch == '>' || ch == '<'
+ || ch == '[' || ch =='/' || ch ==']' || ch ==')' || ch =='(' ||
+ ch == -1 //EOF
+ );
+ }
+
+ /**
+ * This will parse a PDF name from the stream.
+ *
+ * @return The parsed PDF name.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ protected COSName parseCOSName() throws IOException
+ {
+ COSName retval = null;
+ int c = pdfSource.read();
+ if( (char)c != '/')
+ {
+ throw new IOException("expected='/' actual='" + (char)c + "'-" + c + " " + pdfSource );
+ }
+ // costruisce il nome
+ StringBuffer buffer = new StringBuffer();
+ c = pdfSource.read();
+ while( c != -1 )
+ {
+ char ch = (char)c;
+ if(ch == '#')
+ {
+ char ch1 = (char)pdfSource.read();
+ char ch2 = (char)pdfSource.read();
+
+ // Prior to PDF v1.2, the # was not a special character. Also,
+ // it has been observed that various PDF tools do not follow the
+ // spec with respect to the # escape, even though they report
+ // PDF versions of 1.2 or later. The solution here is that we
+ // interpret the # as an escape only when it is followed by two
+ // valid hex digits.
+ //
+ if (isHexDigit(ch1) && isHexDigit(ch2))
+ {
+ String hex = "" + ch1 + ch2;
+ try
+ {
+ buffer.append( (char) Integer.parseInt(hex, 16));
+ }
+ catch (NumberFormatException e)
+ {
+ throw new IOException("Error: expected hex number, actual='" + hex + "'");
+ }
+ c = pdfSource.read();
+ }
+ else
+ {
+ pdfSource.unread(ch2);
+ c = ch1;
+ buffer.append( ch );
+ }
+ }
+ else if (isEndOfName(ch))
+ {
+ break;
+ }
+ else
+ {
+ buffer.append( ch );
+ c = pdfSource.read();
+ }
+ }
+ if (c != -1)
+ {
+ pdfSource.unread(c);
+ }
+ retval = COSName.getPDFName( buffer.toString() );
+ return retval;
+ }
+
+ /**
+ * This will parse a boolean object from the stream.
+ *
+ * @return The parsed boolean object.
+ *
+ * @throws IOException If an IO error occurs during parsing.
+ */
+ protected COSBoolean parseBoolean() throws IOException
+ {
+ COSBoolean retval = null;
+ char c = (char)pdfSource.peek();
+ if( c == 't' )
+ {
+ String trueString = new String( pdfSource.readFully( 4 ), "ISO-8859-1" );
+ if( !trueString.equals( "true" ) )
+ {
+ throw new IOException( "Error parsing boolean: expected='true' actual='" + trueString + "'" );
+ }
+ else
+ {
+ retval = COSBoolean.TRUE;
+ }
+ }
+ else if( c == 'f' )
+ {
+ String falseString = new String( pdfSource.readFully( 5 ), "ISO-8859-1" );
+ if( !falseString.equals( "false" ) )
+ {
+ throw new IOException( "Error parsing boolean: expected='true' actual='" + falseString + "'" );
+ }
+ else
+ {
+ retval = COSBoolean.FALSE;
+ }
+ }
+ else
+ {
+ throw new IOException( "Error parsing boolean expected='t or f' actual='" + c + "'" );
+ }
+ return retval;
+ }
+
+ /**
+ * This will parse a directory object from the stream.
+ *
+ * @return The parsed object.
+ *
+ * @throws IOException If there is an error during parsing.
+ */
+ protected COSBase parseDirObject() throws IOException
+ {
+ COSBase retval = null;
+
+ skipSpaces();
+ int nextByte = pdfSource.peek();
+ char c = (char)nextByte;
+ switch(c)
+ {
+ case '<':
+ {
+ int leftBracket = pdfSource.read();//pull off first left bracket
+ c = (char)pdfSource.peek(); //check for second left bracket
+ pdfSource.unread( leftBracket );
+ if(c == '<')
+ {
+
+ retval = parseCOSDictionary();
+ skipSpaces();
+ }
+ else
+ {
+ retval = parseCOSString();
+ }
+ break;
+ }
+ case '[': // array
+ {
+ retval = parseCOSArray();
+ break;
+ }
+ case '(':
+ retval = parseCOSString();
+ break;
+ case '/': // name
+ retval = parseCOSName();
+ break;
+ case 'n': // null
+ {
+ String nullString = readString();
+ if( !nullString.equals( "null") )
+ {
+ throw new IOException("Expected='null' actual='" + nullString + "'");
+ }
+ retval = COSNull.NULL;
+ break;
+ }
+ case 't':
+ {
+ String trueString = new String( pdfSource.readFully(4), "ISO-8859-1" );
+ if( trueString.equals( "true" ) )
+ {
+ retval = COSBoolean.TRUE;
+ }
+ else
+ {
+ throw new IOException( "expected true actual='" + trueString + "' " + pdfSource );
+ }
+ break;
+ }
+ case 'f':
+ {
+ String falseString = new String( pdfSource.readFully(5), "ISO-8859-1" );
+ if( falseString.equals( "false" ) )
+ {
+ retval = COSBoolean.FALSE;
+ }
+ else
+ {
+ throw new IOException( "expected false actual='" + falseString + "' " + pdfSource );
+ }
+ break;
+ }
+ case 'R':
+ pdfSource.read();
+ retval = new COSObject(null);
+ break;
+ case (char)-1:
+ return null;
+ default:
+ {
+ if( Character.isDigit(c) || c == '-' || c == '+' || c == '.')
+ {
+ StringBuffer buf = new StringBuffer();
+ int ic = pdfSource.read();
+ c = (char)ic;
+ while( Character.isDigit( c )||
+ c == '-' ||
+ c == '+' ||
+ c == '.' ||
+ c == 'E' ||
+ c == 'e' )
+ {
+ buf.append( c );
+ ic = pdfSource.read();
+ c = (char)ic;
+ }
+ if( ic != -1 )
+ {
+ pdfSource.unread( ic );
+ }
+ retval = COSNumber.get( buf.toString() );
+ }
+ else
+ {
+ //This is not suppose to happen, but we will allow for it
+ //so we are more compatible with POS writers that don't
+ //follow the spec
+ String badString = readString();
+ //throw new IOException( "Unknown dir object c='" + c +
+ //"' peek='" + (char)pdfSource.peek() + "' " + pdfSource );
+ if( badString == null || badString.length() == 0 )
+ {
+ int peek = pdfSource.peek();
+ // we can end up in an infinite loop otherwise
+ throw new IOException( "Unknown dir object c='" + c +
+ "' cInt=" + (int)c + " peek='" + (char)peek + "' peekInt=" + peek + " " + pdfSource );
+ }
+
+ // if it's an endstream/endobj, we want to put it back so the caller will see it
+ if("endobj".equals(badString) || "endstream".equals(badString))
+ pdfSource.unread(badString.getBytes("ISO-8859-1"));
+ }
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will read the next string from the stream.
+ *
+ * @return The string that was read from the stream.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ protected String readString() throws IOException
+ {
+ skipSpaces();
+ StringBuffer buffer = new StringBuffer();
+ int c = pdfSource.read();
+ while( !isEndOfName((char)c) && !isClosing(c) && c != -1 )
+ {
+ buffer.append( (char)c );
+ c = pdfSource.read();
+ }
+ if (c != -1)
+ {
+ pdfSource.unread(c);
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * This will read bytes until the end of line marker occurs.
+ *
+ * @param theString The next expected string in the stream.
+ *
+ * @return The characters between the current position and the end of the line.
+ *
+ * @throws IOException If there is an error reading from the stream or theString does not match what was read.
+ */
+ protected String readExpectedString( String theString ) throws IOException
+ {
+ int c = pdfSource.read();
+ while( isWhitespace(c) && c != -1)
+ {
+ c = pdfSource.read();
+ }
+ StringBuffer buffer = new StringBuffer( theString.length() );
+ int charsRead = 0;
+ while( !isEOL(c) && c != -1 && charsRead < theString.length() )
+ {
+ char next = (char)c;
+ buffer.append( next );
+ if( theString.charAt( charsRead ) == next )
+ {
+ charsRead++;
+ }
+ else
+ {
+ pdfSource.unread(buffer.toString().getBytes("ISO-8859-1"));
+ throw new IOException( "Error: Expected to read '" + theString +
+ "' instead started reading '" +buffer.toString() + "'" );
+ }
+ c = pdfSource.read();
+ }
+ while( isEOL(c) && c != -1 )
+ {
+ c = pdfSource.read();
+ }
+ if (c != -1)
+ {
+ pdfSource.unread(c);
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * This will read the next string from the stream up to a certain length.
+ *
+ * @param length The length to stop reading at.
+ *
+ * @return The string that was read from the stream of length 0 to length.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ protected String readString( int length ) throws IOException
+ {
+ skipSpaces();
+
+ int c = pdfSource.read();
+
+ //average string size is around 2 and the normal string buffer size is
+ //about 16 so lets save some space.
+ StringBuffer buffer = new StringBuffer(length);
+ while( !isWhitespace(c) && !isClosing(c) && c != -1 && buffer.length() < length &&
+ c != '[' &&
+ c != '<' &&
+ c != '(' &&
+ c != '/' )
+ {
+ buffer.append( (char)c );
+ c = pdfSource.read();
+ }
+ if (c != -1)
+ {
+ pdfSource.unread(c);
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * This will tell if the next character is a closing brace( close of PDF array ).
+ *
+ * @return true if the next byte is ']', false otherwise.
+ *
+ * @throws IOException If an IO error occurs.
+ */
+ protected boolean isClosing() throws IOException
+ {
+ return isClosing(pdfSource.peek());
+ }
+
+ /**
+ * This will tell if the next character is a closing brace( close of PDF array ).
+ *
+ * @param c The character to check against end of line
+ * @return true if the next byte is ']', false otherwise.
+ */
+ protected boolean isClosing(int c)
+ {
+ return c == ']';
+ }
+
+ /**
+ * This will read bytes until the first end of line marker occurs.
+ * Note: if you later unread the results of this function, you'll
+ * need to add a newline character to the end of the string.
+ *
+ * @return The characters between the current position and the end of the line.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ protected String readLine() throws IOException
+ {
+ if (pdfSource.isEOF())
+ {
+ throw new IOException( "Error: End-of-File, expected line");
+ }
+
+ StringBuffer buffer = new StringBuffer( 11 );
+
+ int c;
+ while ((c = pdfSource.read()) != -1)
+ {
+ if (isEOL(c))
+ {
+ break;
+ }
+ buffer.append( (char)c );
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * This will tell if the next byte to be read is an end of line byte.
+ *
+ * @return true if the next byte is 0x0A or 0x0D.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ protected boolean isEOL() throws IOException
+ {
+ return isEOL(pdfSource.peek());
+ }
+
+ /**
+ * This will tell if the next byte to be read is an end of line byte.
+ *
+ * @param c The character to check against end of line
+ * @return true if the next byte is 0x0A or 0x0D.
+ */
+ protected boolean isEOL(int c)
+ {
+ return c == 10 || c == 13;
+ }
+
+ /**
+ * This will tell if the next byte is whitespace or not.
+ *
+ * @return true if the next byte in the stream is a whitespace character.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ protected boolean isWhitespace() throws IOException
+ {
+ return isWhitespace( pdfSource.peek() );
+ }
+
+ /**
+ * This will tell if the next byte is whitespace or not.
+ *
+ * @param c The character to check against whitespace
+ *
+ * @return true if the next byte in the stream is a whitespace character.
+ */
+ protected boolean isWhitespace( int c )
+ {
+ return c == 0 || c == 9 || c == 12 || c == 10
+ || c == 13 || c == 32;
+ }
+
+ /**
+ * This will skip all spaces and comments that are present.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ protected void skipSpaces() throws IOException
+ {
+ //log( "skipSpaces() " + pdfSource );
+ int c = pdfSource.read();
+ // identical to, but faster as: isWhiteSpace(c) || c == 37
+ while(c == 0 || c == 9 || c == 12 || c == 10
+ || c == 13 || c == 32 || c == 37)//37 is the % character, a comment
+ {
+ if ( c == 37 )
+ {
+ // skip past the comment section
+ c = pdfSource.read();
+ while(!isEOL(c) && c != -1)
+ {
+ c = pdfSource.read();
+ }
+ }
+ else
+ {
+ c = pdfSource.read();
+ }
+ }
+ if (c != -1)
+ {
+ pdfSource.unread(c);
+ }
+ //log( "skipSpaces() done peek='" + (char)pdfSource.peek() + "'" );
+ }
+
+ /**
+ * This will read an integer from the stream.
+ *
+ * @return The integer that was read from the stream.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ protected int readInt() throws IOException
+ {
+ skipSpaces();
+ int retval = 0;
+
+ int lastByte = 0;
+ StringBuffer intBuffer = new StringBuffer();
+ while( (lastByte = pdfSource.read() ) != 32 &&
+ lastByte != 10 &&
+ lastByte != 13 &&
+ lastByte != 60 && //see sourceforge bug 1714707
+ lastByte != 0 && //See sourceforge bug 853328
+ lastByte != -1 )
+ {
+ intBuffer.append( (char)lastByte );
+ }
+ if( lastByte != -1 )
+ {
+ pdfSource.unread( lastByte );
+ }
+
+ try
+ {
+ retval = Integer.parseInt( intBuffer.toString() );
+ }
+ catch( NumberFormatException e )
+ {
+ pdfSource.unread(intBuffer.toString().getBytes("ISO-8859-1"));
+ throw new IOException( "Error: Expected an integer type, actual='" + intBuffer + "'" );
+ }
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.pdfparser;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+
+/**
+ * This will parse a PDF 1.5 object stream and extract all of the objects from the stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDFObjectStreamParser extends BaseParser
+{
+ /**
+ * Log instance.
+ */
+ private static final Log log =
+ LogFactory.getLog(PDFObjectStreamParser.class);
+
+ private List<COSObject> streamObjects = null;
+ private List<Integer> objectNumbers = null;
+ private COSStream stream;
+
+ /**
+ * Constructor.
+ *
+ * @since Apache PDFBox 1.3.0
+ * @param strm The stream to parse.
+ * @param doc The document for the current parsing.
+ * @param forceParcing flag to skip malformed or otherwise unparseable
+ * input where possible
+ * @throws IOException If there is an error initializing the stream.
+ */
+ public PDFObjectStreamParser(
+ COSStream strm, COSDocument doc, boolean forceParsing)
+ throws IOException {
+ super(strm.getUnfilteredStream(), forceParsing);
+ setDocument( doc );
+ stream = strm;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param strm The stream to parse.
+ * @param doc The document for the current parsing.
+ *
+ * @throws IOException If there is an error initializing the stream.
+ */
+ public PDFObjectStreamParser(COSStream strm, COSDocument doc)
+ throws IOException {
+ this(strm, doc, FORCE_PARSING);
+ }
+
+ /**
+ * This will parse the tokens in the stream. This will close the
+ * stream when it is finished parsing.
+ *
+ * @throws IOException If there is an error while parsing the stream.
+ */
+ public void parse() throws IOException
+ {
+ try
+ {
+ //need to first parse the header.
+ int numberOfObjects = stream.getInt( "N" );
+ objectNumbers = new ArrayList<Integer>( numberOfObjects );
+ streamObjects = new ArrayList<COSObject>( numberOfObjects );
+ for( int i=0; i<numberOfObjects; i++ )
+ {
+ int objectNumber = readInt();
+ int offset = readInt();
+ objectNumbers.add( new Integer( objectNumber ) );
+ }
+ COSObject object = null;
+ COSBase cosObject = null;
+ int objectCounter = 0;
+ while( (cosObject = parseDirObject()) != null )
+ {
+ object = new COSObject(cosObject);
+ object.setGenerationNumber( COSInteger.ZERO );
+ COSInteger objNum =
+ COSInteger.get( objectNumbers.get( objectCounter).intValue() );
+ object.setObjectNumber( objNum );
+ streamObjects.add( object );
+ if(log.isDebugEnabled())
+ {
+ log.debug( "parsed=" + object );
+ }
+ objectCounter++;
+ }
+ }
+ finally
+ {
+ pdfSource.close();
+ }
+ }
+
+ /**
+ * This will get the objects that were parsed from the stream.
+ *
+ * @return All of the objects in the stream.
+ */
+ public List<COSObject> getObjects()
+ {
+ return streamObjects;
+ }
+}
--- /dev/null
+/*
+ * 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.pdfparser;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This class will handle the parsing of the PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.53 $
+ */
+public class PDFParser extends BaseParser
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDFParser.class);
+
+ private static final int SPACE_BYTE = 32;
+
+ private static final String PDF_HEADER = "%PDF-";
+ private static final String FDF_HEADER = "%FDF-";
+ /**
+ * A list of duplicate objects found when Parsing the PDF
+ * File.
+ */
+ private List<ConflictObj> conflictList = new ArrayList<ConflictObj>();
+
+ /** Collects all Xref/trailer objects and resolves them into single
+ * object using startxref reference */
+ private XrefTrailerResolver xrefTrailerResolver = new XrefTrailerResolver();
+
+ /**
+ * Temp file directory.
+ */
+ private File tempDirectory = null;
+
+ private RandomAccess raf = null;
+
+ /**
+ * Constructor.
+ *
+ * @param input The input stream that contains the PDF document.
+ *
+ * @throws IOException If there is an error initializing the stream.
+ */
+ public PDFParser( InputStream input ) throws IOException {
+ this(input, null, FORCE_PARSING);
+ }
+
+ /**
+ * Constructor to allow control over RandomAccessFile.
+ * @param input The input stream that contains the PDF document.
+ * @param rafi The RandomAccessFile to be used in internal COSDocument
+ *
+ * @throws IOException If there is an error initializing the stream.
+ */
+ public PDFParser(InputStream input, RandomAccess rafi)
+ throws IOException {
+ this(input, rafi, FORCE_PARSING);
+ }
+
+ /**
+ * Constructor to allow control over RandomAccessFile.
+ * Also enables parser to skip corrupt objects to try and force parsing
+ * @param input The input stream that contains the PDF document.
+ * @param rafi The RandomAccessFile to be used in internal COSDocument
+ * @param force When true, the parser will skip corrupt pdf objects and
+ * will continue parsing at the next object in the file
+ *
+ * @throws IOException If there is an error initializing the stream.
+ */
+ public PDFParser(InputStream input, RandomAccess rafi, boolean force)
+ throws IOException {
+ super(input, force);
+ this.raf = rafi;
+ }
+
+ /**
+ * This is the directory where pdfbox will create a temporary file
+ * for storing pdf document stream in. By default this directory will
+ * be the value of the system property java.io.tmpdir.
+ *
+ * @param tmpDir The directory to create scratch files needed to store
+ * pdf document streams.
+ */
+ public void setTempDirectory( File tmpDir )
+ {
+ tempDirectory = tmpDir;
+ }
+
+ /**
+ * Returns true if parsing should be continued. By default, forceParsing is returned.
+ * This can be overridden to add application specific handling (for example to stop
+ * parsing when the number of exceptions thrown exceed a certain number).
+ *
+ * @param e The exception if vailable. Can be null if there is no exception available
+ */
+ protected boolean isContinueOnError(Exception e)
+ {
+ return forceParsing;
+ }
+
+ /**
+ * This will parse the stream and populate the COSDocument object. This will close
+ * the stream when it is done parsing.
+ *
+ * @throws IOException If there is an error reading from the stream or corrupt data
+ * is found.
+ */
+ public void parse() throws IOException
+ {
+ try
+ {
+ if ( raf == null )
+ {
+ if( tempDirectory != null )
+ {
+ document = new COSDocument( tempDirectory );
+ }
+ else
+ {
+ document = new COSDocument();
+ }
+ }
+ else
+ {
+ document = new COSDocument( raf );
+ }
+ setDocument( document );
+
+ parseHeader();
+
+ //Some PDF files have garbage between the header and the
+ //first object
+ skipToNextObj();
+
+ boolean wasLastParsedObjectEOF = false;
+ while(true)
+ {
+ if(pdfSource.isEOF())
+ {
+ break;
+ }
+ try
+ {
+ wasLastParsedObjectEOF = parseObject();
+ }
+ catch(IOException e)
+ {
+ /*
+ * PDF files may have random data after the EOF marker. Ignore errors if
+ * last object processed is EOF.
+ */
+ if( wasLastParsedObjectEOF )
+ {
+ break;
+ }
+ if(isContinueOnError(e))
+ {
+ /*
+ * Warning is sent to the PDFBox.log and to the Console that
+ * we skipped over an object
+ */
+ log.warn("Parsing Error, Skipping Object", e);
+ skipToNextObj();
+ }
+ else
+ {
+ throw e;
+ }
+ }
+ skipSpaces();
+ }
+
+ // set xref to start with
+ xrefTrailerResolver.setStartxref( document.getStartXref() );
+
+ // get resolved xref table + trailer
+ document.setTrailer( xrefTrailerResolver.getTrailer() );
+ document.addXRefTable( xrefTrailerResolver.getXrefTable() );
+
+ if( !document.isEncrypted() )
+ {
+ document.dereferenceObjectStreams();
+ }
+ ConflictObj.resolveConflicts(document, conflictList);
+ }
+ catch( Throwable t )
+ {
+ //so if the PDF is corrupt then close the document and clear
+ //all resources to it
+ if( document != null )
+ {
+ document.close();
+ }
+ if( t instanceof IOException )
+ {
+ throw (IOException)t;
+ }
+ else
+ {
+ throw new WrappedIOException( t );
+ }
+ }
+ finally
+ {
+ pdfSource.close();
+ }
+ }
+
+ /**
+ * Skip to the start of the next object. This is used to recover
+ * from a corrupt object. This should handle all cases that parseObject
+ * supports. This assumes that the next object will
+ * start on its own line.
+ *
+ * @throws IOException
+ */
+ private void skipToNextObj() throws IOException
+ {
+ byte[] b = new byte[16];
+ Pattern p = Pattern.compile("\\d+\\s+\\d+\\s+obj.*", Pattern.DOTALL);
+ /* Read a buffer of data each time to see if it starts with a
+ * known keyword. This is not the most efficient design, but we should
+ * rarely be needing this function. We could update this to use the
+ * circular buffer, like in readUntilEndStream().
+ */
+ while(!pdfSource.isEOF())
+ {
+ int l = pdfSource.read(b);
+ if(l < 1)
+ {
+ break;
+ }
+ String s = new String(b, "US-ASCII");
+ if(s.startsWith("trailer") ||
+ s.startsWith("xref") ||
+ s.startsWith("startxref") ||
+ s.startsWith("stream") ||
+ p.matcher(s).matches())
+ {
+ pdfSource.unread(b);
+ break;
+ }
+ else
+ {
+ pdfSource.unread(b, 1, l-1);
+ }
+ }
+ }
+
+ private void parseHeader() throws IOException
+ {
+ // read first line
+ String header = readLine();
+ // some pdf-documents are broken and the pdf-version is in one of the following lines
+ if ((header.indexOf( PDF_HEADER ) == -1) && (header.indexOf( FDF_HEADER ) == -1))
+ {
+ header = readLine();
+ while ((header.indexOf( PDF_HEADER ) == -1) && (header.indexOf( FDF_HEADER ) == -1))
+ {
+ // if a line starts with a digit, it has to be the first one with data in it
+ if ((header.length() > 0) && (Character.isDigit(header.charAt(0))))
+ {
+ break;
+ }
+ header = readLine();
+ }
+ }
+
+ // nothing found
+ if ((header.indexOf( PDF_HEADER ) == -1) && (header.indexOf( FDF_HEADER ) == -1))
+ {
+ throw new IOException( "Error: Header doesn't contain versioninfo" );
+ }
+
+ //sometimes there are some garbage bytes in the header before the header
+ //actually starts, so lets try to find the header first.
+ int headerStart = header.indexOf( PDF_HEADER );
+ if (headerStart == -1)
+ {
+ headerStart = header.indexOf(FDF_HEADER);
+ }
+
+ //greater than zero because if it is zero then
+ //there is no point of trimming
+ if ( headerStart > 0 )
+ {
+ //trim off any leading characters
+ header = header.substring( headerStart, header.length() );
+ }
+
+ /*
+ * This is used if there is garbage after the header on the same line
+ */
+ if (header.startsWith(PDF_HEADER))
+ {
+ if(!header.matches(PDF_HEADER + "\\d.\\d"))
+ {
+ String headerGarbage = header.substring(PDF_HEADER.length()+3, header.length()) + "\n";
+ header = header.substring(0, PDF_HEADER.length()+3);
+ pdfSource.unread(headerGarbage.getBytes("ISO-8859-1"));
+ }
+ }
+ else
+ {
+ if(!header.matches(FDF_HEADER + "\\d.\\d"))
+ {
+ String headerGarbage = header.substring(FDF_HEADER.length()+3, header.length()) + "\n";
+ header = header.substring(0, FDF_HEADER.length()+3);
+ pdfSource.unread(headerGarbage.getBytes("ISO-8859-1"));
+ }
+ }
+ document.setHeaderString(header);
+
+ try
+ {
+ if (header.startsWith( PDF_HEADER ))
+ {
+ float pdfVersion = Float. parseFloat(
+ header.substring( PDF_HEADER.length(), Math.min( header.length(), PDF_HEADER .length()+3) ) );
+ document.setVersion( pdfVersion );
+ }
+ else
+ {
+ float pdfVersion = Float. parseFloat(
+ header.substring( FDF_HEADER.length(), Math.min( header.length(), FDF_HEADER.length()+3) ) );
+ document.setVersion( pdfVersion );
+ }
+ }
+ catch ( NumberFormatException e )
+ {
+ throw new IOException( "Error getting pdf version:" + e );
+ }
+ }
+
+ /**
+ * This will get the document that was parsed. parse() must be called before this is called.
+ * When you are done with this document you must call close() on it to release
+ * resources.
+ *
+ * @return The document that was parsed.
+ *
+ * @throws IOException If there is an error getting the document.
+ */
+ public COSDocument getDocument() throws IOException
+ {
+ if( document == null )
+ {
+ throw new IOException( "You must call parse() before calling getDocument()" );
+ }
+ return document;
+ }
+
+ /**
+ * This will get the PD document that was parsed. When you are done with
+ * this document you must call close() on it to release resources.
+ *
+ * @return The document at the PD layer.
+ *
+ * @throws IOException If there is an error getting the document.
+ */
+ public PDDocument getPDDocument() throws IOException
+ {
+ return new PDDocument( getDocument() );
+ }
+
+ /**
+ * This will get the FDF document that was parsed. When you are done with
+ * this document you must call close() on it to release resources.
+ *
+ * @return The document at the PD layer.
+ *
+ * @throws IOException If there is an error getting the document.
+ */
+ public FDFDocument getFDFDocument() throws IOException
+ {
+ return new FDFDocument( getDocument() );
+ }
+
+ /**
+ * This will parse the next object from the stream and add it to
+ * the local state.
+ *
+ * @return Returns true if the processed object had an endOfFile marker
+ *
+ * @throws IOException If an IO error occurs.
+ */
+ private boolean parseObject() throws IOException
+ {
+ int currentObjByteOffset = pdfSource.getOffset();
+ boolean isEndOfFile = false;
+ skipSpaces();
+ //peek at the next character to determine the type of object we are parsing
+ char peekedChar = (char)pdfSource.peek();
+
+ //ignore endobj and endstream sections.
+ while( peekedChar == 'e' )
+ {
+ //there are times when there are multiple endobj, so lets
+ //just read them and move on.
+ readString();
+ skipSpaces();
+ peekedChar = (char)pdfSource.peek();
+ }
+ if( pdfSource.isEOF())
+ {
+ //"Skipping because of EOF" );
+ //end of file we will return a false and call it a day.
+ }
+ //xref table. Note: The contents of the Xref table are currently ignored
+ else if( peekedChar == 'x')
+ {
+ parseXrefTable( currentObjByteOffset );
+ }
+ // Note: startxref can occur in either a trailer section or by itself
+ else if (peekedChar == 't' || peekedChar == 's')
+ {
+ if(peekedChar == 't')
+ {
+ parseTrailer();
+ peekedChar = (char)pdfSource.peek();
+ }
+ if (peekedChar == 's')
+ {
+ parseStartXref();
+ // readString() calls skipSpaces() will skip comments... that's
+ // bad for us b/c the %%EOF flag is a comment
+ while(isWhitespace(pdfSource.peek()) && !pdfSource.isEOF())
+ pdfSource.read(); // read (get rid of) all the whitespace
+ String eof = "";
+ if(!pdfSource.isEOF())
+ eof = readLine(); // if there's more data to read, get the EOF flag
+
+ // verify that EOF exists (see PDFBOX-979 for documentation on special cases)
+ if(!"%%EOF".equals(eof)) {
+ if(eof.startsWith("%%EOF")) {
+ // content after marker -> unread with first space byte for read newline
+ pdfSource.unread(SPACE_BYTE); // we read a whole line; add space as newline replacement
+ pdfSource.unread(eof.substring(5).getBytes("ISO-8859-1"));
+ } else {
+ // PDF does not conform to spec, we should warn someone
+ log.warn("expected='%%EOF' actual='" + eof + "'");
+ // if we're not at the end of a file, just put it back and move on
+ if(!pdfSource.isEOF()) {
+ pdfSource.unread( SPACE_BYTE ); // we read a whole line; add space as newline replacement
+ pdfSource.unread(eof.getBytes("ISO-8859-1"));
+ }
+ }
+ }
+ isEndOfFile = true;
+ }
+ }
+ //we are going to parse an normal object
+ else
+ {
+ int number = -1;
+ int genNum = -1;
+ String objectKey = null;
+ boolean missingObjectNumber = false;
+ try
+ {
+ char peeked = (char)pdfSource.peek();
+ if( peeked == '<' )
+ {
+ missingObjectNumber = true;
+ }
+ else
+ {
+ number = readInt();
+ }
+ }
+ catch( IOException e )
+ {
+ //ok for some reason "GNU Ghostscript 5.10" puts two endobj
+ //statements after an object, of course this is nonsense
+ //but because we want to support as many PDFs as possible
+ //we will simply try again
+ number = readInt();
+ }
+ if( !missingObjectNumber )
+ {
+ skipSpaces();
+ genNum = readInt();
+
+ objectKey = readString( 3 );
+ //System.out.println( "parseObject() num=" + number +
+ //" genNumber=" + genNum + " key='" + objectKey + "'" );
+ if( !objectKey.equals( "obj" ) )
+ {
+ if (!isContinueOnError(null) || !objectKey.equals("o")) {
+ throw new IOException("expected='obj' actual='" + objectKey + "' " + pdfSource);
+ }
+ //assume that "o" was meant to be "obj" (this is a workaround for
+ // PDFBOX-773 attached PDF Andersens_Fairy_Tales.pdf).
+ }
+ }
+ else
+ {
+ number = -1;
+ genNum = -1;
+ }
+
+ skipSpaces();
+ COSBase pb = parseDirObject();
+ String endObjectKey = readString();
+
+ if( endObjectKey.equals( "stream" ) )
+ {
+ pdfSource.unread( endObjectKey.getBytes("ISO-8859-1") );
+ pdfSource.unread( ' ' );
+ if( pb instanceof COSDictionary )
+ {
+ pb = parseCOSStream( (COSDictionary)pb, getDocument().getScratchFile() );
+
+ // test for XRef type
+ final COSStream strmObj = (COSStream) pb;
+ final COSName objectType = (COSName)strmObj.getItem( COSName.TYPE );
+ if( objectType != null && objectType.equals( COSName.XREF ) )
+ {
+ // XRef stream
+ parseXrefStream( strmObj, currentObjByteOffset );
+ }
+ }
+ else
+ {
+ // this is not legal
+ // the combination of a dict and the stream/endstream forms a complete stream object
+ throw new IOException("stream not preceded by dictionary");
+ }
+ skipSpaces();
+ endObjectKey = readLine();
+ }
+
+ COSObjectKey key = new COSObjectKey( number, genNum );
+ COSObject pdfObject = document.getObjectFromPool( key );
+ if(pdfObject.getObject() == null)
+ {
+ pdfObject.setObject(pb);
+ }
+ /*
+ * If the object we returned already has a baseobject, then we have a conflict
+ * which we will resolve using information after we parse the xref table.
+ */
+ else
+ {
+ addObjectToConflicts(currentObjByteOffset, key, pb);
+ }
+
+ if( !endObjectKey.equals( "endobj" ) )
+ {
+ if (endObjectKey.startsWith( "endobj" ) )
+ {
+ /*
+ * Some PDF files don't contain a new line after endobj so we
+ * need to make sure that the next object number is getting read separately
+ * and not part of the endobj keyword. Ex. Some files would have "endobj28"
+ * instead of "endobj"
+ */
+ pdfSource.unread( SPACE_BYTE ); // add a space first in place of the newline consumed by readline()
+ pdfSource.unread( endObjectKey.substring( 6 ).getBytes("ISO-8859-1") );
+ }
+ else if(endObjectKey.trim().endsWith("endobj"))
+ {
+ /*
+ * Some PDF files contain junk (like ">> ", in the case of a PDF
+ * I found which was created by Exstream Dialogue Version 5.0.039)
+ * in which case we ignore the data before endobj and just move on
+ */
+ log.warn("expected='endobj' actual='" + endObjectKey + "' ");
+ }
+ else if( !pdfSource.isEOF() )
+ {
+ //It is possible that the endobj is missing, there
+ //are several PDFs out there that do that so. Unread
+ //and assume that endobj was missing
+ pdfSource.unread( SPACE_BYTE ); // add a space first in place of the newline consumed by readline()
+ pdfSource.unread( endObjectKey.getBytes("ISO-8859-1") );
+ }
+ }
+ skipSpaces();
+ }
+ return isEndOfFile;
+ }
+
+ /**
+ * Adds a new ConflictObj to the conflictList.
+ * @param offset the offset of the ConflictObj
+ * @param key The COSObjectKey of this object
+ * @param pb The COSBase of this conflictObj
+ * @throws IOException
+ */
+ private void addObjectToConflicts(int offset, COSObjectKey key, COSBase pb) throws IOException
+ {
+ COSObject obj = new COSObject(null);
+ obj.setObjectNumber( COSInteger.get( key.getNumber() ) );
+ obj.setGenerationNumber( COSInteger.get( key.getGeneration() ) );
+ obj.setObject(pb);
+ ConflictObj conflictObj = new ConflictObj(offset, key, obj);
+ conflictList.add(conflictObj);
+ }
+
+ /**
+ * This will parse the startxref section from the stream.
+ * The startxref value is ignored.
+ *
+ * @return false on parsing error
+ * @throws IOException If an IO error occurs.
+ */
+ private boolean parseStartXref() throws IOException
+ {
+ if(pdfSource.peek() != 's')
+ {
+ return false;
+ }
+ String startXRef = readString();
+ if( !startXRef.trim().equals( "startxref" ) )
+ {
+ return false;
+ }
+ skipSpaces();
+ /* This integer is the byte offset of the first object referenced by the xref or xref stream
+ * Needed for the incremental update (PREV)
+ */
+ getDocument().setStartXref(readInt());
+ return true;
+ }
+
+
+ /**
+ * This will parse the xref table from the stream and add it to the state
+ * The XrefTable contents are ignored.
+ * @param startByteOffset the offset to start at
+ * @return false on parsing error
+ * @throws IOException If an IO error occurs.
+ */
+ private boolean parseXrefTable( int startByteOffset ) throws IOException
+ {
+ if(pdfSource.peek() != 'x')
+ {
+ return false;
+ }
+ String xref = readString();
+ if( !xref.trim().equals( "xref" ) )
+ {
+ return false;
+ }
+
+ // signal start of new XRef
+ xrefTrailerResolver.nextXrefObj( startByteOffset );
+
+ /*
+ * Xref tables can have multiple sections.
+ * Each starts with a starting object id and a count.
+ */
+ while(true)
+ {
+ int currObjID = readInt(); // first obj id
+ int count = readInt(); // the number of objects in the xref table
+ skipSpaces();
+ for(int i = 0; i < count; i++)
+ {
+ if(pdfSource.isEOF() || isEndOfName((char)pdfSource.peek()))
+ {
+ break;
+ }
+ if(pdfSource.peek() == 't')
+ {
+ break;
+ }
+ //Ignore table contents
+ String currentLine = readLine();
+ String[] splitString = currentLine.split(" ");
+ if (splitString.length < 3)
+ {
+ log.warn("invalid xref line: " + currentLine);
+ break;
+ }
+ /* This supports the corrupt table as reported in
+ * PDFBOX-474 (XXXX XXX XX n) */
+ if(splitString[splitString.length-1].equals("n"))
+ {
+ try
+ {
+ int currOffset = Integer.parseInt(splitString[0]);
+ int currGenID = Integer.parseInt(splitString[1]);
+ COSObjectKey objKey = new COSObjectKey(currObjID, currGenID);
+ xrefTrailerResolver.setXRef(objKey, currOffset);
+ }
+ catch(NumberFormatException e)
+ {
+ throw new IOException(e.getMessage());
+ }
+ }
+ else if(!splitString[2].equals("f"))
+ {
+ throw new IOException("Corrupt XRefTable Entry - ObjID:" + currObjID);
+ }
+ currObjID++;
+ skipSpaces();
+ }
+ skipSpaces();
+ char c = (char)pdfSource.peek();
+ if(c < '0' || c > '9')
+ {
+ break;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * This will parse the trailer from the stream and add it to the state.
+ *
+ * @return false on parsing error
+ * @throws IOException If an IO error occurs.
+ */
+ private boolean parseTrailer() throws IOException
+ {
+ if(pdfSource.peek() != 't')
+ {
+ return false;
+ }
+ //read "trailer"
+ String nextLine = readLine();
+ if( !nextLine.trim().equals( "trailer" ) )
+ {
+ // in some cases the EOL is missing and the trailer immediately
+ // continues with "<<" or with a blank character
+ // even if this does not comply with PDF reference we want to support as many PDFs as possible
+ // Acrobat reader can also deal with this.
+ if (nextLine.startsWith("trailer"))
+ {
+ byte[] b = nextLine.getBytes("ISO-8859-1");
+ int len = "trailer".length();
+ pdfSource.unread('\n');
+ pdfSource.unread(b, len, b.length-len);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ // in some cases the EOL is missing and the trailer continues with " <<"
+ // even if this does not comply with PDF reference we want to support as many PDFs as possible
+ // Acrobat reader can also deal with this.
+ skipSpaces();
+
+ COSDictionary parsedTrailer = parseCOSDictionary();
+ xrefTrailerResolver.setTrailer( parsedTrailer );
+
+ // The version can also be specified within the document /Catalog
+ readVersionInTrailer(parsedTrailer);
+
+ skipSpaces();
+ return true;
+ }
+
+ /**
+ * The document catalog can also have a /Version parameter which overrides the version specified
+ * in the header if, and only if it is greater.
+ *
+ * @param parsedTrailer the parsed catalog in the trailer
+ */
+ private void readVersionInTrailer(COSDictionary parsedTrailer)
+ {
+ COSObject root = (COSObject) parsedTrailer.getItem(COSName.ROOT);
+ if (root != null)
+ {
+ COSName version = (COSName) root.getItem(COSName.VERSION);
+ if (version != null)
+ {
+ float trailerVersion = Float.valueOf(version.getName());
+ if (trailerVersion > document.getVersion())
+ {
+ document.setVersion(trailerVersion);
+ }
+ }
+ }
+ }
+
+ /**
+ * Fills XRefTrailerResolver with data of given stream.
+ * Stream must be of type XRef.
+ * @param stream the stream to be read
+ * @param objByteOffset the offset to start at
+ * @throws IOException if there is an error parsing the stream
+ */
+ public void parseXrefStream( COSStream stream, int objByteOffset ) throws IOException
+ {
+ xrefTrailerResolver.nextXrefObj( objByteOffset );
+ xrefTrailerResolver.setTrailer( stream );
+ PDFXrefStreamParser parser =
+ new PDFXrefStreamParser( stream, document, forceParsing, xrefTrailerResolver );
+ parser.parse();
+ }
+
+ /**
+ * Used to resolve conflicts when a PDF Document has multiple objects with
+ * the same id number. Ideally, we could use the Xref table when parsing
+ * the document to be able to determine which of the objects with the same ID
+ * is correct, but we do not have access to the Xref Table during parsing.
+ * Instead, we queue up the conflicts and resolve them after the Xref has
+ * been parsed. The Objects listed in the Xref Table are kept and the
+ * others are ignored.
+ */
+ private static class ConflictObj
+ {
+
+ private int offset;
+ private COSObjectKey objectKey;
+ private COSObject object;
+
+ public ConflictObj(int offsetValue, COSObjectKey key, COSObject pdfObject)
+ {
+ this.offset = offsetValue;
+ this.objectKey = key;
+ this.object = pdfObject;
+ }
+ @Override
+ public String toString()
+ {
+ return "Object(" + offset + ", " + objectKey + ")";
+ }
+
+ /**
+ * Sometimes pdf files have objects with the same ID number yet are
+ * not referenced by the Xref table and therefore should be excluded.
+ * This method goes through the conflicts list and replaces the object stored
+ * in the objects array with this one if it is referenced by the xref
+ * table.
+ * @throws IOException
+ */
+ private static void resolveConflicts(COSDocument document, List<ConflictObj> conflictList) throws IOException
+ {
+ Iterator<ConflictObj> conflicts = conflictList.iterator();
+ while(conflicts.hasNext())
+ {
+ ConflictObj o = conflicts.next();
+ Integer offset = new Integer(o.offset);
+ if(document.getXrefTable().containsValue(offset))
+ {
+ COSObject pdfObject = document.getObjectFromPool(o.objectKey);
+ pdfObject.setObject(o.object.getObject());
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.pdfparser;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.util.ImageParameters;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * This will parse a PDF byte stream and extract operands and such.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.32 $
+ */
+public class PDFStreamParser extends BaseParser
+{
+ private List<Object> streamObjects = new ArrayList<Object>( 100 );
+ private RandomAccess file;
+ private PDFOperator lastBIToken = null;
+
+ /**
+ * Constructor that takes a stream to parse.
+ *
+ * @since Apache PDFBox 1.3.0
+ * @param stream The stream to read data from.
+ * @param raf The random access file.
+ * @param forceParcing flag to skip malformed or otherwise unparseable
+ * input where possible
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public PDFStreamParser(
+ InputStream stream, RandomAccess raf, boolean forceParsing)
+ throws IOException {
+ super(stream, forceParsing);
+ file = raf;
+ }
+
+ /**
+ * Constructor that takes a stream to parse.
+ *
+ * @param stream The stream to read data from.
+ * @param raf The random access file.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public PDFStreamParser(InputStream stream, RandomAccess raf)
+ throws IOException {
+ this(stream, raf, FORCE_PARSING);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param stream The stream to parse.
+ *
+ * @throws IOException If there is an error initializing the stream.
+ */
+ public PDFStreamParser( PDStream stream ) throws IOException
+ {
+ this( stream.createInputStream(), stream.getStream().getScratchFile() );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @since Apache PDFBox 1.3.0
+ * @param stream The stream to parse.
+ * @param forceParcing flag to skip malformed or otherwise unparseable
+ * input where possible
+ * @throws IOException If there is an error initializing the stream.
+ */
+ public PDFStreamParser(COSStream stream, boolean forceParsing)
+ throws IOException {
+ this(stream.getUnfilteredStream(), stream.getScratchFile(), forceParsing);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param stream The stream to parse.
+ *
+ * @throws IOException If there is an error initializing the stream.
+ */
+ public PDFStreamParser( COSStream stream ) throws IOException
+ {
+ this( stream.getUnfilteredStream(), stream.getScratchFile() );
+ }
+
+ /**
+ * This will parse the tokens in the stream. This will close the
+ * stream when it is finished parsing.
+ *
+ * @throws IOException If there is an error while parsing the stream.
+ */
+ public void parse() throws IOException
+ {
+ try
+ {
+ Object token = null;
+ while( (token = parseNextToken()) != null )
+ {
+ streamObjects.add( token );
+ //logger().fine( "parsed=" + token );
+ }
+ }
+ finally
+ {
+ pdfSource.close();
+ }
+ }
+
+ /**
+ * This will get the tokens that were parsed from the stream.
+ *
+ * @return All of the tokens in the stream.
+ */
+ public List<Object> getTokens()
+ {
+ return streamObjects;
+ }
+
+ public void close() throws IOException
+ {
+ pdfSource.close();
+ }
+
+ /**
+ * This will get an iterator which can be used to parse the stream
+ * one token after the other.
+ *
+ * @return an iterator to get one token after the other
+ */
+ public Iterator<Object> getTokenIterator()
+ {
+ return new Iterator<Object>()
+ {
+ private Object token;
+
+ private void tryNext()
+ {
+ try {
+ if(token == null)
+ {
+ token = parseNextToken();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNext()
+ {
+ tryNext();
+ return token != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object next() {
+ tryNext();
+ Object tmp = token;
+ if(tmp == null)
+ {
+ throw new NoSuchElementException();
+ }
+ token = null;
+ return tmp;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /**
+ * This will parse the next token in the stream.
+ *
+ * @return The next token in the stream or null if there are no more tokens in the stream.
+ *
+ * @throws IOException If an io error occurs while parsing the stream.
+ */
+ private Object parseNextToken() throws IOException
+ {
+ Object retval = null;
+
+ skipSpaces();
+ int nextByte = pdfSource.peek();
+ if( ((byte)nextByte) == -1 )
+ {
+ return null;
+ }
+ char c = (char)nextByte;
+ switch(c)
+ {
+ case '<':
+ {
+ int leftBracket = pdfSource.read();//pull off first left bracket
+ c = (char)pdfSource.peek(); //check for second left bracket
+ pdfSource.unread( leftBracket ); //put back first bracket
+ if(c == '<')
+ {
+
+ COSDictionary pod = parseCOSDictionary();
+ skipSpaces();
+ if((char)pdfSource.peek() == 's')
+ {
+ retval = parseCOSStream( pod, file );
+ }
+ else
+ {
+ retval = pod;
+ }
+ }
+ else
+ {
+ retval = parseCOSString();
+ }
+ break;
+ }
+ case '[': // array
+ {
+ retval = parseCOSArray();
+ break;
+ }
+ case '(': // string
+ retval = parseCOSString();
+ break;
+ case '/': // name
+ retval = parseCOSName();
+ break;
+ case 'n': // null
+ {
+ String nullString = readString();
+ if( nullString.equals( "null") )
+ {
+ retval = COSNull.NULL;
+ }
+ else
+ {
+ retval = PDFOperator.getOperator( nullString );
+ }
+ break;
+ }
+ case 't':
+ case 'f':
+ {
+ String next = readString();
+ if( next.equals( "true" ) )
+ {
+ retval = COSBoolean.TRUE;
+ break;
+ }
+ else if( next.equals( "false" ) )
+ {
+ retval = COSBoolean.FALSE;
+ }
+ else
+ {
+ retval = PDFOperator.getOperator( next );
+ }
+ break;
+ }
+ case 'R':
+ {
+ String line = readString();
+ if( line.equals( "R" ) )
+ {
+ retval = new COSObject( null );
+ }
+ else
+ {
+ retval = PDFOperator.getOperator( line );
+ }
+ break;
+ }
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '-':
+ case '+':
+ case '.':
+ {
+ /* We will be filling buf with the rest of the number. Only
+ * allow 1 "." and "-" and "+" at start of number. */
+ StringBuffer buf = new StringBuffer();
+ buf.append( c );
+ pdfSource.read();
+
+ boolean dotNotRead = (c != '.');
+ while( Character.isDigit(( c = (char)pdfSource.peek()) ) || (dotNotRead && (c == '.')) )
+ {
+ buf.append( c );
+ pdfSource.read();
+
+ if (dotNotRead && (c == '.'))
+ {
+ dotNotRead = false;
+ }
+ }
+ retval = COSNumber.get( buf.toString() );
+ break;
+ }
+ case 'B':
+ {
+ String next = readString();
+ retval = PDFOperator.getOperator( next );
+
+ if( next.equals( "BI" ) )
+ {
+ lastBIToken = (PDFOperator)retval;
+ COSDictionary imageParams = new COSDictionary();
+ lastBIToken.setImageParameters( new ImageParameters( imageParams ) );
+ Object nextToken = null;
+ while( (nextToken = parseNextToken()) instanceof COSName )
+ {
+ Object value = parseNextToken();
+ imageParams.setItem( (COSName)nextToken, (COSBase)value );
+ }
+ //final token will be the image data, maybe??
+ PDFOperator imageData = (PDFOperator)nextToken;
+ lastBIToken.setImageData( imageData.getImageData() );
+ }
+ break;
+ }
+ case 'I':
+ {
+ //ImageParameters imageParams = lastBIToken.getImageParameters();
+
+ //int expectedBytes = (int)Math.ceil(imageParams.getHeight() * imageParams.getWidth() *
+ // (imageParams.getBitsPerComponent()/8) );
+ //Special case for ID operator
+ String id = "" + (char)pdfSource.read() + (char)pdfSource.read();
+ if( !id.equals( "ID" ) )
+ {
+ throw new IOException( "Error: Expected operator 'ID' actual='" + id + "'" );
+ }
+ ByteArrayOutputStream imageData = new ByteArrayOutputStream();
+ //boolean foundEnd = false;
+ if( this.isWhitespace() )
+ {
+ //pull off the whitespace character
+ pdfSource.read();
+ }
+ int twoBytesAgo = 0;
+ int lastByte = pdfSource.read();
+ int currentByte = pdfSource.read();
+ int count = 0;
+ //PDF spec is kinda unclear about this. Should a whitespace
+ //always appear before EI? Not sure, I found a PDF
+ //(UnderstandingWebSphereClassLoaders.pdf) which has EI as part
+ //of the image data and will stop parsing prematurely if there is
+ //not a check for <whitespace>EI<whitespace>.
+ while( !(isWhitespace( twoBytesAgo ) &&
+ lastByte == 'E' &&
+ currentByte == 'I' &&
+ isWhitespace() //&&
+ //amyuni2_05d__pdf1_3_acro4x.pdf has image data that
+ //is compressed, so expectedBytes is useless here.
+ //count >= expectedBytes
+ ) &&
+ !pdfSource.isEOF() )
+ {
+ imageData.write( lastByte );
+ twoBytesAgo = lastByte;
+ lastByte = currentByte;
+ currentByte = pdfSource.read();
+ count++;
+ }
+ pdfSource.unread( 'I' ); //unread the EI operator
+ pdfSource.unread( 'E' );
+ retval = PDFOperator.getOperator( "ID" );
+ ((PDFOperator)retval).setImageData( imageData.toByteArray() );
+ break;
+ }
+ case ']':
+ {
+ // some ']' around without its previous '['
+ // this means a PDF is somewhat corrupt but we will continue to parse.
+ pdfSource.read();
+ retval = COSNull.NULL; // must be a better solution than null...
+ break;
+ }
+ default:
+ {
+ //we must be an operator
+ String operator = readOperator();
+ if( operator.trim().length() == 0 )
+ {
+ //we have a corrupt stream, stop reading here
+ retval = null;
+ }
+ else
+ {
+ retval = PDFOperator.getOperator( operator );
+ }
+ }
+
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will read an operator from the stream.
+ *
+ * @return The operator that was read from the stream.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ protected String readOperator() throws IOException
+ {
+ skipSpaces();
+
+ //average string size is around 2 and the normal string buffer size is
+ //about 16 so lets save some space.
+ StringBuffer buffer = new StringBuffer(4);
+ int nextChar = (int)pdfSource.peek();
+ while(
+ nextChar != -1 && // EOF
+ !isWhitespace(nextChar) &&
+ !isClosing(nextChar) &&
+ nextChar != (int)'[' &&
+ nextChar != (int)'<' &&
+ nextChar != (int)'(' &&
+ nextChar != (int)'/' &&
+ (nextChar < (int)'0' ||
+ nextChar > (int)'9' ) )
+ {
+ char currentChar = (char)pdfSource.read();
+ nextChar = (int)pdfSource.peek();
+ buffer.append( currentChar );
+ // Type3 Glyph description has operators with a number in the name
+ if (currentChar == 'd' && (nextChar == '0' || nextChar == '1') ) {
+ buffer.append( (char)pdfSource.read() );
+ nextChar = (int)pdfSource.peek();
+ }
+ }
+ return buffer.toString();
+ }
+}
--- /dev/null
+/*
+ * 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.pdfparser;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This will parse a PDF 1.5 (or better) Xref stream and
+ * extract the xref information from the stream.
+ *
+ * @author <a href="mailto:justinl@basistech.com">Justin LeFebvre</a>
+ * @version $Revision: 1.0 $
+ */
+public class PDFXrefStreamParser extends BaseParser
+{
+ private COSStream stream;
+ private XrefTrailerResolver xrefTrailerResolver;
+
+ /**
+ * Constructor.
+ *
+ * @since 1.3.0
+ * @param strm The stream to parse.
+ * @param doc The document for the current parsing.
+ * @param forceParcing flag to skip malformed or otherwise unparseable
+ * input where possible
+ * @param xrefTrailerResolver resolver to read the xref/trailer information
+ *
+ * @throws IOException If there is an error initializing the stream.
+ */
+ public PDFXrefStreamParser(
+ COSStream strm, COSDocument doc, boolean forceParsing,
+ XrefTrailerResolver xrefTrailerResolver )
+ throws IOException {
+ super(strm.getUnfilteredStream(), forceParsing);
+ setDocument(doc);
+ stream = strm;
+ this.xrefTrailerResolver = xrefTrailerResolver;
+ }
+
+ /**
+ * Parses through the unfiltered stream and populates the xrefTable HashMap.
+ * @throws IOException If there is an error while parsing the stream.
+ */
+ public void parse() throws IOException
+ {
+ try
+ {
+ COSArray xrefFormat = (COSArray)stream.getDictionaryObject(COSName.W);
+ COSArray indexArray = (COSArray)stream.getDictionaryObject(COSName.INDEX);
+ /*
+ * If Index doesn't exist, we will use the default values.
+ */
+ if(indexArray == null)
+ {
+ indexArray = new COSArray();
+ indexArray.add(COSInteger.ZERO);
+ indexArray.add(stream.getDictionaryObject(COSName.SIZE));
+ }
+
+ ArrayList<Integer> objNums = new ArrayList<Integer>();
+
+ /*
+ * Populates objNums with all object numbers available
+ */
+ Iterator<COSBase> indexIter = indexArray.iterator();
+ while(indexIter.hasNext())
+ {
+ int objID = ((COSInteger)indexIter.next()).intValue();
+ int size = ((COSInteger)indexIter.next()).intValue();
+ for(int i = 0; i < size; i++)
+ {
+ objNums.add(new Integer(objID + i));
+ }
+ }
+ Iterator<Integer> objIter = objNums.iterator();
+ /*
+ * Calculating the size of the line in bytes
+ */
+ int w0 = xrefFormat.getInt(0);
+ int w1 = xrefFormat.getInt(1);
+ int w2 = xrefFormat.getInt(2);
+ int lineSize = w0 + w1 + w2;
+
+ while(pdfSource.available() > 0 && objIter.hasNext())
+ {
+ byte[] currLine = new byte[lineSize];
+ pdfSource.read(currLine);
+
+ int type = 0;
+ /*
+ * Grabs the number of bytes specified for the first column in
+ * the W array and stores it.
+ */
+ for(int i = 0; i < w0; i++)
+ {
+ type += (currLine[i] & 0x00ff) << ((w0 - i - 1)* 8);
+ }
+ //Need to remember the current objID
+ Integer objID = (Integer)objIter.next();
+ /*
+ * 3 different types of entries.
+ */
+ switch(type)
+ {
+ case 0:
+ /*
+ * Skipping free objects
+ */
+ break;
+ case 1:
+ int offset = 0;
+ for(int i = 0; i < w1; i++)
+ {
+ offset += (currLine[i + w0] & 0x00ff) << ((w1 - i - 1) * 8);
+ }
+ int genNum = 0;
+ for(int i = 0; i < w2; i++)
+ {
+ genNum += (currLine[i + w0 + w1] & 0x00ff) << ((w2 - i - 1) * 8);
+ }
+ COSObjectKey objKey = new COSObjectKey(objID.intValue(), genNum);
+ xrefTrailerResolver.setXRef(objKey, offset);
+ break;
+ case 2:
+ /*
+ * These objects are handled by the dereferenceObjects() method
+ * since they're only pointing to object numbers
+ */
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ finally
+ {
+ pdfSource.close();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.pdfparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.regex.Pattern;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.pdfwriter.COSWriter;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+public class VisualSignatureParser extends BaseParser {
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDFParser.class);
+
+ public VisualSignatureParser(InputStream input) throws IOException {
+ super(input);
+ }
+
+ public void parse() throws IOException {
+ document = new COSDocument();
+ skipToNextObj();
+
+ boolean wasLastParsedObjectEOF = false;
+ try {
+ while(!wasLastParsedObjectEOF) {
+ if(pdfSource.isEOF()) {
+ break;
+ }
+ try {
+ wasLastParsedObjectEOF = parseObject();
+ } catch(IOException e) {
+ /*
+ * Warning is sent to the PDFBox.log and to the Console that
+ * we skipped over an object
+ */
+ log.warn("Parsing Error, Skipping Object", e);
+ skipToNextObj();
+ }
+ skipSpaces();
+ }
+ } catch(IOException e) {
+ /*
+ * PDF files may have random data after the EOF marker. Ignore errors if
+ * last object processed is EOF.
+ */
+ if(!wasLastParsedObjectEOF) {
+ throw e;
+ }
+ }
+ }
+
+ private void skipToNextObj() throws IOException {
+ byte[] b = new byte[16];
+ Pattern p = Pattern.compile("\\d+\\s+\\d+\\s+obj.*", Pattern.DOTALL);
+ /* Read a buffer of data each time to see if it starts with a
+ * known keyword. This is not the most efficient design, but we should
+ * rarely be needing this function. We could update this to use the
+ * circular buffer, like in readUntilEndStream().
+ */
+ while(!pdfSource.isEOF()) {
+ int l = pdfSource.read(b);
+ if(l < 1) {
+ break;
+ }
+ String s = new String(b, "US-ASCII");
+ if(s.startsWith("trailer")
+ || s.startsWith("xref")
+ || s.startsWith("startxref")
+ || s.startsWith("stream")
+ || p.matcher(s).matches()) {
+ pdfSource.unread(b);
+ break;
+ } else {
+ pdfSource.unread(b, 1, l - 1);
+ }
+ }
+ }
+
+ private boolean parseObject() throws IOException {
+ boolean isEndOfFile = false;
+ skipSpaces();
+ //peek at the next character to determine the type of object we are parsing
+ char peekedChar = (char) pdfSource.peek();
+
+ //ignore endobj and endstream sections.
+ while(peekedChar == 'e') {
+ //there are times when there are multiple endobj, so lets
+ //just read them and move on.
+ readString();
+ skipSpaces();
+ peekedChar = (char) pdfSource.peek();
+ }
+ if(pdfSource.isEOF()) {
+ // end of file we will return a false and call it a day.
+ } else if(peekedChar == 'x') {
+ //xref table. Note: The contents of the Xref table are currently ignored
+ return true;
+ } else if(peekedChar == 't' || peekedChar == 's') {
+ // Note: startxref can occur in either a trailer section or by itself
+ if(peekedChar == 't') {
+ return true;
+ }
+ if(peekedChar == 's') {
+ skipToNextObj();
+ //verify that EOF exists
+ String eof = readExpectedString("%%EOF");
+ if(eof.indexOf("%%EOF") == -1 && !pdfSource.isEOF()) {
+ throw new IOException("expected='%%EOF' actual='" + eof + "' next=" + readString()
+ + " next=" + readString());
+ }
+ isEndOfFile = true;
+ }
+ } else {
+ //we are going to parse an normal object
+ int number = -1;
+ int genNum = -1;
+ String objectKey = null;
+ boolean missingObjectNumber = false;
+ try {
+ char peeked = (char) pdfSource.peek();
+ if(peeked == '<') {
+ missingObjectNumber = true;
+ } else {
+ number = readInt();
+ }
+ } catch(IOException e) {
+ //ok for some reason "GNU Ghostscript 5.10" puts two endobj
+ //statements after an object, of course this is nonsense
+ //but because we want to support as many PDFs as possible
+ //we will simply try again
+ number = readInt();
+ }
+ if(!missingObjectNumber) {
+ skipSpaces();
+ genNum = readInt();
+
+ objectKey = readString(3);
+ //System.out.println( "parseObject() num=" + number +
+ //" genNumber=" + genNum + " key='" + objectKey + "'" );
+ if(!objectKey.equals("obj")) {
+ throw new IOException("expected='obj' actual='" + objectKey + "' " + pdfSource);
+ }
+ } else {
+ number = -1;
+ genNum = -1;
+ }
+
+ skipSpaces();
+ COSBase pb = parseDirObject();
+ String endObjectKey = readString();
+
+ if(endObjectKey.equals("stream")) {
+ pdfSource.unread(endObjectKey.getBytes());
+ pdfSource.unread(' ');
+ if(pb instanceof COSDictionary) {
+ pb = parseCOSStream((COSDictionary) pb, getDocument().getScratchFile());
+
+ } else {
+ // this is not legal
+ // the combination of a dict and the stream/endstream forms a complete stream object
+ throw new IOException("stream not preceded by dictionary");
+ }
+ endObjectKey = readString();
+ }
+
+ COSObjectKey key = new COSObjectKey(number, genNum);
+ COSObject pdfObject = document.getObjectFromPool(key);
+ pb.setNeedToBeUpdate(true);
+ pdfObject.setObject(pb);
+
+ if(!endObjectKey.equals("endobj")) {
+ if(endObjectKey.startsWith("endobj")) {
+ /*
+ * Some PDF files don't contain a new line after endobj so we
+ * need to make sure that the next object number is getting read separately
+ * and not part of the endobj keyword. Ex. Some files would have "endobj28"
+ * instead of "endobj"
+ */
+ pdfSource.unread(endObjectKey.substring(6).getBytes());
+ } else if(!pdfSource.isEOF()) {
+ try {
+ //It is possible that the endobj is missing, there
+ //are several PDFs out there that do that so skip it and move on.
+ Float.parseFloat(endObjectKey);
+ pdfSource.unread(COSWriter.SPACE);
+ pdfSource.unread(endObjectKey.getBytes());
+ } catch(NumberFormatException e) {
+ //we will try again incase there was some garbage which
+ //some writers will leave behind.
+ String secondEndObjectKey = readString();
+ if(!secondEndObjectKey.equals("endobj")) {
+ if(isClosing()) {
+ //found a case with 17506.pdf object 41 that was like this
+ //41 0 obj [/Pattern /DeviceGray] ] endobj
+ //notice the second array close, here we are reading it
+ //and ignoring and attempting to continue
+ pdfSource.read();
+ }
+ skipSpaces();
+ String thirdPossibleEndObj = readString();
+ if(!thirdPossibleEndObj.equals("endobj")) {
+ throw new IOException("expected='endobj' firstReadAttempt='" + endObjectKey + "' "
+ + "secondReadAttempt='" + secondEndObjectKey + "' " + pdfSource);
+ }
+ }
+ }
+ }
+ }
+ skipSpaces();
+ }
+ return isEndOfFile;
+ }
+
+ public COSDocument getDocument() throws IOException {
+ if(document == null) {
+ throw new IOException("You must call parse() before calling getDocument()");
+ }
+ return document;
+ }
+}
--- /dev/null
+/*
+ * 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.pdfparser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This class will collect all XRef/trailer objects and creates correct
+ * xref/trailer information after all objects are read using startxref
+ * and 'Prev' information (unused XRef/trailer objects are discarded).
+ *
+ * In case of missing startxref or wrong startxref pointer all
+ * XRef/trailer objects are used to create xref table / trailer dictionary
+ * in order they occur.
+ *
+ * For each new xref object/XRef stream method {@link #nextXrefObj(int)}
+ * must be called with start byte position. All following calls to
+ * {@link #setXRef(COSObjectKey, int)} or {@link #setTrailer(COSDictionary)}
+ * will add the data for this byte position.
+ *
+ * After all objects are parsed the startxref position must be provided
+ * using {@link #setStartxref(int)}. This is used to build the chain of
+ * active xref/trailer objects used for creating document trailer and xref table.
+ *
+ * @author Timo Böhme (timo.boehme at ontochem.com)
+ */
+public class XrefTrailerResolver
+{
+
+ /**
+ * A class which represents a xref/trailer object
+ *
+ */
+ class XrefTrailerObj
+ {
+ private COSDictionary trailer = null;
+ private final Map<COSObjectKey, Integer> xrefTable = new HashMap<COSObjectKey, Integer>();
+ }
+
+ private final Map<Integer, XrefTrailerObj> bytePosToXrefMap = new HashMap<Integer, XrefTrailerObj>();
+ private XrefTrailerObj curXrefTrailerObj = null;
+ private XrefTrailerObj resolvedXrefTrailer = null;
+
+ /** Log instance. */
+ private static final Log log = LogFactory.getLog( XrefTrailerResolver.class );
+
+ /**
+ * Signals that a new XRef object (table or stream) starts.
+ * @param startBytePos the offset to start at
+ *
+ */
+ public void nextXrefObj( final int startBytePos )
+ {
+ bytePosToXrefMap.put( startBytePos, curXrefTrailerObj = new XrefTrailerObj() );
+ }
+
+ /**
+ * Populate XRef HashMap of current XRef object.
+ * Will add an Xreftable entry that maps ObjectKeys to byte offsets in the file.
+ * @param objKey The objkey, with id and gen numbers
+ * @param offset The byte offset in this file
+ */
+ public void setXRef( COSObjectKey objKey, int offset )
+ {
+ if ( curXrefTrailerObj == null )
+ {
+ // should not happen...
+ log.warn( "Cannot add XRef entry for '" + objKey.getNumber() + "' because XRef start was not signalled." );
+ return;
+ }
+ curXrefTrailerObj.xrefTable.put( objKey, offset );
+ }
+
+ /**
+ * Adds trailer information for current XRef object.
+ *
+ * @param trailer the current document trailer dictionary
+ */
+ public void setTrailer( COSDictionary trailer )
+ {
+ if ( curXrefTrailerObj == null )
+ {
+ // should not happen...
+ log.warn( "Cannot add trailer because XRef start was not signalled." );
+ return;
+ }
+ curXrefTrailerObj.trailer = trailer;
+ }
+
+ /**
+ * Sets the byte position of the first XRef
+ * (has to be called after very last startxref was read).
+ * This is used to resolve chain of active XRef/trailer.
+ *
+ * In case startxref position is not found we output a
+ * warning and use all XRef/trailer objects combined
+ * in byte position order.
+ * Thus for incomplete PDF documents with missing
+ * startxref one could call this method with parameter value -1.
+ */
+ public void setStartxref( int startxrefBytePos )
+ {
+ if ( resolvedXrefTrailer != null )
+ {
+ log.warn( "Method must be called only ones with last startxref value." );
+ return;
+ }
+
+ resolvedXrefTrailer = new XrefTrailerObj();
+ resolvedXrefTrailer.trailer = new COSDictionary();
+
+ XrefTrailerObj curObj = bytePosToXrefMap.get( startxrefBytePos );
+ List<Integer> xrefSeqBytePos = new ArrayList<Integer>();
+
+ if ( curObj == null )
+ {
+ // no XRef at given position
+ log.warn( "Did not found XRef object at specified startxref position " + startxrefBytePos );
+
+ // use all objects in byte position order (last entries overwrite previous ones)
+ xrefSeqBytePos.addAll( bytePosToXrefMap.keySet() );
+ Collections.sort( xrefSeqBytePos );
+ }
+ else
+ {
+ // found starting Xref object
+ // add this and follow chain defined by 'Prev' keys
+ xrefSeqBytePos.add( startxrefBytePos );
+ while ( curObj.trailer != null )
+ {
+ int prevBytePos = curObj.trailer.getInt( COSName.PREV, -1 );
+ if ( prevBytePos == -1 )
+ {
+ break;
+ }
+
+ curObj = bytePosToXrefMap.get( prevBytePos );
+ if ( curObj == null )
+ {
+ log.warn( "Did not found XRef object pointed to by 'Prev' key at position " + prevBytePos );
+ break;
+ }
+ xrefSeqBytePos.add( prevBytePos );
+
+ // sanity check to prevent infinite loops
+ if ( xrefSeqBytePos.size() >= bytePosToXrefMap.size() )
+ {
+ break;
+ }
+ }
+ // have to reverse order so that later XRefs will overwrite previous ones
+ Collections.reverse( xrefSeqBytePos );
+ }
+
+ // merge used and sorted XRef/trailer
+ for ( Integer bPos : xrefSeqBytePos )
+ {
+ curObj = bytePosToXrefMap.get( bPos );
+ if ( curObj.trailer != null )
+ {
+ resolvedXrefTrailer.trailer.addAll( curObj.trailer );
+ }
+ resolvedXrefTrailer.xrefTable.putAll( curObj.xrefTable );
+ }
+
+ }
+
+ /**
+ * Gets the resolved trailer. Might return <code>null</code> in case
+ * {@link #setStartxref(int)} was not called before.
+ *
+ */
+ public COSDictionary getTrailer()
+ {
+ return ( resolvedXrefTrailer == null ) ? null : resolvedXrefTrailer.trailer;
+ }
+
+ /**
+ * Gets the resolved xref table. Might return <code>null</code> in case
+ * {@link #setStartxref(int)} was not called before.
+ *
+ */
+ public Map<COSObjectKey, Integer> getXrefTable()
+ {
+ return ( resolvedXrefTrailer == null ) ? null : resolvedXrefTrailer.xrefTable;
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The pdfparser package contains classes to parse PDF documents and objects within the document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.pdfviewer;
+
+/**
+ * This is a simple class that will contain an index and a value.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ArrayEntry
+{
+ private int index;
+ private Object value;
+
+ /**
+ * This will get the value for this entry.
+ *
+ * @return The value for this entry.
+ */
+ public Object getValue()
+ {
+ return value;
+ }
+
+ /**
+ * This will set the value for this entry.
+ *
+ * @param val the new value for this entry.
+ */
+ public void setValue(Object val)
+ {
+ this.value = val;
+ }
+
+ /**
+ * This will get the index of the array entry.
+ *
+ * @return The 0-based index into the array
+ */
+ public int getIndex()
+ {
+ return index;
+ }
+
+ /**
+ * This will set the index value.
+ *
+ * @param i The new index value.
+ */
+ public void setIndex(int i)
+ {
+ index = i;
+ }
+}
--- /dev/null
+/*
+ * 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.pdfviewer;
+
+import org.apache.pdfbox.cos.COSName;
+
+
+/**
+ * This is a simple class that will contain a key and a value.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class MapEntry
+{
+ private Object key;
+ private Object value;
+
+ /**
+ * Get the key for this entry.
+ *
+ * @return The entry's key.
+ */
+ public Object getKey()
+ {
+ return key;
+ }
+
+ /**
+ * This will set the key for this entry.
+ *
+ * @param k the new key for this entry.
+ */
+ public void setKey(Object k)
+ {
+ key = k;
+ }
+
+ /**
+ * This will get the value for this entry.
+ *
+ * @return The value for this entry.
+ */
+ public Object getValue()
+ {
+ return value;
+ }
+
+ /**
+ * This will set the value for this entry.
+ *
+ * @param val the new value for this entry.
+ */
+ public void setValue(Object val)
+ {
+ this.value = val;
+ }
+
+ /**
+ * This will output a string representation of this class.
+ *
+ * @return A string representation of this class.
+ */
+ public String toString()
+ {
+ String retval = null;
+ if( key instanceof COSName )
+ {
+ retval = ((COSName)key).getName();
+ }
+ else
+ {
+ retval = "" +key;
+ }
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.pdfviewer;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+
+import java.io.IOException;
+
+import javax.swing.JPanel;
+
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This is a simple JPanel that can be used to display a PDF page.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDFPagePanel extends JPanel
+{
+
+ private PDPage page;
+ private PageDrawer drawer = null;
+ private Dimension pageDimension = null;
+ private Dimension drawDimension = null;
+
+ /**
+ * Constructor.
+ *
+ * @throws IOException If there is an error creating the Page drawing objects.
+ */
+ public PDFPagePanel() throws IOException
+ {
+ drawer = new PageDrawer();
+ }
+
+ /**
+ * This will set the page that should be displayed in this panel.
+ *
+ * @param pdfPage The page to draw.
+ */
+ public void setPage( PDPage pdfPage )
+ {
+ page = pdfPage;
+ PDRectangle pageSize = page.findMediaBox();
+ drawDimension = pageSize.createDimension();
+ int rotation = page.findRotation();
+ if (rotation == 90 || rotation == 270)
+ {
+ pageDimension = new Dimension(drawDimension.height, drawDimension.width);
+ }
+ else
+ {
+ pageDimension = drawDimension;
+ }
+ setSize( pageDimension );
+ setBackground( java.awt.Color.white );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void paint(Graphics g )
+ {
+ try
+ {
+ g.setColor( getBackground() );
+ g.fillRect( 0, 0, getWidth(), getHeight() );
+
+ int rotation = page.findRotation();
+ if (rotation == 90 || rotation == 270)
+ {
+ Graphics2D g2D = (Graphics2D)g;
+ g2D.translate(pageDimension.getWidth(), 0.0f);
+ g2D.rotate(Math.toRadians(rotation));
+ }
+
+ drawer.drawPage( g, page, drawDimension );
+ }
+ catch( IOException e )
+ {
+ e.printStackTrace();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.pdfviewer;
+
+import java.awt.Component;
+
+import javax.swing.JTree;
+
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+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.COSNull;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * A class to render tree cells for the pdfviewer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDFTreeCellRenderer extends DefaultTreeCellRenderer
+{
+ /**
+ * {@inheritDoc}
+ */
+ public Component getTreeCellRendererComponent(
+ JTree tree,
+ Object nodeValue,
+ boolean isSelected,
+ boolean expanded,
+ boolean leaf,
+ int row,
+ boolean componentHasFocus)
+ {
+ nodeValue = convertToTreeObject( nodeValue );
+ return super.getTreeCellRendererComponent( tree, nodeValue, isSelected, expanded, leaf,
+ row, componentHasFocus );
+ }
+
+ private Object convertToTreeObject( Object nodeValue )
+ {
+ if( nodeValue instanceof MapEntry )
+ {
+ MapEntry entry = (MapEntry)nodeValue;
+ COSName key = (COSName)entry.getKey();
+ COSBase value = (COSBase)entry.getValue();
+ nodeValue = key.getName() + ":" + convertToTreeObject( value );
+ }
+ else if( nodeValue instanceof COSFloat )
+ {
+ nodeValue = "" + ((COSFloat)nodeValue).floatValue();
+ }
+ else if( nodeValue instanceof COSInteger )
+ {
+ nodeValue = "" + ((COSInteger)nodeValue).intValue();
+ }
+ else if( nodeValue instanceof COSString )
+ {
+ nodeValue = ((COSString)nodeValue).getString();
+ }
+ else if( nodeValue instanceof COSName )
+ {
+ nodeValue = ((COSName)nodeValue).getName();
+ }
+ else if( nodeValue instanceof ArrayEntry )
+ {
+ ArrayEntry entry = (ArrayEntry)nodeValue;
+ nodeValue = "[" + entry.getIndex() + "]" + convertToTreeObject( entry.getValue() );
+ }
+ else if( nodeValue instanceof COSNull )
+ {
+ nodeValue = "null";
+ }
+ else if( nodeValue instanceof COSDictionary )
+ {
+ COSDictionary dict = (COSDictionary)nodeValue;
+ if( nodeValue instanceof COSStream )
+ {
+ nodeValue = "Stream";
+ }
+ else
+ {
+ nodeValue = "Dictionary";
+ }
+
+ COSName type = (COSName)dict.getDictionaryObject( COSName.TYPE );
+ if( type != null )
+ {
+ nodeValue = nodeValue + "(" + type.getName();
+ COSName subType = (COSName)dict.getDictionaryObject( COSName.SUBTYPE );
+ if( subType != null )
+ {
+ nodeValue = nodeValue + ":" + subType.getName();
+ }
+
+ nodeValue = nodeValue + ")";
+ }
+ }
+ else if( nodeValue instanceof COSArray )
+ {
+ nodeValue="Array";
+ }
+ else if( nodeValue instanceof COSString )
+ {
+ nodeValue = ((COSString)nodeValue).getString();
+ }
+ return nodeValue;
+
+ }
+}
--- /dev/null
+/*
+ * 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.pdfviewer;
+
+/**
+ * A tree model that uses a cos document.
+ *
+ *
+ * @author wurtz
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeModel;
+
+//import java.awt.event.*;
+import javax.swing.event.TreeModelListener;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A class to model a PDF document as a tree structure.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class PDFTreeModel implements TreeModel
+{
+ private PDDocument document;
+
+ /**
+ * constructor.
+ */
+ public PDFTreeModel()
+ {
+ //default constructor
+ }
+
+ /**
+ * Constructor to take a document.
+ *
+ * @param doc The document to display in the tree.
+ */
+ public PDFTreeModel(PDDocument doc)
+ {
+ setDocument(doc);
+ }
+
+ /**
+ * Set the document to display in the tree.
+ *
+ * @param doc The document to display in the tree.
+ */
+ public void setDocument(PDDocument doc)
+ {
+ document = doc;
+ }
+ /**
+ * Adds a listener for the <code>TreeModelEvent</code>
+ * posted after the tree changes.
+ *
+ * @param l the listener to add
+ * @see #removeTreeModelListener
+ *
+ */
+ public void addTreeModelListener(TreeModelListener l)
+ {
+ //required for interface
+ }
+
+ /**
+ * Returns the child of <code>parent</code> at index <code>index</code>
+ * in the parent's
+ * child array. <code>parent</code> must be a node previously obtained
+ * from this data source. This should not return <code>null</code>
+ * if <code>index</code>
+ * is a valid index for <code>parent</code> (that is <code>index >= 0 &&
+ * index < getChildCount(parent</code>)).
+ *
+ * @param parent a node in the tree, obtained from this data source
+ * @param index The index into the parent object to location the child object.
+ * @return the child of <code>parent</code> at index <code>index</code>
+ *
+ */
+ public Object getChild(Object parent, int index)
+ {
+ Object retval = null;
+ if( parent instanceof COSArray )
+ {
+ ArrayEntry entry = new ArrayEntry();
+ entry.setIndex( index );
+ entry.setValue( ((COSArray)parent).getObject( index ) );
+ retval = entry;
+ }
+ else if( parent instanceof COSDictionary )
+ {
+ COSDictionary dict = ((COSDictionary)parent);
+ List<COSName> keys = new ArrayList<COSName>(dict.keySet());
+ Collections.sort( keys );
+ Object key = keys.get( index );
+ Object value = dict.getDictionaryObject( (COSName)key );
+ MapEntry entry = new MapEntry();
+ entry.setKey( key );
+ entry.setValue( value );
+ retval = entry;
+ }
+ else if( parent instanceof MapEntry )
+ {
+ retval = getChild( ((MapEntry)parent).getValue(), index );
+ }
+ else if( parent instanceof ArrayEntry )
+ {
+ retval = getChild( ((ArrayEntry)parent).getValue(), index );
+ }
+ else if( parent instanceof COSDocument )
+ {
+ retval = ((COSDocument)parent).getObjects().get( index );
+ }
+ else if( parent instanceof COSObject )
+ {
+ retval = ((COSObject)parent).getObject();
+ }
+ else
+ {
+ throw new RuntimeException( "Unknown COS type " + parent.getClass().getName() );
+ }
+ return retval;
+ }
+
+ /** Returns the number of children of <code>parent</code>.
+ * Returns 0 if the node
+ * is a leaf or if it has no children. <code>parent</code> must be a node
+ * previously obtained from this data source.
+ *
+ * @param parent a node in the tree, obtained from this data source
+ * @return the number of children of the node <code>parent</code>
+ *
+ */
+ public int getChildCount(Object parent)
+ {
+ int retval = 0;
+ if( parent instanceof COSArray )
+ {
+ retval = ((COSArray)parent).size();
+ }
+ else if( parent instanceof COSDictionary )
+ {
+ retval = ((COSDictionary)parent).size();
+ }
+ else if( parent instanceof MapEntry )
+ {
+ retval = getChildCount( ((MapEntry)parent).getValue() );
+ }
+ else if( parent instanceof ArrayEntry )
+ {
+ retval = getChildCount( ((ArrayEntry)parent).getValue() );
+ }
+ else if( parent instanceof COSDocument )
+ {
+ retval = ((COSDocument)parent).getObjects().size();
+ }
+ else if( parent instanceof COSObject )
+ {
+ retval = 1;
+ }
+ return retval;
+ }
+
+ /** Returns the index of child in parent. If <code>parent</code>
+ * is <code>null</code> or <code>child</code> is <code>null</code>,
+ * returns -1.
+ *
+ * @param parent a note in the tree, obtained from this data source
+ * @param child the node we are interested in
+ * @return the index of the child in the parent, or -1 if either
+ * <code>child</code> or <code>parent</code> are <code>null</code>
+ *
+ */
+ public int getIndexOfChild(Object parent, Object child)
+ {
+ int retval = -1;
+ if( parent != null && child != null )
+ {
+ if( parent instanceof COSArray )
+ {
+ COSArray array = (COSArray)parent;
+ if( child instanceof ArrayEntry )
+ {
+ ArrayEntry arrayEntry = (ArrayEntry)child;
+ retval = arrayEntry.getIndex();
+ }
+ else
+ {
+ retval = array.indexOf( (COSBase)child );
+ }
+ }
+ else if( parent instanceof COSDictionary )
+ {
+ MapEntry entry = (MapEntry)child;
+ COSDictionary dict = (COSDictionary)parent;
+ List<COSName> keys = new ArrayList<COSName>(dict.keySet());
+ Collections.sort( keys );
+ for( int i=0; retval == -1 && i<keys.size(); i++ )
+ {
+ if( keys.get( i ).equals( entry.getKey() ) )
+ {
+ retval = i;
+ }
+ }
+ }
+ else if( parent instanceof MapEntry )
+ {
+ retval = getIndexOfChild( ((MapEntry)parent).getValue(), child );
+ }
+ else if( parent instanceof ArrayEntry )
+ {
+ retval = getIndexOfChild( ((ArrayEntry)parent).getValue(), child );
+ }
+ else if( parent instanceof COSDocument )
+ {
+ retval = ((COSDocument)parent).getObjects().indexOf( child );
+ }
+ else if( parent instanceof COSObject )
+ {
+ retval = 0;
+ }
+ else
+ {
+ throw new RuntimeException( "Unknown COS type " + parent.getClass().getName() );
+ }
+ }
+ return retval;
+ }
+
+ /** Returns the root of the tree. Returns <code>null</code>
+ * only if the tree has no nodes.
+ *
+ * @return the root of the tree
+ *
+ */
+ public Object getRoot()
+ {
+ return document.getDocument().getTrailer();
+ }
+
+ /** Returns <code>true</code> if <code>node</code> is a leaf.
+ * It is possible for this method to return <code>false</code>
+ * even if <code>node</code> has no children.
+ * A directory in a filesystem, for example,
+ * may contain no files; the node representing
+ * the directory is not a leaf, but it also has no children.
+ *
+ * @param node a node in the tree, obtained from this data source
+ * @return true if <code>node</code> is a leaf
+ *
+ */
+ public boolean isLeaf(Object node)
+ {
+ boolean isLeaf = !(node instanceof COSDictionary ||
+ node instanceof COSArray ||
+ node instanceof COSDocument ||
+ node instanceof COSObject ||
+ (node instanceof MapEntry && !isLeaf(((MapEntry)node).getValue()) ) ||
+ (node instanceof ArrayEntry && !isLeaf(((ArrayEntry)node).getValue()) ));
+ return isLeaf;
+ }
+
+ /** Removes a listener previously added with
+ * <code>addTreeModelListener</code>.
+ *
+ * @see #addTreeModelListener
+ * @param l the listener to remove
+ *
+ */
+
+ public void removeTreeModelListener(TreeModelListener l)
+ {
+ //required for interface
+ }
+
+ /** Messaged when the user has altered the value for the item identified
+ * by <code>path</code> to <code>newValue</code>.
+ * If <code>newValue</code> signifies a truly new value
+ * the model should post a <code>treeNodesChanged</code> event.
+ *
+ * @param path path to the node that the user has altered
+ * @param newValue the new value from the TreeCellEditor
+ *
+ */
+ public void valueForPathChanged(TreePath path, Object newValue)
+ {
+ //required for interface
+ }
+}
--- /dev/null
+/*
+ * 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.pdfviewer;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.Area;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.Image;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+import org.apache.pdfbox.pdmodel.graphics.PDShading;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.pdmodel.text.PDTextState;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFStreamEngine;
+import org.apache.pdfbox.util.ResourceLoader;
+import org.apache.pdfbox.util.TextPosition;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSDictionary;
+
+
+/**
+ * This will paint a page in a PDF document to a graphics context.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.22 $
+ */
+public class PageDrawer extends PDFStreamEngine
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PageDrawer.class);
+
+ private Graphics2D graphics;
+ protected Dimension pageSize;
+ protected PDPage page;
+
+ private GeneralPath linePath = new GeneralPath();
+
+ /**
+ * Default constructor, loads properties from file.
+ *
+ * @throws IOException If there is an error loading properties from the file.
+ */
+ public PageDrawer() throws IOException
+ {
+ super( ResourceLoader.loadProperties(
+ "org/apache/pdfbox/resources/PageDrawer.properties", true ) );
+ }
+
+ /**
+ * This will draw the page to the requested context.
+ *
+ * @param g The graphics context to draw onto.
+ * @param p The page to draw.
+ * @param pageDimension The size of the page to draw.
+ *
+ * @throws IOException If there is an IO error while drawing the page.
+ */
+ public void drawPage( Graphics g, PDPage p, Dimension pageDimension ) throws IOException
+ {
+ graphics = (Graphics2D)g;
+ page = p;
+ pageSize = pageDimension;
+ graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
+ graphics.setRenderingHint( RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON );
+ // Only if there is some content, we have to process it.
+ // Otherwise we are done here and we will produce an empty page
+ if ( page.getContents() != null)
+ {
+ PDResources resources = page.findResources();
+ processStream( page, resources, page.getContents().getStream() );
+ }
+ List annotations = page.getAnnotations();
+ for( int i=0; i<annotations.size(); i++ )
+ {
+ PDAnnotation annot = (PDAnnotation)annotations.get( i );
+ PDRectangle rect = annot.getRectangle();
+ String appearanceName = annot.getAppearanceStream();
+ PDAppearanceDictionary appearDictionary = annot.getAppearance();
+ if( appearDictionary != null )
+ {
+ if( appearanceName == null )
+ {
+ appearanceName = "default";
+ }
+ Map appearanceMap = appearDictionary.getNormalAppearance();
+ if (appearanceMap != null) {
+ PDAppearanceStream appearance =
+ (PDAppearanceStream)appearanceMap.get( appearanceName );
+ if( appearance != null )
+ {
+ g.translate( (int)rect.getLowerLeftX(), (int)-rect.getLowerLeftY() );
+ processSubStream( page, appearance.getResources(), appearance.getStream() );
+ g.translate( (int)-rect.getLowerLeftX(), (int)+rect.getLowerLeftY() );
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * You should override this method if you want to perform an action when a
+ * text is being processed.
+ *
+ * @param text The text to process
+ */
+ protected void processTextPosition( TextPosition text )
+ {
+ try
+ {
+ switch(this.getGraphicsState().getTextState().getRenderingMode()) {
+ case PDTextState.RENDERING_MODE_FILL_TEXT:
+ graphics.setComposite( this.getGraphicsState().getNonStrokeJavaComposite() );
+ graphics.setColor( this.getGraphicsState().getNonStrokingColor().getJavaColor() );
+ break;
+ case PDTextState.RENDERING_MODE_STROKE_TEXT:
+ graphics.setComposite( this.getGraphicsState().getStrokeJavaComposite() );
+ graphics.setColor( this.getGraphicsState().getStrokingColor().getJavaColor() );
+ break;
+ case PDTextState.RENDERING_MODE_NEITHER_FILL_NOR_STROKE_TEXT:
+ //basic support for text rendering mode "invisible"
+ Color nsc = this.getGraphicsState().getStrokingColor().getJavaColor();
+ float[] components = {Color.black.getRed(),Color.black.getGreen(),Color.black.getBlue()};
+ Color c = new Color(nsc.getColorSpace(),components,0f);
+ graphics.setComposite( this.getGraphicsState().getStrokeJavaComposite() );
+ graphics.setColor(c);
+ break;
+ default:
+ // TODO : need to implement....
+ log.debug("Unsupported RenderingMode "
+ + this.getGraphicsState().getTextState().getRenderingMode()
+ + " in PageDrawer.processTextPosition()."
+ + " Using RenderingMode "
+ + PDTextState.RENDERING_MODE_FILL_TEXT
+ + " instead");
+ graphics.setComposite( this.getGraphicsState().getNonStrokeJavaComposite() );
+ graphics.setColor( this.getGraphicsState().getNonStrokingColor().getJavaColor() );
+ }
+
+ PDFont font = text.getFont();
+ Matrix textPos = text.getTextPos().copy();
+ float x = textPos.getXPosition();
+ // the 0,0-reference has to be moved from the lower left (PDF) to the upper left (AWT-graphics)
+ float y = pageSize.height - textPos.getYPosition();
+ // Set translation to 0,0. We only need the scaling and shearing
+ textPos.setValue(2, 0, 0);
+ textPos.setValue(2, 1, 0);
+ // because of the moved 0,0-reference, we have to shear in the opposite direction
+ textPos.setValue(0, 1, (-1)*textPos.getValue(0, 1));
+ textPos.setValue(1, 0, (-1)*textPos.getValue(1, 0));
+ AffineTransform at = textPos.createAffineTransform();
+ PDMatrix fontMatrix = font.getFontMatrix();
+ at.scale(fontMatrix.getValue(0, 0) * 1000f, fontMatrix.getValue(1, 1) * 1000f);
+ graphics.setClip(getGraphicsState().getCurrentClippingPath());
+ // the fontSize is no longer needed as it is already part of the transformation
+ // we should remove it from the parameter list in the long run
+ font.drawString( text.getCharacter(), graphics, 1, at, x, y );
+ }
+ catch( IOException io )
+ {
+ io.printStackTrace();
+ }
+ }
+
+ /**
+ * Get the graphics that we are currently drawing on.
+ *
+ * @return The graphics we are drawing on.
+ */
+ public Graphics2D getGraphics()
+ {
+ return graphics;
+ }
+
+ /**
+ * Get the page that is currently being drawn.
+ *
+ * @return The page that is being drawn.
+ */
+ public PDPage getPage()
+ {
+ return page;
+ }
+
+ /**
+ * Get the size of the page that is currently being drawn.
+ *
+ * @return The size of the page that is being drawn.
+ */
+ public Dimension getPageSize()
+ {
+ return pageSize;
+ }
+
+ /**
+ * Fix the y coordinate.
+ *
+ * @param y The y coordinate.
+ * @return The updated y coordinate.
+ */
+ public double fixY( double y )
+ {
+ return pageSize.getHeight() - y;
+ }
+
+ /**
+ * Get the current line path to be drawn.
+ *
+ * @return The current line path to be drawn.
+ */
+ public GeneralPath getLinePath()
+ {
+ return linePath;
+ }
+
+ /**
+ * Set the line path to draw.
+ *
+ * @param newLinePath Set the line path to draw.
+ */
+ public void setLinePath(GeneralPath newLinePath)
+ {
+ if (linePath == null || linePath.getCurrentPoint() == null)
+ {
+ linePath = newLinePath;
+ }
+ else
+ {
+ linePath.append(newLinePath, false);
+ }
+ }
+
+
+ /**
+ * Fill the path.
+ *
+ * @param windingRule The winding rule this path will use.
+ *
+ * @throws IOException If there is an IO error while filling the path.
+ */
+ public void fillPath(int windingRule) throws IOException
+ {
+ graphics.setComposite(getGraphicsState().getNonStrokeJavaComposite());
+ graphics.setColor( getGraphicsState().getNonStrokingColor().getJavaColor() );
+ getLinePath().setWindingRule(windingRule);
+ graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
+ graphics.setClip(getGraphicsState().getCurrentClippingPath());
+ graphics.fill( getLinePath() );
+ getLinePath().reset();
+ }
+
+
+ /**
+ * This will set the current stroke.
+ *
+ * @param newStroke The current stroke.
+ *
+ */
+ public void setStroke(BasicStroke newStroke)
+ {
+ getGraphics().setStroke( newStroke );
+ }
+
+ /**
+ * This will return the current stroke.
+ *
+ * @return The current stroke.
+ *
+ */
+ public BasicStroke getStroke()
+ {
+ return (BasicStroke)getGraphics().getStroke();
+ }
+
+ /**
+ * Stroke the path.
+ *
+ * @throws IOException If there is an IO error while stroking the path.
+ */
+ public void strokePath() throws IOException
+ {
+ graphics.setComposite(getGraphicsState().getStrokeJavaComposite());
+ graphics.setColor( getGraphicsState().getStrokingColor().getJavaColor() );
+ graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
+ graphics.setClip(getGraphicsState().getCurrentClippingPath());
+ GeneralPath path = getLinePath();
+ graphics.draw( path );
+ path.reset();
+ }
+
+ /**
+ * Called when the color changed.
+ * @param bStroking true for the stroking color, false for the non-stroking color
+ * @throws IOException if an I/O error occurs
+ */
+ @Deprecated
+ public void colorChanged(boolean bStroking) throws IOException
+ {
+ //logger().info("changing " + (bStroking ? "" : "non") + "stroking color");
+ }
+
+ //This code generalizes the code Jim Lynch wrote for AppendRectangleToPath
+ /**
+ * use the current transformation matrix to transform a single point.
+ * @param x x-coordinate of the point to be transform
+ * @param y y-coordinate of the point to be transform
+ * @return the transformed coordinates as Point2D.Double
+ */
+ public java.awt.geom.Point2D.Double transformedPoint(double x, double y)
+ {
+ double[] position = {x,y};
+ getGraphicsState().getCurrentTransformationMatrix().createAffineTransform().transform(
+ position, 0, position, 0, 1);
+ position[1] = fixY(position[1]);
+ return new Point2D.Double(position[0],position[1]);
+ }
+
+ /**
+ * Set the clipping Path.
+ *
+ * @param windingRule The winding rule this path will use.
+ *
+ */
+ public void setClippingPath(int windingRule)
+ {
+ PDGraphicsState graphicsState = getGraphicsState();
+ GeneralPath clippingPath = (GeneralPath)getLinePath().clone();
+ clippingPath.setWindingRule(windingRule);
+ // If there is already set a clipping path, we have to intersect the new with the existing one
+ if (graphicsState.getCurrentClippingPath() != null)
+ {
+ Area currentArea = new Area(getGraphicsState().getCurrentClippingPath());
+ Area newArea = new Area(clippingPath);
+ currentArea.intersect(newArea);
+ graphicsState.setCurrentClippingPath(currentArea);
+ }
+ else
+ {
+ graphicsState.setCurrentClippingPath(clippingPath);
+ }
+ getLinePath().reset();
+ }
+
+ /**
+ * Draw the AWT image. Called by Invoke.
+ * Moved into PageDrawer so that Invoke doesn't have to reach in here for Graphics as that breaks extensibility.
+ *
+ * @param awtImage The image to draw.
+ * @param at The transformation to use when drawing.
+ *
+ */
+ public void drawImage(Image awtImage, AffineTransform at){
+ graphics.setComposite(getGraphicsState().getStrokeJavaComposite());
+ graphics.setClip(getGraphicsState().getCurrentClippingPath());
+ graphics.drawImage( awtImage, at, null );
+ }
+
+ /**
+ * Fill with Shading. Called by SHFill operator.
+ *
+ * @param ShadingName The name of the Shading Dictionary to use for this fill instruction.
+ *
+ * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+ */
+ public void SHFill(COSName ShadingName) throws IOException
+ {
+ PDShading Shading =FindShadingDictionary(ShadingName);
+ log.info("Shading = " + Shading.toString());
+
+ switch (Shading.getShadingType()){
+ case 1:
+ SHFill_Function(Shading);
+ break;
+ case 2:
+ SHFill_Axial(Shading);
+ break;
+ case 3:
+ SHFill_Radial(Shading);
+ break;
+ case 4:
+ SHFill_FreeGourad(Shading);
+ break;
+ case 5:
+ SHFill_LatticeGourad(Shading);
+ break;
+ case 6:
+ SHFill_CoonsPatch(Shading);
+ break;
+ case 7:
+ SHFill_TensorPatch(Shading);
+ break;
+
+ default:
+ throw new IOException("Invalid ShadingType " + Shading.getShadingType() + " for Shading " + ShadingName);
+ }
+ }
+
+ /**
+ * Find the appropriate Shading Dictionary. This is its own private function as it is really not appropriate to override when deriving from PageDrawer.
+ *
+ * @param ShadingName The name of the Shading Dictionary to use for this fill instruction.
+ *
+ * @returns The PDShading object
+ * @throws IOException If there is an IO error while attempting to find the appropriate PDShading object.
+ */
+ private PDShading FindShadingDictionary(COSName ShadingName) throws IOException
+ {
+
+ PDResources resources = (PDResources)page.getResources();
+
+ COSDictionary AllShadings = (COSDictionary)(resources.getCOSDictionary().getDictionaryObject(COSName.SHADING));
+
+ PDShading Shading = new PDShading(ShadingName, (COSDictionary)(AllShadings.getDictionaryObject(ShadingName)));
+
+ return Shading;
+
+ }
+
+ /**
+ * Fill with a Function-based gradient / shading.
+ * If extending the class, override this and its siblings, not the public SHFill method.
+ *
+ * @param Shading The Shading Dictionary to use for this fill instruction.
+ *
+ * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+ */
+ protected void SHFill_Function(PDShading Shading) throws IOException
+ {
+ throw new IOException("Not Implemented");
+ }
+
+ /**
+ * Fill with an Axial Shading.
+ * If extending the class, override this and its siblings, not the public SHFill method.
+ *
+ * @param Shading The Shading Dictionary to use for this fill instruction.
+ *
+ * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+ */
+ protected void SHFill_Axial(PDShading Shading) throws IOException
+ {
+ throw new IOException("Not Implemented");
+
+ }
+
+ /**
+ * Fill with a Radial gradient / shading.
+ * If extending the class, override this and its siblings, not the public SHFill method.
+ *
+ * @param Shading The Shading Dictionary to use for this fill instruction.
+ *
+ * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+ */
+ protected void SHFill_Radial(PDShading Shading) throws IOException
+ {
+ throw new IOException("Not Implemented");
+ }
+
+ /**
+ * Fill with a Free-form Gourad-shaded triangle mesh.
+ * If extending the class, override this and its siblings, not the public SHFill method.
+ *
+ * @param Shading The Shading Dictionary to use for this fill instruction.
+ *
+ * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+ */
+ protected void SHFill_FreeGourad(PDShading Shading) throws IOException
+ {
+ throw new IOException("Not Implemented");
+ }
+
+ /**
+ * Fill with a Lattice-form Gourad-shaded triangle mesh.
+ * If extending the class, override this and its siblings, not the public SHFill method.
+ *
+ * @param Shading The Shading Dictionary to use for this fill instruction.
+ *
+ * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+ */
+ protected void SHFill_LatticeGourad(PDShading Shading) throws IOException
+ {
+ throw new IOException("Not Implemented");
+ }
+
+ /**
+ * Fill with a Coons patch mesh
+ * If extending the class, override this and its siblings, not the public SHFill method.
+ *
+ * @param Shading The Shading Dictionary to use for this fill instruction.
+ *
+ * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+ */
+ protected void SHFill_CoonsPatch(PDShading Shading) throws IOException
+ {
+ throw new IOException("Not Implemented");
+ }
+
+ /**
+ * Fill with a Tensor-product patch mesh.
+ * If extending the class, override this and its siblings, not the public SHFill method.
+ *
+ * @param Shading The Shading Dictionary to use for this fill instruction.
+ *
+ * @throws IOException If there is an IO error while shade-filling the path/clipping area.
+ */
+ protected void SHFill_TensorPatch(PDShading Shading) throws IOException
+ {
+ throw new IOException("Not Implemented");
+ }
+}
--- /dev/null
+/*
+ * 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.pdfviewer;
+
+import java.awt.Dimension;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.io.IOException;
+
+import javax.swing.JPanel;
+
+import org.apache.pdfbox.PDFReader;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+/**
+ * A class to handle some prettyness around a single PDF page.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PageWrapper implements MouseMotionListener
+{
+ private JPanel pageWrapper = new JPanel();
+ private PDFPagePanel pagePanel = null;
+ private PDFReader reader = null;
+
+ private static final int SPACE_AROUND_DOCUMENT = 20;
+
+ /**
+ * Constructor.
+ *
+ * @param aReader The reader application that holds this page.
+ *
+ * @throws IOException If there is an error creating the page drawing objects.
+ */
+ public PageWrapper( PDFReader aReader ) throws IOException
+ {
+ reader = aReader;
+ pagePanel = new PDFPagePanel();
+ pageWrapper.setLayout( null );
+ pageWrapper.add( pagePanel );
+ pagePanel.setLocation( SPACE_AROUND_DOCUMENT, SPACE_AROUND_DOCUMENT );
+ pageWrapper.setBorder( javax.swing.border.LineBorder.createBlackLineBorder() );
+ pagePanel.addMouseMotionListener( this );
+ }
+
+ /**
+ * This will display the PDF page in this component.
+ *
+ * @param page The PDF page to display.
+ */
+ public void displayPage( PDPage page )
+ {
+ pagePanel.setPage( page );
+ pagePanel.setPreferredSize( pagePanel.getSize() );
+ Dimension d = pagePanel.getSize();
+ d.width+=(SPACE_AROUND_DOCUMENT*2);
+ d.height+=(SPACE_AROUND_DOCUMENT*2);
+
+ pageWrapper.setPreferredSize( d );
+ pageWrapper.validate();
+ }
+
+ /**
+ * This will get the JPanel that can be displayed.
+ *
+ * @return The panel with the displayed PDF page.
+ */
+ public JPanel getPanel()
+ {
+ return pageWrapper;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void mouseDragged(MouseEvent e)
+ {
+ //do nothing when mouse moves.
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void mouseMoved(MouseEvent e)
+ {
+ //reader.getBottomStatusPanel().getStatusLabel().setText( e.getX() + "," + (pagePanel.getHeight() - e.getY()) );
+ reader.getBottomStatusPanel().getStatusLabel().setText( e.getX() + "," + e.getY() );
+ }
+}
--- /dev/null
+/*
+ * 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.pdfviewer;
+
+import java.awt.Dimension;
+
+import javax.swing.JPanel;
+
+import javax.swing.JLabel;
+import java.awt.FlowLayout;
+/**
+ * A panel to display at the bottom of the window for status and other stuff.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ReaderBottomPanel extends JPanel
+{
+
+ private JLabel statusLabel = null;
+
+ /**
+ * This is the default constructor.
+ */
+ public ReaderBottomPanel()
+ {
+ super();
+ initialize();
+ }
+
+ /**
+ * This method initializes this.
+ */
+ private void initialize()
+ {
+ FlowLayout flowLayout1 = new FlowLayout();
+ this.setLayout(flowLayout1);
+ this.setComponentOrientation(java.awt.ComponentOrientation.LEFT_TO_RIGHT);
+ this.setPreferredSize( new Dimension( 1000, 20 ) );
+ flowLayout1.setAlignment(java.awt.FlowLayout.LEFT);
+ this.add(getStatusLabel(), null);
+ }
+
+ /**
+ * This method initializes status label.
+ *
+ * @return javax.swing.JLabel
+ */
+ public JLabel getStatusLabel()
+ {
+ if (statusLabel == null)
+ {
+ statusLabel = new JLabel();
+ statusLabel.setText("Ready");
+ }
+ return statusLabel;
+ }
+ }
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The pdfviewer package contains classes to graphically display information about a PDF document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.pdfwriter;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+public class COSFilterInputStream extends FilterInputStream
+{
+ int[] byteRange;
+ long position = 0;
+
+ public COSFilterInputStream(InputStream in, int[] byteRange)
+ {
+ super(in);
+ this.byteRange = byteRange;
+ }
+
+ public COSFilterInputStream(byte[] in, int[] byteRange)
+ {
+ super(new ByteArrayInputStream(in));
+ this.byteRange = byteRange;
+ }
+
+ @Override
+ public int read() throws IOException
+ {
+ nextAvailable();
+ int i = super.read();
+ if (i>-1)
+ ++position;
+ return i;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException
+ {
+ return read(b,0,b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ if (b == null) {
+ throw new NullPointerException();
+ } else if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ int c = read();
+ if (c == -1) {
+ return -1;
+ }
+ b[off] = (byte)c;
+
+ int i = 1;
+ try {
+ for (; i < len ; i++) {
+ c = read();
+ if (c == -1) {
+ break;
+ }
+ b[off + i] = (byte)c;
+ }
+ } catch (IOException ee) {
+ }
+ return i;
+ }
+
+ private boolean inRange() throws IOException {
+ long pos = position;
+ for (int i = 0; i<byteRange.length/2;++i)
+ {
+ if(byteRange[i*2] <= pos && byteRange[i*2]+byteRange[i*2+1]>pos)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void nextAvailable() throws IOException {
+ while (!inRange()) {
+ ++position;
+ if(super.read()<0)
+ break;
+ }
+ }
+
+ public byte[] toByteArray() throws IOException
+ {
+ ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int c;
+ while ((c = this.read(buffer)) != -1)
+ byteOS.write(buffer, 0, c);
+ return byteOS.toByteArray();
+ }
+}
--- /dev/null
+/*
+ * 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.pdfwriter;
+
+
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+
+import org.apache.pdfbox.util.StringUtil;
+
+/**
+ * simple output stream with some minor features for generating "pretty"
+ * pdf files.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.5 $
+ */
+public class COSStandardOutputStream extends FilterOutputStream
+{
+
+ /**
+ * To be used when 2 byte sequence is enforced.
+ */
+ public static final byte[] CRLF = StringUtil.getBytes("\r\n");
+
+ /**
+ * Line feed character.
+ */
+ public static final byte[] LF = StringUtil.getBytes("\n");
+
+ /**
+ * standard line separator.
+ */
+ public static final byte[] EOL = StringUtil.getBytes("\n");
+
+ // current byte pos in the output stream
+ private long pos = 0;
+ // flag to prevent generating two newlines in sequence
+ private boolean onNewLine = false;
+ private FileChannel fileChannel = null;
+ private FileDescriptor fileDescriptor = null;
+ private long mark = -1;
+
+ /**
+ * COSOutputStream constructor comment.
+ *
+ * @param out The underlying stream to write to.
+ */
+ public COSStandardOutputStream(OutputStream out)
+ {
+ super(out);
+ if(out instanceof FileOutputStream) {
+ try {
+ fileChannel = ((FileOutputStream)out).getChannel();
+ fileDescriptor = ((FileOutputStream)out).getFD();
+ pos = fileChannel.position();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * This will get the current position in the stream.
+ *
+ * @return The current position in the stream.
+ */
+ public long getPos()
+ {
+ return pos;
+ }
+
+ /**
+ * This will get the current position in the stream.
+ *
+ * @return The current position in the stream.
+ * @throws IOException
+ */
+ public void setPos(long pos) throws IOException
+ {
+ if(fileChannel!=null) {
+ checkPos();
+ this.pos=pos;
+ fileChannel.position(pos);
+ }
+ }
+
+ /**
+ * This will tell if we are on a newline.
+ *
+ * @return true If we are on a newline.
+ */
+ public boolean isOnNewLine()
+ {
+ return onNewLine;
+ }
+ /**
+ * This will set a flag telling if we are on a newline.
+ *
+ * @param newOnNewLine The new value for the onNewLine attribute.
+ */
+ public void setOnNewLine(boolean newOnNewLine)
+ {
+ onNewLine = newOnNewLine;
+ }
+
+ /**
+ * This will write some byte to the stream.
+ *
+ * @param b The source byte array.
+ * @param off The offset into the array to start writing.
+ * @param len The number of bytes to write.
+ *
+ * @throws IOException If the underlying stream throws an exception.
+ */
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException
+ {
+ checkPos();
+ setOnNewLine(false);
+ out.write(b, off, len);
+ pos += len;
+ }
+
+ /**
+ * This will write a single byte to the stream.
+ *
+ * @param b The byte to write to the stream.
+ *
+ * @throws IOException If there is an error writing to the underlying stream.
+ */
+ @Override
+ public void write(int b) throws IOException
+ {
+ checkPos();
+ setOnNewLine(false);
+ out.write(b);
+ pos++;
+ }
+
+ /**
+ * This will write a CRLF to the stream.
+ *
+ * @throws IOException If there is an error writing the data to the stream.
+ */
+ public void writeCRLF() throws IOException
+ {
+ write(CRLF);
+ }
+
+ /**
+ * This will write an EOL to the stream.
+ *
+ * @throws IOException If there is an error writing to the stream
+ */
+ public void writeEOL() throws IOException
+ {
+ if (!isOnNewLine())
+ {
+ write(EOL);
+ setOnNewLine(true);
+ }
+ }
+
+ /**
+ * This will write a Linefeed to the stream.
+ *
+ * @throws IOException If there is an error writing to the underlying stream.
+ */
+ public void writeLF() throws IOException
+ {
+ write(LF);
+ }
+
+ public void mark() throws IOException
+ {
+ checkPos();
+ mark = getPos();
+ }
+
+ public void reset() throws IOException
+ {
+ if(mark<0)
+ return;
+ setPos(mark);
+ }
+
+ private void checkPos() throws IOException
+ {
+ if(fileChannel!=null && fileChannel.position() != getPos())
+ throw new IOException("OutputStream has an invalid position");
+ }
+
+ public byte[] getFileInBytes(int[] byteRange) throws IOException
+ {
+ return null;
+ }
+
+ public InputStream getFilterInputStream(int[] byteRange)
+ {
+ return new COSFilterInputStream(new FileInputStream(fileDescriptor), byteRange);
+ }
+}
--- /dev/null
+/*
+ * 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.pdfwriter;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.cos.ICOSVisitor;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.SignatureException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.encryption.SecurityHandler;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+import org.apache.pdfbox.util.StringUtil;
+
+/**
+ * this class acts on a in-memory representation of a pdf document.
+ *
+ * todo no support for incremental updates
+ * todo single xref section only
+ * todo no linearization
+ *
+ * @author Michael Traut
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.36 $
+ */
+public class COSWriter implements ICOSVisitor
+{
+ /**
+ * The dictionary open token.
+ */
+ public static final byte[] DICT_OPEN = StringUtil.getBytes("<<");
+ /**
+ * The dictionary close token.
+ */
+ public static final byte[] DICT_CLOSE = StringUtil.getBytes(">>");
+ /**
+ * space character.
+ */
+ public static final byte[] SPACE = StringUtil.getBytes(" ");
+ /**
+ * The start to a PDF comment.
+ */
+ public static final byte[] COMMENT = StringUtil.getBytes("%");
+
+ /**
+ * The output version of the PDF.
+ */
+ public static final byte[] VERSION = StringUtil.getBytes("PDF-1.4");
+ /**
+ * Garbage bytes used to create the PDF header.
+ */
+ public static final byte[] GARBAGE = new byte[] {(byte)0xf6, (byte)0xe4, (byte)0xfc, (byte)0xdf};
+ /**
+ * The EOF constant.
+ */
+ public static final byte[] EOF = StringUtil.getBytes("%%EOF");
+ // pdf tokens
+
+ /**
+ * The reference token.
+ */
+ public static final byte[] REFERENCE = StringUtil.getBytes("R");
+ /**
+ * The XREF token.
+ */
+ public static final byte[] XREF = StringUtil.getBytes("xref");
+ /**
+ * The xref free token.
+ */
+ public static final byte[] XREF_FREE = StringUtil.getBytes("f");
+ /**
+ * The xref used token.
+ */
+ public static final byte[] XREF_USED = StringUtil.getBytes("n");
+ /**
+ * The trailer token.
+ */
+ public static final byte[] TRAILER = StringUtil.getBytes("trailer");
+ /**
+ * The start xref token.
+ */
+ public static final byte[] STARTXREF = StringUtil.getBytes("startxref");
+ /**
+ * The starting object token.
+ */
+ public static final byte[] OBJ = StringUtil.getBytes("obj");
+ /**
+ * The end object token.
+ */
+ public static final byte[] ENDOBJ = StringUtil.getBytes("endobj");
+ /**
+ * The array open token.
+ */
+ public static final byte[] ARRAY_OPEN = StringUtil.getBytes("[");
+ /**
+ * The array close token.
+ */
+ public static final byte[] ARRAY_CLOSE = StringUtil.getBytes("]");
+ /**
+ * The open stream token.
+ */
+ public static final byte[] STREAM = StringUtil.getBytes("stream");
+ /**
+ * The close stream token.
+ */
+ public static final byte[] ENDSTREAM = StringUtil.getBytes("endstream");
+
+ private NumberFormat formatXrefOffset = new DecimalFormat("0000000000");
+ /**
+ * The decimal format for the xref object generation number data.
+ */
+ private NumberFormat formatXrefGeneration = new DecimalFormat("00000");
+
+ private NumberFormat formatDecimal = NumberFormat.getNumberInstance( Locale.US );
+
+ // the stream where we create the pdf output
+ private OutputStream output;
+
+ // the stream used to write standard cos data
+ private COSStandardOutputStream standardOutput;
+
+ // the start position of the x ref section
+ private long startxref = 0;
+
+ // the current object number
+ private long number = 0;
+
+ // maps the object to the keys generated in the writer
+ // these are used for indirect references in other objects
+ //A hashtable is used on purpose over a hashmap
+ //so that null entries will not get added.
+ private Map<COSBase,COSObjectKey> objectKeys = new Hashtable<COSBase,COSObjectKey>();
+ private Map<COSObjectKey,COSBase> keyObject = new Hashtable<COSObjectKey,COSBase>();
+
+ // the list of x ref entries to be made so far
+ private List<COSWriterXRefEntry> xRefEntries = new ArrayList<COSWriterXRefEntry>();
+ private HashSet<COSBase> objectsToWriteSet = new HashSet<COSBase>();
+
+ //A list of objects to write.
+ private LinkedList<COSBase> objectsToWrite = new LinkedList<COSBase>();
+
+ //a list of objects already written
+ private Set<COSBase> writtenObjects = new HashSet<COSBase>();
+ //An 'actual' is any COSBase that is not a COSObject.
+ //need to keep a list of the actuals that are added
+ //as well as the objects because there is a problem
+ //when adding a COSObject and then later adding
+ //the actual for that object, so we will track
+ //actuals separately.
+ private Set<COSBase> actualsAdded = new HashSet<COSBase>();
+
+ private COSObjectKey currentObjectKey = null;
+
+ private PDDocument document = null;
+
+ private boolean willEncrypt = false;
+
+ private boolean incrementalUpdate = false;
+
+ private boolean reachedSignature = false;
+
+ private int[] signaturePosition = new int[2];
+
+ private int[] byterangePosition = new int[2];
+
+ private FileInputStream in;
+
+ /**
+ * COSWriter constructor comment.
+ *
+ * @param os The wrapped output stream.
+ */
+ public COSWriter(OutputStream os)
+ {
+ super();
+ setOutput(os);
+ setStandardOutput(new COSStandardOutputStream(output));
+ formatDecimal.setMaximumFractionDigits( 10 );
+ formatDecimal.setGroupingUsed( false );
+ }
+
+ /**
+ * COSWriter constructor for incremental updates.
+ *
+ * @param os The wrapped output stream.
+ */
+ public COSWriter(OutputStream os, FileInputStream is)
+ {
+ this(os);
+ in = is;
+ incrementalUpdate = true;
+ }
+
+ protected void prepareIncrement(PDDocument doc)
+ {
+ try
+ {
+ if (doc != null)
+ {
+ COSDocument cosDoc = doc.getDocument();
+
+ Map<COSObjectKey, Integer> xrefTable = cosDoc.getXrefTable();
+ Set<COSObjectKey> keySet = xrefTable.keySet();
+ long highestNumber=0;
+ for ( COSObjectKey cosObjectKey : keySet ) {
+ COSBase object = cosDoc.getObjectFromPool(cosObjectKey).getObject();
+ if (object != null && cosObjectKey!= null && !(object instanceof COSNumber))
+ {
+ objectKeys.put(object, cosObjectKey);
+ keyObject.put(cosObjectKey,object);
+ }
+
+ long num = cosObjectKey.getNumber();
+ if (num > highestNumber)
+ highestNumber=num;
+ }
+ setNumber(highestNumber);
+ // xrefTable.clear();
+
+ }
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * add an entry in the x ref table for later dump.
+ *
+ * @param entry The new entry to add.
+ */
+ protected void addXRefEntry(COSWriterXRefEntry entry)
+ {
+ getXRefEntries().add(entry);
+ }
+
+ /**
+ * This will close the stream.
+ *
+ * @throws IOException If the underlying stream throws an exception.
+ */
+ public void close() throws IOException
+ {
+ if (getStandardOutput() != null)
+ {
+ getStandardOutput().close();
+ }
+ if (getOutput() != null)
+ {
+ getOutput().close();
+ }
+ }
+
+ /**
+ * This will get the current object number.
+ *
+ * @return The current object number.
+ */
+ protected long getNumber()
+ {
+ return number;
+ }
+
+ /**
+ * This will get all available object keys.
+ *
+ * @return A map of all object keys.
+ */
+ public Map<COSBase,COSObjectKey> getObjectKeys()
+ {
+ return objectKeys;
+ }
+
+ /**
+ * This will get the output stream.
+ *
+ * @return The output stream.
+ */
+ protected java.io.OutputStream getOutput()
+ {
+ return output;
+ }
+
+ /**
+ * This will get the standard output stream.
+ *
+ * @return The standard output stream.
+ */
+ protected COSStandardOutputStream getStandardOutput()
+ {
+ return standardOutput;
+ }
+
+ /**
+ * This will get the current start xref.
+ *
+ * @return The current start xref.
+ */
+ protected long getStartxref()
+ {
+ return startxref;
+ }
+ /**
+ * This will get the xref entries.
+ *
+ * @return All available xref entries.
+ */
+ protected List<COSWriterXRefEntry> getXRefEntries()
+ {
+ return xRefEntries;
+ }
+
+ /**
+ * This will set the current object number.
+ *
+ * @param newNumber The new object number.
+ */
+ protected void setNumber(long newNumber)
+ {
+ number = newNumber;
+ }
+
+ /**
+ * This will set the output stream.
+ *
+ * @param newOutput The new output stream.
+ */
+ private void setOutput( OutputStream newOutput )
+ {
+ output = newOutput;
+ }
+
+ /**
+ * This will set the standard output stream.
+ *
+ * @param newStandardOutput The new standard output stream.
+ */
+ private void setStandardOutput(COSStandardOutputStream newStandardOutput)
+ {
+ standardOutput = newStandardOutput;
+ }
+
+ /**
+ * This will set the start xref.
+ *
+ * @param newStartxref The new start xref attribute.
+ */
+ protected void setStartxref(long newStartxref)
+ {
+ startxref = newStartxref;
+ }
+
+ /**
+ * This will write the body of the document.
+ *
+ * @param doc The document to write the body for.
+ *
+ * @throws IOException If there is an error writing the data.
+ * @throws COSVisitorException If there is an error generating the data.
+ */
+ protected void doWriteBody(COSDocument doc) throws IOException, COSVisitorException
+ {
+ COSDictionary trailer = doc.getTrailer();
+ COSDictionary root = (COSDictionary)trailer.getDictionaryObject( COSName.ROOT );
+ COSDictionary info = (COSDictionary)trailer.getDictionaryObject( COSName.INFO );
+ COSDictionary encrypt = (COSDictionary)trailer.getDictionaryObject( COSName.ENCRYPT );
+ if( root != null )
+ {
+ addObjectToWrite( root );
+ }
+ if( info != null )
+ {
+ addObjectToWrite( info );
+ }
+
+ while( objectsToWrite.size() > 0 )
+ {
+ COSBase nextObject = (COSBase)objectsToWrite.removeFirst();
+ objectsToWriteSet.remove(nextObject);
+ doWriteObject( nextObject );
+ }
+
+
+ willEncrypt = false;
+
+ if( encrypt != null )
+ {
+ addObjectToWrite( encrypt );
+ }
+
+ while( objectsToWrite.size() > 0 )
+ {
+ COSBase nextObject = (COSBase)objectsToWrite.removeFirst();
+ objectsToWriteSet.remove(nextObject);
+ doWriteObject( nextObject );
+ }
+ }
+
+ private void addObjectToWrite( COSBase object )
+ {
+ COSBase actual = object;
+ if( actual instanceof COSObject )
+ {
+ actual = ((COSObject)actual).getObject();
+ }
+
+ if( !writtenObjects.contains( object ) &&
+ !objectsToWriteSet.contains( object ) &&
+ !actualsAdded.contains( actual ) )
+ {
+ COSBase cosBase=null;
+ COSObjectKey cosObjectKey = null;
+ if(actual != null)
+ cosObjectKey= objectKeys.get(actual);
+
+ if(cosObjectKey!=null)
+ cosBase = keyObject.get(cosObjectKey);
+
+ if(actual != null && objectKeys.containsKey(actual) &&
+ !object.isNeedToBeUpdate() && (cosBase!= null &&
+ !cosBase.isNeedToBeUpdate()))
+ {
+ return;
+ }
+
+ objectsToWrite.add( object );
+ objectsToWriteSet.add( object );
+ if( actual != null )
+ {
+ actualsAdded.add( actual );
+ }
+ }
+ }
+
+ /**
+ * This will write a COS object.
+ *
+ * @param obj The object to write.
+ *
+ * @throws COSVisitorException If there is an error visiting objects.
+ */
+ public void doWriteObject( COSBase obj ) throws COSVisitorException
+ {
+ try
+ {
+ writtenObjects.add( obj );
+ if(obj instanceof COSDictionary)
+ {
+ COSDictionary dict = (COSDictionary)obj;
+ COSName item = (COSName)dict.getItem(COSName.TYPE);
+ if(COSName.SIG.equals(item)) {
+ reachedSignature = true;
+ }
+ }
+
+ // find the physical reference
+ currentObjectKey = getObjectKey( obj );
+ // add a x ref entry
+ addXRefEntry( new COSWriterXRefEntry(getStandardOutput().getPos(), obj, currentObjectKey));
+ // write the object
+ getStandardOutput().write(String.valueOf(currentObjectKey.getNumber()).getBytes("ISO-8859-1"));
+ getStandardOutput().write(SPACE);
+ getStandardOutput().write(String.valueOf(currentObjectKey.getGeneration()).getBytes("ISO-8859-1"));
+ getStandardOutput().write(SPACE);
+ getStandardOutput().write(OBJ);
+ getStandardOutput().writeEOL();
+ obj.accept( this );
+ getStandardOutput().writeEOL();
+ getStandardOutput().write(ENDOBJ);
+ getStandardOutput().writeEOL();
+ }
+ catch (IOException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+
+ /**
+ * This will write the header to the PDF document.
+ *
+ * @param doc The document to get the data from.
+ *
+ * @throws IOException If there is an error writing to the stream.
+ */
+ protected void doWriteHeader(COSDocument doc) throws IOException
+ {
+ getStandardOutput().write( doc.getHeaderString().getBytes("ISO-8859-1") );
+ getStandardOutput().writeEOL();
+ getStandardOutput().write(COMMENT);
+ getStandardOutput().write(GARBAGE);
+ getStandardOutput().writeEOL();
+ }
+
+
+ /**
+ * This will write the trailer to the PDF document.
+ *
+ * @param doc The document to create the trailer for.
+ *
+ * @throws IOException If there is an IOError while writing the document.
+ * @throws COSVisitorException If there is an error while generating the data.
+ */
+ protected void doWriteTrailer(COSDocument doc) throws IOException, COSVisitorException
+ {
+ getStandardOutput().write(TRAILER);
+ getStandardOutput().writeEOL();
+
+ COSDictionary trailer = doc.getTrailer();
+ //sort xref, needed only if object keys not regenerated
+ Collections.sort(getXRefEntries());
+ COSWriterXRefEntry lastEntry = getXRefEntries().get( getXRefEntries().size()-1);
+ trailer.setInt(COSName.SIZE, (int)lastEntry.getKey().getNumber()+1);
+ // Only need to stay, if an incremental update will be performed
+ if (!incrementalUpdate)
+ trailer.removeItem( COSName.PREV );
+ // Remove a checksum if present
+ trailer.removeItem( COSName.getPDFName("DocChecksum") );
+
+ /**
+ COSObject catalog = doc.getCatalog();
+ if (catalog != null)
+ {
+ trailer.setItem(COSName.getPDFName("Root"), catalog);
+ }
+ */
+ trailer.accept(this);
+
+ getStandardOutput().write(STARTXREF);
+ getStandardOutput().writeEOL();
+ getStandardOutput().write(String.valueOf(getStartxref()).getBytes("ISO-8859-1"));
+ getStandardOutput().writeEOL();
+ getStandardOutput().write(EOF);
+ getStandardOutput().writeEOL();
+ }
+
+ /**
+ * write the x ref section for the pdf file
+ *
+ * currently, the pdf is reconstructed from the scratch, so we write a single section
+ *
+ * todo support for incremental writing?
+ *
+ * @param doc The document to write the xref from.
+ *
+ * @throws IOException If there is an error writing the data to the stream.
+ */
+ protected void doWriteXRef(COSDocument doc) throws IOException
+ {
+ // sort xref, needed only if object keys not regenerated
+ Collections.sort(getXRefEntries());
+ COSWriterXRefEntry lastEntry = getXRefEntries().get( getXRefEntries().size()-1 );
+
+ // remember the position where x ref is written
+ setStartxref(getStandardOutput().getPos());
+ //
+ getStandardOutput().write(XREF);
+ getStandardOutput().writeEOL();
+ // write start object number and object count for this x ref section
+ // we assume starting from scratch
+ writeXrefRange(0, lastEntry.getKey().getNumber() + 1);
+ // write initial start object with ref to first deleted object and magic generation number
+ writeXrefEntry(COSWriterXRefEntry.getNullEntry());
+ // write entry for every object
+ long lastObjectNumber = 0;
+ for (Iterator<COSWriterXRefEntry> i = getXRefEntries().iterator(); i.hasNext();)
+ {
+ COSWriterXRefEntry entry = i.next();
+ while( lastObjectNumber<entry.getKey().getNumber()-1 )
+ {
+ writeXrefEntry(COSWriterXRefEntry.getNullEntry());
+ }
+ lastObjectNumber = entry.getKey().getNumber();
+ writeXrefEntry(entry);
+ }
+ }
+
+ protected void doWriteXRefInc(COSDocument doc) throws IOException
+ {
+ COSDictionary trailer = doc.getTrailer();
+ trailer.setLong(COSName.PREV, doc.getStartXref());
+ addXRefEntry(COSWriterXRefEntry.getNullEntry());
+
+ // sort xref, needed only if object keys not regenerated
+ Collections.sort(getXRefEntries());
+
+ // remember the position where x ref was written
+ setStartxref(getStandardOutput().getPos());
+
+ getStandardOutput().write(XREF);
+ getStandardOutput().writeEOL();
+ // write start object number and object count for this x ref section
+ // we assume starting from scratch
+
+ Integer[] xRefRanges = getXRefRanges(getXRefEntries());
+ int xRefLength = xRefRanges.length;
+ int x = 0;
+ int j = 0;
+ while(x < xRefLength && (xRefLength % 2) == 0)
+ {
+ writeXrefRange(xRefRanges[x], xRefRanges[x + 1]);
+
+ for(int i = 0 ; i < xRefRanges[x + 1] ; ++i)
+ {
+ writeXrefEntry(xRefEntries.get(j++));
+ }
+ x += 2;
+ }
+ }
+
+ protected void doWriteSignature(COSDocument doc) throws IOException, SignatureException
+ {
+ // need to calculate the ByteRange
+ if (signaturePosition[0]>0 && byterangePosition[1] > 0)
+ {
+ int left = (int)getStandardOutput().getPos()-signaturePosition[1];
+ String newByteRange = "0 "+signaturePosition[0]+" "+signaturePosition[1]+" "+left+"]";
+ int leftByterange = byterangePosition[1]-byterangePosition[0]-newByteRange.length();
+ if(leftByterange<0)
+ throw new IOException("Can't write new ByteRange, not enough space");
+ getStandardOutput().setPos(byterangePosition[0]);
+ getStandardOutput().write(newByteRange.getBytes());
+ for(int i=0;i<leftByterange;++i)
+ {
+ getStandardOutput().write(0x20);
+ }
+
+ getStandardOutput().setPos(0);
+ // Begin - extracting document
+ InputStream filterInputStream = new COSFilterInputStream(in, new int[] {0,signaturePosition[0],signaturePosition[1],left});
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ try {
+ byte[] buffer = new byte[1024];
+ int c;
+ while((c = filterInputStream.read(buffer)) != -1)
+ bytes.write(buffer, 0, c);
+ } finally {
+ if(filterInputStream !=null)
+ filterInputStream.close();
+ }
+
+ byte[] pdfContent = bytes.toByteArray();
+ // End - extracting document
+
+ SignatureInterface signatureInterface = doc.getSignatureInterface();
+ byte[] sign = signatureInterface.sign(new ByteArrayInputStream(pdfContent));
+ String signature = new COSString(sign).getHexString();
+ int leftSignaturerange = signaturePosition[1]-signaturePosition[0]-signature.length();
+ if(leftSignaturerange<0)
+ throw new IOException("Can't write signature, not enough space");
+ getStandardOutput().setPos(signaturePosition[0]+1);
+ getStandardOutput().write(signature.getBytes());
+ }
+ }
+
+ protected void writeXrefRange(long x, long y) throws IOException
+ {
+ getStandardOutput().write(String.valueOf(x).getBytes());
+ getStandardOutput().write(SPACE);
+ getStandardOutput().write(String.valueOf(y).getBytes());
+ getStandardOutput().writeEOL();
+ }
+
+ protected void writeXrefEntry(COSWriterXRefEntry entry) throws IOException
+ {
+ String offset = formatXrefOffset.format(entry.getOffset());
+ String generation = formatXrefGeneration.format(entry.getKey().getGeneration());
+ getStandardOutput().write(offset.getBytes("ISO-8859-1"));
+ getStandardOutput().write(SPACE);
+ getStandardOutput().write(generation.getBytes("ISO-8859-1"));
+ getStandardOutput().write(SPACE);
+ getStandardOutput().write(entry.isFree() ? XREF_FREE : XREF_USED);
+ getStandardOutput().writeCRLF();
+ }
+
+ /**
+ * check the xref entries and write out the ranges. The format of the
+ * returned array is exactly the same as the pdf specification. See section
+ * 7.5.4 of ISO32000-1:2008, example 1 (page 40) for reference.
+ * <p>
+ * example: 0 1 2 5 6 7 8 10
+ * <p>
+ * will create a array with follow ranges
+ * <p>
+ * 0 3 5 4 10 1
+ * <p>
+ * this mean that the element 0 is followed by two other related numbers
+ * that represent a cluster of the size 3. 5 is follow by three other
+ * related numbers and create a cluster of size 4. etc.
+ *
+ * @param xRefEntries list with the xRef entries that was written
+ * @return a integer array with the ranges
+ */
+ protected Integer[] getXRefRanges(List<COSWriterXRefEntry> xRefEntries)
+ {
+ int nr = 0;
+ int last = -2;
+ int count = 1;
+
+ ArrayList<Integer> list = new ArrayList<Integer>();
+ for( Object object : xRefEntries )
+ {
+ nr = (int)((COSWriterXRefEntry)object).getKey().getNumber();
+ if (nr == last + 1)
+ {
+ ++count;
+ last = nr;
+ }
+ else if (last == -2)
+ {
+ last = nr;
+ }
+ else
+ {
+ list.add(last - count + 1);
+ list.add(count);
+ last = nr;
+ count = 1;
+ }
+ }
+ // If no new entry is found, we need to write out the last result
+ if(xRefEntries.size() > 0)
+ {
+ list.add(last - count + 1);
+ list.add(count);
+ }
+ return list.toArray(new Integer[list.size()]);
+ }
+
+ /**
+ * This will get the object key for the object.
+ *
+ * @param obj The object to get the key for.
+ *
+ * @return The object key for the object.
+ */
+ private COSObjectKey getObjectKey( COSBase obj )
+ {
+ COSBase actual = obj;
+ if( actual instanceof COSObject )
+ {
+ actual = ((COSObject)obj).getObject();
+ }
+ COSObjectKey key = null;
+ if( actual != null )
+ {
+ key = objectKeys.get(actual);
+ }
+ if( key == null )
+ {
+ key = objectKeys.get(obj);
+ }
+ if (key == null)
+ {
+ setNumber(getNumber()+1);
+ key = new COSObjectKey(getNumber(),0);
+ objectKeys.put(obj, key);
+ if( actual != null )
+ {
+ objectKeys.put(actual, key);
+ }
+ }
+ return key;
+ }
+
+ /**
+ * visitFromArray method comment.
+ *
+ * @param obj The object that is being visited.
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ *
+ * @return null
+ */
+ public Object visitFromArray( COSArray obj ) throws COSVisitorException
+ {
+ try
+ {
+ int count = 0;
+ getStandardOutput().write(ARRAY_OPEN);
+ for (Iterator<COSBase> i = obj.iterator(); i.hasNext();)
+ {
+ COSBase current = i.next();
+ if( current instanceof COSDictionary )
+ {
+ addObjectToWrite( current );
+ writeReference( current );
+ }
+ else if( current instanceof COSObject )
+ {
+ COSBase subValue = ((COSObject)current).getObject();
+ if( subValue instanceof COSDictionary || subValue == null )
+ {
+ addObjectToWrite( current );
+ writeReference( current );
+ }
+ else
+ {
+ subValue.accept( this );
+ }
+ }
+ else if( current == null )
+ {
+ COSNull.NULL.accept( this );
+ }
+ else if( current instanceof COSString )
+ {
+ COSString copy = new COSString(((COSString)current).getString());
+ copy.accept(this);
+ }
+ else
+ {
+ current.accept(this);
+ }
+ count++;
+ if (i.hasNext())
+ {
+ if (count % 10 == 0)
+ {
+ getStandardOutput().writeEOL();
+ }
+ else
+ {
+ getStandardOutput().write(SPACE);
+ }
+ }
+ }
+ getStandardOutput().write(ARRAY_CLOSE);
+ getStandardOutput().writeEOL();
+ return null;
+ }
+ catch (IOException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+
+ /**
+ * visitFromBoolean method comment.
+ *
+ * @param obj The object that is being visited.
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ *
+ * @return null
+ */
+ public Object visitFromBoolean(COSBoolean obj) throws COSVisitorException
+ {
+
+ try
+ {
+ obj.writePDF( getStandardOutput() );
+ return null;
+ }
+ catch (IOException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+
+ /**
+ * visitFromDictionary method comment.
+ *
+ * @param obj The object that is being visited.
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ *
+ * @return null
+ */
+ public Object visitFromDictionary(COSDictionary obj) throws COSVisitorException
+ {
+ try
+ {
+ getStandardOutput().write(DICT_OPEN);
+ getStandardOutput().writeEOL();
+ for (Map.Entry<COSName, COSBase> entry : obj.entrySet())
+ {
+ COSBase value = entry.getValue();
+ if (value != null)
+ {
+ entry.getKey().accept(this);
+ getStandardOutput().write(SPACE);
+ if( value instanceof COSDictionary )
+ {
+ COSDictionary dict = (COSDictionary)value;
+
+ // write all XObjects as direct objects, this will save some size
+ COSBase item = dict.getItem(COSName.XOBJECT);
+ if(item!=null)
+ {
+ item.setDirect(true);
+ }
+ item = dict.getItem(COSName.RESOURCES);
+ if(item!=null)
+ {
+ item.setDirect(true);
+ }
+
+ if(dict.isDirect())
+ {
+ // If the object should be written direct, we need
+ // to pass the dictionary to the visitor again.
+ visitFromDictionary(dict);
+ }
+ else
+ {
+ addObjectToWrite( dict );
+ writeReference( dict );
+ }
+ }
+ else if( value instanceof COSObject )
+ {
+ COSBase subValue = ((COSObject)value).getObject();
+ if( subValue instanceof COSDictionary || subValue == null )
+ {
+ addObjectToWrite( value );
+ writeReference( value );
+ }
+ else
+ {
+ subValue.accept( this );
+ }
+ }
+ else
+ {
+ // If we reach the pdf signature, we need to determinate the position of the
+ // content and byterange
+ if(reachedSignature && COSName.CONTENTS.equals(entry.getKey()))
+ {
+ signaturePosition = new int[2];
+ signaturePosition[0] = (int)getStandardOutput().getPos();
+ value.accept(this);
+ signaturePosition[1] = (int)getStandardOutput().getPos();
+ }
+ else if(reachedSignature && COSName.BYTERANGE.equals(entry.getKey()))
+ {
+ byterangePosition = new int[2];
+ byterangePosition[0] = (int)getStandardOutput().getPos()+1;
+ value.accept(this);
+ byterangePosition[1] = (int)getStandardOutput().getPos()-1;
+ reachedSignature = false;
+ }
+ else
+ {
+ value.accept(this);
+ }
+ }
+ getStandardOutput().writeEOL();
+
+ }
+ else
+ {
+ //then we won't write anything, there are a couple cases
+ //were the value of an entry in the COSDictionary will
+ //be a dangling reference that points to nothing
+ //so we will just not write out the entry if that is the case
+ }
+ }
+ getStandardOutput().write(DICT_CLOSE);
+ getStandardOutput().writeEOL();
+ return null;
+ }
+ catch( IOException e )
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+
+ /**
+ * The visit from document method.
+ *
+ * @param doc The object that is being visited.
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ *
+ * @return null
+ */
+ public Object visitFromDocument(COSDocument doc) throws COSVisitorException
+ {
+ try
+ {
+ if(!incrementalUpdate)
+ doWriteHeader(doc);
+ doWriteBody(doc);
+ if(incrementalUpdate)
+ doWriteXRefInc(doc);
+ else
+ doWriteXRef(doc);
+ doWriteTrailer(doc);
+ doWriteSignature(doc);
+
+ return null;
+ }
+ catch (IOException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ catch (SignatureException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+
+ /**
+ * visitFromFloat method comment.
+ *
+ * @param obj The object that is being visited.
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ *
+ * @return null
+ */
+ public Object visitFromFloat(COSFloat obj) throws COSVisitorException
+ {
+
+ try
+ {
+ obj.writePDF( getStandardOutput() );
+ return null;
+ }
+ catch (IOException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+
+ /**
+ * visitFromFloat method comment.
+ *
+ * @param obj The object that is being visited.
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ *
+ * @return null
+ */
+ public Object visitFromInt(COSInteger obj) throws COSVisitorException
+ {
+ try
+ {
+ obj.writePDF( getStandardOutput() );
+ return null;
+ }
+ catch (IOException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+
+ /**
+ * visitFromName method comment.
+ *
+ * @param obj The object that is being visited.
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ *
+ * @return null
+ */
+ public Object visitFromName(COSName obj) throws COSVisitorException
+ {
+ try
+ {
+ obj.writePDF( getStandardOutput() );
+ return null;
+ }
+ catch (IOException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+
+ /**
+ * visitFromNull method comment.
+ *
+ * @param obj The object that is being visited.
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ *
+ * @return null
+ */
+ public Object visitFromNull(COSNull obj) throws COSVisitorException
+ {
+ try
+ {
+ obj.writePDF( getStandardOutput() );
+ return null;
+ }
+ catch (IOException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+
+ /**
+ * visitFromObjRef method comment.
+ *
+ * @param obj The object that is being visited.
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ */
+ public void writeReference(COSBase obj) throws COSVisitorException
+ {
+ try
+ {
+ COSObjectKey key = getObjectKey(obj);
+ getStandardOutput().write(String.valueOf(key.getNumber()).getBytes("ISO-8859-1"));
+ getStandardOutput().write(SPACE);
+ getStandardOutput().write(String.valueOf(key.getGeneration()).getBytes("ISO-8859-1"));
+ getStandardOutput().write(SPACE);
+ getStandardOutput().write(REFERENCE);
+ }
+ catch (IOException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+
+ /**
+ * visitFromStream method comment.
+ *
+ * @param obj The object that is being visited.
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ *
+ * @return null
+ */
+ public Object visitFromStream(COSStream obj) throws COSVisitorException
+ {
+ InputStream input = null;
+
+ try
+ {
+ if(willEncrypt)
+ {
+ document.getSecurityHandler().encryptStream(
+ obj,
+ currentObjectKey.getNumber(),
+ currentObjectKey.getGeneration());
+ }
+
+ input = obj.getFilteredStream();
+ // set the length of the stream and write stream dictionary
+ COSObject lengthObject = new COSObject( null );
+
+ obj.setItem(COSName.LENGTH, lengthObject);
+ //obj.accept(this);
+ // write the stream content
+ visitFromDictionary( obj );
+ getStandardOutput().write(STREAM);
+ getStandardOutput().writeCRLF();
+ byte[] buffer = new byte[1024];
+ int amountRead = 0;
+ int totalAmountWritten = 0;
+ while( (amountRead = input.read(buffer,0,1024)) != -1 )
+ {
+ getStandardOutput().write( buffer, 0, amountRead );
+ totalAmountWritten += amountRead;
+ }
+ lengthObject.setObject( COSInteger.get( totalAmountWritten ) );
+ getStandardOutput().writeCRLF();
+ getStandardOutput().write(ENDSTREAM);
+ getStandardOutput().writeEOL();
+ return null;
+ }
+ catch( Exception e )
+ {
+ throw new COSVisitorException(e);
+ }
+ finally
+ {
+ if (input != null)
+ {
+ try
+ {
+ input.close();
+ }
+ catch (IOException e)
+ {
+ throw new COSVisitorException(e);
+ }
+ }
+ }
+ }
+
+ /**
+ * visitFromString method comment.
+ *
+ * @param obj The object that is being visited.
+ *
+ * @return null
+ *
+ * @throws COSVisitorException If there is an exception while visiting this object.
+ */
+ public Object visitFromString(COSString obj) throws COSVisitorException
+ {
+ try
+ {
+ if(willEncrypt)
+ {
+ document.getSecurityHandler().decryptString(
+ obj,
+ currentObjectKey.getNumber(),
+ currentObjectKey.getGeneration());
+ }
+
+ obj.writePDF( getStandardOutput() );
+ }
+ catch (Exception e)
+ {
+ throw new COSVisitorException(e);
+ }
+ return null;
+ }
+
+ /**
+ * This will write the pdf document.
+ *
+ * @param doc The document to write.
+ *
+ * @throws COSVisitorException If an error occurs while generating the data.
+ */
+ public void write(COSDocument doc) throws COSVisitorException
+ {
+ PDDocument pdDoc = new PDDocument( doc );
+ write( pdDoc );
+ }
+
+ /**
+ * This will write the pdf document.
+ *
+ * @param doc The document to write.
+ *
+ * @throws COSVisitorException If an error occurs while generating the data.
+ */
+ public void write(PDDocument doc) throws COSVisitorException
+ {
+ document = doc;
+ if(incrementalUpdate)
+ prepareIncrement(doc);
+
+ // if the document says we should remove encryption, then we shouldn't encrypt
+ if(doc.isAllSecurityToBeRemoved())
+ {
+ this.willEncrypt = false;
+ // also need to get rid of the "Encrypt" in the trailer so readers
+ // don't try to decrypt a document which is not encrypted
+ COSDocument cosDoc = doc.getDocument();
+ COSDictionary trailer = cosDoc.getTrailer();
+ trailer.removeItem(COSName.ENCRYPT);
+ }
+ else
+ {
+ SecurityHandler securityHandler = document.getSecurityHandler();
+ if(securityHandler != null)
+ {
+ try
+ {
+ securityHandler.prepareDocumentForEncryption(document);
+ this.willEncrypt = true;
+ }
+ catch(IOException e)
+ {
+ throw new COSVisitorException( e );
+ }
+ catch(CryptographyException e)
+ {
+ throw new COSVisitorException( e );
+ }
+ }
+ else
+ {
+ this.willEncrypt = false;
+ }
+ }
+
+ COSDocument cosDoc = document.getDocument();
+ COSDictionary trailer = cosDoc.getTrailer();
+ COSArray idArray = (COSArray)trailer.getDictionaryObject( COSName.ID );
+ if( idArray == null || incrementalUpdate)
+ {
+ try
+ {
+
+ //algorithm says to use time/path/size/values in doc to generate
+ //the id. We don't have path or size, so do the best we can
+ MessageDigest md = MessageDigest.getInstance( "MD5" );
+ md.update( Long.toString( System.currentTimeMillis()).getBytes("ISO-8859-1") );
+ COSDictionary info = (COSDictionary)trailer.getDictionaryObject( COSName.INFO );
+ if( info != null )
+ {
+ Iterator<COSBase> values = info.getValues().iterator();
+ while( values.hasNext() )
+ {
+ md.update( values.next().toString().getBytes("ISO-8859-1") );
+ }
+ }
+ idArray = new COSArray();
+ COSString id = new COSString( md.digest() );
+ idArray.add( id );
+ idArray.add( id );
+ trailer.setItem( COSName.ID, idArray );
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new COSVisitorException( e );
+ }
+ catch( UnsupportedEncodingException e )
+ {
+ throw new COSVisitorException( e );
+ }
+ }
+ cosDoc.accept(this);
+ }
+}
--- /dev/null
+/*
+ * 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.pdfwriter;
+
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * this is en entry in the xref section of the physical pdf document
+ * generated by the COSWriter.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.7 $
+ */
+public class COSWriterXRefEntry implements Comparable<COSWriterXRefEntry>
+{
+ private long offset;
+ private COSBase object;
+ private COSObjectKey key;
+ private boolean free = false;
+ private static COSWriterXRefEntry nullEntry;
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(COSWriterXRefEntry obj)
+ {
+ if (obj instanceof COSWriterXRefEntry)
+ {
+ return (int)(getKey().getNumber() - obj.getKey().getNumber());
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ /**
+ * This will return a null entry: 0000000000 65535 f
+ *
+ * @return null COSWriterXRefEntry
+ */
+ public static COSWriterXRefEntry getNullEntry()
+ {
+ if (nullEntry == null)
+ {
+ nullEntry = new COSWriterXRefEntry(0, null, new COSObjectKey(0, 65535));
+ nullEntry.setFree(true);
+ }
+ return nullEntry;
+ }
+
+ /**
+ * This will get the Object key.
+ *
+ * @return The object key.
+ */
+ public COSObjectKey getKey()
+ {
+ return key;
+ }
+
+ /**
+ * This will get the offset into the document.
+ *
+ * @return The offset into the document.
+ */
+ public long getOffset()
+ {
+ return offset;
+ }
+
+ /**
+ * Gets the xref 'free' attribute.
+ *
+ * @return The free attribute.
+ */
+ public boolean isFree()
+ {
+ return free;
+ }
+
+ /**
+ * This will set the free attribute.
+ *
+ * @param newFree The newly freed attribute.
+ */
+ public void setFree(boolean newFree)
+ {
+ free = newFree;
+ }
+
+ /**
+ * This will set the object key.
+ *
+ * @param newKey The new object key.
+ */
+ private void setKey(COSObjectKey newKey)
+ {
+ key = newKey;
+ }
+
+ /**
+ * The offset attribute.
+ *
+ * @param newOffset The new value for the offset.
+ */
+ public void setOffset(long newOffset)
+ {
+ offset = newOffset;
+ }
+
+ /**
+ * COSWriterXRefEntry constructor comment.
+ *
+ * @param start The start attribute.
+ * @param obj The COS object that this entry represents.
+ * @param keyValue The key to the COS object.
+ */
+ public COSWriterXRefEntry(long start, COSBase obj, COSObjectKey keyValue)
+ {
+ super();
+ setOffset(start);
+ setObject(obj);
+ setKey(keyValue);
+ }
+
+ /**
+ * This will get the object.
+ *
+ * @return The object.
+ */
+ public COSBase getObject()
+ {
+ return object;
+ }
+
+ /**
+ * This will set the object for this xref.
+ *
+ * @param newObject The object that is being set.
+ */
+ private void setObject(COSBase newObject)
+ {
+ object = newObject;
+ }
+}
--- /dev/null
+/*
+ * 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.pdfwriter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+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.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.util.ImageParameters;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * A class that will take a list of tokens and write out a stream with them.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class ContentStreamWriter
+{
+ private OutputStream output;
+ /**
+ * space character.
+ */
+ public static final byte[] SPACE = new byte[] { 32 };
+
+ /**
+ * standard line separator
+ */
+ public static final byte[] EOL = new byte[] { 0x0A };
+
+ /**
+ * This will create a new content stream writer.
+ *
+ * @param out The stream to write the data to.
+ */
+ public ContentStreamWriter( OutputStream out )
+ {
+ output = out;
+ }
+
+ /**
+ * This will write out the list of tokens to the stream.
+ *
+ * @param tokens The tokens to write to the stream.
+ * @param start The start index into the list of tokens.
+ * @param end The end index into the list of tokens.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void writeTokens( List tokens, int start, int end ) throws IOException
+ {
+ for( int i=start; i<end; i++ )
+ {
+ Object o = tokens.get( i );
+ writeObject( o );
+ //write a space between each object.
+ output.write( 32 );
+ }
+ output.flush();
+ }
+
+ private void writeObject( Object o ) throws IOException
+ {
+ if( o instanceof COSString )
+ {
+ ((COSString)o).writePDF( output );
+ }
+ else if( o instanceof COSFloat )
+ {
+ ((COSFloat)o).writePDF( output );
+ }
+ else if( o instanceof COSInteger )
+ {
+ ((COSInteger)o).writePDF( output );
+ }
+ else if( o instanceof COSBoolean )
+ {
+ ((COSBoolean)o).writePDF( output );
+ }
+ else if( o instanceof COSName )
+ {
+ ((COSName)o).writePDF( output );
+ }
+ else if( o instanceof COSArray )
+ {
+ COSArray array = (COSArray)o;
+ output.write(COSWriter.ARRAY_OPEN);
+ for( int i=0; i<array.size(); i++ )
+ {
+ writeObject( array.get( i ) );
+ output.write( SPACE );
+ }
+
+ output.write( COSWriter.ARRAY_CLOSE );
+ }
+ else if( o instanceof COSDictionary )
+ {
+ COSDictionary obj = (COSDictionary)o;
+ output.write( COSWriter.DICT_OPEN );
+ for (Map.Entry<COSName, COSBase> entry : obj.entrySet())
+ {
+ if (entry.getValue() != null)
+ {
+ writeObject( entry.getKey() );
+ output.write( SPACE );
+ writeObject( entry.getValue() );
+ output.write( SPACE );
+ }
+ }
+ output.write( COSWriter.DICT_CLOSE );
+ output.write( SPACE );
+ }
+ else if( o instanceof PDFOperator )
+ {
+ PDFOperator op = (PDFOperator)o;
+ if( op.getOperation().equals( "BI" ) )
+ {
+ output.write( "BI".getBytes("ISO-8859-1") );
+ ImageParameters params = op.getImageParameters();
+ COSDictionary dic = params.getDictionary();
+ for( COSName key : dic.keySet() )
+ {
+ Object value = dic.getDictionaryObject( key );
+ key.writePDF( output );
+ output.write( SPACE );
+ writeObject( value );
+ output.write( EOL );
+ }
+ output.write( "ID".getBytes("ISO-8859-1") );
+ output.write( EOL );
+ output.write( op.getImageData() );
+ }
+ else
+ {
+ output.write( op.getOperation().getBytes("ISO-8859-1") );
+ output.write( EOL );
+ }
+ }
+ else
+ {
+ throw new IOException( "Error:Unknown type in content stream:" + o );
+ }
+ }
+
+ /**
+ * This will write out the list of tokens to the stream.
+ *
+ * @param tokens The tokens to write to the stream.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void writeTokens( List tokens ) throws IOException
+ {
+ writeTokens( tokens, 0, tokens.size() );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This is the persistence layer used to write the PDFBox documents to a stream.
+</body>
+</html>
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
+
+/**
+ * This class holds all of the name trees that are available at the document level.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDDestinationNameTreeNode extends PDNameTreeNode
+{
+
+ /**
+ * Constructor.
+ */
+ public PDDestinationNameTreeNode()
+ {
+ super( PDPageDestination.class );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dic The COS dictionary.
+ */
+ public PDDestinationNameTreeNode( COSDictionary dic )
+ {
+ super( dic, PDPageDestination.class );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected Object convertCOSToPD( COSBase base ) throws IOException
+ {
+ COSBase destination = base;
+ if( base instanceof COSDictionary )
+ {
+ //the destination is sometimes stored in the D dictionary
+ //entry instead of being directly an array, so just dereference
+ //it for now
+ destination = ((COSDictionary)base).getDictionaryObject( COSName.D );
+ }
+ return PDDestination.create( destination );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected PDNameTreeNode createChildNode( COSDictionary dic )
+ {
+ return new PDDestinationNameTreeNode(dic);
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.awt.print.PageFormat;
+import java.awt.print.Pageable;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.ArrayList;
+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.COSDocument;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.exceptions.SignatureException;
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.pdfparser.PDFParser;
+import org.apache.pdfbox.pdfwriter.COSWriter;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
+import org.apache.pdfbox.pdmodel.encryption.BadSecurityHandlerException;
+import org.apache.pdfbox.pdmodel.encryption.DecryptionMaterial;
+import org.apache.pdfbox.pdmodel.encryption.PDEncryptionDictionary;
+import org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy;
+import org.apache.pdfbox.pdmodel.encryption.SecurityHandler;
+import org.apache.pdfbox.pdmodel.encryption.SecurityHandlersManager;
+import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial;
+import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
+
+/**
+ * This is the in-memory representation of the PDF document. You need to call
+ * close() on this object when you are done using it!!
+ * <p>
+ * This class implements the {@link Pageable} interface, but since PDFBox
+ * version 1.3.0 you should be using the {@link PDPageable} adapter instead
+ * (see <a href="https://issues.apache.org/jira/browse/PDFBOX-788">PDFBOX-788</a>).
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.47 $
+ */
+public class PDDocument implements Pageable
+{
+
+ private COSDocument document;
+
+ //cached values
+ private PDDocumentInformation documentInformation;
+ private PDDocumentCatalog documentCatalog;
+
+ //The encParameters will be cached here. When the document is decrypted then
+ //the COSDocument will not have an "Encrypt" dictionary anymore and this object
+ //must be used.
+ private PDEncryptionDictionary encParameters = null;
+
+ /**
+ * The security handler used to decrypt / encrypt the document.
+ */
+ private SecurityHandler securityHandler = null;
+
+
+ /**
+ * This assocates object ids with a page number. It's used to determine
+ * the page number for bookmarks (or page numbers for anything else for
+ * which you have an object id for that matter).
+ */
+ private Map<String, Integer> pageMap = null;
+
+ /**
+ * This will hold a flag which tells us if we should remove all security
+ * from this documents
+ */
+ private boolean allSecurityToBeRemoved = false;
+
+ /**
+ * Constructor, creates a new PDF Document with no pages. You need to add
+ * at least one page for the document to be valid.
+ *
+ * @throws IOException If there is an error creating this document.
+ */
+ public PDDocument() throws IOException
+ {
+ document = new COSDocument();
+
+ //First we need a trailer
+ COSDictionary trailer = new COSDictionary();
+ document.setTrailer( trailer );
+
+ //Next we need the root dictionary.
+ COSDictionary rootDictionary = new COSDictionary();
+ trailer.setItem( COSName.ROOT, rootDictionary );
+ rootDictionary.setItem( COSName.TYPE, COSName.CATALOG );
+ rootDictionary.setItem( COSName.VERSION, COSName.getPDFName( "1.4" ) );
+
+ //next we need the pages tree structure
+ COSDictionary pages = new COSDictionary();
+ rootDictionary.setItem( COSName.PAGES, pages );
+ pages.setItem( COSName.TYPE, COSName.PAGES );
+ COSArray kidsArray = new COSArray();
+ pages.setItem( COSName.KIDS, kidsArray );
+ pages.setItem( COSName.COUNT, COSInteger.ZERO );
+ }
+
+ private void generatePageMap()
+ {
+ pageMap = new HashMap<String,Integer>();
+ // these page nodes could be references to pages,
+ // or references to arrays which have references to pages
+ // or references to arrays which have references to arrays which have references to pages
+ // or ... (I think you get the idea...)
+ processListOfPageReferences(getDocumentCatalog().getPages().getKids());
+ }
+
+ private void processListOfPageReferences(List<Object> pageNodes)
+ {
+ for(int i=0; i < pageNodes.size(); ++i)
+ {
+ Object pageOrArray = pageNodes.get(i);
+ if(pageOrArray instanceof PDPage)
+ {
+ List pageArray = ((((PDPage)pageOrArray).getParent()).getKids());
+ parseCatalogObject((COSObject)pageArray.get(i));
+ }
+ else if(pageOrArray instanceof PDPageNode)
+ {
+ processListOfPageReferences(((PDPageNode)pageOrArray).getKids());
+ }
+ }
+ }
+
+ /**
+ * This will either add the page passed in, or, if it's a pointer to an array
+ * of pages, it'll recursivly call itself and process everything in the list.
+ */
+ private void parseCatalogObject(COSObject thePageOrArrayObject)
+ {
+ COSBase arrayCountBase = thePageOrArrayObject.getItem(COSName.COUNT);
+ int arrayCount = -1;
+ if(arrayCountBase instanceof COSInteger)
+ {
+ arrayCount = ((COSInteger)arrayCountBase).intValue();
+ }
+
+ COSBase kidsBase = thePageOrArrayObject.getItem(COSName.KIDS);
+ int kidsCount = -1;
+ if(kidsBase instanceof COSArray)
+ {
+ kidsCount = ((COSArray)kidsBase).size();
+ }
+
+ if(arrayCount == -1 || kidsCount == -1)
+ {
+ // these cases occur when we have a page, not an array of pages
+ String objStr = String.valueOf(thePageOrArrayObject.getObjectNumber().intValue());
+ String genStr = String.valueOf(thePageOrArrayObject.getGenerationNumber().intValue());
+ getPageMap().put(objStr+","+genStr, new Integer(getPageMap().size()+1));
+ }
+ else
+ {
+ // we either have an array of page pointers, or an array of arrays
+ if(arrayCount == kidsCount)
+ {
+ // process the kids... they're all references to pages
+ COSArray kidsArray = ((COSArray)kidsBase);
+ for(int i=0; i<kidsArray.size(); ++i)
+ {
+ COSObject thisObject = (COSObject)kidsArray.get(i);
+ String objStr = String.valueOf(thisObject.getObjectNumber().intValue());
+ String genStr = String.valueOf(thisObject.getGenerationNumber().intValue());
+ getPageMap().put(objStr+","+genStr, new Integer(getPageMap().size()+1));
+ }
+ }
+ else
+ {
+ // this object is an array of references to other arrays
+ COSArray list = null;
+ if(kidsBase instanceof COSArray)
+ {
+ list = ((COSArray)kidsBase);
+ }
+ if(list != null)
+ {
+ for(int arrayCounter=0; arrayCounter < list.size(); ++arrayCounter)
+ {
+ parseCatalogObject((COSObject)list.get(arrayCounter));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This will return the Map containing the mapping from object-ids to pagenumbers.
+ *
+ * @return the pageMap
+ */
+ public final Map<String,Integer> getPageMap()
+ {
+ if (pageMap == null)
+ {
+ generatePageMap();
+ }
+ return pageMap;
+ }
+
+ /**
+ * This will add a page to the document. This is a convenience method, that
+ * will add the page to the root of the hierarchy and set the parent of the
+ * page to the root.
+ *
+ * @param page The page to add to the document.
+ */
+ public void addPage( PDPage page )
+ {
+ PDPageNode rootPages = getDocumentCatalog().getPages();
+ rootPages.getKids().add( page );
+ page.setParent( rootPages );
+ rootPages.updateCount();
+ }
+
+ public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface) throws IOException, SignatureException
+ {
+ SignatureOptions defaultOptions = new SignatureOptions();
+ defaultOptions.setPage(1);
+ addSignature(sigObject, signatureInterface,defaultOptions);
+ }
+
+ /**
+ * This will add a signature to the document.
+ *
+ * @param sigObject is the PDSignature model
+ * @param signatureInterface is a interface which provides signing capabilities
+ * @param options
+ * @throws IOException if there is an error creating required fields
+ */
+ public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface, SignatureOptions options) throws IOException, SignatureException
+ {
+ // Reserve content
+ // We need to reserve some space for the signature. Some signatures including
+ // big certificate chain and we need enough space to store it.
+ int preferedSignatureSize = options.getPreferedSignatureSize();
+ if (preferedSignatureSize > 0)
+ {
+ sigObject.setContents(new byte[preferedSignatureSize * 2 + 2]);
+ }
+ else
+ {
+ sigObject.setContents(new byte[0x2500 * 2 + 2]);
+ }
+
+ // Reserve ByteRange
+ sigObject.setByteRange(new int[] {0,1000000000,1000000000,1000000000});
+
+ getDocument().setSignatureInterface(signatureInterface);
+
+ // #########################################
+ // # Create SignatureForm for signature #
+ // # and appending it to the document #
+ // #########################################
+
+ // Get the first page
+ PDDocumentCatalog root = getDocumentCatalog();
+ PDPageNode rootPages = root.getPages();
+ List<PDPage> kids = new ArrayList<PDPage>();
+ rootPages.getAllKids(kids);
+
+ int size = (int)rootPages.getCount();
+ PDPage page = null;
+ if (size == 0)
+ {
+ throw new SignatureException(SignatureException.INVALID_PAGE_FOR_SIGNATURE, "The PDF file has no pages");
+ }
+ if (options.getPage()>size)
+ {
+ page = kids.get(size-1);
+ }
+ else if(options.getPage()<=0)
+ {
+ page = kids.get(0);
+ }
+ else
+ {
+ page = kids.get(options.getPage()-1);
+ }
+
+
+ // Get the AcroForm from the Root-Dictionary and append the annotation
+ PDAcroForm acroForm = root.getAcroForm();
+ root.getCOSObject().setNeedToBeUpdate(true);
+
+ if (acroForm==null)
+ {
+ acroForm = new PDAcroForm(this);
+ root.setAcroForm(acroForm);
+ }
+ else
+ {
+ acroForm.getCOSObject().setNeedToBeUpdate(true);
+ }
+
+ /*
+ * For invisible signatures, the annotation has a rectangle array with values [ 0 0 0 0 ].
+ * This annotation is usually attached to the viewed page when the signature is created.
+ * Despite not having an appearance, the annotation AP and N dictionaries may be present
+ * in some versions of Acrobat. If present, N references the DSBlankXObj (blank) XObject.
+ */
+
+ // Create Annotation / Field for signature
+ PDSignatureField signatureField = new PDSignatureField(acroForm);
+ signatureField.setSignature(sigObject); // append the signature object
+ signatureField.getWidget().setPage(page); // backward linking
+
+ // Set the AcroForm Fields
+ List acroFormFields = acroForm.getFields();
+ COSDictionary acroFormDict = acroForm.getDictionary();
+ acroFormDict.setDirect(true);
+ acroFormDict.setInt(COSName.SIG_FLAGS, 3);
+ acroFormFields.add(signatureField);
+
+ // Get the object from the visual signature
+ COSDocument visualSignature = options.getVisualSignature();
+
+ // Distinction of case for visual and non-visual signature
+ if (visualSignature == null) // non-visual signature
+ {
+ // Set rectangle for non-visual signature to 0 0 0 0
+ signatureField.getWidget().setRectangle(new PDRectangle()); // rectangle array [ 0 0 0 0 ]
+ // Clear AcroForm / Set DefaultRessource
+ acroFormDict.setItem(COSName.DR, null);
+ // Set empty Appearance-Dictionary
+ PDAppearanceDictionary ap = new PDAppearanceDictionary();
+ COSStream apsStream = new COSStream(getDocument().getScratchFile());
+ apsStream.createUnfilteredStream();
+ PDAppearanceStream aps = new PDAppearanceStream(apsStream);
+ COSDictionary cosObject = (COSDictionary)aps.getCOSObject();
+ cosObject.setItem(COSName.SUBTYPE, COSName.FORM);
+ cosObject.setItem(COSName.BBOX, new PDRectangle());
+
+ ap.setNormalAppearance(aps);
+ ap.getDictionary().setDirect(true);
+ signatureField.getWidget().setAppearance(ap);
+ }
+ else // visual signature
+ {
+ // Obtain visual signature object
+ List<COSObject> cosObjects = visualSignature.getObjects();
+
+ boolean annotNotFound = true;
+ boolean sigFieldNotFound = true;
+
+ for ( COSObject cosObject : cosObjects )
+ {
+ COSBase base = cosObject.getObject();
+ if (base != null && base instanceof COSDictionary)
+ {
+ COSBase ft = ((COSDictionary)base).getItem(COSName.FT);
+ COSBase type = ((COSDictionary)base).getItem(COSName.TYPE);
+ COSBase apDict = ((COSDictionary)base).getItem(COSName.AP);
+
+ // Search for signature annotation
+ if (annotNotFound && COSName.ANNOT.equals(type))
+ {
+ COSDictionary cosBaseDict = (COSDictionary)base;
+
+ // Read and set the Rectangle for visual signature
+ COSArray rectAry = (COSArray)cosBaseDict.getItem(COSName.RECT);
+ PDRectangle rect = new PDRectangle(rectAry);
+ signatureField.getWidget().setRectangle(rect);
+ annotNotFound = false;
+ }
+
+ // Search for Signature-Field
+ if (sigFieldNotFound && COSName.SIG.equals(ft) && apDict != null)
+ {
+ COSDictionary cosBaseDict = (COSDictionary)base;
+
+ // Appearance Dictionary auslesen und setzen
+ PDAppearanceDictionary ap = new PDAppearanceDictionary((COSDictionary)cosBaseDict.getItem(COSName.AP));
+ ap.getDictionary().setDirect(true);
+ signatureField.getWidget().setAppearance(ap);
+
+ // AcroForm DefaultRessource auslesen und setzen
+ COSBase dr = cosBaseDict.getItem(COSName.DR);
+ dr.setDirect(true);
+ dr.setNeedToBeUpdate(true);
+ acroFormDict.setItem(COSName.DR, dr);
+ sigFieldNotFound=false;
+ }
+ }
+ }
+
+ if (annotNotFound || sigFieldNotFound )
+ {
+ throw new SignatureException(SignatureException.VISUAL_SIGNATURE_INVALID, "Could not read all needed objects from template");
+ }
+ }
+
+ // Get the annotations of the page and append the signature-annotation to it
+ List annotations = page.getAnnotations();
+ if (annotations == null)
+ {
+ annotations = new COSArrayList();
+ page.setAnnotations(annotations);
+ }
+ // take care that page and acroforms do not share the same array (if so, we don't need to add it twice)
+ if (!(annotations instanceof COSArrayList)
+ || !(acroFormFields instanceof COSArrayList)
+ || !((COSArrayList)annotations).toList().equals(((COSArrayList)acroFormFields).toList()))
+ {
+ annotations.add(signatureField.getWidget());
+ }
+ page.getCOSObject().setNeedToBeUpdate(true);
+ }
+
+ /**
+ * Remove the page from the document.
+ *
+ * @param page The page to remove from the document.
+ *
+ * @return true if the page was found false otherwise.
+ */
+ public boolean removePage( PDPage page )
+ {
+ PDPageNode parent = page.getParent();
+ boolean retval = parent.getKids().remove( page );
+ if( retval )
+ {
+ //do a recursive updateCount starting at the root
+ //of the document
+ getDocumentCatalog().getPages().updateCount();
+ }
+ return retval;
+ }
+
+ /**
+ * Remove the page from the document.
+ *
+ * @param pageNumber 0 based index to page number.
+ * @return true if the page was found false otherwise.
+ */
+ public boolean removePage( int pageNumber )
+ {
+ boolean removed = false;
+ List allPages = getDocumentCatalog().getAllPages();
+ if( allPages.size() > pageNumber)
+ {
+ PDPage page = (PDPage)allPages.get( pageNumber );
+ removed = removePage( page );
+ }
+ return removed;
+ }
+
+ /**
+ * This will import and copy the contents from another location. Currently
+ * the content stream is stored in a scratch file. The scratch file is
+ * associated with the document. If you are adding a page to this document
+ * from another document and want to copy the contents to this document's
+ * scratch file then use this method otherwise just use the addPage method.
+ *
+ * @param page The page to import.
+ * @return The page that was imported.
+ *
+ * @throws IOException If there is an error copying the page.
+ */
+ public PDPage importPage( PDPage page ) throws IOException
+ {
+ PDPage importedPage = new PDPage( new COSDictionary( page.getCOSDictionary() ) );
+ InputStream is = null;
+ OutputStream os = null;
+ try
+ {
+ PDStream src = page.getContents();
+ if(src != null)
+ {
+ PDStream dest = new PDStream( new COSStream( src.getStream(), document.getScratchFile() ) );
+ importedPage.setContents( dest );
+ os = dest.createOutputStream();
+
+ byte[] buf = new byte[10240];
+ int amountRead = 0;
+ is = src.createInputStream();
+ while((amountRead = is.read(buf,0,10240)) > -1)
+ {
+ os.write(buf, 0, amountRead);
+ }
+ }
+ addPage( importedPage );
+ }
+ finally
+ {
+ if( is != null )
+ {
+ is.close();
+ }
+ if( os != null )
+ {
+ os.close();
+ }
+ }
+ return importedPage;
+
+ }
+
+ /**
+ * Constructor that uses an existing document. The COSDocument that
+ * is passed in must be valid.
+ *
+ * @param doc The COSDocument that this document wraps.
+ */
+ public PDDocument( COSDocument doc )
+ {
+ document = doc;
+ }
+
+ /**
+ * This will get the low level document.
+ *
+ * @return The document that this layer sits on top of.
+ */
+ public COSDocument getDocument()
+ {
+ return document;
+ }
+
+ /**
+ * This will get the document info dictionary. This is guaranteed to not return null.
+ *
+ * @return The documents /Info dictionary
+ */
+ public PDDocumentInformation getDocumentInformation()
+ {
+ if( documentInformation == null )
+ {
+ COSDictionary trailer = document.getTrailer();
+ COSDictionary infoDic = (COSDictionary)trailer.getDictionaryObject( COSName.INFO );
+ if( infoDic == null )
+ {
+ infoDic = new COSDictionary();
+ trailer.setItem( COSName.INFO, infoDic );
+ }
+ documentInformation = new PDDocumentInformation( infoDic );
+ }
+ return documentInformation;
+ }
+
+ /**
+ * This will set the document information for this document.
+ *
+ * @param info The updated document information.
+ */
+ public void setDocumentInformation( PDDocumentInformation info )
+ {
+ documentInformation = info;
+ document.getTrailer().setItem( COSName.INFO, info.getDictionary() );
+ }
+
+ /**
+ * This will get the document CATALOG. This is guaranteed to not return null.
+ *
+ * @return The documents /Root dictionary
+ */
+ public PDDocumentCatalog getDocumentCatalog()
+ {
+ if( documentCatalog == null )
+ {
+ COSDictionary trailer = document.getTrailer();
+ COSBase dictionary = trailer.getDictionaryObject( COSName.ROOT );
+ if (dictionary instanceof COSDictionary) {
+ documentCatalog =
+ new PDDocumentCatalog(this, (COSDictionary) dictionary);
+ } else {
+ documentCatalog = new PDDocumentCatalog(this);
+ }
+
+ }
+ return documentCatalog;
+ }
+
+ /**
+ * This will tell if this document is encrypted or not.
+ *
+ * @return true If this document is encrypted.
+ */
+ public boolean isEncrypted()
+ {
+ return document.isEncrypted();
+ }
+
+ /**
+ * This will get the encryption dictionary for this document. This will still
+ * return the parameters if the document was decrypted. If the document was
+ * never encrypted then this will return null. As the encryption architecture
+ * in PDF documents is plugable this returns an abstract class, but the only
+ * supported subclass at this time is a PDStandardEncryption object.
+ *
+ * @return The encryption dictionary(most likely a PDStandardEncryption object)
+ *
+ * @throws IOException If there is an error determining which security handler to use.
+ */
+ public PDEncryptionDictionary getEncryptionDictionary() throws IOException
+ {
+ if( encParameters == null )
+ {
+ if( isEncrypted() )
+ {
+ encParameters = new PDEncryptionDictionary(document.getEncryptionDictionary());
+ }
+ }
+ return encParameters;
+ }
+
+ /**
+ * This will set the encryption dictionary for this document.
+ *
+ * @param encDictionary The encryption dictionary(most likely a PDStandardEncryption object)
+ *
+ * @throws IOException If there is an error determining which security handler to use.
+ */
+ public void setEncryptionDictionary( PDEncryptionDictionary encDictionary ) throws IOException
+ {
+ encParameters = encDictionary;
+ }
+
+ public PDSignature getSignatureDictionary() throws IOException
+ {
+ COSDictionary signatureDictionary = document.getLastSignatureDictionary();
+
+ if (signatureDictionary!= null)
+ {
+ return new PDSignature(signatureDictionary);
+ }
+ return null;
+ }
+
+ /**
+ * This will determine if this is the user password. This only applies when
+ * the document is encrypted and uses standard encryption.
+ *
+ * @param password The plain text user password.
+ *
+ * @return true If the password passed in matches the user password used to encrypt the document.
+ *
+ * @throws IOException If there is an error determining if it is the user password.
+ * @throws CryptographyException If there is an error in the encryption algorithms.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ public boolean isUserPassword( String password ) throws IOException, CryptographyException
+ {
+ return false;
+ /*boolean retval = false;
+ if( password == null )
+ {
+ password = "";
+ }
+ PDFEncryption encryptor = new PDFEncryption();
+ PDEncryptionDictionary encryptionDictionary = getEncryptionDictionary();
+ if( encryptionDictionary == null )
+ {
+ throw new IOException( "Error: Document is not encrypted" );
+ }
+ else
+ {
+ if( encryptionDictionary instanceof PDStandardEncryption )
+ {
+ COSString documentID = (COSString)document.getDocumentID().get(0);
+ PDStandardEncryption standard = (PDStandardEncryption)encryptionDictionary;
+ retval = encryptor.isUserPassword(
+ password.getBytes("ISO-8859-1"),
+ standard.getUserKey(),
+ standard.getOwnerKey(),
+ standard.getPermissions(),
+ documentID.getBytes(),
+ standard.getRevision(),
+ standard.getLength()/8 );
+ }
+ else
+ {
+ throw new IOException( "Error: Encyption dictionary is not 'Standard'" +
+ encryptionDictionary.getClass().getName() );
+ }
+ }
+ return retval;*/
+ }
+
+ /**
+ * This will determine if this is the owner password. This only applies when
+ * the document is encrypted and uses standard encryption.
+ *
+ * @param password The plain text owner password.
+ *
+ * @return true If the password passed in matches the owner password used to encrypt the document.
+ *
+ * @throws IOException If there is an error determining if it is the user password.
+ * @throws CryptographyException If there is an error in the encryption algorithms.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ public boolean isOwnerPassword( String password ) throws IOException, CryptographyException
+ {
+ return false;
+ /*boolean retval = false;
+ if( password == null )
+ {
+ password = "";
+ }
+ PDFEncryption encryptor = new PDFEncryption();
+ PDEncryptionDictionary encryptionDictionary = getEncryptionDictionary();
+ if( encryptionDictionary == null )
+ {
+ throw new IOException( "Error: Document is not encrypted" );
+ }
+ else
+ {
+ if( encryptionDictionary instanceof PDStandardEncryption )
+ {
+ COSString documentID = (COSString)document.getDocumentID().get( 0 );
+ PDStandardEncryption standard = (PDStandardEncryption)encryptionDictionary;
+ retval = encryptor.isOwnerPassword(
+ password.getBytes("ISO-8859-1"),
+ standard.getUserKey(),
+ standard.getOwnerKey(),
+ standard.getPermissions(),
+ documentID.getBytes(),
+ standard.getRevision(),
+ standard.getLength()/8 );
+ }
+ else
+ {
+ throw new IOException( "Error: Encyption dictionary is not 'Standard'" +
+ encryptionDictionary.getClass().getName() );
+ }
+ }
+ return retval;*/
+ }
+
+ /**
+ * This will decrypt a document. This method is provided for compatibility reasons only. User should use
+ * the new security layer instead and the openProtection method especially.
+ *
+ * @param password Either the user or owner password.
+ *
+ * @throws CryptographyException If there is an error decrypting the document.
+ * @throws IOException If there is an error getting the stream data.
+ * @throws InvalidPasswordException If the password is not a user or owner password.
+ *
+ */
+ public void decrypt( String password ) throws CryptographyException, IOException, InvalidPasswordException
+ {
+ try
+ {
+ StandardDecryptionMaterial m = new StandardDecryptionMaterial(password);
+ this.openProtection(m);
+ document.dereferenceObjectStreams();
+ }
+ catch(BadSecurityHandlerException e)
+ {
+ throw new CryptographyException(e);
+ }
+ }
+
+ /**
+ * This will tell if the document was decrypted with the master password. This
+ * entry is invalid if the PDF was not decrypted.
+ *
+ * @return true if the pdf was decrypted with the master password.
+ *
+ * @deprecated use <code>getCurrentAccessPermission</code> instead
+ */
+ @Deprecated
+ public boolean wasDecryptedWithOwnerPassword()
+ {
+ return false;
+ }
+
+ /**
+ * This will <b>mark</b> a document to be encrypted. The actual encryption
+ * will occur when the document is saved.
+ * This method is provided for compatibility reasons only. User should use
+ * the new security layer instead and the openProtection method especially.
+ *
+ * @param ownerPassword The owner password to encrypt the document.
+ * @param userPassword The user password to encrypt the document.
+ *
+ * @throws CryptographyException If an error occurs during encryption.
+ * @throws IOException If there is an error accessing the data.
+ *
+ */
+ public void encrypt( String ownerPassword, String userPassword )
+ throws CryptographyException, IOException
+ {
+ try
+ {
+ StandardProtectionPolicy policy =
+ new StandardProtectionPolicy(ownerPassword, userPassword, new AccessPermission());
+ this.protect(policy);
+ }
+ catch(BadSecurityHandlerException e)
+ {
+ throw new CryptographyException(e);
+ }
+ }
+
+
+ /**
+ * The owner password that was passed into the encrypt method. You should
+ * never use this method. This will not longer be valid once encryption
+ * has occured.
+ *
+ * @return The owner password passed to the encrypt method.
+ *
+ * @deprecated Do not rely on this method anymore.
+ */
+ @Deprecated
+ public String getOwnerPasswordForEncryption()
+ {
+ return null;
+ }
+
+ /**
+ * The user password that was passed into the encrypt method. You should
+ * never use this method. This will not longer be valid once encryption
+ * has occured.
+ *
+ * @return The user password passed to the encrypt method.
+ *
+ * @deprecated Do not rely on this method anymore.
+ */
+ @Deprecated
+ public String getUserPasswordForEncryption()
+ {
+ return null;
+ }
+
+ /**
+ * Internal method do determine if the document will be encrypted when it is saved.
+ *
+ * @return True if encrypt has been called and the document
+ * has not been saved yet.
+ *
+ * @deprecated Do not rely on this method anymore. It is the responsibility of
+ * COSWriter to hold this state
+ */
+ @Deprecated
+ public boolean willEncryptWhenSaving()
+ {
+ return false;
+ }
+
+ /**
+ * This shoule only be called by the COSWriter after encryption has completed.
+ *
+ * @deprecated Do not rely on this method anymore. It is the responsability of
+ * COSWriter to hold this state.
+ */
+ @Deprecated
+ public void clearWillEncryptWhenSaving()
+ {
+ //method is deprecated.
+ }
+
+ /**
+ * This will load a document from a url.
+ *
+ * @param url The url to load the PDF from.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load( URL url ) throws IOException
+ {
+ return load( url.openStream() );
+ }
+ /**
+ * This will load a document from a url. Used for skipping corrupt
+ * pdf objects
+ *
+ * @param url The url to load the PDF from.
+ * @param force When true, the parser will skip corrupt pdf objects and
+ * will continue parsing at the next object in the file
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load(URL url, boolean force) throws IOException
+ {
+ return load(url.openStream(), force);
+ }
+
+ /**
+ * This will load a document from a url.
+ *
+ * @param url The url to load the PDF from.
+ * @param scratchFile A location to store temp PDFBox data for this document.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load( URL url, RandomAccess scratchFile ) throws IOException
+ {
+ return load( url.openStream(), scratchFile );
+ }
+
+ /**
+ * This will load a document from a file.
+ *
+ * @param filename The name of the file to load.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load( String filename ) throws IOException
+ {
+ return load( new FileInputStream( filename ) );
+ }
+
+ /**
+ * This will load a document from a file. Allows for skipping corrupt pdf
+ * objects
+ *
+ * @param filename The name of the file to load.
+ * @param force When true, the parser will skip corrupt pdf objects and
+ * will continue parsing at the next object in the file
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load(String filename, boolean force) throws IOException
+ {
+ return load(new FileInputStream( filename ), force);
+ }
+
+ /**
+ * This will load a document from a file.
+ *
+ * @param filename The name of the file to load.
+ * @param scratchFile A location to store temp PDFBox data for this document.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load( String filename, RandomAccess scratchFile ) throws IOException
+ {
+ return load( new FileInputStream( filename ), scratchFile );
+ }
+
+ /**
+ * This will load a document from a file.
+ *
+ * @param file The name of the file to load.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load( File file ) throws IOException
+ {
+ return load( new FileInputStream( file ) );
+ }
+
+ /**
+ * This will load a document from a file.
+ *
+ * @param file The name of the file to load.
+ * @param scratchFile A location to store temp PDFBox data for this document.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load( File file, RandomAccess scratchFile ) throws IOException
+ {
+ return load( new FileInputStream( file ), scratchFile );
+ }
+
+ /**
+ * This will load a document from an input stream.
+ *
+ * @param input The stream that contains the document.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load( InputStream input ) throws IOException
+ {
+ return load( input, null );
+ }
+
+ /**
+ * This will load a document from an input stream.
+ * Allows for skipping corrupt pdf objects
+ *
+ * @param input The stream that contains the document.
+ * @param force When true, the parser will skip corrupt pdf objects and
+ * will continue parsing at the next object in the file
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load(InputStream input, boolean force) throws IOException
+ {
+ return load(input, null, force);
+ }
+
+ /**
+ * This will load a document from an input stream.
+ *
+ * @param input The stream that contains the document.
+ * @param scratchFile A location to store temp PDFBox data for this document.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load( InputStream input, RandomAccess scratchFile ) throws IOException
+ {
+ PDFParser parser = new PDFParser( new BufferedInputStream( input ) , scratchFile );
+ parser.parse();
+ return parser.getPDDocument();
+ }
+
+ /**
+ * This will load a document from an input stream. Allows for skipping corrupt pdf objects
+ *
+ * @param input The stream that contains the document.
+ * @param scratchFile A location to store temp PDFBox data for this document.
+ * @param force When true, the parser will skip corrupt pdf objects and
+ * will continue parsing at the next object in the file
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static PDDocument load(InputStream input, RandomAccess scratchFile, boolean force) throws IOException
+ {
+ PDFParser parser = new PDFParser( new BufferedInputStream( input ), scratchFile, force);
+ parser.parse();
+ return parser.getPDDocument();
+ }
+
+ /**
+ * This will save this document to the filesystem.
+ *
+ * @param fileName The file to save as.
+ *
+ * @throws IOException If there is an error saving the document.
+ * @throws COSVisitorException If an error occurs while generating the data.
+ */
+ public void save( String fileName ) throws IOException, COSVisitorException
+ {
+ save( new FileOutputStream( fileName ) );
+ }
+
+ /**
+ * This will save the document to an output stream.
+ *
+ * @param output The stream to write to.
+ *
+ * @throws IOException If there is an error writing the document.
+ * @throws COSVisitorException If an error occurs while generating the data.
+ */
+ public void save( OutputStream output ) throws IOException, COSVisitorException
+ {
+ //update the count in case any pages have been added behind the scenes.
+ getDocumentCatalog().getPages().updateCount();
+ COSWriter writer = null;
+ try
+ {
+ writer = new COSWriter( output );
+ writer.write( this );
+ writer.close();
+ }
+ finally
+ {
+ if( writer != null )
+ {
+ writer.close();
+ }
+ }
+ }
+
+ public void saveIncremental( String fileName ) throws IOException, COSVisitorException
+ {
+ saveIncremental( new FileInputStream( fileName ) , new FileOutputStream( fileName , true) );
+ }
+
+ public void saveIncremental( FileInputStream input, OutputStream output ) throws IOException, COSVisitorException
+ {
+ //update the count in case any pages have been added behind the scenes.
+ getDocumentCatalog().getPages().updateCount();
+ COSWriter writer = null;
+ try
+ {
+ writer = new COSWriter( output, input );
+ writer.write( this );
+ writer.close();
+ }
+ finally
+ {
+ if( writer != null )
+ {
+ writer.close();
+ }
+ }
+ }
+
+ /**
+ * This will return the total page count of the PDF document. Note: This method
+ * is deprecated in favor of the getNumberOfPages method. The getNumberOfPages is
+ * a required interface method of the Pageable interface. This method will
+ * be removed in a future version of PDFBox!!
+ *
+ * @return The total number of pages in the PDF document.
+ * @deprecated Use the getNumberOfPages method instead!
+ */
+ @Deprecated
+ public int getPageCount()
+ {
+ return getNumberOfPages();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getNumberOfPages()
+ {
+ PDDocumentCatalog cat = getDocumentCatalog();
+ return (int)cat.getPages().getCount();
+ }
+
+ /**
+ * Returns the format of the page at the given index when using a
+ * default printer job returned by {@link PrinterJob#getPrinterJob()}.
+ *
+ * @deprecated Use the {@link PDPageable} adapter class
+ * @param i page index, zero-based
+ * @return page format
+ * @throws IndexOutOfBoundsException if the page index is invalid
+ */
+ @Deprecated
+ public PageFormat getPageFormat(int pageIndex)
+ {
+ try {
+ PrinterJob printerJob = PrinterJob.getPrinterJob();
+ return new PDPageable(this, printerJob).getPageFormat(pageIndex);
+ } catch (PrinterException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Printable getPrintable(int pageIndex)
+ {
+ return (Printable)getDocumentCatalog().getAllPages().get( pageIndex );
+ }
+
+ /**
+ * @see PDDocument#print()
+ *
+ * @param printJob The printer job.
+ *
+ * @throws PrinterException If there is an error while sending the PDF to
+ * the printer, or you do not have permissions to print this document.
+ */
+ public void print(PrinterJob printJob) throws PrinterException
+ {
+ print(printJob, false);
+ }
+
+ /**
+ * This will send the PDF document to a printer. The printing functionality
+ * depends on the org.apache.pdfbox.pdfviewer.PageDrawer functionality. The PageDrawer
+ * is a work in progress and some PDFs will print correctly and some will
+ * not. This is a convenience method to create the java.awt.print.PrinterJob.
+ * The PDDocument implements the java.awt.print.Pageable interface and
+ * PDPage implementes the java.awt.print.Printable interface, so advanced printing
+ * capabilities can be done by using those interfaces instead of this method.
+ *
+ * @throws PrinterException If there is an error while sending the PDF to
+ * the printer, or you do not have permissions to print this document.
+ */
+ public void print() throws PrinterException
+ {
+ print( PrinterJob.getPrinterJob() );
+ }
+
+ /**
+ * This will send the PDF to the default printer without prompting the user
+ * for any printer settings.
+ *
+ * @see PDDocument#print()
+ *
+ * @throws PrinterException If there is an error while printing.
+ */
+ public void silentPrint() throws PrinterException
+ {
+ silentPrint( PrinterJob.getPrinterJob() );
+ }
+
+ /**
+ * This will send the PDF to the default printer without prompting the user
+ * for any printer settings.
+ *
+ * @param printJob A printer job definition.
+ * @see PDDocument#print()
+ *
+ * @throws PrinterException If there is an error while printing.
+ */
+ public void silentPrint( PrinterJob printJob ) throws PrinterException
+ {
+ print(printJob, true);
+ }
+
+ private void print(PrinterJob job, boolean silent) throws PrinterException {
+ if (job == null) {
+ throw new PrinterException("The given printer job is null.");
+ } else {
+ job.setPageable(new PDPageable(this, job));
+ if (silent || job.printDialog()) {
+ job.print();
+ }
+ }
+ }
+
+ /**
+ * This will close the underlying COSDocument object.
+ *
+ * @throws IOException If there is an error releasing resources.
+ */
+ public void close() throws IOException
+ {
+ document.close();
+ }
+
+
+ /**
+ * Protects the document with the protection policy pp. The document content will be really encrypted
+ * when it will be saved. This method only marks the document for encryption.
+ *
+ * @see org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy
+ * @see org.apache.pdfbox.pdmodel.encryption.PublicKeyProtectionPolicy
+ *
+ * @param pp The protection policy.
+ *
+ * @throws BadSecurityHandlerException If there is an error during protection.
+ */
+ public void protect(ProtectionPolicy pp) throws BadSecurityHandlerException
+ {
+ SecurityHandler handler = SecurityHandlersManager.getInstance().getSecurityHandler(pp);
+ securityHandler = handler;
+ }
+
+ /**
+ * Tries to decrypt the document in memory using the provided decryption material.
+ *
+ * @see org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial
+ * @see org.apache.pdfbox.pdmodel.encryption.PublicKeyDecryptionMaterial
+ *
+ * @param pm The decryption material (password or certificate).
+ *
+ * @throws BadSecurityHandlerException If there is an error during decryption.
+ * @throws IOException If there is an error reading cryptographic information.
+ * @throws CryptographyException If there is an error during decryption.
+ */
+ public void openProtection(DecryptionMaterial pm)
+ throws BadSecurityHandlerException, IOException, CryptographyException
+ {
+ PDEncryptionDictionary dict = this.getEncryptionDictionary();
+ if(dict.getFilter() != null)
+ {
+ securityHandler = SecurityHandlersManager.getInstance().getSecurityHandler(dict.getFilter());
+ securityHandler.decryptDocument(this, pm);
+ document.dereferenceObjectStreams();
+ document.setEncryptionDictionary( null );
+ }
+ else
+ {
+ throw new RuntimeException("This document does not need to be decrypted");
+ }
+ }
+
+ /**
+ * Returns the access permissions granted when the document was decrypted.
+ * If the document was not decrypted this method returns the access permission
+ * for a document owner (ie can do everything).
+ * The returned object is in read only mode so that permissions cannot be changed.
+ * Methods providing access to content should rely on this object to verify if the current
+ * user is allowed to proceed.
+ *
+ * @return the access permissions for the current user on the document.
+ */
+
+ public AccessPermission getCurrentAccessPermission()
+ {
+ if(this.securityHandler == null)
+ {
+ return AccessPermission.getOwnerAccessPermission();
+ }
+ return securityHandler.getCurrentAccessPermission();
+ }
+
+ /**
+ * Get the security handler that is used for document encryption.
+ *
+ * @return The handler used to encrypt/decrypt the document.
+ */
+ public SecurityHandler getSecurityHandler()
+ {
+ return securityHandler;
+ }
+
+ public boolean isAllSecurityToBeRemoved() {
+ return allSecurityToBeRemoved;
+ }
+
+ public void setAllSecurityToBeRemoved(boolean allSecurityToBeRemoved) {
+ this.allSecurityToBeRemoved = allSecurityToBeRemoved;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.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;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+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.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;
+
+/**
+ * This class represents the acroform of a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.21 $
+ */
+public class PDDocumentCatalog implements COSObjectable
+{
+ private COSDictionary root;
+ private PDDocument document;
+
+ private PDAcroForm acroForm = null;
+
+ /**
+ * Page mode where neither the outline nor the thumbnails
+ * are displayed.
+ */
+ public static final String PAGE_MODE_USE_NONE = "UseNone";
+ /**
+ * Show bookmarks when pdf is opened.
+ */
+ public static final String PAGE_MODE_USE_OUTLINES = "UseOutlines";
+ /**
+ * Show thumbnails when pdf is opened.
+ */
+ public static final String PAGE_MODE_USE_THUMBS = "UseThumbs";
+ /**
+ * Full screen mode with no menu bar, window controls.
+ */
+ public static final String PAGE_MODE_FULL_SCREEN = "FullScreen";
+ /**
+ * Optional content group panel is visible when opened.
+ */
+ public static final String PAGE_MODE_USE_OPTIONAL_CONTENT = "UseOC";
+ /**
+ * Attachments panel is visible.
+ */
+ public static final String PAGE_MODE_USE_ATTACHMENTS = "UseAttachments";
+
+ /**
+ * Display one page at a time.
+ */
+ public static final String PAGE_LAYOUT_SINGLE_PAGE = "SinglePage";
+ /**
+ * Display the pages in one column.
+ */
+ public static final String PAGE_LAYOUT_ONE_COLUMN = "OneColumn";
+ /**
+ * Display the pages in two columns, with odd numbered pagse on the left.
+ */
+ public static final String PAGE_LAYOUT_TWO_COLUMN_LEFT = "TwoColumnLeft";
+ /**
+ * Display the pages in two columns, with odd numbered pagse on the right.
+ */
+ public static final String PAGE_LAYOUT_TWO_COLUMN_RIGHT ="TwoColumnRight";
+ /**
+ * Display the pages two at a time, with odd-numbered pages on the left.
+ * @since PDF Version 1.5
+ */
+ public static final String PAGE_LAYOUT_TWO_PAGE_LEFT = "TwoPageLeft";
+ /**
+ * Display the pages two at a time, with odd-numbered pages on the right.
+ * @since PDF Version 1.5
+ */
+ public static final String PAGE_LAYOUT_TWO_PAGE_RIGHT = "TwoPageRight";
+
+
+
+ /**
+ * Constructor.
+ *
+ * @param doc The document that this catalog is part of.
+ */
+ public PDDocumentCatalog( PDDocument doc )
+ {
+ document = doc;
+ root = new COSDictionary();
+ root.setItem( COSName.TYPE, COSName.CATALOG );
+ document.getDocument().getTrailer().setItem( COSName.ROOT, root );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param doc The document that this catalog is part of.
+ * @param rootDictionary The root dictionary that this object wraps.
+ */
+ public PDDocumentCatalog( PDDocument doc, COSDictionary rootDictionary )
+ {
+ document = doc;
+ root = rootDictionary;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return root;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return root;
+ }
+
+ /**
+ * This will get the documents acroform. This will return null if
+ * no acroform is part of the document.
+ *
+ * @return The documents acroform.
+ */
+ public PDAcroForm getAcroForm()
+ {
+ if( acroForm == null )
+ {
+ COSDictionary acroFormDic =
+ (COSDictionary)root.getDictionaryObject( COSName.ACRO_FORM );
+ if( acroFormDic != null )
+ {
+ acroForm = new PDAcroForm( document, acroFormDic );
+ }
+ }
+ return acroForm;
+ }
+
+ /**
+ * Set the acro form for this catalog.
+ *
+ * @param acro The new acro form.
+ */
+ public void setAcroForm( PDAcroForm acro )
+ {
+ root.setItem( COSName.ACRO_FORM, acro );
+ }
+
+ /**
+ * This will get the root node for the pages.
+ *
+ * @return The parent page node.
+ */
+ public PDPageNode getPages()
+ {
+ return new PDPageNode( (COSDictionary)root.getDictionaryObject( COSName.PAGES ) );
+ }
+
+ /**
+ * The PDF document contains a hierarchical structure of PDPageNode and PDPages, which
+ * is mostly just a way to store this information. This method will return a flat list
+ * of all PDPage objects in this document.
+ *
+ * @return A list of PDPage objects.
+ */
+ public List getAllPages()
+ {
+ List retval = new ArrayList();
+ PDPageNode rootNode = getPages();
+ //old (slower):
+ //getPageObjects( rootNode, retval );
+ rootNode.getAllKids(retval);
+ return retval;
+ }
+
+ /**
+ * Get the viewer preferences associated with this document or null if they
+ * do not exist.
+ *
+ * @return The document's viewer preferences.
+ */
+ public PDViewerPreferences getViewerPreferences()
+ {
+ PDViewerPreferences retval = null;
+ COSDictionary dict = (COSDictionary)root.getDictionaryObject( COSName.VIEWER_PREFERENCES );
+ if( dict != null )
+ {
+ retval = new PDViewerPreferences( dict );
+ }
+
+ return retval;
+ }
+
+ /**
+ * Set the viewer preferences.
+ *
+ * @param prefs The new viewer preferences.
+ */
+ public void setViewerPreferences( PDViewerPreferences prefs )
+ {
+ root.setItem( COSName.VIEWER_PREFERENCES, prefs );
+ }
+
+ /**
+ * Get the outline associated with this document or null if it
+ * does not exist.
+ *
+ * @return The document's outline.
+ */
+ public PDDocumentOutline getDocumentOutline()
+ {
+ PDDocumentOutline retval = null;
+ COSDictionary dict = (COSDictionary)root.getDictionaryObject( COSName.OUTLINES );
+ if( dict != null )
+ {
+ retval = new PDDocumentOutline( dict );
+ }
+
+ return retval;
+ }
+
+ /**
+ * Set the document outlines.
+ *
+ * @param outlines The new document outlines.
+ */
+ public void setDocumentOutline( PDDocumentOutline outlines )
+ {
+ root.setItem( COSName.OUTLINES, outlines );
+ }
+
+ /**
+ * Get the list threads for this pdf document.
+ *
+ * @return A list of PDThread objects.
+ */
+ public List getThreads()
+ {
+ COSArray array = (COSArray)root.getDictionaryObject( COSName.THREADS );
+ if( array == null )
+ {
+ array = new COSArray();
+ root.setItem( COSName.THREADS, array );
+ }
+ List pdObjects = new ArrayList();
+ for( int i=0; i<array.size(); i++ )
+ {
+ pdObjects.add( new PDThread( (COSDictionary)array.getObject( i ) ) );
+ }
+ return new COSArrayList( pdObjects, array );
+ }
+
+ /**
+ * Set the list of threads for this pdf document.
+ *
+ * @param threads The list of threads, or null to clear it.
+ */
+ public void setThreads( List threads )
+ {
+ root.setItem( COSName.THREADS, COSArrayList.converterToCOSArray( threads ) );
+ }
+
+ /**
+ * Get the metadata that is part of the document catalog. This will
+ * return null if there is no meta data for this object.
+ *
+ * @return The metadata for this object.
+ */
+ public PDMetadata getMetadata()
+ {
+ PDMetadata retval = null;
+ COSStream stream = (COSStream)root.getDictionaryObject( COSName.METADATA );
+ if( stream != null )
+ {
+ retval = new PDMetadata( stream );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the metadata for this object. This can be null.
+ *
+ * @param meta The meta data for this object.
+ */
+ public void setMetadata( PDMetadata meta )
+ {
+ root.setItem( COSName.METADATA, meta );
+ }
+
+ /**
+ * Set the Document Open Action for this object.
+ *
+ * @param action The action you want to perform.
+ */
+ public void setOpenAction( PDDestinationOrAction action )
+ {
+ root.setItem( COSName.OPEN_ACTION, action );
+ }
+
+ /**
+ * Get the Document Open Action for this object.
+ *
+ * @return The action to perform when the document is opened.
+ *
+ * @throws IOException If there is an error creating the destination
+ * or action.
+ */
+ public PDDestinationOrAction getOpenAction() throws IOException
+ {
+ PDDestinationOrAction action = null;
+ COSBase actionObj = root.getDictionaryObject(COSName.OPEN_ACTION);
+
+ if( actionObj == null )
+ {
+ //no op
+ }
+ else if( actionObj instanceof COSDictionary )
+ {
+ action = PDActionFactory.createAction((COSDictionary)actionObj);
+ }
+ else if( actionObj instanceof COSArray )
+ {
+ action = PDDestination.create( actionObj );
+ }
+ else
+ {
+ throw new IOException( "Unknown OpenAction " + actionObj );
+ }
+
+ return action;
+ }
+ /**
+ * @return The Additional Actions for this Document
+ */
+ public PDDocumentCatalogAdditionalActions getActions()
+ {
+ COSDictionary addAct = (COSDictionary) root.getDictionaryObject( COSName.AA );
+ if (addAct == null)
+ {
+ addAct = new COSDictionary();
+ root.setItem(COSName.AA, addAct);
+ }
+ return new PDDocumentCatalogAdditionalActions(addAct);
+ }
+
+ /**
+ * Set the additional actions for the document.
+ *
+ * @param actions The actions that are associated with this document.
+ */
+ public void setActions( PDDocumentCatalogAdditionalActions actions )
+ {
+ root.setItem(COSName.AA, actions );
+ }
+
+ /**
+ * @return The names dictionary for this document or null if none exist.
+ */
+ public PDDocumentNameDictionary getNames()
+ {
+ PDDocumentNameDictionary nameDic = null;
+ COSDictionary names = (COSDictionary) root.getDictionaryObject(COSName.NAMES);
+ if(names != null)
+ {
+ nameDic = new PDDocumentNameDictionary(this,names);
+ }
+ return nameDic;
+ }
+
+ /**
+ * Set the names dictionary for the document.
+ *
+ * @param names The names dictionary that is associated with this document.
+ */
+ public void setNames( PDDocumentNameDictionary names )
+ {
+ root.setItem(COSName.NAMES, names );
+ }
+
+ /**
+ * Get info about doc's usage of tagged features. This will return
+ * null if there is no information.
+ *
+ * @return The new mark info.
+ */
+ public PDMarkInfo getMarkInfo()
+ {
+ PDMarkInfo retval = null;
+ COSDictionary dic = (COSDictionary)root.getDictionaryObject( COSName.MARK_INFO );
+ if( dic != null )
+ {
+ retval = new PDMarkInfo( dic );
+ }
+ return retval;
+ }
+
+ /**
+ * Set information about the doc's usage of tagged features.
+ *
+ * @param markInfo The new MarkInfo data.
+ */
+ public void setMarkInfo( PDMarkInfo markInfo )
+ {
+ root.setItem( COSName.MARK_INFO, markInfo );
+ }
+
+ /**
+ * Set the page display mode, see the PAGE_MODE_XXX constants.
+ * @return A string representing the page mode.
+ */
+ public String getPageMode()
+ {
+ return root.getNameAsString( COSName.PAGE_MODE, PAGE_MODE_USE_NONE );
+ }
+
+ /**
+ * Set the page mode. See the PAGE_MODE_XXX constants for valid values.
+ * @param mode The new page mode.
+ */
+ public void setPageMode( String mode )
+ {
+ root.setName( COSName.PAGE_MODE, mode );
+ }
+
+ /**
+ * Set the page layout, see the PAGE_LAYOUT_XXX constants.
+ * @return A string representing the page layout.
+ */
+ public String getPageLayout()
+ {
+ return root.getNameAsString( COSName.PAGE_LAYOUT, PAGE_LAYOUT_SINGLE_PAGE );
+ }
+
+ /**
+ * Set the page layout. See the PAGE_LAYOUT_XXX constants for valid values.
+ * @param layout The new page layout.
+ */
+ public void setPageLayout( String layout )
+ {
+ root.setName( COSName.PAGE_LAYOUT, layout );
+ }
+
+ /**
+ * Document level information in the URI.
+ * @return Document level URI.
+ */
+ public PDURIDictionary getURI()
+ {
+ PDURIDictionary retval = null;
+ COSDictionary uri = (COSDictionary)root.getDictionaryObject( COSName.URI );
+ if( uri != null )
+ {
+ retval = new PDURIDictionary( uri );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the document level uri.
+ * @param uri The new document level uri.
+ */
+ public void setURI( PDURIDictionary uri )
+ {
+ root.setItem( COSName.URI, uri );
+ }
+
+ /**
+ * Get the document's structure tree root.
+ *
+ * @return The document's structure tree root or null if none exists.
+ */
+ public PDStructureTreeRoot getStructureTreeRoot()
+ {
+ PDStructureTreeRoot treeRoot = null;
+ COSDictionary dic = (COSDictionary)root.getDictionaryObject( COSName.STRUCT_TREE_ROOT );
+ if( dic != null )
+ {
+ treeRoot = new PDStructureTreeRoot( dic );
+ }
+ return treeRoot;
+ }
+
+ /**
+ * Set the document's structure tree root.
+ *
+ * @param treeRoot The new structure tree.
+ */
+ public void setStructureTreeRoot( PDStructureTreeRoot treeRoot )
+ {
+ root.setItem( COSName.STRUCT_TREE_ROOT, treeRoot );
+ }
+
+ /**
+ * The language for the document.
+ *
+ * @return The language for the document.
+ */
+ public String getLanguage()
+ {
+ return root.getString( COSName.LANG );
+ }
+
+ /**
+ * Set the Language for the document.
+ *
+ * @param language The new document language.
+ */
+ public void setLanguage( String language )
+ {
+ root.setString( COSName.LANG, language );
+ }
+
+ /**
+ * 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
+ {
+ PDPageLabels labels = null;
+ COSDictionary dict = (COSDictionary) root.getDictionaryObject(COSName.PAGE_LABELS);
+ if (dict != null)
+ {
+ labels = new PDPageLabels(document, dict);
+ }
+ return labels;
+ }
+
+ /**
+ * Set the page label descriptor for the document.
+ *
+ * @param labels the new page label descriptor to set.
+ */
+ 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+
+import java.util.Calendar;
+import java.util.Set;
+import java.util.TreeSet;
+
+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 is the document metadata. Each getXXX method will return the entry if
+ * it exists or null if it does not exist. If you pass in null for the setXXX
+ * method then it will clear the value.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.12 $
+ */
+public class PDDocumentInformation implements COSObjectable
+{
+ private COSDictionary info;
+
+ /**
+ * Default Constructor.
+ */
+ public PDDocumentInformation()
+ {
+ info = new COSDictionary();
+ }
+
+ /**
+ * Constructor that is used for a preexisting dictionary.
+ *
+ * @param dic The underlying dictionary.
+ */
+ public PDDocumentInformation( COSDictionary dic )
+ {
+ info = dic;
+ }
+
+ /**
+ * This will get the underlying dictionary that this object wraps.
+ *
+ * @return The underlying info dictionary.
+ */
+ public COSDictionary getDictionary()
+ {
+ return info;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return info;
+ }
+
+ /**
+ * This will get the title of the document. This will return null if no title exists.
+ *
+ * @return The title of the document.
+ */
+ public String getTitle()
+ {
+ return info.getString( COSName.TITLE );
+ }
+
+ /**
+ * This will set the title of the document.
+ *
+ * @param title The new title for the document.
+ */
+ public void setTitle( String title )
+ {
+ info.setString( COSName.TITLE, title );
+ }
+
+ /**
+ * This will get the author of the document. This will return null if no author exists.
+ *
+ * @return The author of the document.
+ */
+ public String getAuthor()
+ {
+ return info.getString( COSName.AUTHOR );
+ }
+
+ /**
+ * This will set the author of the document.
+ *
+ * @param author The new author for the document.
+ */
+ public void setAuthor( String author )
+ {
+ info.setString( COSName.AUTHOR, author );
+ }
+
+ /**
+ * This will get the subject of the document. This will return null if no subject exists.
+ *
+ * @return The subject of the document.
+ */
+ public String getSubject()
+ {
+ return info.getString( COSName.SUBJECT );
+ }
+
+ /**
+ * This will set the subject of the document.
+ *
+ * @param subject The new subject for the document.
+ */
+ public void setSubject( String subject )
+ {
+ info.setString( COSName.SUBJECT, subject );
+ }
+
+ /**
+ * This will get the keywords of the document. This will return null if no keywords exists.
+ *
+ * @return The keywords of the document.
+ */
+ public String getKeywords()
+ {
+ return info.getString( COSName.KEYWORDS );
+ }
+
+ /**
+ * This will set the keywords of the document.
+ *
+ * @param keywords The new keywords for the document.
+ */
+ public void setKeywords( String keywords )
+ {
+ info.setString( COSName.KEYWORDS, keywords );
+ }
+
+ /**
+ * This will get the creator of the document. This will return null if no creator exists.
+ *
+ * @return The creator of the document.
+ */
+ public String getCreator()
+ {
+ return info.getString( COSName.CREATOR );
+ }
+
+ /**
+ * This will set the creator of the document.
+ *
+ * @param creator The new creator for the document.
+ */
+ public void setCreator( String creator )
+ {
+ info.setString( COSName.CREATOR, creator );
+ }
+
+ /**
+ * This will get the producer of the document. This will return null if no producer exists.
+ *
+ * @return The producer of the document.
+ */
+ public String getProducer()
+ {
+ return info.getString( COSName.PRODUCER );
+ }
+
+ /**
+ * This will set the producer of the document.
+ *
+ * @param producer The new producer for the document.
+ */
+ public void setProducer( String producer )
+ {
+ info.setString( COSName.PRODUCER, producer );
+ }
+
+ /**
+ * This will get the creation date of the document. This will return null if no creation date exists.
+ *
+ * @return The creation date of the document.
+ *
+ * @throws IOException If there is an error creating the date.
+ */
+ public Calendar getCreationDate() throws IOException
+ {
+ return info.getDate( COSName.CREATION_DATE );
+ }
+
+ /**
+ * This will set the creation date of the document.
+ *
+ * @param date The new creation date for the document.
+ */
+ public void setCreationDate( Calendar date )
+ {
+ info.setDate( COSName.CREATION_DATE, date );
+ }
+
+ /**
+ * This will get the modification date of the document. This will return null if no modification date exists.
+ *
+ * @return The modification date of the document.
+ *
+ * @throws IOException If there is an error creating the date.
+ */
+ public Calendar getModificationDate() throws IOException
+ {
+ return info.getDate( COSName.MOD_DATE );
+ }
+
+ /**
+ * This will set the modification date of the document.
+ *
+ * @param date The new modification date for the document.
+ */
+ public void setModificationDate( Calendar date )
+ {
+ info.setDate( COSName.MOD_DATE, date );
+ }
+
+ /**
+ * This will get the trapped value for the document.
+ * This will return null if one is not found.
+ *
+ * @return The trapped value for the document.
+ */
+ public String getTrapped()
+ {
+ return info.getNameAsString( COSName.TRAPPED );
+ }
+
+ /**
+ * This will get the keys of all metadata information fields for the document.
+ *
+ * @return all metadata key strings.
+ * @since Apache PDFBox 1.3.0
+ */
+ public Set<String> getMetadataKeys()
+ {
+ Set<String> keys = new TreeSet<String>();
+ for (COSName key : info.keySet()) {
+ keys.add(key.getName());
+ }
+ return keys;
+ }
+
+ /**
+ * This will get the value of a custom metadata information field for the document.
+ * This will return null if one is not found.
+ *
+ * @param fieldName Name of custom metadata field from pdf document.
+ *
+ * @return String Value of metadata field
+ *
+ * @author Gerardo Ortiz
+ */
+ public String getCustomMetadataValue(String fieldName)
+ {
+ return info.getString( fieldName );
+ }
+
+ /**
+ * Set the custom metadata value.
+ *
+ * @param fieldName The name of the custom metadata field.
+ * @param fieldValue The value to the custom metadata field.
+ */
+ public void setCustomMetadataValue( String fieldName, String fieldValue )
+ {
+ info.setString( fieldName, fieldValue );
+ }
+
+ /**
+ * This will set the trapped of the document. This will be
+ * 'True', 'False', or 'Unknown'.
+ *
+ * @param value The new trapped value for the document.
+ */
+ public void setTrapped( String value )
+ {
+ if( value != null &&
+ !value.equals( "True" ) &&
+ !value.equals( "False" ) &&
+ !value.equals( "Unknown" ) )
+ {
+ throw new RuntimeException( "Valid values for trapped are " +
+ "'True', 'False', or 'Unknown'" );
+ }
+
+ info.setName( COSName.TRAPPED, value );
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+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 holds all of the name trees that are available at the document level.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDDocumentNameDictionary implements COSObjectable
+{
+ private COSDictionary nameDictionary;
+ private PDDocumentCatalog catalog;
+
+ /**
+ * Constructor.
+ *
+ * @param cat The document catalog that this dictionary is part of.
+ */
+ public PDDocumentNameDictionary( PDDocumentCatalog cat )
+ {
+ nameDictionary = new COSDictionary();
+ catalog = cat;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param cat The document that this dictionary is part of.
+ * @param names The names dictionary.
+ */
+ public PDDocumentNameDictionary( PDDocumentCatalog cat, COSDictionary names )
+ {
+ catalog = cat;
+ nameDictionary = names;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return nameDictionary;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos dictionary for this object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return nameDictionary;
+ }
+
+ /**
+ * Get the destination named tree node. The value in this name tree will be PDDestination
+ * objects.
+ *
+ * @return The destination name tree node.
+ */
+ public PDDestinationNameTreeNode getDests()
+ {
+ PDDestinationNameTreeNode dests = null;
+
+ COSDictionary dic = (COSDictionary)nameDictionary.getDictionaryObject( COSName.DESTS );
+
+ //The document catalog also contains the Dests entry sometimes
+ //so check there as well.
+ if( dic == null )
+ {
+ dic = (COSDictionary)catalog.getCOSDictionary().getDictionaryObject( COSName.DESTS );
+ }
+
+ if( dic != null )
+ {
+ dests = new PDDestinationNameTreeNode( dic );
+ }
+
+
+ return dests;
+ }
+
+ /**
+ * Set the named destinations that are associated with this document.
+ *
+ * @param dests The destination names.
+ */
+ public void setDests( PDDestinationNameTreeNode dests )
+ {
+ nameDictionary.setItem( COSName.DESTS, dests );
+ //The dests can either be in the document catalog or in the
+ //names dictionary, PDFBox will just maintain the one in the
+ //names dictionary for now unless there is a reason to do
+ //something else.
+ //clear the potentially out of date Dests reference.
+ catalog.getCOSDictionary().setItem( COSName.DESTS, (COSObjectable)null);
+ }
+
+ /**
+ * Get the embedded files named tree node. The value in this name tree will be PDComplexFileSpecification
+ * objects.
+ *
+ * @return The embedded files name tree node.
+ */
+ public PDEmbeddedFilesNameTreeNode getEmbeddedFiles()
+ {
+ PDEmbeddedFilesNameTreeNode retval = null;
+
+ COSDictionary dic = (COSDictionary)nameDictionary.getDictionaryObject( COSName.EMBEDDED_FILES );
+
+ if( dic != null )
+ {
+ retval = new PDEmbeddedFilesNameTreeNode( dic );
+ }
+
+ return retval;
+ }
+
+ /**
+ * Set the named embedded files that are associated with this document.
+ *
+ * @param ef The new embedded files
+ */
+ public void setEmbeddedFiles( PDEmbeddedFilesNameTreeNode ef )
+ {
+ nameDictionary.setItem( COSName.EMBEDDED_FILES, ef );
+ }
+
+ /**
+ * Get the document level javascript entries. The value in this name tree will be PDTextStream.
+ *
+ * @return The document level named javascript.
+ */
+ public PDJavascriptNameTreeNode getJavaScript()
+ {
+ PDJavascriptNameTreeNode retval = null;
+
+ COSDictionary dic = (COSDictionary)nameDictionary.getDictionaryObject( COSName.JAVA_SCRIPT );
+
+ if( dic != null )
+ {
+ retval = new PDJavascriptNameTreeNode( dic );
+ }
+
+ return retval;
+ }
+
+ /**
+ * Set the named javascript entries that are associated with this document.
+ *
+ * @param js The new Javascript entries.
+ */
+ public void setJavascript( PDJavascriptNameTreeNode js )
+ {
+ nameDictionary.setItem( COSName.JAVA_SCRIPT, js );
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
+
+/**
+ * This class holds all of the name trees that are available at the document level.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDEmbeddedFilesNameTreeNode extends PDNameTreeNode
+{
+ /**
+ * Constructor.
+ */
+ public PDEmbeddedFilesNameTreeNode()
+ {
+ super( PDComplexFileSpecification.class );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dic The COS dictionary.
+ */
+ public PDEmbeddedFilesNameTreeNode( COSDictionary dic )
+ {
+ super( dic, PDComplexFileSpecification.class );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected Object convertCOSToPD( COSBase base ) throws IOException
+ {
+ COSBase destination = base;
+ return new PDComplexFileSpecification( (COSDictionary)destination );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected PDNameTreeNode createChildNode( COSDictionary dic )
+ {
+ return new PDEmbeddedFilesNameTreeNode(dic);
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+
+/**
+ * This class holds all of the name trees that are available at the document level.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class PDJavascriptNameTreeNode extends PDNameTreeNode
+{
+ /**
+ * Constructor.
+ */
+ public PDJavascriptNameTreeNode()
+ {
+ super( PDTextStream.class );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dic The COS dictionary.
+ */
+ public PDJavascriptNameTreeNode( COSDictionary dic )
+ {
+ super( dic, PDTextStream.class );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected Object convertCOSToPD( COSBase base ) throws IOException
+ {
+ PDTextStream stream = null;
+ if( base instanceof COSString )
+ {
+ stream = new PDTextStream((COSString)base);
+ }
+ else if( base instanceof COSStream )
+ {
+ stream = new PDTextStream((COSStream)base);
+ }
+ else
+ {
+ throw new IOException( "Error creating Javascript object, expected either COSString or COSStream and not "
+ + base );
+ }
+ return stream;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected PDNameTreeNode createChildNode( COSDictionary dic )
+ {
+ return new PDJavascriptNameTreeNode(dic);
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.interactive.action.PDPageAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.pagenavigation.PDThreadBead;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImagingOpException;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterIOException;
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+/**
+ * This represents a single page in a PDF document.
+ * <p>
+ * This class implements the {@link Printable} interface, but since PDFBox
+ * version 1.3.0 you should be using the {@link PDPageable} adapter instead
+ * (see <a href="https://issues.apache.org/jira/browse/PDFBOX-788">PDFBOX-788</a>).
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.29 $
+ */
+public class PDPage implements COSObjectable, Printable
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDPage.class);
+
+ private static final int DEFAULT_USER_SPACE_UNIT_DPI = 72;
+
+ private static final float MM_TO_UNITS = 1/(10*2.54f)*DEFAULT_USER_SPACE_UNIT_DPI;
+
+ /**
+ * Fully transparent that can fall back to white when image type has no alpha.
+ */
+ private static final Color TRANSPARENT_WHITE = new Color( 255, 255, 255, 0 );
+
+ private COSDictionary page;
+
+ /**
+ * A page size of LETTER or 8.5x11.
+ */
+ public static final PDRectangle PAGE_SIZE_LETTER = new PDRectangle( 8.5f*DEFAULT_USER_SPACE_UNIT_DPI, 11f*DEFAULT_USER_SPACE_UNIT_DPI );
+ /**
+ * A page size of A0 Paper.
+ */
+ public static final PDRectangle PAGE_SIZE_A0 = new PDRectangle( 841*MM_TO_UNITS, 1189*MM_TO_UNITS );
+ /**
+ * A page size of A1 Paper.
+ */
+ public static final PDRectangle PAGE_SIZE_A1 = new PDRectangle( 594*MM_TO_UNITS, 841*MM_TO_UNITS );
+ /**
+ * A page size of A2 Paper.
+ */
+ public static final PDRectangle PAGE_SIZE_A2 = new PDRectangle( 420*MM_TO_UNITS, 594*MM_TO_UNITS );
+ /**
+ * A page size of A3 Paper.
+ */
+ public static final PDRectangle PAGE_SIZE_A3 = new PDRectangle( 297*MM_TO_UNITS, 420*MM_TO_UNITS );
+ /**
+ * A page size of A4 Paper.
+ */
+ public static final PDRectangle PAGE_SIZE_A4 = new PDRectangle( 210*MM_TO_UNITS, 297*MM_TO_UNITS );
+ /**
+ * A page size of A5 Paper.
+ */
+ public static final PDRectangle PAGE_SIZE_A5 = new PDRectangle( 148*MM_TO_UNITS, 210*MM_TO_UNITS );
+ /**
+ * A page size of A6 Paper.
+ */
+ public static final PDRectangle PAGE_SIZE_A6 = new PDRectangle( 105*MM_TO_UNITS, 148*MM_TO_UNITS );
+
+ /**
+ * Creates a new instance of PDPage with a size of 8.5x11.
+ */
+ public PDPage()
+ {
+ page = new COSDictionary();
+ page.setItem( COSName.TYPE, COSName.PAGE );
+ setMediaBox( PAGE_SIZE_LETTER );
+ }
+
+ /**
+ * Creates a new instance of PDPage.
+ *
+ * @param size The MediaBox or the page.
+ */
+ public PDPage(PDRectangle size)
+ {
+ page = new COSDictionary();
+ page.setItem( COSName.TYPE, COSName.PAGE );
+ setMediaBox( size );
+ }
+
+
+
+ /**
+ * Creates a new instance of PDPage.
+ *
+ * @param pageDic The existing page dictionary.
+ */
+ public PDPage( COSDictionary pageDic )
+ {
+ page = pageDic;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return page;
+ }
+
+ /**
+ * This will get the underlying dictionary that this class acts on.
+ *
+ * @return The underlying dictionary for this class.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return page;
+ }
+
+
+ /**
+ * This is the parent page node. The parent is a required element of the
+ * page. This will be null until this page is added to the document.
+ *
+ * @return The parent to this page.
+ */
+ public PDPageNode getParent()
+ {
+ if( parent == null){
+ COSDictionary parentDic = (COSDictionary)page.getDictionaryObject( "Parent", "P" );
+ if( parentDic != null )
+ {
+ parent = new PDPageNode( parentDic );
+ }
+ }
+ return parent;
+ }
+
+ private PDPageNode parent = null;
+
+ /**
+ * This will set the parent of this page.
+ *
+ * @param parent The parent to this page node.
+ */
+ public void setParent( PDPageNode parent )
+ {
+ this.parent = parent;
+ page.setItem( COSName.PARENT, parent.getDictionary() );
+ }
+
+ /**
+ * This will update the last modified time for the page object.
+ */
+ public void updateLastModified()
+ {
+ page.setDate( "LastModified", new GregorianCalendar() );
+ }
+
+ /**
+ * This will get the date that the content stream was last modified. This
+ * may return null.
+ *
+ * @return The date the content stream was last modified.
+ *
+ * @throws IOException If there is an error accessing the date information.
+ */
+ public Calendar getLastModified() throws IOException
+ {
+ return page.getDate( "LastModified" );
+ }
+
+ /**
+ * This will get the resources at this page and not look up the hierarchy.
+ * This attribute is inheritable, and findResources() should probably used.
+ * This will return null if no resources are available at this level.
+ *
+ * @return The resources at this level in the hierarchy.
+ */
+ public PDResources getResources()
+ {
+ PDResources retval = null;
+ COSDictionary resources = (COSDictionary)page.getDictionaryObject( COSName.RESOURCES );
+ if( resources != null )
+ {
+ retval = new PDResources( resources );
+ }
+ return retval;
+ }
+
+ /**
+ * This will find the resources for this page by looking up the hierarchy until
+ * it finds them.
+ *
+ * @return The resources at this level in the hierarchy.
+ */
+ public PDResources findResources()
+ {
+ PDResources retval = getResources();
+ PDPageNode parent = getParent();
+ if( retval == null && parent != null )
+ {
+ retval = parent.findResources();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the resources for this page.
+ *
+ * @param resources The new resources for this page.
+ */
+ public void setResources( PDResources resources )
+ {
+ page.setItem( COSName.RESOURCES, resources );
+ }
+
+ /**
+ * A rectangle, expressed
+ * in default user space units, defining the boundaries of the physical
+ * medium on which the page is intended to be displayed or printed
+ *
+ * This will get the MediaBox at this page and not look up the hierarchy.
+ * This attribute is inheritable, and findMediaBox() should probably used.
+ * This will return null if no MediaBox are available at this level.
+ *
+ * @return The MediaBox at this level in the hierarchy.
+ */
+ public PDRectangle getMediaBox()
+ {
+ if( mediaBox == null){
+ COSArray array = (COSArray)page.getDictionaryObject( COSName.MEDIA_BOX );
+ if( array != null )
+ {
+ mediaBox = new PDRectangle( array );
+ }
+ }
+ return mediaBox;
+ }
+
+ private PDRectangle mediaBox = null;
+
+ /**
+ * This will find the MediaBox for this page by looking up the hierarchy until
+ * it finds them.
+ *
+ * @return The MediaBox at this level in the hierarchy.
+ */
+ public PDRectangle findMediaBox()
+ {
+ PDRectangle retval = getMediaBox();
+ if( retval == null && getParent() != null )
+ {
+ retval = getParent().findMediaBox();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the mediaBox for this page.
+ *
+ * @param mediaBox The new mediaBox for this page.
+ */
+ public void setMediaBox( PDRectangle mediaBox )
+ {
+ this.mediaBox = mediaBox;
+ if( mediaBox == null )
+ {
+ page.removeItem( COSName.MEDIA_BOX );
+ }
+ else
+ {
+ page.setItem( COSName.MEDIA_BOX, mediaBox.getCOSArray() );
+ }
+ }
+
+ /**
+ * A rectangle, expressed in default user space units,
+ * defining the visible region of default user space. When the page is displayed
+ * or printed, its contents are to be clipped (cropped) to this rectangle
+ * and then imposed on the output medium in some implementationdefined
+ * manner
+ *
+ * This will get the CropBox at this page and not look up the hierarchy.
+ * This attribute is inheritable, and findCropBox() should probably used.
+ * This will return null if no CropBox is available at this level.
+ *
+ * @return The CropBox at this level in the hierarchy.
+ */
+ public PDRectangle getCropBox()
+ {
+ PDRectangle retval = null;
+ COSArray array = (COSArray)page.getDictionaryObject( COSName.CROP_BOX);
+ if( array != null )
+ {
+ retval = new PDRectangle( array );
+ }
+ return retval;
+ }
+
+ /**
+ * This will find the CropBox for this page by looking up the hierarchy until
+ * it finds them.
+ *
+ * @return The CropBox at this level in the hierarchy.
+ */
+ public PDRectangle findCropBox()
+ {
+ PDRectangle retval = getCropBox();
+ PDPageNode parent = getParent();
+ if( retval == null && parent != null )
+ {
+ retval = findParentCropBox( parent );
+ }
+
+ //default value for cropbox is the media box
+ if( retval == null )
+ {
+ retval = findMediaBox();
+ }
+ return retval;
+ }
+
+ /**
+ * This will search for a crop box in the parent and return null if it is not
+ * found. It will NOT default to the media box if it cannot be found.
+ *
+ * @param node The node
+ */
+ private PDRectangle findParentCropBox( PDPageNode node )
+ {
+ PDRectangle rect = node.getCropBox();
+ PDPageNode parent = node.getParent();
+ if( rect == null && parent != null )
+ {
+ rect = findParentCropBox( parent );
+ }
+ return rect;
+ }
+
+ /**
+ * This will set the CropBox for this page.
+ *
+ * @param cropBox The new CropBox for this page.
+ */
+ public void setCropBox( PDRectangle cropBox )
+ {
+ if( cropBox == null )
+ {
+ page.removeItem( COSName.CROP_BOX );
+ }
+ else
+ {
+ page.setItem( COSName.CROP_BOX, cropBox.getCOSArray() );
+ }
+ }
+
+ /**
+ * A rectangle, expressed in default user space units, defining
+ * the region to which the contents of the page should be clipped
+ * when output in a production environment. The default is the CropBox.
+ *
+ * @return The BleedBox attribute.
+ */
+ public PDRectangle getBleedBox()
+ {
+ PDRectangle retval = null;
+ COSArray array = (COSArray)page.getDictionaryObject( COSName.BLEED_BOX );
+ if( array != null )
+ {
+ retval = new PDRectangle( array );
+ }
+ else
+ {
+ retval = findCropBox();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the BleedBox for this page.
+ *
+ * @param bleedBox The new BleedBox for this page.
+ */
+ public void setBleedBox( PDRectangle bleedBox )
+ {
+ if( bleedBox == null )
+ {
+ page.removeItem( COSName.BLEED_BOX );
+ }
+ else
+ {
+ page.setItem( COSName.BLEED_BOX, bleedBox.getCOSArray() );
+ }
+ }
+
+ /**
+ * A rectangle, expressed in default user space units, defining
+ * the intended dimensions of the finished page after trimming.
+ * The default is the CropBox.
+ *
+ * @return The TrimBox attribute.
+ */
+ public PDRectangle getTrimBox()
+ {
+ PDRectangle retval = null;
+ COSArray array = (COSArray)page.getDictionaryObject( COSName.TRIM_BOX );
+ if( array != null )
+ {
+ retval = new PDRectangle( array );
+ }
+ else
+ {
+ retval = findCropBox();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the TrimBox for this page.
+ *
+ * @param trimBox The new TrimBox for this page.
+ */
+ public void setTrimBox( PDRectangle trimBox )
+ {
+ if( trimBox == null )
+ {
+ page.removeItem( COSName.TRIM_BOX );
+ }
+ else
+ {
+ page.setItem( COSName.TRIM_BOX, trimBox.getCOSArray() );
+ }
+ }
+
+ /**
+ * A rectangle, expressed in default user space units, defining
+ * the extent of the page's meaningful content (including potential
+ * white space) as intended by the page's creator The default isthe CropBox.
+ *
+ * @return The ArtBox attribute.
+ */
+ public PDRectangle getArtBox()
+ {
+ PDRectangle retval = null;
+ COSArray array = (COSArray)page.getDictionaryObject( COSName.ART_BOX );
+ if( array != null )
+ {
+ retval = new PDRectangle( array );
+ }
+ else
+ {
+ retval = findCropBox();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the ArtBox for this page.
+ *
+ * @param artBox The new ArtBox for this page.
+ */
+ public void setArtBox( PDRectangle artBox )
+ {
+ if( artBox == null )
+ {
+ page.removeItem( COSName.ART_BOX );
+ }
+ else
+ {
+ page.setItem( COSName.ART_BOX, artBox.getCOSArray() );
+ }
+ }
+
+
+ //todo BoxColorInfo
+ //todo Contents
+
+ /**
+ * A value representing the rotation. This will be null if not set at this level
+ * The number of degrees by which the page should
+ * be rotated clockwise when displayed or printed. The value must be a multiple
+ * of 90.
+ *
+ * This will get the rotation at this page and not look up the hierarchy.
+ * This attribute is inheritable, and findRotation() should probably used.
+ * This will return null if no rotation is available at this level.
+ *
+ * @return The rotation at this level in the hierarchy.
+ */
+ public Integer getRotation()
+ {
+ Integer retval = null;
+ COSNumber value = (COSNumber)page.getDictionaryObject( COSName.ROTATE );
+ if( value != null )
+ {
+ retval = new Integer( value.intValue() );
+ }
+ return retval;
+ }
+
+ /**
+ * This will find the rotation for this page by looking up the hierarchy until
+ * it finds them.
+ *
+ * @return The rotation at this level in the hierarchy.
+ */
+ public int findRotation()
+ {
+ int retval = 0;
+ Integer rotation = getRotation();
+ if( rotation != null )
+ {
+ retval = rotation.intValue();
+ }
+ else
+ {
+ PDPageNode parent = getParent();
+ if( parent != null )
+ {
+ retval = parent.findRotation();
+ }
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set the rotation for this page.
+ *
+ * @param rotation The new rotation for this page.
+ */
+ public void setRotation( int rotation )
+ {
+ page.setInt( COSName.ROTATE, rotation );
+ }
+
+ /**
+ * This will get the contents of the PDF Page, in the case that the contents
+ * of the page is an array then then the entire array of streams will be
+ * be wrapped and appear as a single stream.
+ *
+ * @return The page content stream.
+ *
+ * @throws IOException If there is an error obtaining the stream.
+ */
+ public PDStream getContents() throws IOException
+ {
+ return PDStream.createFromCOS( page.getDictionaryObject( COSName.CONTENTS ) );
+ }
+
+ /**
+ * This will set the contents of this page.
+ *
+ * @param contents The new contents of the page.
+ */
+ public void setContents( PDStream contents )
+ {
+ page.setItem( COSName.CONTENTS, contents );
+ }
+
+ /**
+ * This will get a list of PDThreadBead objects, which are article threads in the
+ * document. This will return an empty list of there are no thread beads.
+ *
+ * @return A list of article threads on this page.
+ */
+ public List getThreadBeads()
+ {
+ COSArray beads = (COSArray)page.getDictionaryObject( COSName.B );
+ if( beads == null )
+ {
+ beads = new COSArray();
+ }
+ List pdObjects = new ArrayList();
+ for( int i=0; i<beads.size(); i++)
+ {
+ COSDictionary beadDic = (COSDictionary)beads.getObject( i );
+ PDThreadBead bead = null;
+ //in some cases the bead is null
+ if( beadDic != null )
+ {
+ bead = new PDThreadBead( beadDic );
+ }
+ pdObjects.add( bead );
+ }
+ return new COSArrayList(pdObjects, beads);
+
+ }
+
+ /**
+ * This will set the list of thread beads.
+ *
+ * @param beads A list of PDThreadBead objects or null.
+ */
+ public void setThreadBeads( List beads )
+ {
+ page.setItem( COSName.B, COSArrayList.converterToCOSArray( beads ) );
+ }
+
+ /**
+ * Get the metadata that is part of the document catalog. This will
+ * return null if there is no meta data for this object.
+ *
+ * @return The metadata for this object.
+ */
+ public PDMetadata getMetadata()
+ {
+ PDMetadata retval = null;
+ COSStream stream = (COSStream)page.getDictionaryObject( COSName.METADATA );
+ if( stream != null )
+ {
+ retval = new PDMetadata( stream );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the metadata for this object. This can be null.
+ *
+ * @param meta The meta data for this object.
+ */
+ public void setMetadata( PDMetadata meta )
+ {
+ page.setItem( COSName.METADATA, meta );
+ }
+
+ /**
+ * Convert this page to an output image with 8 bits per pixel and the double
+ * default screen resolution.
+ *
+ * @return A graphical representation of this page.
+ *
+ * @throws IOException If there is an error drawing to the image.
+ */
+ public BufferedImage convertToImage() throws IOException
+ {
+ //note we are doing twice as many pixels because
+ //the default size is not really good resolution,
+ //so create an image that is twice the size
+ //and let the client scale it down.
+ return convertToImage(8, 2 * DEFAULT_USER_SPACE_UNIT_DPI);
+ }
+
+ /**
+ * Convert this page to an output image.
+ *
+ * @param imageType the image type (see {@link BufferedImage}.TYPE_*)
+ * @param resolution the resolution in dpi (dots per inch)
+ * @return A graphical representation of this page.
+ *
+ * @throws IOException If there is an error drawing to the image.
+ */
+ public BufferedImage convertToImage(int imageType, int resolution) throws IOException
+ {
+ PDRectangle mBox = findMediaBox();
+ float widthPt = mBox.getWidth();
+ float heightPt = mBox.getHeight();
+ float scaling = resolution / (float)DEFAULT_USER_SPACE_UNIT_DPI;
+ int widthPx = Math.round(widthPt * scaling);
+ int heightPx = Math.round(heightPt * scaling);
+ //TODO The following reduces accuracy. It should really be a Dimension2D.Float.
+ Dimension pageDimension = new Dimension( (int)widthPt, (int)heightPt );
+
+ BufferedImage retval = new BufferedImage( widthPx, heightPx, imageType );
+ Graphics2D graphics = (Graphics2D)retval.getGraphics();
+ graphics.setBackground( TRANSPARENT_WHITE );
+ graphics.clearRect( 0, 0, retval.getWidth(), retval.getHeight() );
+ graphics.scale( scaling, scaling );
+ PageDrawer drawer = new PageDrawer();
+ drawer.drawPage( graphics, this, pageDimension );
+
+ //TODO This could be done directly by manipulating the transformation matrix before painting.
+ //That could result in a better image quality.
+ try
+ {
+ int rotation = findRotation();
+ if (rotation == 90 || rotation == 270)
+ {
+ int w = retval.getWidth();
+ int h = retval.getHeight();
+ BufferedImage rotatedImg = new BufferedImage(w, h, retval.getType());
+ Graphics2D g = rotatedImg.createGraphics();
+ g.rotate(Math.toRadians(rotation), w/2, h/2);
+ g.drawImage(retval, null, 0, 0);
+ }
+ }
+ catch (ImagingOpException e)
+ {
+ log.warn("Unable to rotate page image", e);
+ }
+
+ return retval;
+ }
+
+ /**
+ * Get the page actions.
+ *
+ * @return The Actions for this Page
+ */
+ public PDPageAdditionalActions getActions()
+ {
+ COSDictionary addAct = (COSDictionary) page.getDictionaryObject(COSName.AA);
+ if (addAct == null)
+ {
+ addAct = new COSDictionary();
+ page.setItem(COSName.AA, addAct);
+ }
+ return new PDPageAdditionalActions(addAct);
+ }
+
+ /**
+ * Set the page actions.
+ *
+ * @param actions The actions for the page.
+ */
+ public void setActions( PDPageAdditionalActions actions )
+ {
+ page.setItem( COSName.AA, actions );
+ }
+
+ /**
+ * This will return a list of the Annotations for this page.
+ *
+ * @return List of the PDAnnotation objects.
+ *
+ * @throws IOException If there is an error while creating the annotations.
+ */
+ public List getAnnotations() throws IOException
+ {
+ COSArrayList retval = null;
+ COSArray annots = (COSArray)page.getDictionaryObject(COSName.ANNOTS);
+ if (annots == null)
+ {
+ annots = new COSArray();
+ page.setItem(COSName.ANNOTS, annots);
+ retval = new COSArrayList(new ArrayList(), annots);
+ }
+ else
+ {
+ List actuals = new ArrayList();
+
+ for (int i=0; i < annots.size(); i++)
+ {
+ COSBase item = annots.getObject(i);
+ actuals.add( PDAnnotation.createAnnotation( item ) );
+ }
+ retval = new COSArrayList(actuals, annots);
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the list of annotations.
+ *
+ * @param annots The new list of annotations.
+ */
+ public void setAnnotations( List annots )
+ {
+ page.setItem( COSName.ANNOTS, COSArrayList.converterToCOSArray( annots ) );
+ }
+
+ /**
+ * @deprecated Use the {@link PDPageable} adapter class
+ */
+ public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
+ throws PrinterException
+ {
+ try
+ {
+ PageDrawer drawer = new PageDrawer();
+ PDRectangle cropBox = findCropBox();
+ drawer.drawPage( graphics, this, cropBox.createDimension() );
+ return PAGE_EXISTS;
+ }
+ catch( IOException io )
+ {
+ throw new PrinterIOException( io );
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals( Object other )
+ {
+ return other instanceof PDPage && ((PDPage)other).getCOSObject() == this.getCOSObject();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return this.getCOSDictionary().hashCode();
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This represents a page node in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class PDPageNode implements COSObjectable
+{
+ private COSDictionary page;
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDPageNode.class);
+
+ /**
+ * Creates a new instance of PDPage.
+ */
+ public PDPageNode()
+ {
+ page = new COSDictionary();
+ page.setItem( COSName.TYPE, COSName.PAGES );
+ page.setItem( COSName.KIDS, new COSArray() );
+ page.setItem( COSName.COUNT, COSInteger.ZERO );
+ }
+
+ /**
+ * Creates a new instance of PDPage.
+ *
+ * @param pages The dictionary pages.
+ */
+ public PDPageNode( COSDictionary pages )
+ {
+ page = pages;
+ }
+
+ /**
+ * This will update the count attribute of the page node. This only needs to
+ * be called if you add or remove pages. The PDDocument will call this for you
+ * when you use the PDDocumnet persistence methods. So, basically most clients
+ * will never need to call this.
+ *
+ * @return The update count for this node.
+ */
+ public long updateCount()
+ {
+ long totalCount = 0;
+ List kids = getKids();
+ Iterator kidIter = kids.iterator();
+ while( kidIter.hasNext() )
+ {
+ Object next = kidIter.next();
+ if( next instanceof PDPage )
+ {
+ totalCount++;
+ }
+ else
+ {
+ PDPageNode node = (PDPageNode)next;
+ totalCount += node.updateCount();
+ }
+ }
+ page.setLong( COSName.COUNT, totalCount );
+ return totalCount;
+ }
+
+ /**
+ * This will get the count of descendent page objects.
+ *
+ * @return The total number of descendent page objects.
+ */
+ public long getCount()
+ {
+ if(page == null)
+ {
+ return 0L;
+ }
+ COSBase num = page.getDictionaryObject(COSName.COUNT);
+ if(num == null)
+ {
+ return 0L;
+ }
+ return ((COSNumber) num).intValue();
+ }
+
+ /**
+ * This will get the underlying dictionary that this class acts on.
+ *
+ * @return The underlying dictionary for this class.
+ */
+ public COSDictionary getDictionary()
+ {
+ return page;
+ }
+
+ /**
+ * This is the parent page node.
+ *
+ * @return The parent to this page.
+ */
+ public PDPageNode getParent()
+ {
+ PDPageNode parent = null;
+ COSDictionary parentDic = (COSDictionary)page.getDictionaryObject(COSName.PARENT, COSName.P);
+ if( parentDic != null )
+ {
+ parent = new PDPageNode( parentDic );
+ }
+ return parent;
+ }
+
+ /**
+ * This will set the parent of this page.
+ *
+ * @param parent The parent to this page node.
+ */
+ public void setParent( PDPageNode parent )
+ {
+ page.setItem( COSName.PARENT, parent.getDictionary() );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return page;
+ }
+
+ /**
+ * This will return all kids of this node, either PDPageNode or PDPage.
+ *
+ * @return All direct descendents of this node.
+ */
+ public List getKids()
+ {
+ List actuals = new ArrayList();
+ COSArray kids = getAllKids(actuals, page, false);
+ return new COSArrayList( actuals, kids );
+ }
+
+ /**
+ * This will return all kids of this node as PDPage.
+ *
+ * @param result All direct and indirect descendents of this node are added to this list.
+ */
+ public void getAllKids(List result)
+ {
+ getAllKids(result, page, true);
+ }
+
+ /**
+ * This will return all kids of the given page node as PDPage.
+ *
+ * @param result All direct and optionally indirect descendents of this node are added to this list.
+ * @param page Page dictionary of a page node.
+ * @param recurse if true indirect descendents are processed recursively
+ */
+ private static COSArray getAllKids(List result, COSDictionary page, boolean recurse)
+ {
+ if(page == null)
+ return null;
+ COSArray kids = (COSArray)page.getDictionaryObject( COSName.KIDS );
+ if ( kids == null)
+ {
+ log.error("No Kids found in getAllKids(). Probably a malformed pdf.");
+ return null;
+ }
+ for( int i=0; i<kids.size(); i++ )
+ {
+ COSBase obj = kids.getObject( i );
+ if (obj instanceof COSDictionary)
+ {
+ COSDictionary kid = (COSDictionary)obj;
+ if( COSName.PAGE.equals( kid.getDictionaryObject( COSName.TYPE ) ) )
+ {
+ result.add( new PDPage( kid ) );
+ }
+ else
+ {
+ if (recurse)
+ {
+ getAllKids(result, kid, recurse);
+ }
+ else
+ {
+ result.add( new PDPageNode( kid ) );
+ }
+ }
+ }
+ }
+ return kids;
+ }
+
+ /**
+ * This will get the resources at this page node and not look up the hierarchy.
+ * This attribute is inheritable, and findResources() should probably used.
+ * This will return null if no resources are available at this level.
+ *
+ * @return The resources at this level in the hierarchy.
+ */
+ public PDResources getResources()
+ {
+ PDResources retval = null;
+ COSDictionary resources = (COSDictionary)page.getDictionaryObject( COSName.RESOURCES );
+ if( resources != null )
+ {
+ retval = new PDResources( resources );
+ }
+ return retval;
+ }
+
+ /**
+ * This will find the resources for this page by looking up the hierarchy until
+ * it finds them.
+ *
+ * @return The resources at this level in the hierarchy.
+ */
+ public PDResources findResources()
+ {
+ PDResources retval = getResources();
+ PDPageNode parent = getParent();
+ if( retval == null && parent != null )
+ {
+ retval = parent.findResources();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the resources for this page.
+ *
+ * @param resources The new resources for this page.
+ */
+ public void setResources( PDResources resources )
+ {
+ if( resources == null )
+ {
+ page.removeItem( COSName.RESOURCES );
+ }
+ else
+ {
+ page.setItem( COSName.RESOURCES, resources.getCOSDictionary() );
+ }
+ }
+
+ /**
+ * This will get the MediaBox at this page and not look up the hierarchy.
+ * This attribute is inheritable, and findMediaBox() should probably used.
+ * This will return null if no MediaBox are available at this level.
+ *
+ * @return The MediaBox at this level in the hierarchy.
+ */
+ public PDRectangle getMediaBox()
+ {
+ PDRectangle retval = null;
+ COSArray array = (COSArray)page.getDictionaryObject( COSName.MEDIA_BOX );
+ if( array != null )
+ {
+ retval = new PDRectangle( array );
+ }
+ return retval;
+ }
+
+ /**
+ * This will find the MediaBox for this page by looking up the hierarchy until
+ * it finds them.
+ *
+ * @return The MediaBox at this level in the hierarchy.
+ */
+ public PDRectangle findMediaBox()
+ {
+ PDRectangle retval = getMediaBox();
+ PDPageNode parent = getParent();
+ if( retval == null && parent != null )
+ {
+ retval = parent.findMediaBox();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the mediaBox for this page.
+ *
+ * @param mediaBox The new mediaBox for this page.
+ */
+ public void setMediaBox( PDRectangle mediaBox )
+ {
+ if( mediaBox == null )
+ {
+ page.removeItem( COSName.MEDIA_BOX );
+ }
+ else
+ {
+ page.setItem( COSName.MEDIA_BOX , mediaBox.getCOSArray() );
+ }
+ }
+
+ /**
+ * This will get the CropBox at this page and not look up the hierarchy.
+ * This attribute is inheritable, and findCropBox() should probably used.
+ * This will return null if no CropBox is available at this level.
+ *
+ * @return The CropBox at this level in the hierarchy.
+ */
+ public PDRectangle getCropBox()
+ {
+ PDRectangle retval = null;
+ COSArray array = (COSArray)page.getDictionaryObject( COSName.CROP_BOX );
+ if( array != null )
+ {
+ retval = new PDRectangle( array );
+ }
+ return retval;
+ }
+
+ /**
+ * This will find the CropBox for this page by looking up the hierarchy until
+ * it finds them.
+ *
+ * @return The CropBox at this level in the hierarchy.
+ */
+ public PDRectangle findCropBox()
+ {
+ PDRectangle retval = getCropBox();
+ PDPageNode parent = getParent();
+ if( retval == null && parent != null )
+ {
+ retval = findParentCropBox( parent );
+ }
+
+ //default value for cropbox is the media box
+ if( retval == null )
+ {
+ retval = findMediaBox();
+ }
+ return retval;
+ }
+
+ /**
+ * This will search for a crop box in the parent and return null if it is not
+ * found. It will NOT default to the media box if it cannot be found.
+ *
+ * @param node The node
+ */
+ private PDRectangle findParentCropBox( PDPageNode node )
+ {
+ PDRectangle rect = node.getCropBox();
+ PDPageNode parent = node.getParent();
+ if( rect == null && parent != null )
+ {
+ rect = findParentCropBox( node );
+ }
+ return rect;
+ }
+
+ /**
+ * This will set the CropBox for this page.
+ *
+ * @param cropBox The new CropBox for this page.
+ */
+ public void setCropBox( PDRectangle cropBox )
+ {
+ if( cropBox == null )
+ {
+ page.removeItem( COSName.CROP_BOX );
+ }
+ else
+ {
+ page.setItem( COSName.CROP_BOX, cropBox.getCOSArray() );
+ }
+ }
+
+ /**
+ * A value representing the rotation. This will be null if not set at this level
+ * The number of degrees by which the page should
+ * be rotated clockwise when displayed or printed. The value must be a multiple
+ * of 90.
+ *
+ * This will get the rotation at this page and not look up the hierarchy.
+ * This attribute is inheritable, and findRotation() should probably used.
+ * This will return null if no rotation is available at this level.
+ *
+ * @return The rotation at this level in the hierarchy.
+ */
+ public Integer getRotation()
+ {
+ Integer retval = null;
+ COSNumber value = (COSNumber)page.getDictionaryObject( COSName.ROTATE );
+ if( value != null )
+ {
+ retval = new Integer( value.intValue() );
+ }
+ return retval;
+ }
+
+ /**
+ * This will find the rotation for this page by looking up the hierarchy until
+ * it finds them.
+ *
+ * @return The rotation at this level in the hierarchy.
+ */
+ public int findRotation()
+ {
+ int retval = 0;
+ Integer rotation = getRotation();
+ if( rotation != null )
+ {
+ retval = rotation.intValue();
+ }
+ else
+ {
+ PDPageNode parent = getParent();
+ if( parent != null )
+ {
+ retval = parent.findRotation();
+ }
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set the rotation for this page.
+ *
+ * @param rotation The new rotation for this page.
+ */
+ public void setRotation( int rotation )
+ {
+ page.setInt( COSName.ROTATE, rotation );
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import static javax.print.attribute.standard.OrientationRequested.LANDSCAPE;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.print.PageFormat;
+import java.awt.print.Pageable;
+import java.awt.print.Paper;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.print.PrintService;
+import javax.print.attribute.standard.OrientationRequested;
+
+/**
+ * Adapter class that implements the {@link Pageable} and {@link Printable}
+ * interfaces for printing a given PDF document. Note that the given PDF
+ * document should not be modified (pages added, removed, etc.) while an
+ * instance of this class is being used.
+ *
+ * @since Apache PDFBox 1.3.0
+ * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-788">PDFBOX-788</a>
+ */
+public class PDPageable implements Pageable, Printable {
+
+ /**
+ * List of all pages in the given PDF document.
+ */
+ private final List<PDPage> pages = new ArrayList<PDPage>();
+
+ /**
+ * The printer job for printing the given PDF document.
+ */
+ private final PrinterJob job;
+
+ /**
+ * Creates a {@link Pageable} adapter for the given PDF document and
+ * printer job.
+ *
+ * @param document PDF document
+ * @param job printer job
+ * @throws IllegalArgumentException if an argument is <code>null</code>
+ * @throws PrinterException if the document permissions prevent printing
+ */
+ public PDPageable(PDDocument document, PrinterJob job)
+ throws IllegalArgumentException, PrinterException {
+ if (document == null || job == null) {
+ throw new IllegalArgumentException(
+ "PDPageable(" + document + ", " + job + ")");
+ } else if (!document.getCurrentAccessPermission().canPrint()) {
+ throw new PrinterException(
+ "You do not have permission to print this document");
+ } else {
+ document.getDocumentCatalog().getPages().getAllKids(pages);
+ this.job = job;
+ }
+ }
+
+ /**
+ * Creates a {@link Pageable} adapter for the given PDF document using
+ * a default printer job returned by {@link PrinterJob#getPrinterJob()}.
+ *
+ * @param document PDF document
+ * @throws IllegalArgumentException if the argument is <code>null</code>
+ * @throws PrinterException if the document permissions prevent printing
+ */
+ public PDPageable(PDDocument document)
+ throws IllegalArgumentException, PrinterException {
+ this(document, PrinterJob.getPrinterJob());
+ }
+
+ /**
+ * Returns the printer job for printing the given PDF document.
+ *
+ * @return printer job
+ */
+ public PrinterJob getPrinterJob() {
+ return job;
+ }
+
+ //------------------------------------------------------------< Pageable >
+
+ /**
+ * Returns the number of pages in the given PDF document.
+ *
+ * @return number of pages
+ */
+ public int getNumberOfPages() {
+ return pages.size();
+ }
+
+ /**
+ * Returns the format of the page at the given index.
+ *
+ * @param i page index, zero-based
+ * @return page format
+ * @throws IndexOutOfBoundsException if the page index is invalid
+ */
+ public PageFormat getPageFormat(int i) throws IndexOutOfBoundsException {
+ PageFormat format = job.defaultPage();
+
+ PDPage page = pages.get(i); // can throw IOOBE
+ Dimension media = page.findMediaBox().createDimension();
+ Dimension crop = page.findCropBox().createDimension();
+
+ // Center the ImageableArea if the crop is smaller than the media
+ double diffWidth = 0.0;
+ double diffHeight = 0.0;
+ if (!media.equals(crop)) {
+ diffWidth = (media.getWidth() - crop.getWidth()) / 2.0;
+ diffHeight = (media.getHeight() - crop.getHeight()) / 2.0;
+ }
+
+ Paper paper = format.getPaper();
+ PrintService service = job.getPrintService(); // can be null
+ Class<OrientationRequested> orientation = OrientationRequested.class;
+ if (service != null
+ && service.getDefaultAttributeValue(orientation) == LANDSCAPE) {
+ format.setOrientation(PageFormat.LANDSCAPE);
+ paper.setImageableArea(
+ diffHeight, diffWidth, crop.getHeight(), crop.getWidth());
+ paper.setSize(media.getHeight(), media.getWidth());
+ } else {
+ format.setOrientation(PageFormat.PORTRAIT);
+ paper.setImageableArea(
+ diffWidth, diffHeight, crop.getWidth(), crop.getHeight());
+ paper.setSize(media.getWidth(), media.getHeight());
+ }
+ format.setPaper(paper);
+
+ return format;
+ }
+
+ /**
+ * Returns a {@link Printable} for the page at the given index.
+ * Currently this method simply returns the underlying {@link PDPage}
+ * object that directly implements the {@link Printable} interface, but
+ * future versions may choose to return a different adapter instance.
+ *
+ * @param i page index, zero-based
+ * @return printable
+ * @throws IndexOutOfBoundsException if the page index is invalid
+ */
+ public Printable getPrintable(int i) throws IndexOutOfBoundsException {
+ return pages.get(i);
+ }
+
+ //-----------------------------------------------------------< Printable >
+
+ /**
+ * Prints the page at the given index.
+ *
+ * @param graphics printing target
+ * @param format page format
+ * @param i page index, zero-based
+ * @return {@link Printable#PAGE_EXISTS} if the page was printed,
+ * or {@link Printable#NO_SUCH_PAGE} if page index was invalid
+ * @throws PrinterException if printing failed
+ */
+ @SuppressWarnings("deprecation")
+ public int print(Graphics graphics, PageFormat format, int i)
+ throws PrinterException {
+ if (0 <= i && i < pages.size()) {
+ return pages.get(i).print(graphics, format, i);
+ } else {
+ return NO_SUCH_PAGE;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+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.common.COSDictionaryMap;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.font.PDFontFactory;
+import org.apache.pdfbox.pdmodel.graphics.PDExtendedGraphicsState;
+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.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.16 $
+ */
+public class PDResources implements COSObjectable
+{
+ private COSDictionary resources;
+
+ /**
+ * Default constructor.
+ */
+ public PDResources()
+ {
+ resources = new COSDictionary();
+ }
+
+ /**
+ * Prepopulated resources.
+ *
+ * @param resourceDictionary The cos dictionary for this resource.
+ */
+ public PDResources( COSDictionary resourceDictionary )
+ {
+ resources = resourceDictionary;
+ }
+
+ /**
+ * This will get the underlying dictionary.
+ *
+ * @return The dictionary for these resources.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return resources;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return resources;
+ }
+
+ /**
+ * This will get the map of fonts. This will never return null. The keys are string
+ * and the values are PDFont objects.
+ *
+ * @param fontCache A map of existing PDFont objects to reuse.
+ * @return The map of fonts.
+ *
+ * @throws IOException If there is an error getting the fonts.
+ */
+ public Map getFonts( Map fontCache ) throws IOException
+ {
+ Map retval = null;
+ COSDictionary fonts = (COSDictionary)resources.getDictionaryObject( COSName.FONT );
+
+ if( fonts == null )
+ {
+ fonts = new COSDictionary();
+ resources.setItem( COSName.FONT, fonts );
+ }
+
+ Map actuals = new HashMap();
+ retval = new COSDictionaryMap( actuals, fonts );
+ for( COSName fontName : fonts.keySet() )
+ {
+ COSBase font = fonts.getDictionaryObject( fontName );
+ //data-000174.pdf contains a font that is a COSArray, looks to be an error in the
+ //PDF, we will just ignore entries that are not dictionaries.
+ if( font instanceof COSDictionary )
+ {
+ COSDictionary fontDictionary = (COSDictionary)font;
+ actuals.put( fontName.getName(), PDFontFactory.createFont( fontDictionary, fontCache ));
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the map of fonts. This will never return null. The keys are string
+ * and the values are PDFont objects.
+ *
+ * @return The map of fonts.
+ *
+ * @throws IOException If there is an error getting the fonts.
+ */
+ public Map getFonts() throws IOException
+ {
+ return getFonts( null );
+ }
+
+ /**
+ * This will get the map of PDXObjects that are in the resource dictionary.
+ *
+ * @return The map of xobjects.
+ *
+ * @throws IOException If there is an error creating the xobjects.
+ */
+ public Map getXObjects() throws IOException
+ {
+ Map retval = null;
+ COSDictionary xobjects = (COSDictionary)resources.getDictionaryObject( COSName.XOBJECT );
+
+ if( xobjects == null )
+ {
+ xobjects = new COSDictionary();
+ resources.setItem( COSName.XOBJECT, xobjects );
+ }
+
+ Map actuals = new HashMap();
+ retval = new COSDictionaryMap( actuals, xobjects );
+ for( COSName objName : xobjects.keySet() )
+ {
+ COSBase cosObject = xobjects.getDictionaryObject(objName);
+ PDXObject xobject = PDXObject.createXObject( cosObject );
+ if( xobject !=null )
+ {
+ actuals.put( objName.getName(), xobject);
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the map of images. An empty map will be returned if there
+ * are no underlying images.
+ * So far the keys are COSName of the image
+ * and the value is the corresponding PDXObjectImage.
+ *
+ * @author By BM
+ * @return The map of images.
+ * @throws IOException If there is an error writing the picture.
+ */
+ public Map getImages() throws IOException
+ {
+ Map retval = null;
+ COSDictionary images = (COSDictionary)resources.getDictionaryObject( COSName.XOBJECT );
+
+ if( images == null )
+ {
+ images = new COSDictionary();
+ resources.setItem( COSName.XOBJECT, images );
+ }
+
+ Map actuals = new HashMap();
+ retval = new COSDictionaryMap( actuals, images );
+ for( COSName imageName : images.keySet() )
+ {
+ COSStream image = (COSStream)(images.getDictionaryObject(imageName));
+
+ COSName subType =(COSName)image.getDictionaryObject(COSName.SUBTYPE);
+ if( subType.equals(COSName.IMAGE) )
+ {
+ PDXObjectImage ximage = (PDXObjectImage)PDXObject.createXObject( image );
+ if( ximage !=null )
+ {
+ actuals.put( imageName.getName(), ximage);
+ }
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the map of fonts.
+ *
+ * @param fonts The new map of fonts.
+ */
+ public void setFonts( Map fonts )
+ {
+ resources.setItem( COSName.FONT, COSDictionaryMap.convert( fonts ) );
+ }
+
+ /**
+ * This will get the map of colorspaces. This will return null if the underlying
+ * resources dictionary does not have a colorspace dictionary. The keys are string
+ * and the values are PDColorSpace objects.
+ *
+ * @return The map of colorspaces.
+ *
+ * @throws IOException If there is an error getting the colorspaces.
+ */
+ public Map getColorSpaces() throws IOException
+ {
+ Map retval = null;
+ COSDictionary colorspaces = (COSDictionary)resources.getDictionaryObject( COSName.getPDFName( "ColorSpace" ) );
+
+ if( colorspaces != null )
+ {
+ Map actuals = new HashMap();
+ retval = new COSDictionaryMap( actuals, colorspaces );
+ for( COSName csName : colorspaces.keySet() )
+ {
+ COSBase cs = colorspaces.getDictionaryObject( csName );
+ actuals.put( csName.getName(), PDColorSpaceFactory.createColorSpace( cs ) );
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the map of colorspaces.
+ *
+ * @param colorspaces The new map of colorspaces.
+ */
+ public void setColorSpaces( Map colorspaces )
+ {
+ resources.setItem( COSName.COLORSPACE, COSDictionaryMap.convert( colorspaces ) );
+ }
+
+ /**
+ * This will get the map of graphic states. This will return null if the underlying
+ * resources dictionary does not have a graphics dictionary. The keys are the graphic state
+ * name as a String and the values are PDExtendedGraphicsState objects.
+ *
+ * @return The map of extended graphic state objects.
+ */
+ public Map getGraphicsStates()
+ {
+ Map retval = null;
+ COSDictionary states = (COSDictionary)resources.getDictionaryObject( COSName.EXT_G_STATE );
+
+ if( states != null )
+ {
+ Map actuals = new HashMap();
+ retval = new COSDictionaryMap( actuals, states );
+ for( COSName name : states.keySet() )
+ {
+ COSDictionary dictionary = (COSDictionary)states.getDictionaryObject( name );
+ actuals.put( name.getName(), new PDExtendedGraphicsState( dictionary ) );
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the map of graphics states.
+ *
+ * @param states The new map of states.
+ */
+ public void setGraphicsStates( Map states )
+ {
+ Iterator iter = states.keySet().iterator();
+ COSDictionary dic = new COSDictionary();
+ while( iter.hasNext() )
+ {
+ String name = (String)iter.next();
+ PDExtendedGraphicsState state = (PDExtendedGraphicsState)states.get( name );
+ dic.setItem( COSName.getPDFName( name ), state.getCOSObject() );
+ }
+ 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());
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+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.COSFloat;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSNumber;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * This is an implementation of a List that will sync its contents to a COSArray.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.15 $
+ */
+public class COSArrayList implements List
+{
+ private COSArray array;
+ private List actual;
+
+ private COSDictionary parentDict;
+ private COSName dictKey;
+
+ /**
+ * Default constructor.
+ */
+ public COSArrayList()
+ {
+ array = new COSArray();
+ actual = new ArrayList();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param actualList The list of standard java objects
+ * @param cosArray The COS array object to sync to.
+ */
+ public COSArrayList( List actualList, COSArray cosArray )
+ {
+ actual = actualList;
+ array = cosArray;
+ }
+
+ /**
+ * This is a really special constructor. Sometimes the PDF spec says
+ * that a dictionary entry can either be a single item or an array of those
+ * items. But in the PDModel interface we really just want to always return
+ * a java.util.List. In the case were we get the list and never modify it
+ * we don't want to convert to COSArray and put one element, unless we append
+ * to the list. So here we are going to create this object with a single
+ * item instead of a list, but allow more items to be added and then converted
+ * to an array.
+ *
+ * @param actualObject The PDModel object.
+ * @param item The COS Model object.
+ * @param dictionary The dictionary that holds the item, and will hold the array if an item is added.
+ * @param dictionaryKey The key into the dictionary to set the item.
+ */
+ public COSArrayList( Object actualObject, COSBase item, COSDictionary dictionary, COSName dictionaryKey )
+ {
+ array = new COSArray();
+ array.add( item );
+ actual = new ArrayList();
+ actual.add( actualObject );
+
+ parentDict = dictionary;
+ dictKey = dictionaryKey;
+ }
+
+ /**
+ * @deprecated use the {@link #COSArrayList(Object, COSBase, COSDictionary, COSName)} method instead
+ */
+ public COSArrayList( Object actualObject, COSBase item, COSDictionary dictionary, String dictionaryKey )
+ {
+ this( actualObject, item, dictionary, COSName.getPDFName(dictionaryKey) );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int size()
+ {
+ return actual.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isEmpty()
+ {
+ return actual.isEmpty();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean contains(Object o)
+ {
+ return actual.contains(o);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator iterator()
+ {
+ return actual.iterator();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object[] toArray()
+ {
+ return actual.toArray();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object[] toArray(Object[] a)
+ {
+ return actual.toArray(a);
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean add(Object o)
+ {
+ //when adding if there is a parentDict then change the item
+ //in the dictionary from a single item to an array.
+ if( parentDict != null )
+ {
+ parentDict.setItem( dictKey, array );
+ //clear the parent dict so it doesn't happen again, there might be
+ //a usecase for keeping the parentDict around but not now.
+ parentDict = null;
+ }
+ //string is a special case because we can't subclass to be COSObjectable
+ if( o instanceof String )
+ {
+ array.add( new COSString( (String)o ) );
+ }
+ else if( o instanceof DualCOSObjectable )
+ {
+ DualCOSObjectable dual = (DualCOSObjectable)o;
+ array.add( dual.getFirstCOSObject() );
+ array.add( dual.getSecondCOSObject() );
+ }
+ else
+ {
+ if(array != null)
+ {
+ array.add(((COSObjectable)o).getCOSObject());
+ }
+ }
+ return actual.add(o);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean remove(Object o)
+ {
+ boolean retval = true;
+ int index = actual.indexOf( o );
+ if( index >= 0 )
+ {
+ actual.remove( index );
+ array.remove( index );
+ }
+ else
+ {
+ retval = false;
+ }
+ return retval;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsAll(Collection c)
+ {
+ return actual.containsAll( c );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAll(Collection c)
+ {
+ //when adding if there is a parentDict then change the item
+ //in the dictionary from a single item to an array.
+ if( parentDict != null && c.size() > 0)
+ {
+ parentDict.setItem( dictKey, array );
+ //clear the parent dict so it doesn't happen again, there might be
+ //a usecase for keeping the parentDict around but not now.
+ parentDict = null;
+ }
+ array.addAll( toCOSObjectList( c ) );
+ return actual.addAll( c );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean addAll(int index, Collection c)
+ {
+ //when adding if there is a parentDict then change the item
+ //in the dictionary from a single item to an array.
+ if( parentDict != null && c.size() > 0)
+ {
+ parentDict.setItem( dictKey, array );
+ //clear the parent dict so it doesn't happen again, there might be
+ //a usecase for keeping the parentDict around but not now.
+ parentDict = null;
+ }
+
+ if( c.size() >0 && c.toArray()[0] instanceof DualCOSObjectable )
+ {
+ array.addAll( index*2, toCOSObjectList( c ) );
+ }
+ else
+ {
+ array.addAll( index, toCOSObjectList( c ) );
+ }
+ return actual.addAll( index, c );
+ }
+
+ /**
+ * This will take an array of COSNumbers and return a COSArrayList of
+ * java.lang.Integer values.
+ *
+ * @param intArray The existing integer Array.
+ *
+ * @return A list that is part of the core Java collections.
+ */
+ public static List convertIntegerCOSArrayToList( COSArray intArray )
+ {
+ List numbers = new ArrayList();
+ for( int i=0; i<intArray.size(); i++ )
+ {
+ numbers.add( new Integer( ((COSNumber)intArray.get( i )).intValue() ) );
+ }
+ return new COSArrayList( numbers, intArray );
+ }
+
+ /**
+ * This will take an array of COSNumbers and return a COSArrayList of
+ * java.lang.Float values.
+ *
+ * @param floatArray The existing float Array.
+ *
+ * @return The list of Float objects.
+ */
+ public static List convertFloatCOSArrayToList( COSArray floatArray )
+ {
+ List retval = null;
+ if( floatArray != null )
+ {
+ List numbers = new ArrayList();
+ for( int i=0; i<floatArray.size(); i++ )
+ {
+ numbers.add( new Float( ((COSNumber)floatArray.get( i )).floatValue() ) );
+ }
+ retval = new COSArrayList( numbers, floatArray );
+ }
+ return retval;
+ }
+
+ /**
+ * This will take an array of COSName and return a COSArrayList of
+ * java.lang.String values.
+ *
+ * @param nameArray The existing name Array.
+ *
+ * @return The list of String objects.
+ */
+ public static List convertCOSNameCOSArrayToList( COSArray nameArray )
+ {
+ List retval = null;
+ if( nameArray != null )
+ {
+ List names = new ArrayList();
+ for( int i=0; i<nameArray.size(); i++ )
+ {
+ names.add( ((COSName)nameArray.getObject( i )).getName() );
+ }
+ retval = new COSArrayList( names, nameArray );
+ }
+ return retval;
+ }
+
+ /**
+ * This will take an array of COSString and return a COSArrayList of
+ * java.lang.String values.
+ *
+ * @param stringArray The existing name Array.
+ *
+ * @return The list of String objects.
+ */
+ public static List convertCOSStringCOSArrayToList( COSArray stringArray )
+ {
+ List retval = null;
+ if( stringArray != null )
+ {
+ List string = new ArrayList();
+ for( int i=0; i<stringArray.size(); i++ )
+ {
+ string.add( ((COSString)stringArray.getObject( i )).getString() );
+ }
+ retval = new COSArrayList( string, stringArray );
+ }
+ return retval;
+ }
+
+ /**
+ * This will take an list of string objects and return a COSArray of COSName
+ * objects.
+ *
+ * @param strings A list of strings
+ *
+ * @return An array of COSName objects
+ */
+ public static COSArray convertStringListToCOSNameCOSArray( List strings )
+ {
+ COSArray retval = new COSArray();
+ for( int i=0; i<strings.size(); i++ )
+ {
+ Object next = strings.get( i );
+ if( next instanceof COSName )
+ {
+ retval.add( (COSName)next );
+ }
+ else
+ {
+ retval.add( COSName.getPDFName( (String)next ) );
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will take an list of string objects and return a COSArray of COSName
+ * objects.
+ *
+ * @param strings A list of strings
+ *
+ * @return An array of COSName objects
+ */
+ public static COSArray convertStringListToCOSStringCOSArray( List strings )
+ {
+ COSArray retval = new COSArray();
+ for( int i=0; i<strings.size(); i++ )
+ {
+ retval.add( new COSString( (String)strings.get( i ) ) );
+ }
+ return retval;
+ }
+
+ /**
+ * This will convert a list of COSObjectables to an
+ * array list of COSBase objects.
+ *
+ * @param cosObjectableList A list of COSObjectable.
+ *
+ * @return A list of COSBase.
+ */
+ public static COSArray converterToCOSArray( List cosObjectableList )
+ {
+ COSArray array = null;
+ if( cosObjectableList != null )
+ {
+ if( cosObjectableList instanceof COSArrayList )
+ {
+ //if it is already a COSArrayList then we don't want to recreate the array, we want to reuse it.
+ array = ((COSArrayList)cosObjectableList).array;
+ }
+ else
+ {
+ array = new COSArray();
+ Iterator iter = cosObjectableList.iterator();
+ while( iter.hasNext() )
+ {
+ Object next = iter.next();
+ if( next instanceof String )
+ {
+ array.add( new COSString( (String)next ) );
+ }
+ else if( next instanceof Integer || next instanceof Long )
+ {
+ array.add( COSInteger.get( ((Number)next).longValue() ) );
+ }
+ else if( next instanceof Float || next instanceof Double )
+ {
+ array.add( new COSFloat( ((Number)next).floatValue() ) );
+ }
+ else if( next instanceof COSObjectable )
+ {
+ COSObjectable object = (COSObjectable)next;
+ array.add( object.getCOSObject() );
+ }
+ else if( next instanceof DualCOSObjectable )
+ {
+ DualCOSObjectable object = (DualCOSObjectable)next;
+ array.add( object.getFirstCOSObject() );
+ array.add( object.getSecondCOSObject() );
+ }
+ else if( next == null )
+ {
+ array.add( COSNull.NULL );
+ }
+ else
+ {
+ throw new RuntimeException( "Error: Don't know how to convert type to COSBase '" +
+ next.getClass().getName() + "'" );
+ }
+ }
+ }
+ }
+ return array;
+ }
+
+ private List toCOSObjectList( Collection list )
+ {
+ List cosObjects = new ArrayList();
+ Iterator iter = list.iterator();
+ while( iter.hasNext() )
+ {
+ Object next = iter.next();
+ if( next instanceof String )
+ {
+ cosObjects.add( new COSString( (String)next ) );
+ }
+ else if( next instanceof DualCOSObjectable )
+ {
+ DualCOSObjectable object = (DualCOSObjectable)next;
+ array.add( object.getFirstCOSObject() );
+ array.add( object.getSecondCOSObject() );
+ }
+ else
+ {
+ COSObjectable cos = (COSObjectable)next;
+ cosObjects.add( cos.getCOSObject() );
+ }
+ }
+ return cosObjects;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean removeAll(Collection c)
+ {
+ array.removeAll( toCOSObjectList( c ) );
+ return actual.removeAll( c );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean retainAll(Collection c)
+ {
+ array.retainAll( toCOSObjectList( c ) );
+ return actual.retainAll( c );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void clear()
+ {
+ //when adding if there is a parentDict then change the item
+ //in the dictionary from a single item to an array.
+ if( parentDict != null )
+ {
+ parentDict.setItem( dictKey, (COSBase)null );
+ }
+ actual.clear();
+ array.clear();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object o)
+ {
+ return actual.equals( o );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return actual.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object get(int index)
+ {
+ return actual.get( index );
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object set(int index, Object element)
+ {
+ if( element instanceof String )
+ {
+ COSString item = new COSString( (String)element );
+ if( parentDict != null && index == 0 )
+ {
+ parentDict.setItem( dictKey, item );
+ }
+ array.set( index, item );
+ }
+ else if( element instanceof DualCOSObjectable )
+ {
+ DualCOSObjectable dual = (DualCOSObjectable)element;
+ array.set( index*2, dual.getFirstCOSObject() );
+ array.set( index*2+1, dual.getSecondCOSObject() );
+ }
+ else
+ {
+ if( parentDict != null && index == 0 )
+ {
+ parentDict.setItem( dictKey, ((COSObjectable)element).getCOSObject() );
+ }
+ array.set( index, ((COSObjectable)element).getCOSObject() );
+ }
+ return actual.set( index, element );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void add(int index, Object element)
+ {
+ //when adding if there is a parentDict then change the item
+ //in the dictionary from a single item to an array.
+ if( parentDict != null )
+ {
+ parentDict.setItem( dictKey, array );
+ //clear the parent dict so it doesn't happen again, there might be
+ //a usecase for keeping the parentDict around but not now.
+ parentDict = null;
+ }
+ actual.add( index, element );
+ if( element instanceof String )
+ {
+ array.add( index, new COSString( (String)element ) );
+ }
+ else if( element instanceof DualCOSObjectable )
+ {
+ DualCOSObjectable dual = (DualCOSObjectable)element;
+ array.add( index*2, dual.getFirstCOSObject() );
+ array.add( index*2+1, dual.getSecondCOSObject() );
+ }
+ else
+ {
+ array.add( index, ((COSObjectable)element).getCOSObject() );
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object remove(int index)
+ {
+ if( array.size() > index && array.get( index ) instanceof DualCOSObjectable )
+ {
+ //remove both objects
+ array.remove( index );
+ array.remove( index );
+ }
+ else
+ {
+ array.remove( index );
+ }
+ return actual.remove( index );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int indexOf(Object o)
+ {
+ return actual.indexOf( o );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int lastIndexOf(Object o)
+ {
+ return actual.indexOf( o );
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ListIterator listIterator()
+ {
+ return actual.listIterator();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ListIterator listIterator(int index)
+ {
+ return actual.listIterator( index );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public List subList(int fromIndex, int toIndex)
+ {
+ return actual.subList( fromIndex, toIndex );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return "COSArrayList{" + array.toString() + "}";
+ }
+
+ /**
+ * This will return then underlying COSArray.
+ *
+ * @return the COSArray
+ */
+ public COSArray toList()
+ {
+ return array;
+ }
+
+}
--- /dev/null
+/*
+ * 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.common;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * This is a Map that will automatically sync the contents to a COSDictionary.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class COSDictionaryMap implements Map
+{
+ private COSDictionary map;
+ private Map actuals;
+
+ /**
+ * Constructor for this map.
+ *
+ * @param actualsMap The map with standard java objects as values.
+ * @param dicMap The map with COSBase objects as values.
+ */
+ public COSDictionaryMap( Map actualsMap, COSDictionary dicMap )
+ {
+ actuals = actualsMap;
+ map = dicMap;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int size()
+ {
+ return map.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isEmpty()
+ {
+ return size() == 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsKey(Object key)
+ {
+ return map.keySet().contains( key );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsValue(Object value)
+ {
+ return actuals.containsValue( value );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object get(Object key)
+ {
+ return actuals.get( key );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object put(Object key, Object value)
+ {
+ COSObjectable object = (COSObjectable)value;
+
+ map.setItem( COSName.getPDFName( (String)key ), object.getCOSObject() );
+ return actuals.put( key, value );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object remove(Object key)
+ {
+ map.removeItem( COSName.getPDFName( (String)key ) );
+ return actuals.remove( key );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void putAll(Map t)
+ {
+ throw new RuntimeException( "Not yet implemented" );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void clear()
+ {
+ map.clear();
+ actuals.clear();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Set keySet()
+ {
+ return actuals.keySet();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Collection values()
+ {
+ return actuals.values();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Set entrySet()
+ {
+ return Collections.unmodifiableSet(actuals.entrySet());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object o)
+ {
+ boolean retval = false;
+ if( o instanceof COSDictionaryMap )
+ {
+ COSDictionaryMap other = (COSDictionaryMap)o;
+ retval = other.map.equals( this.map );
+ }
+ return retval;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return actuals.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return map.hashCode();
+ }
+
+ /**
+ * This will take a map<java.lang.String,org.apache.pdfbox.pdmodel.COSObjectable>
+ * and convert it into a COSDictionary<COSName,COSBase>.
+ *
+ * @param someMap A map containing COSObjectables
+ *
+ * @return A proper COSDictionary
+ */
+ public static COSDictionary convert( Map someMap )
+ {
+ Iterator iter = someMap.keySet().iterator();
+ COSDictionary dic = new COSDictionary();
+ while( iter.hasNext() )
+ {
+ String name = (String)iter.next();
+ COSObjectable object = (COSObjectable)someMap.get( name );
+ dic.setItem( COSName.getPDFName( name ), object.getCOSObject() );
+ }
+ return dic;
+ }
+
+ /**
+ * This will take a COS dictionary and convert it into COSDictionaryMap. All cos
+ * objects will be converted to their primitive form.
+ *
+ * @param map The COS mappings.
+ * @return A standard java map.
+ * @throws IOException If there is an error during the conversion.
+ */
+ public static COSDictionaryMap convertBasicTypesToMap( COSDictionary map ) throws IOException
+ {
+ COSDictionaryMap retval = null;
+ if( map != null )
+ {
+ Map actualMap = new HashMap();
+ for( COSName key : map.keySet() )
+ {
+ COSBase cosObj = map.getDictionaryObject( key );
+ Object actualObject = null;
+ if( cosObj instanceof COSString )
+ {
+ actualObject = ((COSString)cosObj).getString();
+ }
+ else if( cosObj instanceof COSInteger )
+ {
+ actualObject = new Integer( ((COSInteger)cosObj).intValue() );
+ }
+ else if( cosObj instanceof COSName )
+ {
+ actualObject = ((COSName)cosObj).getName();
+ }
+ else if( cosObj instanceof COSFloat )
+ {
+ actualObject = new Float( ((COSFloat)cosObj).floatValue() );
+ }
+ else if( cosObj instanceof COSBoolean )
+ {
+ actualObject = ((COSBoolean)cosObj).getValue() ? Boolean.TRUE : Boolean.FALSE;
+ }
+ else
+ {
+ throw new IOException( "Error:unknown type of object to convert:" + cosObj );
+ }
+ actualMap.put( key.getName(), actualObject );
+ }
+ retval = new COSDictionaryMap( actualMap, map );
+ }
+
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This is an interface used to get/create the underlying COSObject.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public interface COSObjectable
+{
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject();
+}
--- /dev/null
+/*
+ * 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.common;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.SequenceInputStream;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+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.COSStream;
+import org.apache.pdfbox.cos.ICOSVisitor;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.io.RandomAccess;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+
+/**
+ * This will take an array of streams and sequence them together.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class COSStreamArray extends COSStream
+{
+ private COSArray streams;
+
+ /**
+ * The first stream will be used to delegate some of the methods for this
+ * class.
+ */
+ private COSStream firstStream;
+
+ /**
+ * Constructor.
+ *
+ * @param array The array of COSStreams to concatenate together.
+ */
+ public COSStreamArray( COSArray array )
+ {
+ super( new COSDictionary(), null );
+ streams = array;
+ if( array.size() > 0 )
+ {
+ firstStream = (COSStream)array.getObject( 0 );
+ }
+ }
+
+ /**
+ * This will get a stream (or the reference to a stream) from the array.
+ *
+ * @param index The index of the requested stream
+ * @return The stream object or a reference to a stream
+ */
+ public COSBase get( int index )
+ {
+ return streams.get( index );
+ }
+
+ /**
+ * This will get the number of streams in the array.
+ *
+ * @return the number of streams
+ */
+ public int getStreamCount()
+ {
+ return streams.size();
+ }
+
+ /**
+ * This will get the scratch file associated with this stream.
+ *
+ * @return The scratch file where this stream is being stored.
+ */
+ public RandomAccess getScratchFile()
+ {
+ return firstStream.getScratchFile();
+ }
+
+ /**
+ * This will get an object from this streams dictionary.
+ *
+ * @param key The key to the object.
+ *
+ * @return The dictionary object with the key or null if one does not exist.
+ */
+ public COSBase getItem( COSName key )
+ {
+ return firstStream.getItem( key );
+ }
+
+ /**
+ * This will get an object from this streams dictionary and dereference it
+ * if necessary.
+ *
+ * @param key The key to the object.
+ *
+ * @return The dictionary object with the key or null if one does not exist.
+ */
+ public COSBase getDictionaryObject( COSName key )
+ {
+ return firstStream.getDictionaryObject( key );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return "COSStream{}";
+ }
+
+ /**
+ * This will get all the tokens in the stream.
+ *
+ * @return All of the tokens in the stream.
+ *
+ * @throws IOException If there is an error parsing the stream.
+ */
+ public List getStreamTokens() throws IOException
+ {
+ List retval = null;
+ if( streams.size() > 0 )
+ {
+ PDFStreamParser parser = new PDFStreamParser( this );
+ parser.parse();
+ retval = parser.getTokens();
+ }
+ else
+ {
+ retval = new ArrayList();
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the dictionary that is associated with this stream.
+ *
+ * @return the object that is associated with this stream.
+ */
+ public COSDictionary getDictionary()
+ {
+ return firstStream;
+ }
+
+ /**
+ * This will get the stream with all of the filters applied.
+ *
+ * @return the bytes of the physical (endoced) stream
+ *
+ * @throws IOException when encoding/decoding causes an exception
+ */
+ public InputStream getFilteredStream() throws IOException
+ {
+ throw new IOException( "Error: Not allowed to get filtered stream from array of streams." );
+ }
+
+ /**
+ * This will get the logical content stream with none of the filters.
+ *
+ * @return the bytes of the logical (decoded) stream
+ *
+ * @throws IOException when encoding/decoding causes an exception
+ */
+ public InputStream getUnfilteredStream() throws IOException
+ {
+ Vector<InputStream> inputStreams = new Vector<InputStream>();
+ byte[] inbetweenStreamBytes = "\n".getBytes("ISO-8859-1");
+
+ for( int i=0;i<streams.size(); i++ )
+ {
+ COSStream stream = (COSStream)streams.getObject( i );
+ inputStreams.add( stream.getUnfilteredStream() );
+ //handle the case where there is no whitespace in the
+ //between streams in the contents array, without this
+ //it is possible that two operators will get concatenated
+ //together
+ inputStreams.add( new ByteArrayInputStream( inbetweenStreamBytes ) );
+ }
+
+ return new SequenceInputStream( inputStreams.elements() );
+ }
+
+ /**
+ * visitor pattern double dispatch method.
+ *
+ * @param visitor The object to notify when visiting this object.
+ * @return any object, depending on the visitor implementation, or null
+ * @throws COSVisitorException If an error occurs while visiting this object.
+ */
+ public Object accept(ICOSVisitor visitor) throws COSVisitorException
+ {
+ return streams.accept( visitor );
+ }
+
+
+ /**
+ * This will return the filters to apply to the byte stream
+ * the method will return.
+ * - null if no filters are to be applied
+ * - a COSName if one filter is to be applied
+ * - a COSArray containing COSNames if multiple filters are to be applied
+ *
+ * @return the COSBase object representing the filters
+ */
+ public COSBase getFilters()
+ {
+ return firstStream.getFilters();
+ }
+
+ /**
+ * This will create a new stream for which filtered byte should be
+ * written to. You probably don't want this but want to use the
+ * createUnfilteredStream, which is used to write raw bytes to.
+ *
+ * @return A stream that can be written to.
+ *
+ * @throws IOException If there is an error creating the stream.
+ */
+ public OutputStream createFilteredStream() throws IOException
+ {
+ return firstStream.createFilteredStream();
+ }
+
+ /**
+ * This will create a new stream for which filtered byte should be
+ * written to. You probably don't want this but want to use the
+ * createUnfilteredStream, which is used to write raw bytes to.
+ *
+ * @param expectedLength An entry where a length is expected.
+ *
+ * @return A stream that can be written to.
+ *
+ * @throws IOException If there is an error creating the stream.
+ */
+ public OutputStream createFilteredStream( COSBase expectedLength ) throws IOException
+ {
+ return firstStream.createFilteredStream( expectedLength );
+ }
+
+ /**
+ * set the filters to be applied to the stream.
+ *
+ * @param filters The filters to set on this stream.
+ *
+ * @throws IOException If there is an error clearing the old filters.
+ */
+ public void setFilters(COSBase filters) throws IOException
+ {
+ //should this be allowed? Should this
+ //propagate to all streams in the array?
+ firstStream.setFilters( filters );
+ }
+
+ /**
+ * This will create an output stream that can be written to.
+ *
+ * @return An output stream which raw data bytes should be written to.
+ *
+ * @throws IOException If there is an error creating the stream.
+ */
+ public OutputStream createUnfilteredStream() throws IOException
+ {
+ return firstStream.createUnfilteredStream();
+ }
+
+ /**
+ * Appends a new stream to the array that represents this object's stream.
+ *
+ * @param streamToAppend The stream to append.
+ */
+ public void appendStream(COSStream streamToAppend)
+ {
+ streams.add(streamToAppend);
+ }
+
+ /**
+ * Insert the given stream at the beginning of the existing stream array.
+ * @param streamToBeInserted
+ */
+ public void insertCOSStream(PDStream streamToBeInserted)
+ {
+ COSArray tmp = new COSArray();
+ tmp.add(streamToBeInserted);
+ tmp.addAll(streams);
+ streams.clear();
+ streams = tmp;
+ }
+
+}
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This is an interface to represent a PDModel object that holds two COS objects.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public interface DualCOSObjectable
+{
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getFirstCOSObject();
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getSecondCOSObject();
+}
--- /dev/null
+/*
+ * 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.common;
+
+/**
+ * This is an interface used for some elements such as the document
+ * OpenAction that can be either a Destination or an Action.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ *
+ * @version $Revision: 1.2 $
+ */
+public interface PDDestinationOrAction extends COSObjectable
+{
+
+}
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * A wrapper for a COS dictionary.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ *
+ */
+public class PDDictionaryWrapper implements COSObjectable
+{
+
+ private final COSDictionary dictionary;
+
+ /**
+ * Default constructor
+ */
+ public PDDictionaryWrapper()
+ {
+ this.dictionary = new COSDictionary();
+ }
+
+ /**
+ * Creates a new instance with a given COS dictionary.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDDictionaryWrapper(COSDictionary dictionary)
+ {
+ this.dictionary = dictionary;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return this.dictionary;
+ }
+
+ /**
+ * Gets the COS dictionary.
+ *
+ * @return the COS dictionary
+ */
+ protected COSDictionary getCOSDictionary()
+ {
+ return this.dictionary;
+ }
+
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj instanceof PDDictionaryWrapper)
+ {
+ return this.dictionary.equals(((PDDictionaryWrapper) obj).dictionary);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return this.dictionary.hashCode();
+ }
+
+}
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+/**
+ * This class will be used for matrix manipulation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDMatrix implements COSObjectable
+{
+ private COSArray matrix;
+ // the number of row elements depends on the number of elements
+ // within the given matrix
+ // 3x3 e.g. Matrix of a CalRGB colorspace dictionary
+ // 3x2 e.g. FontMatrix of a type 3 font
+ private int numberOfRowElements = 3;
+
+ /**
+ * Constructor.
+ */
+ public PDMatrix()
+ {
+ matrix = new COSArray();
+ matrix.add( new COSFloat( 1.0f ) );
+ matrix.add( new COSFloat( 0.0f ) );
+ matrix.add( new COSFloat( 0.0f ) );
+ matrix.add( new COSFloat( 0.0f ) );
+ matrix.add( new COSFloat( 1.0f ) );
+ matrix.add( new COSFloat( 0.0f ) );
+ matrix.add( new COSFloat( 0.0f ) );
+ matrix.add( new COSFloat( 0.0f ) );
+ matrix.add( new COSFloat( 1.0f ) );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array The array that describes the matrix.
+ */
+ public PDMatrix( COSArray array )
+ {
+ if ( array.size() == 6)
+ {
+ numberOfRowElements = 2;
+ }
+ matrix = array;
+ }
+
+ /**
+ * This will get the underlying array value.
+ *
+ * @return The cos object that this object wraps.
+ */
+ public COSArray getCOSArray()
+ {
+ return matrix;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return matrix;
+ }
+
+
+ /**
+ * This will get a matrix value at some point.
+ *
+ * @param row The row to get the value from.
+ * @param column The column to get the value from.
+ *
+ * @return The value at the row/column position.
+ */
+ public float getValue( int row, int column )
+ {
+ return ((COSNumber)matrix.get( row*numberOfRowElements + column )).floatValue();
+ }
+
+ /**
+ * This will set a value at a position.
+ *
+ * @param row The row to set the value at.
+ * @param column the column to set the value at.
+ * @param value The value to set at the position.
+ */
+ public void setValue( int row, int column, float value )
+ {
+ matrix.set( row*numberOfRowElements+column, new COSFloat( value ) );
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * A PDStream represents a stream in a PDF document. Streams are tied to a single
+ * PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDMemoryStream extends PDStream
+{
+ private byte[] data;
+
+ /**
+ * This will create a new PDStream object.
+ *
+ * @param buffer The data for this in memory stream.
+ */
+ public PDMemoryStream( byte[] buffer )
+ {
+ data = buffer;
+ }
+
+
+
+ /**
+ * If there are not compression filters on the current stream then this
+ * will add a compression filter, flate compression for example.
+ */
+ public void addCompression()
+ {
+ //no compression to add
+ }
+
+
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ throw new UnsupportedOperationException( "not supported for memory stream" );
+ }
+
+ /**
+ * This will get a stream that can be written to.
+ *
+ * @return An output stream to write data to.
+ *
+ * @throws IOException If an IO error occurs during writing.
+ */
+ public OutputStream createOutputStream() throws IOException
+ {
+ throw new UnsupportedOperationException( "not supported for memory stream" );
+ }
+
+ /**
+ * This will get a stream that can be read from.
+ *
+ * @return An input stream that can be read from.
+ *
+ * @throws IOException If an IO error occurs during reading.
+ */
+ public InputStream createInputStream() throws IOException
+ {
+ return new ByteArrayInputStream( data );
+ }
+
+ /**
+ * This will get a stream with some filters applied but not others. This is useful
+ * when doing images, ie filters = [flate,dct], we want to remove flate but leave dct
+ *
+ * @param stopFilters A list of filters to stop decoding at.
+ * @return A stream with decoded data.
+ * @throws IOException If there is an error processing the stream.
+ */
+ public InputStream getPartiallyFilteredStream( List stopFilters ) throws IOException
+ {
+ return createInputStream();
+ }
+
+ /**
+ * Get the cos stream associated with this object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSStream getStream()
+ {
+ throw new UnsupportedOperationException( "not supported for memory stream" );
+ }
+
+ /**
+ * This will get the length of the filtered/compressed stream. This is readonly in the
+ * PD Model and will be managed by this class.
+ *
+ * @return The length of the filtered stream.
+ */
+ public int getLength()
+ {
+ return data.length;
+ }
+
+ /**
+ * This will get the list of filters that are associated with this stream. Or
+ * null if there are none.
+ * @return A list of all encoding filters to apply to this stream.
+ */
+ public List getFilters()
+ {
+ return null;
+ }
+
+ /**
+ * This will set the filters that are part of this stream.
+ *
+ * @param filters The filters that are part of this stream.
+ */
+ public void setFilters( List filters )
+ {
+ throw new UnsupportedOperationException( "not supported for memory stream" );
+ }
+
+ /**
+ * Get the list of decode parameters. Each entry in the list will refer to
+ * an entry in the filters list.
+ *
+ * @return The list of decode parameters.
+ *
+ * @throws IOException if there is an error retrieving the parameters.
+ */
+ public List getDecodeParams() throws IOException
+ {
+ return null;
+ }
+
+ /**
+ * This will set the list of decode params.
+ *
+ * @param decodeParams The list of decode params.
+ */
+ public void setDecodeParams( List decodeParams )
+ {
+ //do nothing
+ }
+
+ /**
+ * This will get the file specification for this stream. This is only
+ * required for external files.
+ *
+ * @return The file specification.
+ */
+ public PDFileSpecification getFile()
+ {
+ return null;
+ }
+
+ /**
+ * Set the file specification.
+ * @param f The file specification.
+ */
+ public void setFile( PDFileSpecification f )
+ {
+ //do nothing.
+ }
+
+ /**
+ * This will get the list of filters that are associated with this stream. Or
+ * null if there are none.
+ * @return A list of all encoding filters to apply to this stream.
+ */
+ public List getFileFilters()
+ {
+ return null;
+ }
+
+ /**
+ * This will set the filters that are part of this stream.
+ *
+ * @param filters The filters that are part of this stream.
+ */
+ public void setFileFilters( List filters )
+ {
+ //do nothing.
+ }
+
+ /**
+ * Get the list of decode parameters. Each entry in the list will refer to
+ * an entry in the filters list.
+ *
+ * @return The list of decode parameters.
+ *
+ * @throws IOException if there is an error retrieving the parameters.
+ */
+ public List getFileDecodeParams() throws IOException
+ {
+ return null;
+ }
+
+ /**
+ * This will set the list of decode params.
+ *
+ * @param decodeParams The list of decode params.
+ */
+ public void setFileDecodeParams( List decodeParams )
+ {
+ //do nothing
+ }
+
+ /**
+ * This will copy the stream into a byte array.
+ *
+ * @return The byte array of the filteredStream
+ * @throws IOException When getFilteredStream did not work
+ */
+ public byte[] getByteArray() throws IOException
+ {
+ return data;
+ }
+
+ /**
+ * Get the metadata that is part of the document catalog. This will
+ * return null if there is no meta data for this object.
+ *
+ * @return The metadata for this object.
+ */
+ public PDMetadata getMetadata()
+ {
+ return null;
+ }
+
+ /**
+ * Set the metadata for this object. This can be null.
+ *
+ * @param meta The meta data for this object.
+ */
+ public void setMetadata( PDMetadata meta )
+ {
+ //do nothing
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.transform.TransformerException;
+
+import org.apache.jempbox.xmp.XMPMetadata;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * This class represents metadata for various objects in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDMetadata extends PDStream
+{
+
+ /**
+ * This will create a new PDMetadata object.
+ *
+ * @param document The document that the stream will be part of.
+ */
+ public PDMetadata( PDDocument document )
+ {
+ super( document );
+ getStream().setName( "Type", "Metadata" );
+ getStream().setName( "Subtype", "XML" );
+ }
+
+ /**
+ * Constructor. Reads all data from the input stream and embeds it into the
+ * document, this will close the InputStream.
+ *
+ * @param doc The document that will hold the stream.
+ * @param str The stream parameter.
+ * @param filtered True if the stream already has a filter applied.
+ * @throws IOException If there is an error creating the stream in the document.
+ */
+ public PDMetadata( PDDocument doc, InputStream str, boolean filtered ) throws IOException
+ {
+ super( doc, str, filtered );
+ getStream().setName( "Type", "Metadata" );
+ getStream().setName( "Subtype", "XML" );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param str The stream parameter.
+ */
+ public PDMetadata( COSStream str )
+ {
+ super( str );
+ }
+
+ /**
+ * Extract the XMP metadata and create and build an in memory object.
+ * To persist changes back to the PDF you must call importXMPMetadata.
+ *
+ * @return A parsed XMP object.
+ *
+ * @throws IOException If there is an error parsing the XMP data.
+ */
+ public XMPMetadata exportXMPMetadata() throws IOException
+ {
+ return XMPMetadata.load( createInputStream() );
+ }
+
+ /**
+ * Import an XMP stream into the PDF document.
+ *
+ * @param xmp The XMP data.
+ *
+ * @throws IOException If there is an error generating the XML document.
+ * @throws TransformerException If there is an error generating the XML document.
+ */
+ public void importXMPMetadata( XMPMetadata xmp )
+ throws IOException, TransformerException
+ {
+ xmp.save( createOutputStream() );
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * This class represends a PDF Name tree. See the PDF Reference 1.5 section 3.8.5
+ * for more details.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDNameTreeNode implements COSObjectable
+{
+ private static final Log log = LogFactory.getLog(PDNameTreeNode.class);
+ private COSDictionary node;
+ private Class valueType = null;
+
+ /**
+ * Constructor.
+ *
+ * @param valueClass The PD Model type of object that is the value.
+ */
+ public PDNameTreeNode( Class valueClass )
+ {
+ node = new COSDictionary();
+ valueType = valueClass;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dict The dictionary that holds the name information.
+ * @param valueClass The PD Model type of object that is the value.
+ */
+ public PDNameTreeNode( COSDictionary dict, Class valueClass )
+ {
+ node = dict;
+ valueType = valueClass;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return node;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return node;
+ }
+
+ /**
+ * Return the children of this node. This list will contain PDNameTreeNode objects.
+ *
+ * @return The list of children or null if there are no children.
+ */
+ public List getKids()
+ {
+
+ List retval = null;
+ COSArray kids = (COSArray)node.getDictionaryObject( COSName.KIDS );
+ if( kids != null )
+ {
+ List pdObjects = new ArrayList();
+ for( int i=0; i<kids.size(); i++ )
+ {
+ pdObjects.add( createChildNode( (COSDictionary)kids.getObject(i) ) );
+ }
+ retval = new COSArrayList(pdObjects,kids);
+ }
+
+ return retval;
+ }
+
+ /**
+ * Set the children of this named tree.
+ *
+ * @param kids The children of this named tree.
+ */
+ public void setKids( List kids )
+ {
+ if (kids != null && kids.size() > 0)
+ {
+ PDNameTreeNode firstKid = (PDNameTreeNode) kids.get(0);
+ PDNameTreeNode lastKid = (PDNameTreeNode) kids.get(kids.size() - 1);
+ String lowerLimit = firstKid.getLowerLimit();
+ this.setLowerLimit(lowerLimit);
+ String upperLimit = lastKid.getUpperLimit();
+ this.setUpperLimit(upperLimit);
+ }
+ node.setItem( "Kids", COSArrayList.converterToCOSArray( kids ) );
+ }
+
+ /**
+ * The name to retrieve.
+ *
+ * @param name The name in the tree.
+ *
+ * @return The value of the name in the tree.
+ *
+ * @throws IOException If an there is a problem creating the destinations.
+ */
+ public Object getValue( String name ) throws IOException
+ {
+ Object retval = null;
+ Map names = getNames();
+ if( names != null )
+ {
+ retval = names.get( name );
+ }
+ else
+ {
+ List kids = getKids();
+ if (kids != null)
+ {
+ for( int i=0; i<kids.size() && retval == null; i++ )
+ {
+ PDNameTreeNode childNode = (PDNameTreeNode)kids.get( i );
+ if( childNode.getLowerLimit().compareTo( name ) <= 0 &&
+ childNode.getUpperLimit().compareTo( name ) >= 0 )
+ {
+ retval = childNode.getValue( name );
+ }
+ }
+ }
+ else
+ {
+ log.warn("NameTreeNode does not have \"names\" nor \"kids\" objects.");
+ }
+ }
+ return retval;
+ }
+
+
+ /**
+ * This will return a map of names. The key will be a string, and the
+ * value will depend on where this class is being used.
+ *
+ * @return ordered map of cos objects
+ * @throws IOException If there is an error while creating the sub types.
+ */
+ public Map<String, Object> getNames() throws IOException
+ {
+ COSArray namesArray = (COSArray)node.getDictionaryObject( COSName.NAMES );
+ if( namesArray != null )
+ {
+ Map<String, Object> names = new LinkedHashMap<String, Object>();
+ for( int i=0; i<namesArray.size(); i+=2 )
+ {
+ COSString key = (COSString)namesArray.getObject(i);
+ COSBase cosValue = namesArray.getObject( i+1 );
+ names.put( key.getString(), convertCOSToPD( cosValue ) );
+ }
+ return Collections.unmodifiableMap(names);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Method to convert the COS value in the name tree to the PD Model object. The
+ * default implementation will simply use reflection to create the correct object
+ * type. Subclasses can do whatever they want.
+ *
+ * @param base The COS object to convert.
+ * @return The converted PD Model object.
+ * @throws IOException If there is an error during creation.
+ */
+ protected Object convertCOSToPD( COSBase base ) throws IOException
+ {
+ Object retval = null;
+ try
+ {
+ Constructor ctor = valueType.getConstructor( new Class[] { base.getClass() } );
+ retval = ctor.newInstance( new Object[] { base } );
+ }
+ catch( Throwable t )
+ {
+ throw new IOException( "Error while trying to create value in named tree:" + t.getMessage());
+
+ }
+ return retval;
+ }
+
+ /**
+ * Create a child node object.
+ *
+ * @param dic The dictionary for the child node object to refer to.
+ * @return The new child node object.
+ */
+ protected PDNameTreeNode createChildNode( COSDictionary dic )
+ {
+ return new PDNameTreeNode(dic,valueType);
+ }
+
+ /**
+ * Set the names of for this node. The keys should be java.lang.String and the
+ * values must be a COSObjectable. This method will set the appropriate upper and lower
+ * limits based on the keys in the map.
+ *
+ * @param names map of names to objects, or <code>null</code>
+ */
+ public void setNames( Map<String, ? extends COSObjectable> names )
+ {
+ if( names == null )
+ {
+ node.setItem( "Names", (COSObjectable)null );
+ node.setItem( COSName.LIMITS, (COSObjectable)null);
+ }
+ else
+ {
+ COSArray array = new COSArray();
+ List<String> keys = new ArrayList<String>(names.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ array.add(new COSString(key));
+ array.add(names.get(key));
+ }
+ setLowerLimit(keys.get(0));
+ setUpperLimit(keys.get(keys.size() - 1));
+ node.setItem("Names", array);
+ }
+ }
+
+ /**
+ * Get the highest value for a key in the name map.
+ *
+ * @return The highest value for a key in the map.
+ */
+ public String getUpperLimit()
+ {
+ String retval = null;
+ COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+ if( arr != null )
+ {
+ retval = arr.getString( 1 );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the highest value for the key in the map.
+ *
+ * @param upper The new highest value for a key in the map.
+ */
+ private void setUpperLimit( String upper )
+ {
+ COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+ if( arr == null )
+ {
+ arr = new COSArray();
+ arr.add( null );
+ arr.add( null );
+ node.setItem(COSName.LIMITS, arr);
+ }
+ arr.setString( 1, upper );
+ }
+
+ /**
+ * Get the lowest value for a key in the name map.
+ *
+ * @return The lowest value for a key in the map.
+ */
+ public String getLowerLimit()
+ {
+ String retval = null;
+ COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+ if( arr != null )
+ {
+ retval = arr.getString( 0 );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the lowest value for the key in the map.
+ *
+ * @param lower The new lowest value for a key in the map.
+ */
+ private void setLowerLimit( String lower )
+ {
+ COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+ if( arr == null )
+ {
+ arr = new COSArray();
+ arr.add( null );
+ arr.add( null );
+ node.setItem(COSName.LIMITS, arr);
+ }
+ arr.setString( 0, lower );
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * A named text stream is a combination of a name and a PDTextStream object. This
+ * is used in name trees.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDNamedTextStream implements DualCOSObjectable
+{
+ private COSName streamName;
+ private PDTextStream stream;
+
+ /**
+ * Constructor.
+ */
+ public PDNamedTextStream()
+ {
+ //default constructor
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param name The name of the stream.
+ * @param str The stream.
+ */
+ public PDNamedTextStream( COSName name, COSBase str )
+ {
+ streamName = name;
+ stream = PDTextStream.createTextStream( str );
+ }
+
+ /**
+ * The name of the named text stream.
+ *
+ * @return The stream name.
+ */
+ public String getName()
+ {
+ String name = null;
+ if( streamName != null )
+ {
+ name = streamName.getName();
+ }
+ return name;
+ }
+
+ /**
+ * This will set the name of the named text stream.
+ *
+ * @param name The name of the named text stream.
+ */
+ public void setName( String name )
+ {
+ streamName = COSName.getPDFName( name );
+ }
+
+ /**
+ * This will get the stream.
+ *
+ * @return The stream associated with this name.
+ */
+ public PDTextStream getStream()
+ {
+ return stream;
+ }
+
+ /**
+ * This will set the stream.
+ *
+ * @param str The stream associated with this name.
+ */
+ public void setStream( PDTextStream str )
+ {
+ stream = str;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getFirstCOSObject()
+ {
+ return streamName;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getSecondCOSObject()
+ {
+ COSBase retval = null;
+ if( stream != null )
+ {
+ retval = stream.getCOSObject();
+ }
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collections;
+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.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This class represents a PDF Number tree. See the PDF Reference 1.7 section
+ * 7.9.7 for more details.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>,
+ * <a href="igor.podolskiy@ievvwi.uni-stuttgart.de">Igor Podolskiy</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDNumberTreeNode implements COSObjectable
+{
+ private COSDictionary node;
+ private Class valueType = null;
+
+ /**
+ * Constructor.
+ *
+ * @param valueClass The PD Model type of object that is the value.
+ */
+ public PDNumberTreeNode( Class valueClass )
+ {
+ node = new COSDictionary();
+ valueType = valueClass;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dict The dictionary that holds the name information.
+ * @param valueClass The PD Model type of object that is the value.
+ */
+ public PDNumberTreeNode( COSDictionary dict, Class valueClass )
+ {
+ node = dict;
+ valueType = valueClass;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return node;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return node;
+ }
+
+ /**
+ * Return the children of this node. This list will contain PDNumberTreeNode objects.
+ *
+ * @return The list of children or null if there are no children.
+ */
+ public List getKids()
+ {
+
+ List retval = null;
+ COSArray kids = (COSArray)node.getDictionaryObject( COSName.KIDS );
+ if( kids != null )
+ {
+ List pdObjects = new ArrayList();
+ for( int i=0; i<kids.size(); i++ )
+ {
+ pdObjects.add( createChildNode( (COSDictionary)kids.getObject(i) ) );
+ }
+ retval = new COSArrayList(pdObjects,kids);
+ }
+
+ return retval;
+ }
+
+ /**
+ * Set the children of this number tree.
+ *
+ * @param kids The children of this number tree.
+ */
+ public void setKids( List kids )
+ {
+ node.setItem( "Kids", COSArrayList.converterToCOSArray( kids ) );
+ }
+
+ /**
+ * Returns the value corresponding to an index in the number tree.
+ *
+ * @param index The index in the number tree.
+ *
+ * @return The value corresponding to the index.
+ *
+ * @throws IOException If there is a problem creating the values.
+ */
+ public Object getValue( Integer index ) throws IOException
+ {
+ Object retval = null;
+ Map names = getNumbers();
+ if( names != null )
+ {
+ retval = names.get( index );
+ }
+ else
+ {
+ List kids = getKids();
+ for( int i=0; i<kids.size() && retval == null; i++ )
+ {
+ PDNumberTreeNode childNode = (PDNumberTreeNode)kids.get( i );
+ if( childNode.getLowerLimit().compareTo( index ) <= 0 &&
+ childNode.getUpperLimit().compareTo( index ) >= 0 )
+ {
+ retval = childNode.getValue( index );
+ }
+ }
+ }
+ return retval;
+ }
+
+
+ /**
+ * This will return a map of numbers. The key will be a java.lang.Integer, the value will
+ * depend on where this class is being used.
+ *
+ * @return A map of COS objects.
+ *
+ * @throws IOException If there is a problem creating the values.
+ */
+ public Map getNumbers() throws IOException
+ {
+ Map indices = null;
+ COSArray namesArray = (COSArray)node.getDictionaryObject( COSName.NUMS );
+ if( namesArray != null )
+ {
+ indices = new HashMap();
+ for( int i=0; i<namesArray.size(); i+=2 )
+ {
+ COSInteger key = (COSInteger)namesArray.getObject(i);
+ COSBase cosValue = namesArray.getObject( i+1 );
+ Object pdValue = convertCOSToPD( cosValue );
+
+ indices.put( Integer.valueOf(key.intValue()), pdValue );
+ }
+ indices = Collections.unmodifiableMap(indices);
+ }
+
+ return indices;
+ }
+
+ /**
+ * Method to convert the COS value in the name tree to the PD Model object. The
+ * default implementation will simply use reflection to create the correct object
+ * type. Subclasses can do whatever they want.
+ *
+ * @param base The COS object to convert.
+ * @return The converted PD Model object.
+ * @throws IOException If there is an error during creation.
+ */
+ protected Object convertCOSToPD( COSBase base ) throws IOException
+ {
+ Object retval = null;
+ try
+ {
+ Constructor ctor = valueType.getConstructor( new Class[] { base.getClass() } );
+ retval = ctor.newInstance( new Object[] { base } );
+ }
+ catch( Throwable t )
+ {
+ throw new IOException( "Error while trying to create value in number tree:" + t.getMessage());
+
+ }
+ return retval;
+ }
+
+ /**
+ * Create a child node object.
+ *
+ * @param dic The dictionary for the child node object to refer to.
+ * @return The new child node object.
+ */
+ protected PDNumberTreeNode createChildNode( COSDictionary dic )
+ {
+ return new PDNumberTreeNode(dic,valueType);
+ }
+
+ /**
+ * Set the names of for this node. The keys should be java.lang.String and the
+ * values must be a COSObjectable. This method will set the appropriate upper and lower
+ * limits based on the keys in the map.
+ *
+ * @param numbers The map of names to objects.
+ */
+ public void setNumbers( Map numbers )
+ {
+ if( numbers == null )
+ {
+ node.setItem( COSName.NUMS, (COSObjectable)null );
+ node.setItem( "Limits", (COSObjectable)null);
+ }
+ else
+ {
+ List keys = new ArrayList( numbers.keySet() );
+ Collections.sort( keys );
+ COSArray array = new COSArray();
+ for( int i=0; i<keys.size(); i++ )
+ {
+ Integer key = (Integer)keys.get(i);
+ array.add( COSInteger.get( key ) );
+ COSObjectable obj = (COSObjectable)numbers.get( key );
+ array.add( obj );
+ }
+ Integer lower = null;
+ Integer upper = null;
+ if( keys.size() > 0 )
+ {
+ lower = (Integer)keys.get( 0 );
+ upper = (Integer)keys.get( keys.size()-1 );
+ }
+ setUpperLimit( upper );
+ setLowerLimit( lower );
+ node.setItem( "Names", array );
+ }
+ }
+
+ /**
+ * Get the highest value for a key in the name map.
+ *
+ * @return The highest value for a key in the map.
+ */
+ public Integer getUpperLimit()
+ {
+ Integer retval = null;
+ COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+ if( arr != null )
+ {
+ retval = Integer.valueOf(arr.getInt( 1 ));
+ }
+ return retval;
+ }
+
+ /**
+ * Set the highest value for the key in the map.
+ *
+ * @param upper The new highest value for a key in the map.
+ */
+ private void setUpperLimit( Integer upper )
+ {
+ COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+ if( arr == null )
+ {
+ arr = new COSArray();
+ arr.add( null );
+ arr.add( null );
+ }
+ arr.setInt( 1, upper.intValue() );
+ }
+
+ /**
+ * Get the lowest value for a key in the name map.
+ *
+ * @return The lowest value for a key in the map.
+ */
+ public Integer getLowerLimit()
+ {
+ Integer retval = null;
+ COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+ if( arr != null )
+ {
+ retval = Integer.valueOf(arr.getInt( 0 ));
+ }
+ return retval;
+ }
+
+ /**
+ * Set the lowest value for the key in the map.
+ *
+ * @param lower The new lowest value for a key in the map.
+ */
+ private void setLowerLimit( Integer lower )
+ {
+ COSArray arr = (COSArray)node.getDictionaryObject( COSName.LIMITS );
+ if( arr == null )
+ {
+ arr = new COSArray();
+ arr.add( null );
+ arr.add( null );
+ }
+ arr.setInt( 0, lower.intValue() );
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+
+
+/**
+ * A PDStream represents a stream in a PDF document. Streams are tied to a single
+ * PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDObjectStream extends PDStream
+{
+
+ /**
+ * Constructor.
+ *
+ * @param str The stream parameter.
+ */
+ public PDObjectStream( COSStream str )
+ {
+ super( str );
+ }
+
+ /**
+ * This will create a new PDStream object.
+ *
+ * @param document The document that the stream will be part of.
+ * @return A new stream object.
+ */
+ public static PDObjectStream createStream( PDDocument document )
+ {
+ COSStream cosStream = new COSStream( document.getDocument().getScratchFile() );
+ PDObjectStream strm = new PDObjectStream( cosStream );
+ strm.getStream().setName( "Type", "ObjStm" );
+ return strm;
+ }
+
+ /**
+ * Get the type of this object, should always return "ObjStm".
+ *
+ * @return The type of this object.
+ */
+ public String getType()
+ {
+ return getStream().getNameAsString( "Type" );
+ }
+
+ /**
+ * Get the number of compressed object.
+ *
+ * @return The number of compressed objects.
+ */
+ public int getNumberOfObjects()
+ {
+ return getStream().getInt( "N", 0 );
+ }
+
+ /**
+ * Set the number of objects.
+ *
+ * @param n The new number of objects.
+ */
+ public void setNumberOfObjects( int n )
+ {
+ getStream().setInt( "N", n );
+ }
+
+ /**
+ * The byte offset (in the decoded stream) of the first compressed object.
+ *
+ * @return The byte offset to the first object.
+ */
+ public int getFirstByteOffset()
+ {
+ return getStream().getInt( "First", 0 );
+ }
+
+ /**
+ * The byte offset (in the decoded stream) of the first compressed object.
+ *
+ * @param n The byte offset to the first object.
+ */
+ public void setFirstByteOffset( int n )
+ {
+ getStream().setInt( "First", n );
+ }
+
+ /**
+ * A reference to an object stream, of which the current object stream is
+ * considered an extension.
+ *
+ * @return The object that this stream is an extension.
+ */
+ public PDObjectStream getExtends()
+ {
+ PDObjectStream retval = null;
+ COSStream stream = (COSStream)getStream().getDictionaryObject( COSName.EXTENDS );
+ if( stream != null )
+ {
+ retval = new PDObjectStream( stream );
+ }
+ return retval;
+
+ }
+
+ /**
+ * A reference to an object stream, of which the current object stream is
+ * considered an extension.
+ *
+ * @param stream The object stream extension.
+ */
+ public void setExtends( PDObjectStream stream )
+ {
+ getStream().setItem( COSName.EXTENDS, stream );
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * Contains information for a page label range.
+ *
+ * @author <a href="mailto:igor.podolskiy@ievvwi.uni-stuttgart.de">Igor
+ * Podolskiy</a>
+ *
+ * @see PDPageLabels
+ *
+ * @version $Revision$
+ */
+public class PDPageLabelRange implements COSObjectable
+{
+
+ private COSDictionary root;
+
+ // Page label dictonary (PDF32000-1:2008 Section 12.4.2, Table 159)
+ private static final COSName KEY_START = COSName.getPDFName("St");
+ private static final COSName KEY_PREFIX = COSName.P;
+ private static final COSName KEY_STYLE = COSName.getPDFName("S");
+
+ // Style entry values (PDF32000-1:2008 Section 12.4.2, Table 159)
+
+ /**
+ * Decimal page numbering style (1, 2, 3, ...).
+ */
+ public static final String STYLE_DECIMAL = "D";
+
+ /**
+ * Roman numbers (upper case) numbering style (I, II, III, IV, ...).
+ */
+ public static final String STYLE_ROMAN_UPPER = "R";
+
+ /**
+ * Roman numbers (lower case) numbering style (i, ii, iii, iv, ...).
+ */
+ public static final String STYLE_ROMAN_LOWER = "r";
+
+ /**
+ * Letter (upper case) numbering style (A, B, ..., Z, AA, BB, ..., ZZ, AAA,
+ * ...).
+ */
+ public static final String STYLE_LETTERS_UPPER = "A";
+
+ /**
+ * Letter (lower case) numbering style (a, b, ..., z, aa, bb, ..., zz, aaa,
+ * ...).
+ */
+ public static final String STYLE_LETTERS_LOWER = "a";
+
+ /**
+ * Creates a new empty page label range object.
+ */
+ public PDPageLabelRange()
+ {
+ this(new COSDictionary());
+ }
+
+ /**
+ * Creates a new page label range object from the given dictionary.
+ *
+ * @param dict
+ * the base dictionary for the new object.
+ */
+ public PDPageLabelRange(COSDictionary dict)
+ {
+ root = dict;
+ }
+
+ /**
+ * Returns the underlying dictionary.
+ *
+ * @return the underlying dictionary.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return root;
+ }
+
+ public COSBase getCOSObject()
+ {
+ return root;
+ }
+
+ /**
+ * Returns the numbering style for this page range.
+ *
+ * @return one of the STYLE_* constants
+ */
+ public String getStyle()
+ {
+ return root.getNameAsString(KEY_STYLE);
+ }
+
+ /**
+ * Sets the numbering style for this page range.
+ *
+ * @param style
+ * one of the STYLE_* constants or {@code null} if no page
+ * numbering is desired.
+ */
+ public void setStyle(String style)
+ {
+ if (style != null)
+ {
+ root.setName(KEY_STYLE, style);
+ }
+ else
+ {
+ root.removeItem(KEY_STYLE);
+ }
+ }
+
+ /**
+ * Returns the start value for page numbering in this page range.
+ *
+ * @return a positive integer the start value for numbering.
+ */
+ public int getStart()
+ {
+ return root.getInt(KEY_START, 1);
+ }
+
+ /**
+ * Sets the start value for page numbering in this page range.
+ *
+ * @param start
+ * a positive integer representing the start value.
+ * @throws IllegalArgumentException
+ * if {@code start} is not a positive integer
+ */
+ public void setStart(int start)
+ {
+ if (start <= 0)
+ {
+ throw new IllegalArgumentException(
+ "The page numbering start value must be a positive integer");
+ }
+ root.setInt(KEY_START, start);
+ }
+
+ /**
+ * Returns the page label prefix for this page range.
+ *
+ * @return the page label prefix for this page range, or {@code null} if no
+ * prefix has been defined.
+ */
+ public String getPrefix()
+ {
+ return root.getString(KEY_PREFIX);
+ }
+
+ /**
+ * Sets the page label prefix for this page range.
+ *
+ * @param prefix
+ * the page label prefix for this page range, or {@code null} to
+ * unset the prefix.
+ */
+ public void setPrefix(String prefix)
+ {
+ if (prefix != null)
+ {
+ root.setString(KEY_PREFIX, prefix);
+ }
+ else
+ {
+ root.removeItem(KEY_PREFIX);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+
+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.pdmodel.PDDocument;
+
+/**
+ * Represents the page label dictionary of a document.
+ *
+ * @author <a href="mailto:igor.podolskiy@ievvwi.uni-stuttgart.de">Igor
+ * Podolskiy</a>
+ * @version $Revision$
+ */
+public class PDPageLabels implements COSObjectable
+{
+
+ private SortedMap<Integer, PDPageLabelRange> labels;
+
+ private PDDocument doc;
+
+ /**
+ * Creates an empty page label dictionary for the given document.
+ *
+ * <p>
+ * Note that the page label dictionary won't be automatically added to the
+ * document; you will still need to do it manually (see
+ * {@link PDDocumentCatalog#setPageLabels(PDPageLabels)}.
+ * </p>
+ *
+ * @param document
+ * The document the page label dictionary is created for.
+ * @see PDDocumentCatalog#setPageLabels(PDPageLabels)
+ */
+ public PDPageLabels(PDDocument document)
+ {
+ labels = new TreeMap<Integer, PDPageLabelRange>();
+ this.doc = document;
+ PDPageLabelRange defaultRange = new PDPageLabelRange();
+ defaultRange.setStyle(PDPageLabelRange.STYLE_DECIMAL);
+ labels.put(0, defaultRange);
+ }
+
+ /**
+ * Creates an page label dictionary for a document using the information in
+ * the given COS dictionary.
+ *
+ * <p>
+ * Note that the page label dictionary won't be automatically added to the
+ * document; you will still need to do it manually (see
+ * {@link PDDocumentCatalog#setPageLabels(PDPageLabels)}.
+ * </p>
+ *
+ * @param document
+ * The document the page label dictionary is created for.
+ * @param dict
+ * an existing page label dictionary
+ * @see PDDocumentCatalog#setPageLabels(PDPageLabels)
+ * @throws IOException
+ * If something goes wrong during the number tree conversion.
+ */
+ public PDPageLabels(PDDocument document, COSDictionary dict) throws IOException
+ {
+ this(document);
+ if (dict == null)
+ {
+ return;
+ }
+ PDNumberTreeNode root = new PDNumberTreeNode(dict, COSDictionary.class);
+ findLabels(root);
+ }
+
+ private void findLabels(PDNumberTreeNode node) throws IOException {
+ if (node.getKids() != null) {
+ List<PDNumberTreeNode> kids = node.getKids();
+ for (PDNumberTreeNode kid : kids) {
+ findLabels(kid);
+ }
+ }
+ else if (node.getNumbers() != null) {
+ Map<Integer, COSDictionary> numbers = node.getNumbers();
+ for (Entry<Integer, COSDictionary> i : numbers.entrySet())
+ {
+ if(i.getKey() >= 0)
+ labels.put(i.getKey(), new PDPageLabelRange(i.getValue()));
+ }
+ }
+ }
+
+
+ /**
+ * Returns the number of page label ranges.
+ *
+ * <p>
+ * This will be always >= 1, as the required default entry for the page
+ * range starting at the first page is added automatically by this
+ * implementation (see PDF32000-1:2008, p. 375).
+ * </p>
+ *
+ * @return the number of page label ranges.
+ */
+ public int getPageRangeCount()
+ {
+ return labels.size();
+ }
+
+ /**
+ * Returns the page label range starting at the given page, or {@code null}
+ * if no such range is defined.
+ *
+ * @param startPage
+ * the 0-based page index representing the start page of the page
+ * range the item is defined for.
+ * @return the page label range or {@code null} if no label range is defined
+ * for the given start page.
+ */
+ public PDPageLabelRange getPageLabelRange(int startPage)
+ {
+ return labels.get(startPage);
+ }
+
+ /**
+ * Sets the page label range beginning at the specified start page.
+ *
+ * @param startPage
+ * the 0-based index of the page representing the start of the
+ * page label range.
+ * @param item
+ * the page label item to set.
+ */
+ public void setLabelItem(int startPage, PDPageLabelRange item)
+ {
+ labels.put(startPage, item);
+ }
+
+ public COSBase getCOSObject()
+ {
+ COSDictionary dict = new COSDictionary();
+ COSArray arr = new COSArray();
+ for (Entry<Integer, PDPageLabelRange> i : labels.entrySet())
+ {
+ arr.add(COSInteger.get(i.getKey()));
+ arr.add(i.getValue());
+ }
+ dict.setItem(COSName.NUMS, arr);
+ return dict;
+ }
+
+ /**
+ * Returns a mapping with computed page labels as keys and corresponding
+ * 0-based page indices as values. The returned map will contain at most as
+ * much entries as the document has pages.
+ *
+ * <p>
+ * <strong>NOTE:</strong> If the document contains duplicate page labels,
+ * the returned map will contain <em>less</em> entries than the document has
+ * pages. The page index returned in this case is the <em>highest</em> index
+ * among all pages sharing the same label.
+ * </p>
+ *
+ * @return a mapping from labels to 0-based page indices.
+ */
+ public Map<String, Integer> getPageIndicesByLabels()
+ {
+ final Map<String, Integer> labelMap =
+ new HashMap<String, Integer>(doc.getNumberOfPages());
+ computeLabels(new LabelHandler()
+ {
+ public void newLabel(int pageIndex, String label)
+ {
+ labelMap.put(label, pageIndex);
+ }
+ });
+ return labelMap;
+ }
+
+ /**
+ * Returns a mapping with 0-based page indices as keys and corresponding
+ * page labels as values as an array. The array will have exactly as much
+ * entries as the document has pages.
+ *
+ * @return an array mapping from 0-based page indices to labels.
+ */
+ public String[] getLabelsByPageIndices()
+ {
+ final String[] map = new String[doc.getNumberOfPages()];
+ computeLabels(new LabelHandler()
+ {
+ public void newLabel(int pageIndex, String label)
+ {
+ if(pageIndex < doc.getNumberOfPages())
+ {
+ map[pageIndex] = label;
+ }
+ }
+ });
+ return map;
+ }
+
+ /**
+ * Internal interface for the control flow support.
+ *
+ * @author Igor Podolskiy
+ */
+ private static interface LabelHandler
+ {
+ public void newLabel(int pageIndex, String label);
+ }
+
+ private void computeLabels(LabelHandler handler)
+ {
+ Iterator<Entry<Integer, PDPageLabelRange>> iterator =
+ labels.entrySet().iterator();
+ if (!iterator.hasNext())
+ {
+ return;
+ }
+ int pageIndex = 0;
+ Entry<Integer, PDPageLabelRange> lastEntry = iterator.next();
+ while (iterator.hasNext())
+ {
+ Entry<Integer, PDPageLabelRange> entry = iterator.next();
+ int numPages = entry.getKey() - lastEntry.getKey();
+ LabelGenerator gen = new LabelGenerator(lastEntry.getValue(),
+ numPages);
+ while (gen.hasNext())
+ {
+ handler.newLabel(pageIndex, gen.next());
+ pageIndex++;
+ }
+ lastEntry = entry;
+ }
+ LabelGenerator gen = new LabelGenerator(lastEntry.getValue(),
+ doc.getNumberOfPages() - lastEntry.getKey());
+ while (gen.hasNext())
+ {
+ handler.newLabel(pageIndex, gen.next());
+ pageIndex++;
+ }
+ }
+
+ /**
+ * Generates the labels in a page range.
+ *
+ * @author Igor Podolskiy
+ *
+ */
+ private static class LabelGenerator implements Iterator<String>
+ {
+ private PDPageLabelRange labelInfo;
+ private int numPages;
+ private int currentPage;
+
+ public LabelGenerator(PDPageLabelRange label, int pages)
+ {
+ this.labelInfo = label;
+ this.numPages = pages;
+ this.currentPage = 0;
+ }
+
+ public boolean hasNext()
+ {
+ return currentPage < numPages;
+ }
+
+ public String next()
+ {
+ if (!hasNext())
+ {
+ throw new NoSuchElementException();
+ }
+ StringBuilder buf = new StringBuilder();
+ if (labelInfo.getPrefix() != null)
+ {
+ buf.append(labelInfo.getPrefix());
+ }
+ if (labelInfo.getStyle() != null)
+ {
+ buf.append(getNumber(labelInfo.getStart() + currentPage,
+ labelInfo.getStyle()));
+ }
+ currentPage++;
+ return buf.toString();
+ }
+
+ private String getNumber(int pageIndex, String style)
+ {
+ if (PDPageLabelRange.STYLE_DECIMAL.equals(style))
+ {
+ return Integer.toString(pageIndex);
+ }
+ else if (PDPageLabelRange.STYLE_LETTERS_LOWER.equals(style))
+ {
+ return makeLetterLabel(pageIndex);
+ }
+ else if (PDPageLabelRange.STYLE_LETTERS_UPPER.equals(style))
+ {
+ return makeLetterLabel(pageIndex).toUpperCase();
+ }
+ else if (PDPageLabelRange.STYLE_ROMAN_LOWER.equals(style))
+ {
+ return makeRomanLabel(pageIndex);
+ }
+ else if (PDPageLabelRange.STYLE_ROMAN_UPPER.equals(style))
+ {
+ return makeRomanLabel(pageIndex).toUpperCase();
+ }
+ else
+ {
+ // Fall back to decimals.
+ return Integer.toString(pageIndex);
+ }
+ }
+
+ /**
+ * Lookup table used by the {@link #makeRomanLabel(int)} method.
+ */
+ private static final String[][] ROMANS = new String[][] {
+ { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" },
+ { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" },
+ { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" }
+ };
+
+ private static String makeRomanLabel(int pageIndex)
+ {
+ StringBuilder buf = new StringBuilder();
+ int power = 0;
+ while (power < 3 && pageIndex > 0)
+ {
+ buf.insert(0, ROMANS[power][pageIndex % 10]);
+ pageIndex = pageIndex / 10;
+ power++;
+ }
+ // Prepend as many m as there are thousands (which is
+ // incorrect by the roman numeral rules for numbers > 3999,
+ // but is unbounded and Adobe Acrobat does it this way).
+ // This code is somewhat inefficient for really big numbers,
+ // but those don't occur too often (and the numbers in those cases
+ // would be incomprehensible even if we and Adobe
+ // used strict Roman rules).
+ for (int i = 0; i < pageIndex; i++)
+ {
+ buf.insert(0, 'm');
+ }
+ return buf.toString();
+ }
+
+ /**
+ * A..Z, AA..ZZ, AAA..ZZZ ... labeling as described in PDF32000-1:2008,
+ * Table 159, Page 375.
+ */
+ private static String makeLetterLabel(int num)
+ {
+ StringBuilder buf = new StringBuilder();
+ int numLetters = num / 26 + Integer.signum(num % 26);
+ int letter = num % 26 + 26 * (1 - Integer.signum(num % 26)) + 64;
+ for (int i = 0; i < numLetters; i++)
+ {
+ buf.appendCodePoint(letter);
+ }
+ return buf.toString();
+ }
+
+ public void remove()
+ {
+ // This is a generator, no removing allowed.
+ throw new UnsupportedOperationException();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+/**
+ * This class will be used to signify a range. a(min) <= a* <= a(max)
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDRange implements COSObjectable
+{
+ private COSArray rangeArray;
+ private int startingIndex;
+
+ /**
+ * Constructor with an initial range of 0..1.
+ */
+ public PDRange()
+ {
+ rangeArray = new COSArray();
+ rangeArray.add( new COSFloat( 0.0f ) );
+ rangeArray.add( new COSFloat( 1.0f ) );
+ startingIndex = 0;
+ }
+
+ /**
+ * Constructor assumes a starting index of 0.
+ *
+ * @param range The array that describes the range.
+ */
+ public PDRange( COSArray range )
+ {
+ rangeArray = range;
+ }
+
+ /**
+ * Constructor with an index into an array. Because some arrays specify
+ * multiple ranges ie [ 0,1, 0,2, 2,3 ] It is convenient for this
+ * class to take an index into an array. So if you want this range to
+ * represent 0,2 in the above example then you would say <code>new PDRange( array, 1 )</code>.
+ *
+ * @param range The array that describes the index
+ * @param index The range index into the array for the start of the range.
+ */
+ public PDRange( COSArray range, int index )
+ {
+ rangeArray = range;
+ startingIndex = index;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return rangeArray;
+ }
+
+ /**
+ * This will get the underlying array value.
+ *
+ * @return The cos object that this object wraps.
+ */
+ public COSArray getCOSArray()
+ {
+ return rangeArray;
+ }
+
+ /**
+ * This will get the minimum value of the range.
+ *
+ * @return The min value.
+ */
+ public float getMin()
+ {
+ COSNumber min = (COSNumber)rangeArray.getObject( startingIndex*2 );
+ return min.floatValue();
+ }
+
+ /**
+ * This will set the minimum value for the range.
+ *
+ * @param min The new minimum for the range.
+ */
+ public void setMin( float min )
+ {
+ rangeArray.set( startingIndex*2, new COSFloat( min ) );
+ }
+
+ /**
+ * This will get the maximum value of the range.
+ *
+ * @return The max value.
+ */
+ public float getMax()
+ {
+ COSNumber max = (COSNumber)rangeArray.getObject( startingIndex*2+1 );
+ return max.floatValue();
+ }
+
+ /**
+ * This will set the maximum value for the range.
+ *
+ * @param max The new maximum for the range.
+ */
+ public void setMax( float max )
+ {
+ rangeArray.set( startingIndex*2+1, new COSFloat( max ) );
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.fontbox.util.BoundingBox;
+
+import java.awt.Dimension;
+
+/**
+ * This represents a rectangle in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.12 $
+ */
+public class PDRectangle implements COSObjectable
+{
+ private COSArray rectArray;
+
+ /**
+ * Constructor.
+ *
+ * Initializes to 0,0,0,0
+ */
+ public PDRectangle()
+ {
+ rectArray = new COSArray();
+ rectArray.add( new COSFloat( 0.0f ) );
+ rectArray.add( new COSFloat( 0.0f ) );
+ rectArray.add( new COSFloat( 0.0f ) );
+ rectArray.add( new COSFloat( 0.0f ) );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param width The width of the rectangle.
+ * @param height The height of the rectangle.
+ */
+ public PDRectangle( float width, float height )
+ {
+ rectArray = new COSArray();
+ rectArray.add( new COSFloat( 0.0f ) );
+ rectArray.add( new COSFloat( 0.0f ) );
+ rectArray.add( new COSFloat( width ) );
+ rectArray.add( new COSFloat( height ) );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param box The non PD bouding box.
+ */
+ public PDRectangle( BoundingBox box )
+ {
+ rectArray = new COSArray();
+ rectArray.add( new COSFloat( box.getLowerLeftX() ) );
+ rectArray.add( new COSFloat( box.getLowerLeftY() ) );
+ rectArray.add( new COSFloat( box.getUpperRightX() ) );
+ rectArray.add( new COSFloat( box.getUpperRightY() ) );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array An array of numbers as specified in the PDF Reference for a rectangle type.
+ */
+ public PDRectangle( COSArray array )
+ {
+ rectArray = array;
+ }
+
+ /**
+ * Method to determine if the x/y point is inside this rectangle.
+ * @param x The x-coordinate to test.
+ * @param y The y-coordinate to test.
+ * @return True if the point is inside this rectangle.
+ */
+ public boolean contains( float x, float y )
+ {
+ float llx = getLowerLeftX();
+ float urx = getUpperRightX();
+ float lly = getLowerLeftY();
+ float ury = getUpperRightY();
+ return x >= llx && x <= urx &&
+ y >= lly && y <= ury;
+ }
+
+ /**
+ * This will create a translated rectangle based off of this rectangle, such
+ * that the new rectangle retains the same dimensions(height/width), but the
+ * lower left x,y values are zero. <br />
+ * 100, 100, 400, 400 (llx, lly, urx, ury ) <br />
+ * will be translated to 0,0,300,300
+ *
+ * @return A new rectangle that has been translated back to the origin.
+ */
+ public PDRectangle createRetranslatedRectangle()
+ {
+ PDRectangle retval = new PDRectangle();
+ retval.setUpperRightX( getWidth() );
+ retval.setUpperRightY( getHeight() );
+ return retval;
+ }
+
+ /**
+ * This will get the underlying array for this rectangle.
+ *
+ * @return The cos array.
+ */
+ public COSArray getCOSArray()
+ {
+ return rectArray;
+ }
+
+ /**
+ * This will get the lower left x coordinate.
+ *
+ * @return The lower left x.
+ */
+ public float getLowerLeftX()
+ {
+ return ((COSNumber)rectArray.get(0)).floatValue();
+ }
+
+ /**
+ * This will set the lower left x coordinate.
+ *
+ * @param value The lower left x.
+ */
+ public void setLowerLeftX(float value)
+ {
+ rectArray.set(0, new COSFloat( value ) );
+ }
+
+ /**
+ * This will get the lower left y coordinate.
+ *
+ * @return The lower left y.
+ */
+ public float getLowerLeftY()
+ {
+ return ((COSNumber)rectArray.get(1)).floatValue();
+ }
+
+ /**
+ * This will set the lower left y coordinate.
+ *
+ * @param value The lower left y.
+ */
+ public void setLowerLeftY(float value)
+ {
+ rectArray.set(1, new COSFloat( value ) );
+ }
+
+ /**
+ * This will get the upper right x coordinate.
+ *
+ * @return The upper right x .
+ */
+ public float getUpperRightX()
+ {
+ return ((COSNumber)rectArray.get(2)).floatValue();
+ }
+
+ /**
+ * This will set the upper right x coordinate.
+ *
+ * @param value The upper right x .
+ */
+ public void setUpperRightX(float value)
+ {
+ rectArray.set(2, new COSFloat( value ) );
+ }
+
+ /**
+ * This will get the upper right y coordinate.
+ *
+ * @return The upper right y.
+ */
+ public float getUpperRightY()
+ {
+ return ((COSNumber)rectArray.get(3)).floatValue();
+ }
+
+ /**
+ * This will set the upper right y coordinate.
+ *
+ * @param value The upper right y.
+ */
+ public void setUpperRightY(float value)
+ {
+ rectArray.set(3, new COSFloat( value ) );
+ }
+
+ /**
+ * This will get the width of this rectangle as calculated by
+ * upperRightX - lowerLeftX.
+ *
+ * @return The width of this rectangle.
+ */
+ public float getWidth()
+ {
+ return getUpperRightX() - getLowerLeftX();
+ }
+
+ /**
+ * This will get the height of this rectangle as calculated by
+ * upperRightY - lowerLeftY.
+ *
+ * @return The height of this rectangle.
+ */
+ public float getHeight()
+ {
+ return getUpperRightY() - getLowerLeftY();
+ }
+
+ /**
+ * A convenience method to create a dimension object for AWT operations.
+ *
+ * @return A dimension that matches the width and height of this rectangle.
+ */
+ public Dimension createDimension()
+ {
+ return new Dimension( (int)getWidth(), (int)getHeight() );
+ }
+
+ /**
+ * This will move the rectangle the given relative amount.
+ *
+ * @param horizontalAmount positive values will move rectangle to the right, negative's to the left.
+ * @param verticalAmount positive values will move the rectangle up, negative's down.
+ */
+ public void move(float horizontalAmount, float verticalAmount)
+ {
+ setUpperRightX(getUpperRightX() + horizontalAmount);
+ setLowerLeftX(getLowerLeftX() + horizontalAmount);
+ setUpperRightY(getUpperRightY() + verticalAmount);
+ setLowerLeftY(getLowerLeftY() + verticalAmount);
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return rectArray;
+ }
+
+
+ /**
+ * This will return a string representation of this rectangle.
+ *
+ * @return This object as a string.
+ */
+ public String toString()
+ {
+ return "[" + getLowerLeftX() + "," + getLowerLeftY() + "," +
+ getUpperRightX() + "," + getUpperRightY() +"]";
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.util.ArrayList;
+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.COSStream;
+
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterManager;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * A PDStream represents a stream in a PDF document. Streams are tied to a single
+ * PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.17 $
+ */
+public class PDStream implements COSObjectable
+{
+ private COSStream stream;
+
+ /**
+ * This will create a new PDStream object.
+ */
+ protected PDStream()
+ {
+ //should only be called by PDMemoryStream
+ }
+
+ /**
+ * This will create a new PDStream object.
+ *
+ * @param document The document that the stream will be part of.
+ */
+ public PDStream( PDDocument document )
+ {
+ stream = new COSStream( document.getDocument().getScratchFile() );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param str The stream parameter.
+ */
+ public PDStream( COSStream str )
+ {
+ stream = str;
+ }
+
+ /**
+ * Constructor. Reads all data from the input stream and embeds it into the
+ * document, this will close the InputStream.
+ *
+ * @param doc The document that will hold the stream.
+ * @param str The stream parameter.
+ * @throws IOException If there is an error creating the stream in the document.
+ */
+ public PDStream( PDDocument doc, InputStream str ) throws IOException
+ {
+ this( doc, str, false );
+ }
+
+ /**
+ * Constructor. Reads all data from the input stream and embeds it into the
+ * document, this will close the InputStream.
+ *
+ * @param doc The document that will hold the stream.
+ * @param str The stream parameter.
+ * @param filtered True if the stream already has a filter applied.
+ * @throws IOException If there is an error creating the stream in the document.
+ */
+ public PDStream( PDDocument doc, InputStream str, boolean filtered ) throws IOException
+ {
+ OutputStream output = null;
+ try
+ {
+ stream = new COSStream( doc.getDocument().getScratchFile() );
+ if( filtered )
+ {
+ output = stream.createFilteredStream();
+ }
+ else
+ {
+ output = stream.createUnfilteredStream();
+ }
+ byte[] buffer = new byte[ 1024 ];
+ int amountRead = -1;
+ while( (amountRead = str.read(buffer)) != -1 )
+ {
+ output.write( buffer, 0, amountRead );
+ }
+ }
+ finally
+ {
+ if( output != null )
+ {
+ output.close();
+ }
+ if( str != null )
+ {
+ str.close();
+ }
+ }
+ }
+
+ /**
+ * If there are not compression filters on the current stream then this
+ * will add a compression filter, flate compression for example.
+ */
+ public void addCompression()
+ {
+ List filters = getFilters();
+ if( filters == null )
+ {
+ filters = new ArrayList();
+ filters.add( COSName.FLATE_DECODE );
+ setFilters( filters );
+ }
+ }
+
+ /**
+ * Create a pd stream from either a regular COSStream on a COSArray of cos streams.
+ * @param base Either a COSStream or COSArray.
+ * @return A PDStream or null if base is null.
+ * @throws IOException If there is an error creating the PDStream.
+ */
+ public static PDStream createFromCOS( COSBase base ) throws IOException
+ {
+ PDStream retval = null;
+ if( base instanceof COSStream )
+ {
+ retval = new PDStream( (COSStream)base );
+ }
+ else if( base instanceof COSArray )
+ {
+ if (((COSArray)base).size() > 0) {
+ retval = new PDStream( new COSStreamArray( (COSArray)base ) );
+ }
+ }
+ else
+ {
+ if( base != null )
+ {
+ throw new IOException( "Contents are unknown type:" + base.getClass().getName() );
+ }
+ }
+ return retval;
+ }
+
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return stream;
+ }
+
+ /**
+ * This will get a stream that can be written to.
+ *
+ * @return An output stream to write data to.
+ *
+ * @throws IOException If an IO error occurs during writing.
+ */
+ public OutputStream createOutputStream() throws IOException
+ {
+ return stream.createUnfilteredStream();
+ }
+
+ /**
+ * This will get a stream that can be read from.
+ *
+ * @return An input stream that can be read from.
+ *
+ * @throws IOException If an IO error occurs during reading.
+ */
+ public InputStream createInputStream() throws IOException
+ {
+ return stream.getUnfilteredStream();
+ }
+
+ /**
+ * This will get a stream with some filters applied but not others. This is useful
+ * when doing images, ie filters = [flate,dct], we want to remove flate but leave dct
+ *
+ * @param stopFilters A list of filters to stop decoding at.
+ * @return A stream with decoded data.
+ * @throws IOException If there is an error processing the stream.
+ */
+ public InputStream getPartiallyFilteredStream( List stopFilters ) throws IOException
+ {
+ FilterManager manager = stream.getFilterManager();
+ InputStream is = stream.getFilteredStream();
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ List filters = getFilters();
+ String nextFilter = null;
+ boolean done = false;
+ for( int i=0; i<filters.size() && !done; i++ )
+ {
+ os.reset();
+ nextFilter = (String)filters.get( i );
+ if( stopFilters.contains( nextFilter ) )
+ {
+ done = true;
+ }
+ else
+ {
+ Filter filter = manager.getFilter( COSName.getPDFName(nextFilter) );
+ filter.decode( is, os, stream, i );
+ is = new ByteArrayInputStream( os.toByteArray() );
+ }
+ }
+ return is;
+ }
+
+ /**
+ * Get the cos stream associated with this object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSStream getStream()
+ {
+ return stream;
+ }
+
+ /**
+ * This will get the length of the filtered/compressed stream. This is readonly in the
+ * PD Model and will be managed by this class.
+ *
+ * @return The length of the filtered stream.
+ */
+ public int getLength()
+ {
+ return stream.getInt( "Length", 0 );
+ }
+
+ /**
+ * This will get the list of filters that are associated with this stream. Or
+ * null if there are none.
+ * @return A list of all encoding filters to apply to this stream.
+ */
+ public List getFilters()
+ {
+ List retval = null;
+ COSBase filters = stream.getFilters();
+ if( filters instanceof COSName )
+ {
+ COSName name = (COSName)filters;
+ retval = new COSArrayList( name.getName(), name, stream, COSName.FILTER );
+ }
+ else if( filters instanceof COSArray )
+ {
+ retval = COSArrayList.convertCOSNameCOSArrayToList( (COSArray)filters );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the filters that are part of this stream.
+ *
+ * @param filters The filters that are part of this stream.
+ */
+ public void setFilters( List filters )
+ {
+ COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray( filters );
+ stream.setItem( COSName.FILTER, obj );
+ }
+
+ /**
+ * Get the list of decode parameters. Each entry in the list will refer to
+ * an entry in the filters list.
+ *
+ * @return The list of decode parameters.
+ *
+ * @throws IOException if there is an error retrieving the parameters.
+ */
+ public List getDecodeParms() throws IOException
+ {
+ List retval = null;
+
+ COSBase dp = stream.getDictionaryObject( COSName.DECODE_PARMS );
+ if( dp == null )
+ {
+ //See PDF Ref 1.5 implementation note 7, the DP is sometimes used instead.
+ dp = stream.getDictionaryObject( COSName.DP );
+ }
+ if( dp instanceof COSDictionary )
+ {
+ Map map = COSDictionaryMap.convertBasicTypesToMap( (COSDictionary)dp );
+ retval = new COSArrayList(map, dp, stream, COSName.DECODE_PARMS );
+ }
+ else if( dp instanceof COSArray )
+ {
+ COSArray array = (COSArray)dp;
+ List actuals = new ArrayList();
+ for( int i=0; i<array.size(); i++ )
+ {
+ actuals.add(
+ COSDictionaryMap.convertBasicTypesToMap(
+ (COSDictionary)array.getObject( i ) ) );
+ }
+ retval = new COSArrayList(actuals, array);
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set the list of decode parameterss.
+ *
+ * @param decodeParams The list of decode parameterss.
+ */
+ public void setDecodeParms( List decodeParams )
+ {
+ stream.setItem(
+ COSName.DECODE_PARMS, COSArrayList.converterToCOSArray( decodeParams ) );
+ }
+
+ /**
+ * This will get the file specification for this stream. This is only
+ * required for external files.
+ *
+ * @return The file specification.
+ *
+ * @throws IOException If there is an error creating the file spec.
+ */
+ public PDFileSpecification getFile() throws IOException
+ {
+ COSBase f = stream.getDictionaryObject( COSName.F );
+ PDFileSpecification retval = PDFileSpecification.createFS( f );
+ return retval;
+ }
+
+ /**
+ * Set the file specification.
+ * @param f The file specification.
+ */
+ public void setFile( PDFileSpecification f )
+ {
+ stream.setItem( COSName.F, f );
+ }
+
+ /**
+ * This will get the list of filters that are associated with this stream. Or
+ * null if there are none.
+ * @return A list of all encoding filters to apply to this stream.
+ */
+ public List getFileFilters()
+ {
+ List retval = null;
+ COSBase filters = stream.getDictionaryObject( COSName.F_FILTER );
+ if( filters instanceof COSName )
+ {
+ COSName name = (COSName)filters;
+ retval = new COSArrayList( name.getName(), name, stream, COSName.F_FILTER );
+ }
+ else if( filters instanceof COSArray )
+ {
+ retval = COSArrayList.convertCOSNameCOSArrayToList( (COSArray)filters );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the filters that are part of this stream.
+ *
+ * @param filters The filters that are part of this stream.
+ */
+ public void setFileFilters( List filters )
+ {
+ COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray( filters );
+ stream.setItem( COSName.F_FILTER, obj );
+ }
+
+ /**
+ * Get the list of decode parameters. Each entry in the list will refer to
+ * an entry in the filters list.
+ *
+ * @return The list of decode parameters.
+ *
+ * @throws IOException if there is an error retrieving the parameters.
+ */
+ public List getFileDecodeParams() throws IOException
+ {
+ List retval = null;
+
+ COSBase dp = stream.getDictionaryObject( COSName.F_DECODE_PARMS );
+ if( dp instanceof COSDictionary )
+ {
+ Map map = COSDictionaryMap.convertBasicTypesToMap( (COSDictionary)dp );
+ retval = new COSArrayList(map, dp, stream, COSName.F_DECODE_PARMS );
+ }
+ else if( dp instanceof COSArray )
+ {
+ COSArray array = (COSArray)dp;
+ List actuals = new ArrayList();
+ for( int i=0; i<array.size(); i++ )
+ {
+ actuals.add(
+ COSDictionaryMap.convertBasicTypesToMap(
+ (COSDictionary)array.getObject( i ) ) );
+ }
+ retval = new COSArrayList(actuals, array);
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set the list of decode params.
+ *
+ * @param decodeParams The list of decode params.
+ */
+ public void setFileDecodeParams( List decodeParams )
+ {
+ stream.setItem(
+ "FDecodeParams", COSArrayList.converterToCOSArray( decodeParams ) );
+ }
+
+ /**
+ * This will copy the stream into a byte array.
+ *
+ * @return The byte array of the filteredStream
+ * @throws IOException When getFilteredStream did not work
+ */
+ public byte[] getByteArray() throws IOException
+ {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ byte[] buf = new byte[1024];
+ InputStream is = null;
+ try
+ {
+ is = createInputStream();
+ int amountRead = -1;
+ while( (amountRead = is.read( buf )) != -1)
+ {
+ output.write( buf, 0, amountRead );
+ }
+ }
+ finally
+ {
+ if( is != null )
+ {
+ is.close();
+ }
+ }
+ return output.toByteArray();
+ }
+
+ /**
+ * A convenience method to get this stream as a string. Uses
+ * the default system encoding.
+ *
+ * @return a String representation of this (input) stream.
+ *
+ * @throws IOException if there is an error while converting the stream
+ * to a string.
+ */
+ public String getInputStreamAsString() throws IOException
+ {
+ byte[] bStream = getByteArray();
+ return new String(bStream, "ISO-8859-1");
+ }
+
+ /**
+ * Get the metadata that is part of the document catalog. This will
+ * return null if there is no meta data for this object.
+ *
+ * @return The metadata for this object.
+ */
+ public PDMetadata getMetadata()
+ {
+ PDMetadata retval = null;
+ COSStream mdStream = (COSStream)stream.getDictionaryObject( COSName.METADATA );
+ if( mdStream != null )
+ {
+ retval = new PDMetadata( mdStream );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the metadata for this object. This can be null.
+ *
+ * @param meta The meta data for this object.
+ */
+ public void setMetadata( PDMetadata meta )
+ {
+ stream.setItem( COSName.METADATA, meta );
+ }
+
+ /**
+ * Get the decoded stream length.
+ *
+ * @since Apache PDFBox 1.1.0
+ * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-636">PDFBOX-636</a>
+ * @return the decoded stream length
+ */
+ public int getDecodedStreamLength()
+ {
+ return this.stream.getInt(COSName.DL);
+ }
+
+ /**
+ * Set the decoded stream length.
+ *
+ * @since Apache PDFBox 1.1.0
+ * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-636">PDFBOX-636</a>
+ * @param decodedStreamLength the decoded stream length
+ */
+ public void setDecodedStreamLength(int decodedStreamLength)
+ {
+ this.stream.setInt(COSName.DL, decodedStreamLength);
+ }
+
+}
--- /dev/null
+/*
+ * 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.common;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * A PDTextStream class is used when the PDF specification supports either
+ * a string or a stream for the value of an object. This is usually when
+ * a value could be large or small, for example a JavaScript method. This
+ * class will help abstract that and give a single unified interface to
+ * those types of fields.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDTextStream implements COSObjectable
+{
+ private COSString string;
+ private COSStream stream;
+
+ /**
+ * Constructor.
+ *
+ * @param str The string parameter.
+ */
+ public PDTextStream( COSString str )
+ {
+ string = str;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param str The string parameter.
+ */
+ public PDTextStream( String str )
+ {
+ string = new COSString( str );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param str The stream parameter.
+ */
+ public PDTextStream( COSStream str )
+ {
+ stream = str;
+ }
+
+ /**
+ * This will create the text stream object. base must either be a string
+ * or a stream.
+ *
+ * @param base The COS text stream object.
+ *
+ * @return A PDTextStream that wraps the base object.
+ */
+ public static PDTextStream createTextStream( COSBase base )
+ {
+ PDTextStream retval = null;
+ if( base instanceof COSString )
+ {
+ retval = new PDTextStream( (COSString) base );
+ }
+ else if( base instanceof COSStream )
+ {
+ retval = new PDTextStream( (COSStream)base );
+ }
+ return retval;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ COSBase retval = null;
+ if( string == null )
+ {
+ retval = stream;
+ }
+ else
+ {
+ retval = string;
+ }
+ return retval;
+ }
+
+ /**
+ * This will get this value as a string. If this is a stream then it
+ * will load the entire stream into memory, so you should only do this when
+ * the stream is a manageable size.
+ *
+ * @return This value as a string.
+ *
+ * @throws IOException If an IO error occurs while accessing the stream.
+ */
+ public String getAsString() throws IOException
+ {
+ String retval = null;
+ if( string != null )
+ {
+ retval = string.getString();
+ }
+ else
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] buffer = new byte[ 1024 ];
+ int amountRead = -1;
+ InputStream is = stream.getUnfilteredStream();
+ while( (amountRead = is.read( buffer ) ) != -1 )
+ {
+ out.write( buffer, 0, amountRead );
+ }
+ retval = new String( out.toByteArray(), "ISO-8859-1" );
+ }
+ return retval;
+ }
+
+ /**
+ * This is the preferred way of getting data with this class as it uses
+ * a stream object.
+ *
+ * @return The stream object.
+ *
+ * @throws IOException If an IO error occurs while accessing the stream.
+ */
+ public InputStream getAsStream() throws IOException
+ {
+ InputStream retval = null;
+ if( string != null )
+ {
+ retval = new ByteArrayInputStream( string.getBytes() );
+ }
+ else
+ {
+ retval = stream.getUnfilteredStream();
+ }
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.common;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * A wrapper for a COS dictionary including Type information.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ *
+ */
+public class PDTypedDictionaryWrapper extends PDDictionaryWrapper
+{
+
+ /**
+ * Creates a new instance with a given type.
+ *
+ * @param type the type (Type)
+ */
+ public PDTypedDictionaryWrapper(String type)
+ {
+ super();
+ this.getCOSDictionary().setName(COSName.TYPE, type);
+ }
+
+ /**
+ * Creates a new instance with a given COS dictionary.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDTypedDictionaryWrapper(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+
+ /**
+ * Gets the type.
+ *
+ * @return the type
+ */
+ public String getType()
+ {
+ return this.getCOSDictionary().getNameAsString(COSName.TYPE);
+ }
+
+ // There is no setType(String) method because changing the Type would most
+ // probably also change the type of PD object.
+}
--- /dev/null
+/*
+ * 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.common.filespecification;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSStream;
+
+/**
+ * This represents a file specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDComplexFileSpecification extends PDFileSpecification
+{
+ private COSDictionary fs;
+
+ /**
+ * Default Constructor.
+ */
+ public PDComplexFileSpecification()
+ {
+ fs = new COSDictionary();
+ fs.setName( "Type", "Filespec" );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dict The dictionary that fulfils this file specification.
+ */
+ public PDComplexFileSpecification( COSDictionary dict )
+ {
+ fs = dict;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return fs;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return fs;
+ }
+
+ /**
+ * This will get the file name.
+ *
+ * @return The file name.
+ */
+ public String getFile()
+ {
+ return fs.getString( "F" );
+ }
+
+ /**
+ * This will set the file name.
+ *
+ * @param file The name of the file.
+ */
+ public void setFile( String file )
+ {
+ fs.setString( "F", file );
+ }
+
+ /**
+ * This will get the name representing a Dos file.
+ *
+ * @return The file name.
+ */
+ public String getFileDos()
+ {
+ return fs.getString( "DOS" );
+ }
+
+ /**
+ * This will set name representing a dos file.
+ *
+ * @param file The name of the file.
+ */
+ public void setFileDos( String file )
+ {
+ fs.setString( "DOS", file );
+ }
+
+ /**
+ * This will get the name representing a Mac file.
+ *
+ * @return The file name.
+ */
+ public String getFileMac()
+ {
+ return fs.getString( "Mac" );
+ }
+
+ /**
+ * This will set name representing a Mac file.
+ *
+ * @param file The name of the file.
+ */
+ public void setFileMac( String file )
+ {
+ fs.setString( "Mac", file );
+ }
+
+ /**
+ * This will get the name representing a Unix file.
+ *
+ * @return The file name.
+ */
+ public String getFileUnix()
+ {
+ return fs.getString( "Unix" );
+ }
+
+ /**
+ * This will set name representing a Unix file.
+ *
+ * @param file The name of the file.
+ */
+ public void setFileUnix( String file )
+ {
+ fs.setString( "Unix", file );
+ }
+
+ /**
+ * Tell if the underlying file is volatile and should not be cached by the
+ * reader application. Default: false
+ *
+ * @param fileIsVolatile The new value for the volatility of the file.
+ */
+ public void setVolatile( boolean fileIsVolatile )
+ {
+ fs.setBoolean( "V", fileIsVolatile );
+ }
+
+ /**
+ * Get if the file is volatile. Default: false
+ *
+ * @return True if the file is volatile attribute is set.
+ */
+ public boolean isVolatile()
+ {
+ return fs.getBoolean( "V", false );
+ }
+
+ /**
+ * Get the embedded file.
+ *
+ * @return The embedded file for this file spec.
+ */
+ public PDEmbeddedFile getEmbeddedFile()
+ {
+ PDEmbeddedFile file = null;
+ COSStream stream = (COSStream)fs.getObjectFromPath( "EF/F" );
+ if( stream != null )
+ {
+ file = new PDEmbeddedFile( stream );
+ }
+ return file;
+ }
+
+ /**
+ * Set the embedded file for this spec.
+ *
+ * @param file The file to be embedded.
+ */
+ public void setEmbeddedFile( PDEmbeddedFile file )
+ {
+ COSDictionary ef = (COSDictionary)fs.getDictionaryObject( "EF" );
+ if( ef == null && file != null )
+ {
+ ef = new COSDictionary();
+ fs.setItem( "EF", ef );
+ }
+ if( ef != null )
+ {
+ ef.setItem( "F", file );
+ }
+ }
+
+ /**
+ * Get the embedded dos file.
+ *
+ * @return The embedded file for this file spec.
+ */
+ public PDEmbeddedFile getEmbeddedFileDos()
+ {
+ PDEmbeddedFile file = null;
+ COSStream stream = (COSStream)fs.getObjectFromPath( "EF/DOS" );
+ if( stream != null )
+ {
+ file = new PDEmbeddedFile( stream );
+ }
+ return file;
+ }
+
+ /**
+ * Set the embedded dos file for this spec.
+ *
+ * @param file The dos file to be embedded.
+ */
+ public void setEmbeddedFileDos( PDEmbeddedFile file )
+ {
+ COSDictionary ef = (COSDictionary)fs.getDictionaryObject( "DOS" );
+ if( ef == null && file != null )
+ {
+ ef = new COSDictionary();
+ fs.setItem( "EF", ef );
+ }
+ if( ef != null )
+ {
+ ef.setItem( "DOS", file );
+ }
+ }
+
+ /**
+ * Get the embedded Mac file.
+ *
+ * @return The embedded file for this file spec.
+ */
+ public PDEmbeddedFile getEmbeddedFileMac()
+ {
+ PDEmbeddedFile file = null;
+ COSStream stream = (COSStream)fs.getObjectFromPath( "EF/Mac" );
+ if( stream != null )
+ {
+ file = new PDEmbeddedFile( stream );
+ }
+ return file;
+ }
+
+ /**
+ * Set the embedded Mac file for this spec.
+ *
+ * @param file The Mac file to be embedded.
+ */
+ public void setEmbeddedFileMac( PDEmbeddedFile file )
+ {
+ COSDictionary ef = (COSDictionary)fs.getDictionaryObject( "Mac" );
+ if( ef == null && file != null )
+ {
+ ef = new COSDictionary();
+ fs.setItem( "EF", ef );
+ }
+ if( ef != null )
+ {
+ ef.setItem( "Mac", file );
+ }
+ }
+
+ /**
+ * Get the embedded Unix file.
+ *
+ * @return The embedded file for this file spec.
+ */
+ public PDEmbeddedFile getEmbeddedFileUnix()
+ {
+ PDEmbeddedFile file = null;
+ COSStream stream = (COSStream)fs.getObjectFromPath( "EF/Unix" );
+ if( stream != null )
+ {
+ file = new PDEmbeddedFile( stream );
+ }
+ return file;
+ }
+
+ /**
+ * Set the embedded Unix file for this spec.
+ *
+ * @param file The Unix file to be embedded.
+ */
+ public void setEmbeddedFileUnix( PDEmbeddedFile file )
+ {
+ COSDictionary ef = (COSDictionary)fs.getDictionaryObject( "Unix" );
+ if( ef == null && file != null )
+ {
+ ef = new COSDictionary();
+ fs.setItem( "EF", ef );
+ }
+ if( ef != null )
+ {
+ ef.setItem( "Unix", file );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.common.filespecification;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This represents an embedded file in a file specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDEmbeddedFile extends PDStream
+{
+
+ /**
+ * Constructor.
+ *
+ * @param document {@inheritDoc}
+ */
+ public PDEmbeddedFile( PDDocument document )
+ {
+ super( document );
+ getStream().setName( "Type", "EmbeddedFile" );
+
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param str The stream parameter.
+ */
+ public PDEmbeddedFile( COSStream str )
+ {
+ super( str );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param doc {@inheritDoc}
+ * @param str {@inheritDoc}
+ *
+ * @throws IOException {@inheritDoc}
+ */
+ public PDEmbeddedFile( PDDocument doc, InputStream str ) throws IOException
+ {
+ super( doc, str );
+ getStream().setName( "Type", "EmbeddedFile" );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param doc {@inheritDoc}
+ * @param str {@inheritDoc}
+ * @param filtered {@inheritDoc}
+ *
+ * @throws IOException {@inheritDoc}
+ */
+ public PDEmbeddedFile( PDDocument doc, InputStream str, boolean filtered ) throws IOException
+ {
+ super( doc, str, filtered );
+ getStream().setName( "Type", "EmbeddedFile" );
+ }
+
+ /**
+ * Set the subtype for this embedded file. This should be a mime type value. Optional.
+ *
+ * @param mimeType The mimeType for the file.
+ */
+ public void setSubtype( String mimeType )
+ {
+ getStream().setName( "Subtype", mimeType );
+ }
+
+ /**
+ * Get the subtype(mimetype) for the embedded file.
+ *
+ * @return The type of embedded file.
+ */
+ public String getSubtype()
+ {
+ return getStream().getNameAsString( "Subtype" );
+ }
+
+ /**
+ * Get the size of the embedded file.
+ *
+ * @return The size of the embedded file.
+ */
+ public int getSize()
+ {
+ return getStream().getEmbeddedInt( "Params", "Size" );
+ }
+
+ /**
+ * Set the size of the embedded file.
+ *
+ * @param size The size of the embedded file.
+ */
+ public void setSize( int size )
+ {
+ getStream().setEmbeddedInt( "Params", "Size", size );
+ }
+
+ /**
+ * Get the creation date of the embedded file.
+ *
+ * @return The Creation date.
+ * @throws IOException If there is an error while constructing the date.
+ */
+ public Calendar getCreationDate() throws IOException
+ {
+ return getStream().getEmbeddedDate( "Params", "CreationDate" );
+ }
+
+ /**
+ * Set the creation date.
+ *
+ * @param creation The new creation date.
+ */
+ public void setCreationDate( Calendar creation )
+ {
+ getStream().setEmbeddedDate( "Params", "CreationDate", creation );
+ }
+
+ /**
+ * Get the mod date of the embedded file.
+ *
+ * @return The mod date.
+ * @throws IOException If there is an error while constructing the date.
+ */
+ public Calendar getModDate() throws IOException
+ {
+ return getStream().getEmbeddedDate( "Params", "ModDate" );
+ }
+
+ /**
+ * Set the mod date.
+ *
+ * @param mod The new creation mod.
+ */
+ public void setModDate( Calendar mod )
+ {
+ getStream().setEmbeddedDate( "Params", "ModDate", mod );
+ }
+
+ /**
+ * Get the check sum of the embedded file.
+ *
+ * @return The check sum of the file.
+ */
+ public String getCheckSum()
+ {
+ return getStream().getEmbeddedString( "Params", "CheckSum" );
+ }
+
+ /**
+ * Set the check sum.
+ *
+ * @param checksum The checksum of the file.
+ */
+ public void setCheckSum( String checksum )
+ {
+ getStream().setEmbeddedString( "Params", "CheckSum", checksum );
+ }
+
+ /**
+ * Get the mac subtype.
+ *
+ * @return The mac subtype.
+ */
+ public String getMacSubtype()
+ {
+ String retval = null;
+ COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+ if( params != null )
+ {
+ retval = params.getEmbeddedString( "Mac", "Subtype" );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the mac subtype.
+ *
+ * @param macSubtype The mac subtype.
+ */
+ public void setMacSubtype( String macSubtype )
+ {
+ COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+ if( params == null && macSubtype != null )
+ {
+ params = new COSDictionary();
+ getStream().setItem( "Params", params );
+ }
+ if( params != null )
+ {
+ params.setEmbeddedString( "Mac", "Subtype", macSubtype );
+ }
+ }
+
+ /**
+ * Get the mac Creator.
+ *
+ * @return The mac Creator.
+ */
+ public String getMacCreator()
+ {
+ String retval = null;
+ COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+ if( params != null )
+ {
+ retval = params.getEmbeddedString( "Mac", "Creator" );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the mac Creator.
+ *
+ * @param macCreator The mac Creator.
+ */
+ public void setMacCreator( String macCreator )
+ {
+ COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+ if( params == null && macCreator != null )
+ {
+ params = new COSDictionary();
+ getStream().setItem( "Params", params );
+ }
+ if( params != null )
+ {
+ params.setEmbeddedString( "Mac", "Creator", macCreator );
+ }
+ }
+
+ /**
+ * Get the mac ResFork.
+ *
+ * @return The mac ResFork.
+ */
+ public String getMacResFork()
+ {
+ String retval = null;
+ COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+ if( params != null )
+ {
+ retval = params.getEmbeddedString( "Mac", "ResFork" );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the mac ResFork.
+ *
+ * @param macResFork The mac ResFork.
+ */
+ public void setMacResFork( String macResFork )
+ {
+ COSDictionary params = (COSDictionary)getStream().getDictionaryObject( "Params" );
+ if( params == null && macResFork != null )
+ {
+ params = new COSDictionary();
+ getStream().setItem( "Params", params );
+ }
+ if( params != null )
+ {
+ params.setEmbeddedString( "Mac", "ResFork", macResFork);
+ }
+ }
+
+
+
+}
--- /dev/null
+/*
+ * 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.common.filespecification;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents a file specification.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public abstract class PDFileSpecification implements COSObjectable
+{
+
+ /**
+ * A file specfication can either be a COSString or a COSDictionary. This
+ * will create the file specification either way.
+ *
+ * @param base The cos object that describes the fs.
+ *
+ * @return The file specification for the COSBase object.
+ *
+ * @throws IOException If there is an error creating the file spec.
+ */
+ public static PDFileSpecification createFS( COSBase base ) throws IOException
+ {
+ PDFileSpecification retval = null;
+ if( base == null )
+ {
+ //then simply return null
+ }
+ else if( base instanceof COSString )
+ {
+ retval = new PDSimpleFileSpecification( (COSString)base );
+ }
+ else if( base instanceof COSDictionary )
+ {
+ retval = new PDComplexFileSpecification( (COSDictionary)base );
+ }
+ else
+ {
+ throw new IOException( "Error: Unknown file specification " + base );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the file name.
+ *
+ * @return The file name.
+ */
+ public abstract String getFile();
+
+ /**
+ * This will set the file name.
+ *
+ * @param file The name of the file.
+ */
+ public abstract void setFile( String file );
+}
--- /dev/null
+/*
+ * 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.common.filespecification;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * A file specification that is just a string.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDSimpleFileSpecification extends PDFileSpecification
+{
+ private COSString file;
+
+ /**
+ * Constructor.
+ *
+ */
+ public PDSimpleFileSpecification()
+ {
+ file = new COSString( "" );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fileName The file that this spec represents.
+ */
+ public PDSimpleFileSpecification( COSString fileName )
+ {
+ file = fileName;
+ }
+
+ /**
+ * This will get the file name.
+ *
+ * @return The file name.
+ */
+ public String getFile()
+ {
+ return file.getString();
+ }
+
+ /**
+ * This will set the file name.
+ *
+ * @param fileName The name of the file.
+ */
+ public void setFile( String fileName )
+ {
+ file = new COSString( fileName );
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return file;
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The file specification package defines classes that are used for the PDF File Specification logic.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.common.function;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRange;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This class represents a function in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public abstract class PDFunction implements COSObjectable
+{
+
+ private PDStream functionStream = null;
+ private COSDictionary functionDictionary = null;
+ private COSArray domain = null;
+ private COSArray range = null;
+
+ /**
+ * Constructor.
+ *
+ * @param functionStream The function stream.
+ */
+ public PDFunction( COSBase function )
+ {
+ if (function instanceof COSStream)
+ {
+ functionStream = new PDStream( (COSStream)function );
+ functionStream.getStream().setName( COSName.TYPE, "Function" );
+ }
+ else if (function instanceof COSDictionary)
+ {
+ functionDictionary = (COSDictionary)function;
+ }
+ }
+
+ /**
+ * Returns the function type.
+ *
+ * Possible values are:
+ *
+ * 0 - Sampled function
+ * 2 - Exponential interpolation function
+ * 3 - Stitching function
+ * 4 - PostScript calculator function
+ *
+ * @return the function type.
+ */
+ public abstract int getFunctionType();
+
+ /**
+ * Returns the COSObject.
+ *
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ if (functionStream != null)
+ {
+ return functionStream.getCOSObject();
+ }
+ else
+ {
+ return functionDictionary;
+ }
+ }
+
+ /**
+ * Returns the stream.
+ * @return The stream for this object.
+ */
+ public COSDictionary getDictionary()
+ {
+ if (functionStream != null)
+ {
+ return functionStream.getStream();
+ }
+ else
+ {
+ return functionDictionary;
+ }
+ }
+
+ /**
+ * Returns the underlying PDStream.
+ * @return The stream.
+ */
+ protected PDStream getPDStream()
+ {
+ return functionStream;
+ }
+ /**
+ * Create the correct PD Model function based on the COS base function.
+ *
+ * @param function The COS function dictionary.
+ *
+ * @return The PDModel Function object.
+ *
+ * @throws IOException If we are unable to create the PDFunction object.
+ */
+ public static PDFunction create( COSBase function ) throws IOException
+ {
+ PDFunction retval = null;
+ if( function instanceof COSObject )
+ {
+ function = ((COSObject)function).getCOSObject();
+ }
+ COSDictionary functionDictionary = (COSDictionary)function;
+ int functionType = functionDictionary.getInt( COSName.FUNCTION_TYPE );
+ if( functionType == 0 )
+ {
+ retval = new PDFunctionType0(functionDictionary);
+ }
+ else if( functionType == 2 )
+ {
+ retval = new PDFunctionType2(functionDictionary);
+ }
+ else if( functionType == 3 )
+ {
+ retval = new PDFunctionType3(functionDictionary);
+ }
+ else if( functionType == 4 )
+ {
+ retval = new PDFunctionType4(functionDictionary);
+ }
+ else
+ {
+ throw new IOException( "Error: Unknown function type " + functionType );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the number of output parameters that
+ * have a range specified. A range for output parameters
+ * is optional so this may return zero for a function
+ * that does have output parameters, this will simply return the
+ * number that have the rnage specified.
+ *
+ * @return The number of input parameters that have a range
+ * specified.
+ */
+ public int getNumberOfOutputParameters()
+ {
+ COSArray rangeValues = getRangeValues();
+ return rangeValues.size() / 2;
+ }
+
+ /**
+ * This will get the range for a certain output parameters. This is will never
+ * return null. If it is not present then the range 0 to 0 will
+ * be returned.
+ *
+ * @param n The output parameter number to get the range for.
+ *
+ * @return The range for this component.
+ */
+ public PDRange getRangeForOutput(int n)
+ {
+ COSArray rangeValues = getRangeValues();
+ return new PDRange( rangeValues, n );
+ }
+
+ /**
+ * This will set the range values.
+ *
+ * @param range The new range values.
+ */
+ public void setRangeValues(COSArray rangeValues)
+ {
+ range = rangeValues;
+ getDictionary().setItem(COSName.RANGE, rangeValues);
+ }
+
+ /**
+ * This will get the number of input parameters that
+ * have a domain specified.
+ *
+ * @return The number of input parameters that have a domain
+ * specified.
+ */
+ public int getNumberOfInputParameters()
+ {
+ COSArray array = getDomainValues();
+ return array.size() / 2;
+ }
+
+ /**
+ * This will get the range for a certain input parameter. This is will never
+ * return null. If it is not present then the range 0 to 0 will
+ * be returned.
+ *
+ * @param n The parameter number to get the domain for.
+ *
+ * @return The domain range for this component.
+ */
+ public PDRange getDomainForInput(int n)
+ {
+ COSArray domainValues = getDomainValues();
+ return new PDRange( domainValues, n );
+ }
+
+ /**
+ * This will set the domain values.
+ *
+ * @param range The new domain values.
+ */
+ public void setDomainValues(COSArray domainValues)
+ {
+ domain = domainValues;
+ getDictionary().setItem(COSName.DOMAIN, domainValues);
+ }
+
+ /**
+ * Evaluates the function at the given input.
+ * ReturnValue = f(input)
+ *
+ * @param input The array of input values for the function. In many cases will be an array of a single value, but not always.
+ * @return The of outputs the function returns based on those inputs. In many cases will be an array of a single value, but not always.
+ */
+ public abstract COSArray eval(COSArray input) throws IOException;
+
+ /**
+ * Returns all ranges for the output values as COSArray .
+ * Required for type 0 and type 4 functions
+ * @return the ranges array.
+ */
+ protected COSArray getRangeValues()
+ {
+ if (range == null)
+ {
+ range = (COSArray)getDictionary().getDictionaryObject( COSName.RANGE );
+ }
+ return range;
+ }
+
+ /**
+ * Returns all domains for the input values as COSArray.
+ * Required for all function types.
+ * @return the domains array.
+ */
+ private COSArray getDomainValues()
+ {
+ if (domain == null)
+ {
+ domain = (COSArray)getDictionary().getDictionaryObject( COSName.DOMAIN );
+ }
+ return domain;
+ }
+
+ /**
+ * Clip the given input values to the ranges.
+ *
+ * @param inputArray the input values
+ * @return the clipped values
+ */
+ protected COSArray clipToRange(COSArray inputArray)
+ {
+ COSArray rangesArray = getRangeValues();
+ COSArray result = null;
+ if (rangesArray != null)
+ {
+ float[] inputValues = inputArray.toFloatArray();
+ float[] rangeValues = rangesArray.toFloatArray();
+ result = new COSArray();
+ int numberOfRanges = rangeValues.length/2;
+ for (int i=0; i<numberOfRanges; i++)
+ result.add(new COSFloat( clipToRange(inputValues[i], rangeValues[2*i], rangeValues[2*i+1])));
+ }
+ else
+ {
+ result = inputArray;
+ }
+ return result;
+ }
+
+ /**
+ * Clip the given input value to the given range.
+ *
+ * @param x the input value
+ * @param rangeMin the min value of the range
+ * @param rangeMax the max value of the range
+
+ * @return the clipped value
+ */
+ protected float clipToRange(float x, float rangeMin, float rangeMax)
+ {
+ return Math.min(Math.max(x, rangeMin), rangeMax);
+ }
+
+ /**
+ * For a given value of x, interpolate calculates the y value
+ * on the line defined by the two points (xRangeMin , xRangeMax )
+ * and (yRangeMin , yRangeMax ).
+ *
+ * @param x
+ * @param xRangeMin
+ * @param xRangeMax
+ * @param yRangeMin
+ * @param yRangeMax
+ * @return the interpolated y value
+ */
+ protected float interpolate(float x, float xRangeMin, float xRangeMax, float yRangeMin, float yRangeMax)
+ {
+ return yRangeMin + ((x - xRangeMin) * (yRangeMax - yRangeMin)/(xRangeMax - xRangeMin));
+ }
+
+}
--- /dev/null
+/*
+ * 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.common.function;
+
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDRange;
+
+/**
+ * This class represents a type 0 function in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDFunctionType0 extends PDFunction
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDFunctionType0.class);
+
+ /**
+ * An array of 2 × m numbers specifying the linear mapping of input values
+ * into the domain of the function’s sample table.
+ * Default value: [ 0 (Size0 − 1) 0 (Size1 − 1) … ].
+ */
+ private COSArray encode = null;
+ /**
+ * An array of 2 × n numbers specifying the linear mapping of sample values
+ * into the range appropriate for the function’s output values.
+ * Default value: same as the value of Range
+ */
+ private COSArray decode = null;
+ /**
+ * An array of m positive integers specifying the number of samples in each
+ * input dimension of the sample table.
+ */
+ private COSArray size = null;
+ /**
+ * The samples of the function.
+ */
+ private int[][] samples = null;
+
+ /**
+ * Constructor.
+ *
+ * @param functionStream The function .
+ */
+ public PDFunctionType0(COSBase function)
+ {
+ super( function );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getFunctionType()
+ {
+ return 0;
+ }
+ /**
+ * The "Size" entry, which is the number of samples in
+ * each input dimension of the sample table.
+ *
+ * @return A List of java.lang.Integer objects.
+ */
+ public COSArray getSize()
+ {
+ if (size == null)
+ {
+ size = (COSArray)getDictionary().getDictionaryObject( COSName.SIZE );
+ }
+ return size;
+ }
+
+ /**
+ * Get all sample values of this function.
+ *
+ * @return an array with all samples.
+ */
+ public int[][] getSamples()
+ {
+ if (samples == null)
+ {
+ int arraySize = 1;
+ int numberOfInputValues = getNumberOfInputParameters();
+ int numberOfOutputValues = getNumberOfOutputParameters();
+ COSArray sizes = getSize();
+ for (int i=0;i<numberOfInputValues;i++)
+ {
+ arraySize *= sizes.getInt(i);
+ }
+ samples = new int[arraySize][getNumberOfOutputParameters()];
+ int bitsPerSample = getBitsPerSample();
+ int index = 0;
+ int arrayIndex = 0;
+ try {
+ byte[] samplesArray = getPDStream().getByteArray();
+ for (int i=0;i<numberOfInputValues;i++)
+ {
+ int sizeInputValues = sizes.getInt(i);
+ for (int j=0;j<sizeInputValues;j++)
+ {
+ int bitsLeft = 0;
+ int bitsToRead = bitsPerSample;
+ int currentValue = 0;
+ for (int k=0;k<numberOfOutputValues;k++)
+ {
+ if (bitsLeft == 0)
+ {
+ currentValue = (samplesArray[arrayIndex++]+256)%256;
+ bitsLeft = 8;
+ }
+ int value = 0;
+ while (bitsToRead > 0)
+ {
+ int bits = Math.min(bitsToRead, bitsLeft);
+ value = value << bits;
+ int valueToAdd = currentValue >> (8 - bits);
+ value |= valueToAdd;
+ bitsToRead -= bits;
+ bitsLeft -= bits;
+ if (bitsLeft == 0 && bitsToRead > 0)
+ {
+ currentValue = (samplesArray[arrayIndex++]+256)%256;
+ bitsLeft = 8;
+ }
+ }
+ samples[index][k] = value;
+ bitsToRead = bitsPerSample;
+ }
+ index++;
+ }
+ }
+ }
+ catch (IOException exception)
+ {
+ log.error("IOException while reading the sample values of this function.");
+ }
+ }
+ return samples;
+ }
+ /**
+ * Get the number of bits that the output value will take up.
+ *
+ * Valid values are 1,2,4,8,12,16,24,32.
+ *
+ * @return Number of bits for each output value.
+ */
+ public int getBitsPerSample()
+ {
+ return getDictionary().getInt( COSName.BITS_PER_SAMPLE );
+ }
+
+ /**
+ * Set the number of bits that the output value will take up. Valid values
+ * are 1,2,4,8,12,16,24,32.
+ *
+ * @param bps The number of bits for each output value.
+ */
+ public void setBitsPerSample( int bps )
+ {
+ getDictionary().setInt( COSName.BITS_PER_SAMPLE, bps );
+ }
+
+ /**
+ * Returns all encode values as COSArray.
+ *
+ * @return the encode array.
+ */
+ private COSArray getEncodeValues()
+ {
+ if (encode == null)
+ {
+ encode = (COSArray)getDictionary().getDictionaryObject( COSName.ENCODE );
+ // the default value is [0 (size[0]-1) 0 (size[1]-1) ...]
+ if (encode == null)
+ {
+ encode = new COSArray();
+ COSArray sizeValues = getSize();
+ int sizeValuesSize = sizeValues.size();
+ for (int i=0; i <sizeValuesSize; i++)
+ {
+ encode.add( COSInteger.ZERO );
+ encode.add( COSInteger.get( sizeValues.getInt(i) - 1) );
+ }
+ }
+ }
+ return encode;
+ }
+
+ /**
+ * Returns all decode values as COSArray.
+ *
+ * @return the decode array.
+ */
+ private COSArray getDecodeValues()
+ {
+ if (decode == null)
+ {
+ decode = (COSArray)getDictionary().getDictionaryObject( COSName.DECODE );
+ // if decode is null, the default values are the range values
+ if (decode == null)
+ {
+ decode = getRangeValues();
+ }
+ }
+ return decode;
+ }
+
+ /**
+ * Get the encode for the input parameter.
+ *
+ * @param paramNum The function parameter number.
+ *
+ * @return The encode parameter range or null if none is set.
+ */
+ public PDRange getEncodeForParameter( int paramNum )
+ {
+ PDRange retval = null;
+ COSArray encodeValues = getEncodeValues();
+ if( encodeValues != null && encodeValues.size() >= paramNum*2+1 )
+ {
+ retval = new PDRange(encodeValues, paramNum );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the encode values.
+ *
+ * @param range The new encode values.
+ */
+ public void setEncodeValues(COSArray encodeValues)
+ {
+ encode = encodeValues;
+ getDictionary().setItem(COSName.ENCODE, encodeValues);
+ }
+
+ /**
+ * Get the decode for the input parameter.
+ *
+ * @param paramNum The function parameter number.
+ *
+ * @return The decode parameter range or null if none is set.
+ */
+ public PDRange getDecodeForParameter( int paramNum )
+ {
+ PDRange retval = null;
+ COSArray decodeValues = getDecodeValues();
+ if( decodeValues != null && decodeValues.size() >= paramNum*2+1 )
+ {
+ retval = new PDRange(decodeValues, paramNum );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the decode values.
+ *
+ * @param range The new decode values.
+ */
+ public void setDecodeValues(COSArray decodeValues)
+ {
+ decode = decodeValues;
+ getDictionary().setItem(COSName.DECODE, decodeValues);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSArray eval(COSArray input) throws IOException
+ {
+ //This involves linear interpolation based on a set of sample points.
+ //Theoretically it's not that difficult ... see section 3.9.1 of the PDF Reference.
+ float[] inputValues = input.toFloatArray();
+ float[] sizeValues = getSize().toFloatArray();
+ int bitsPerSample = getBitsPerSample();
+ int numberOfInputValues = inputValues.length;
+ int numberOfOutputValues = getNumberOfOutputParameters();
+ int[] intInputValuesPrevious = new int[numberOfInputValues];
+ int[] intInputValuesNext = new int[numberOfInputValues];
+ for (int i=0; i<numberOfInputValues; i++) {
+ PDRange domain = getDomainForInput(i);
+ PDRange encode = getEncodeForParameter(i);
+ inputValues[i] = clipToRange(inputValues[i], domain.getMin(), domain.getMax());
+ inputValues[i] = interpolate(inputValues[i], domain.getMin(), domain.getMax(), encode.getMin(), encode.getMax());
+ inputValues[i] = clipToRange(inputValues[i], 0, sizeValues[i]-1);
+ intInputValuesPrevious[i] = (int)Math.floor(inputValues[i]);
+ intInputValuesNext[i] = (int)Math.ceil(inputValues[i]);
+ }
+ float[] outputValuesPrevious = null;
+ float[] outputValuesNext = null;
+ outputValuesPrevious = getSample(intInputValuesPrevious);
+ outputValuesNext = getSample(intInputValuesNext);
+ float[] outputValues = new float[numberOfOutputValues];
+ for (int i=0;i<numberOfOutputValues;i++)
+ {
+ PDRange range = getRangeForOutput(i);
+ PDRange decode = getDecodeForParameter(i);
+ // TODO using only a linear interpolation.
+ // See "Order" entry in table 3.36 of the PDF reference
+ outputValues[i] = (outputValuesPrevious[i] + outputValuesNext[i]) / 2;
+ outputValues[i] = interpolate(outputValues[i], 0, (float)Math.pow(2, bitsPerSample), decode.getMin(), decode.getMax());
+ outputValues[i] = clipToRange(outputValues[i], range.getMin(), range.getMax());
+ }
+
+ COSArray result = new COSArray();
+ result.setFloatArray(outputValues);
+ return result;
+ }
+
+ /**
+ * Get the samples for the given input values.
+ *
+ * @param inputValues an array containing the input values
+ * @return an array with the corresponding samples
+ */
+ private float[] getSample(int[] inputValues)
+ {
+ int[][] sampleValues = getSamples();
+ COSArray sizes = getSize();
+ int numberOfInputValues = getNumberOfInputParameters();
+ int index = 0;
+ int previousSize = 1;
+ for (int i=0;i<numberOfInputValues;i++)
+ {
+ index += inputValues[i];
+ previousSize *= sizes.getInt(i);
+ }
+ int numberOfOutputValues = getNumberOfOutputParameters();
+ float[] result = new float[numberOfOutputValues];
+ for (int i=0;i<numberOfOutputValues;i++)
+ {
+ result[i] = sampleValues[index][i];
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * 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.common.function;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import java.io.IOException;
+import java.lang.Math;
+
+/**
+ * This class represents a type 2 function in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDFunctionType2 extends PDFunction
+{
+
+ /**
+ * The C0 values of the exponential function.
+ */
+ private COSArray C0;
+ /**
+ * The C1 values of the exponential function.
+ */
+ private COSArray C1;
+
+ /**
+ * Constructor.
+ *
+ * @param functionStream The function .
+ */
+ public PDFunctionType2(COSBase function)
+ {
+ super( function );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getFunctionType()
+ {
+ return 2;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSArray eval(COSArray input) throws IOException
+ {
+ //This function performs exponential interpolation.
+ //It uses only a single value as its input, but may produce a multi-valued output.
+ //See PDF Reference section 3.9.2.
+
+ double inputValue = input.toFloatArray()[0];
+ double exponent = getN();
+ COSArray c0 = getC0();
+ COSArray c1 = getC1();
+ COSArray functionResult = new COSArray();
+ int c0Size = c0.size();
+ for (int j=0;j<c0Size;j++)
+ {
+ //y[j] = C0[j] + x^N*(C1[j] - C0[j])
+ float result = ((COSNumber)c0.get(j)).floatValue() + (float)Math.pow(inputValue,exponent)*(((COSNumber)c1.get(j)).floatValue() - ((COSNumber)c0.get(j)).floatValue());
+ functionResult.add( new COSFloat( result));
+ }
+ // clip to range if available
+ return clipToRange(functionResult);
+ }
+
+ /**
+ * Returns the C0 values of the function, 0 if empty.
+ * @return a COSArray with the C0 values
+ */
+ public COSArray getC0()
+ {
+ if(C0 == null)
+ {
+ C0 = (COSArray)getDictionary().getDictionaryObject( COSName.C0 );
+ if ( C0 == null )
+ {
+ // C0 is optional, default = 0
+ C0 = new COSArray();
+ C0.add( new COSFloat( 0 ) );
+ }
+ }
+ return C0;
+ }
+
+ /**
+ * Returns the C1 values of the function, 1 if empty.
+ * @return a COSArray with the C1 values
+ */
+ public COSArray getC1()
+ {
+ if(C1 == null)
+ {
+ C1 = (COSArray)getDictionary().getDictionaryObject( COSName.C1 );
+ if( C1 == null )
+ {
+ // C1 is optional, default = 1
+ C1 = new COSArray();
+ C1.add( new COSFloat( 1 ) );
+ }
+ }
+ return C1;
+ }
+
+ /**
+ * Returns the exponent of the function.
+ * @return the float value of the exponent
+ */
+ public float getN()
+ {
+ return getDictionary().getFloat(COSName.N);
+ }
+}
--- /dev/null
+/*
+ * 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.common.function;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.common.PDRange;
+
+import java.io.IOException;
+
+/**
+ * This class represents a type 3 function in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDFunctionType3 extends PDFunction
+{
+
+ private COSArray functions = null;
+ private COSArray encode = null;
+ private COSArray bounds = null;
+
+ /**
+ * Constructor.
+ *
+ * @param functionStream The function .
+ */
+ public PDFunctionType3(COSBase function)
+ {
+ super( function );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getFunctionType()
+ {
+ return 3;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSArray eval(COSArray input) throws IOException
+ {
+ //This function is known as a "stitching" function. Based on the input, it decides which child function to call.
+ //See PDF Reference section 3.9.3.
+ PDFunction function = null;
+ float x = ((COSNumber)input.get(0)).floatValue();
+ PDRange domain = getDomainForInput(1);
+ // clip input value to domain
+ x = clipToRange(x, domain.getMin(), domain.getMax());
+
+ float[] boundsValues = getBounds().toFloatArray();
+ int boundsSize = boundsValues.length;
+ if (boundsSize == 0 || x < boundsValues[0])
+ {
+ function = PDFunction.create(getFunctions().get(0));
+ PDRange encode = getEncodeForParameter(0);
+ if (boundsSize == 0)
+ {
+ x = interpolate(x, domain.getMin(), domain.getMax(), encode.getMin(), encode.getMax());
+ }
+ else
+ {
+ x = interpolate(x, domain.getMin(), boundsValues[0], encode.getMin(), encode.getMax());
+ }
+ }
+ else
+ {
+ for (int i=0; i<boundsSize-1; i++)
+ {
+ if ( x >= boundsValues[i] && x < boundsValues[i+1] )
+ {
+ function = PDFunction.create(getFunctions().get(i+1));
+ PDRange encode = getEncodeForParameter(i+1);
+ x = interpolate(x, boundsValues[i], boundsValues[i+1], encode.getMin(), encode.getMax());
+ break;
+ }
+ }
+ if(function==null) //must be in last partition
+ {
+ function = PDFunction.create(getFunctions().get(boundsSize+1));
+ PDRange encode = getEncodeForParameter(boundsSize+1);
+ x = interpolate(x, boundsValues[boundsSize-1], domain.getMax(), encode.getMin(), encode.getMax());
+ }
+ }
+ COSArray functionValues = new COSArray();
+ functionValues.add(new COSFloat(x));
+ COSArray functionResult = function.eval(functionValues);
+ // clip to range if available
+ return clipToRange(functionResult);
+ }
+
+ /**
+ * Returns all functions values as COSArray.
+ *
+ * @return the functions array.
+ */
+ public COSArray getFunctions()
+ {
+ if (functions == null)
+ {
+ functions = (COSArray)(getDictionary().getDictionaryObject( COSName.FUNCTIONS ));
+ }
+ return functions;
+ }
+
+ /**
+ * Returns all bounds values as COSArray.
+ *
+ * @return the bounds array.
+ */
+ public COSArray getBounds()
+ {
+ if (bounds == null)
+ {
+ bounds = (COSArray)(getDictionary().getDictionaryObject( COSName.BOUNDS ));
+ }
+ return bounds;
+ }
+
+ /**
+ * Returns all encode values as COSArray.
+ *
+ * @return the encode array.
+ */
+ public COSArray getEncode()
+ {
+ if (encode == null)
+ {
+ encode = (COSArray)(getDictionary().getDictionaryObject( COSName.ENCODE ));
+ }
+ return encode;
+ }
+
+ /**
+ * Get the encode for the input parameter.
+ *
+ * @param paramNum The function parameter number.
+ *
+ * @return The encode parameter range or null if none is set.
+ */
+ private PDRange getEncodeForParameter(int n)
+ {
+ COSArray encodeValues = getEncode();
+ return new PDRange( encodeValues, n );
+ }
+
+}
--- /dev/null
+/*
+ * 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.common.function;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+
+import java.io.IOException;
+
+/**
+ * This class represents a type 4 function in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDFunctionType4 extends PDFunction
+{
+
+ /**
+ * Constructor.
+ *
+ * @param functionStream The function .
+ */
+ public PDFunctionType4(COSBase function)
+ {
+ super( function );
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getFunctionType()
+ {
+ return 4;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSArray eval(COSArray input) throws IOException
+ {
+ //Implementation here will require evaluation of PostScript functions.
+ //See section 3.9.4 of the PDF Reference.
+ throw new IOException("Not Implemented");
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains functions that are available in the PDF specification.
+</body>
+</html>
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+High level PD classes that are used throughout several packages are placed in the PDModel common package.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDDictionaryWrapper;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDExportFormatAttributeObject;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDLayoutAttributeObject;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDListAttributeObject;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDPrintFieldAttributeObject;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDTableAttributeObject;
+
+/**
+ * An attribute object.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ *
+ */
+public abstract class PDAttributeObject extends PDDictionaryWrapper
+{
+
+ /**
+ * Creates an attribute object.
+ *
+ * @param dictionary the dictionary
+ * @return the attribute object
+ */
+ public static PDAttributeObject create(COSDictionary dictionary)
+ {
+ String owner = dictionary.getNameAsString(COSName.O);
+ if (PDUserAttributeObject.OWNER_USER_PROPERTIES.equals(owner))
+ {
+ return new PDUserAttributeObject(dictionary);
+ }
+ else if (PDListAttributeObject.OWNER_LIST.equals(owner))
+ {
+ return new PDListAttributeObject(dictionary);
+ }
+ else if (PDPrintFieldAttributeObject.OWNER_PRINT_FIELD.equals(owner))
+ {
+ return new PDPrintFieldAttributeObject(dictionary);
+ }
+ else if (PDTableAttributeObject.OWNER_TABLE.equals(owner))
+ {
+ return new PDTableAttributeObject(dictionary);
+ }
+ else if (PDLayoutAttributeObject.OWNER_LAYOUT.equals(owner))
+ {
+ return new PDLayoutAttributeObject(dictionary);
+ }
+ else if (PDExportFormatAttributeObject.OWNER_XML_1_00.equals(owner)
+ || PDExportFormatAttributeObject.OWNER_HTML_3_20.equals(owner)
+ || PDExportFormatAttributeObject.OWNER_HTML_4_01.equals(owner)
+ || PDExportFormatAttributeObject.OWNER_OEB_1_00.equals(owner)
+ || PDExportFormatAttributeObject.OWNER_RTF_1_05.equals(owner)
+ || PDExportFormatAttributeObject.OWNER_CSS_1_00.equals(owner)
+ || PDExportFormatAttributeObject.OWNER_CSS_2_00.equals(owner))
+ {
+ return new PDExportFormatAttributeObject(dictionary);
+ }
+ return new PDDefaultAttributeObject(dictionary);
+ }
+
+ private PDStructureElement structureElement;
+
+ /**
+ * Gets the structure element.
+ *
+ * @return the structure element
+ */
+ private PDStructureElement getStructureElement()
+ {
+ return this.structureElement;
+ }
+
+ /**
+ * Sets the structure element.
+ *
+ * @param structureElement the structure element
+ */
+ protected void setStructureElement(PDStructureElement structureElement)
+ {
+ this.structureElement = structureElement;
+ }
+
+
+ /**
+ * Default constructor.
+ */
+ public PDAttributeObject()
+ {
+ }
+
+ /**
+ * Creates a new attribute object with a given dictionary.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDAttributeObject(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+
+ /**
+ * Returns the owner of the attributes.
+ *
+ * @return the owner of the attributes
+ */
+ public String getOwner()
+ {
+ return this.getCOSDictionary().getNameAsString(COSName.O);
+ }
+
+ /**
+ * Sets the owner of the attributes.
+ *
+ * @param owner the owner of the attributes
+ */
+ protected void setOwner(String owner)
+ {
+ this.getCOSDictionary().setName(COSName.O, owner);
+ }
+
+ /**
+ * Detects whether there are no properties in the attribute object.
+ *
+ * @return <code>true</code> if the attribute object is empty,
+ * <code>false</code> otherwise
+ */
+ public boolean isEmpty()
+ {
+ // only entry is the owner?
+ return (this.getCOSDictionary().size() == 1) && (this.getOwner() != null);
+ }
+
+
+ /**
+ * Notifies the attribute object change listeners if the attribute is changed.
+ *
+ * @param oldBase old value
+ * @param newBase new value
+ */
+ protected void potentiallyNotifyChanged(COSBase oldBase, COSBase newBase)
+ {
+ if (this.isValueChanged(oldBase, newBase))
+ {
+ this.notifyChanged();
+ }
+ }
+
+ /**
+ * Is the value changed?
+ *
+ * @param oldValue old value
+ * @param newValue new value
+ * @return <code>true</code> if the value is changed, <code>false</code>
+ * otherwise
+ */
+ private boolean isValueChanged(COSBase oldValue, COSBase newValue)
+ {
+ if (oldValue == null)
+ {
+ if (newValue == null)
+ {
+ return false;
+ }
+ return true;
+ }
+ return !oldValue.equals(newValue);
+ }
+
+ /**
+ * Notifies the attribute object change listeners about a change in this
+ * attribute object.
+ */
+ protected void notifyChanged()
+ {
+ if (this.getStructureElement() != null)
+ {
+ this.getStructureElement().attributeChanged(this);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return new StringBuilder("O=").append(this.getOwner()).toString();
+ }
+
+ /**
+ * Creates a String representation of an Object array.
+ *
+ * @param array the Object array
+ * @return the String representation
+ */
+ protected static String arrayToString(Object[] array)
+ {
+ StringBuilder sb = new StringBuilder("[");
+ for (int i = 0; i < array.length; i++)
+ {
+ if (i > 0)
+ {
+ sb.append(", ");
+ }
+ sb.append(array[i]);
+ }
+ return sb.append(']').toString();
+ }
+
+ /**
+ * Creates a String representation of a float array.
+ *
+ * @param array the float array
+ * @return the String representation
+ */
+ protected static String arrayToString(float[] array)
+ {
+ StringBuilder sb = new StringBuilder("[");
+ for (int i = 0; i < array.length; i++)
+ {
+ if (i > 0)
+ {
+ sb.append(", ");
+ }
+ sb.append(array[i]);
+ }
+ return sb.append(']').toString();
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * A default attribute object.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDDefaultAttributeObject extends PDAttributeObject
+{
+
+ /**
+ * Default constructor.
+ */
+ public PDDefaultAttributeObject()
+ {
+ }
+
+ /**
+ * Creates a default attribute object with a given dictionary.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDDefaultAttributeObject(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+
+ /**
+ * Gets the attribute names.
+ *
+ * @return the attribute names
+ */
+ public List<String> getAttributeNames()
+ {
+ List<String> attrNames = new ArrayList<String>();
+ for (Entry<COSName, COSBase> entry : this.getCOSDictionary().entrySet())
+ {
+ COSName key = entry.getKey();
+ if (!COSName.O.equals(key))
+ {
+ attrNames.add(key.getName());
+ }
+ }
+ return attrNames;
+ }
+
+ /**
+ * Gets the attribute value for a given name.
+ *
+ * @param attrName the given attribute name
+ * @return the attribute value for a given name
+ */
+ public COSBase getAttributeValue(String attrName)
+ {
+ return this.getCOSDictionary().getDictionaryObject(attrName);
+ }
+
+ /**
+ * Gets the attribute value for a given name.
+ *
+ * @param attrName the given attribute name
+ * @param defaultValue the default value
+ * @return the attribute value for a given name
+ */
+ protected COSBase getAttributeValue(String attrName, COSBase defaultValue)
+ {
+ COSBase value = this.getCOSDictionary().getDictionaryObject(attrName);
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ /**
+ * Sets an attribute.
+ *
+ * @param attrName the attribute name
+ * @param attrValue the attribute value
+ */
+ public void setAttribute(String attrName, COSBase attrValue)
+ {
+ COSBase old = this.getAttributeValue(attrName);
+ this.getCOSDictionary().setItem(COSName.getPDFName(attrName), attrValue);
+ this.potentiallyNotifyChanged(old, attrValue);
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder().append(super.toString())
+ .append(", attributes={");
+ Iterator<String> it = this.getAttributeNames().iterator();
+ while (it.hasNext())
+ {
+ String name = it.next();
+ sb.append(name).append('=').append(this.getAttributeValue(name));
+ if (it.hasNext())
+ {
+ sb.append(", ");
+ }
+ }
+ return sb.append('}').toString();
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * The MarkInfo provides additional information relevant to specialized
+ * uses of structured documents.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDMarkInfo implements COSObjectable
+{
+ private COSDictionary dictionary;
+
+ /**
+ * Default Constructor.
+ *
+ */
+ public PDMarkInfo()
+ {
+ dictionary = new COSDictionary();
+ }
+
+ /**
+ * Constructor for an existing MarkInfo element.
+ *
+ * @param dic The existing dictionary.
+ */
+ public PDMarkInfo( COSDictionary dic )
+ {
+ dictionary = dic;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return dictionary;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getDictionary()
+ {
+ return dictionary;
+ }
+
+ /**
+ * Tells if this is a tagged PDF.
+ *
+ * @return true If this is a tagged PDF.
+ */
+ public boolean isMarked()
+ {
+ return dictionary.getBoolean( "Marked", false );
+ }
+
+ /**
+ * Set if this is a tagged PDF.
+ *
+ * @param value The new marked value.
+ */
+ public void setMarked( boolean value )
+ {
+ dictionary.setBoolean( "Marked", value );
+ }
+
+ /**
+ * Tells if structure elements use user properties.
+ *
+ * @return A boolean telling if the structure elements use user properties.
+ */
+ public boolean usesUserProperties()
+ {
+ return dictionary.getBoolean( "UserProperties", false );
+ }
+
+ /**
+ * Set if the structure elements contain user properties.
+ *
+ * @param userProps The new value for this property.
+ */
+ public void setUserProperties( boolean userProps )
+ {
+ dictionary.setBoolean( "UserProperties", userProps );
+ }
+
+ /**
+ * Tells if this PDF contain 'suspect' tags. See PDF Reference 1.6
+ * section 10.6 "Logical Structure" for more information about this property.
+ *
+ * @return true if the suspect flag has been set.
+ */
+ public boolean isSuspect()
+ {
+ return dictionary.getBoolean( "Suspects", false );
+ }
+
+ /**
+ * Set the value of the suspects property. See PDF Reference 1.6
+ * section 10.6 "Logical Structure" for more information about this
+ * property.
+ *
+ * @param suspect The new "Suspects" value.
+ */
+ public void setSuspect( boolean suspect )
+ {
+ dictionary.setBoolean( "Suspects", false );
+ }
+}
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * A marked-content reference.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDMarkedContentReference implements COSObjectable
+{
+
+ public static final String TYPE = "MCR";
+
+ private COSDictionary dictionary;
+
+ protected COSDictionary getCOSDictionary()
+ {
+ return this.dictionary;
+ }
+
+ /**
+ * Default constructor
+ */
+ public PDMarkedContentReference()
+ {
+ this.dictionary = new COSDictionary();
+ this.dictionary.setName(COSName.TYPE, TYPE);
+ }
+
+ /**
+ * Constructor for an existing marked content reference.
+ *
+ * @param pageDic the page dictionary
+ * @param mcid the marked content indentifier
+ */
+ public PDMarkedContentReference(COSDictionary dictionary)
+ {
+ this.dictionary = dictionary;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return this.dictionary;
+ }
+
+ /**
+ * Gets the page.
+ *
+ * @return the page
+ */
+ public PDPage getPage()
+ {
+ COSDictionary pg = (COSDictionary) this.getCOSDictionary()
+ .getDictionaryObject(COSName.PG);
+ if (pg != null)
+ {
+ return new PDPage(pg);
+ }
+ return null;
+ }
+
+ /**
+ * Sets the page.
+ *
+ * @param page the page
+ */
+ public void setPage(PDPage page)
+ {
+ this.getCOSDictionary().setItem(COSName.PG, page);
+ }
+
+ /**
+ * Gets the marked content identifier.
+ *
+ * @return the marked content identifier
+ */
+ public int getMCID()
+ {
+ return this.getCOSDictionary().getInt(COSName.MCID);
+ }
+
+ /**
+ * Sets the marked content identifier.
+ *
+ * @param mcid the marked content identifier
+ */
+ public void setMCID(int mcid)
+ {
+ this.getCOSDictionary().setInt(COSName.MCID, mcid);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return new StringBuilder()
+ .append("mcid=").append(this.getMCID()).toString();
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import java.io.IOException;
+
+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.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+
+/**
+ * An object reference.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDObjectReference implements COSObjectable
+{
+
+ public static final String TYPE = "OBJR";
+
+ private COSDictionary dictionary;
+
+ protected COSDictionary getCOSDictionary()
+ {
+ return this.dictionary;
+ }
+
+ /**
+ * Default Constructor.
+ *
+ */
+ public PDObjectReference()
+ {
+ this.dictionary = new COSDictionary();
+ this.dictionary.setName(COSName.TYPE, TYPE);
+ }
+
+ /**
+ * Constructor for an existing object reference.
+ *
+ * @param dictionary The existing dictionary.
+ */
+ public PDObjectReference(COSDictionary dictionary)
+ {
+ this.dictionary = dictionary;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return this.dictionary;
+ }
+
+ /**
+ * Gets a higher-level object for the referenced object.
+ * Currently this method may return a {@link PDAnnotation},
+ * a {@link PDXObject} or <code>null</code>.
+ *
+ * @return a higher-level object for the referenced object
+ */
+ public COSObjectable getReferencedObject()
+ {
+ COSBase obj = this.getCOSDictionary().getDictionaryObject(COSName.OBJ);
+ try
+ {
+ return PDAnnotation.createAnnotation(obj);
+ }
+ catch (IOException e)
+ {
+ // No Annotation
+ try
+ {
+ return PDXObject.createXObject(obj);
+ }
+ catch (IOException e1)
+ {
+ // No XObject
+ // TODO what else can be the target of the object reference?
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the referenced annotation.
+ *
+ * @param annotation the referenced annotation
+ */
+ public void setReferencedObject(PDAnnotation annotation)
+ {
+ this.getCOSDictionary().setItem(COSName.OBJ, annotation);
+ }
+
+ /**
+ * Sets the referenced XObject.
+ *
+ * @param xobject the referenced XObject
+ */
+ public void setReferencedObject(PDXObject xobject)
+ {
+ this.getCOSDictionary().setItem(COSName.OBJ, xobject);
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import java.util.Iterator;
+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.COSObject;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDMarkedContent;
+
+/**
+ * A structure element.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>,
+ * <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDStructureElement extends PDStructureNode
+{
+ public static final String TYPE = "StructElem";
+
+
+ /**
+ * Constructor with required values.
+ *
+ * @param structureType the structure type
+ * @param parent the parent structure node
+ */
+ public PDStructureElement(String structureType, PDStructureNode parent)
+ {
+ super(TYPE);
+ this.setStructureType(structureType);
+ this.setParent(parent);
+ }
+
+ /**
+ * Constructor for an existing structure element.
+ *
+ * @param dic The existing dictionary.
+ */
+ public PDStructureElement( COSDictionary dic )
+ {
+ super(dic);
+ }
+
+
+ /**
+ * Returns the structure type (S).
+ *
+ * @return the structure type
+ */
+ public String getStructureType()
+ {
+ return this.getCOSDictionary().getNameAsString(COSName.S);
+ }
+
+ /**
+ * Sets the structure type (S).
+ *
+ * @param structureType the structure type
+ */
+ public void setStructureType(String structureType)
+ {
+ this.getCOSDictionary().setName(COSName.S, structureType);
+ }
+
+ /**
+ * Returns the parent in the structure hierarchy (P).
+ *
+ * @return the parent in the structure hierarchy
+ */
+ public PDStructureNode getParent()
+ {
+ COSDictionary p = (COSDictionary) this.getCOSDictionary()
+ .getDictionaryObject(COSName.P);
+ if (p == null)
+ {
+ return null;
+ }
+ return PDStructureNode.create((COSDictionary) p);
+ }
+
+ /**
+ * Sets the parent in the structure hierarchy (P).
+ *
+ * @param structureNode the parent in the structure hierarchy
+ */
+ public void setParent(PDStructureNode structureNode)
+ {
+ this.getCOSDictionary().setItem(COSName.P, structureNode);
+ }
+
+ /**
+ * Returns the element identifier (ID).
+ *
+ * @return the element identifier
+ */
+ public String getElementIdentifier()
+ {
+ return this.getCOSDictionary().getString(COSName.ID);
+ }
+
+ /**
+ * Sets the element identifier (ID).
+ *
+ * @param id the element identifier
+ */
+ public void setElementIdentifier(String id)
+ {
+ this.getCOSDictionary().setString(COSName.ID, id);
+ }
+
+ /**
+ * Returns the page on which some or all of the content items designated by
+ * the K entry shall be rendered (Pg).
+ *
+ * @return the page on which some or all of the content items designated by
+ * the K entry shall be rendered
+ */
+ public PDPage getPage()
+ {
+ COSDictionary pageDic = (COSDictionary) this.getCOSDictionary()
+ .getDictionaryObject(COSName.PG);
+ if (pageDic == null)
+ {
+ return null;
+ }
+ return new PDPage(pageDic);
+ }
+
+ /**
+ * Sets the page on which some or all of the content items designated by
+ * the K entry shall be rendered (Pg).
+ * @param page the page on which some or all of the content items designated
+ * by the K entry shall be rendered.
+ */
+ public void setPage(PDPage page)
+ {
+ this.getCOSDictionary().setItem(COSName.PG, page);
+ }
+
+ /**
+ * Returns the attributes together with their revision numbers (A).
+ *
+ * @return the attributes
+ */
+ public Revisions<PDAttributeObject> getAttributes()
+ {
+ Revisions<PDAttributeObject> attributes =
+ new Revisions<PDAttributeObject>();
+ COSBase a = this.getCOSDictionary().getDictionaryObject(COSName.A);
+ if (a instanceof COSArray)
+ {
+ COSArray aa = (COSArray) a;
+ Iterator<COSBase> it = aa.iterator();
+ PDAttributeObject ao = null;
+ while (it.hasNext())
+ {
+ COSBase item = it.next();
+ if (item instanceof COSDictionary)
+ {
+ ao = PDAttributeObject.create((COSDictionary) item);
+ ao.setStructureElement(this);
+ attributes.addObject(ao, 0);
+ }
+ else if (item instanceof COSInteger)
+ {
+ attributes.setRevisionNumber(ao,
+ ((COSInteger) item).intValue());
+ }
+ }
+ }
+ if (a instanceof COSDictionary)
+ {
+ PDAttributeObject ao = PDAttributeObject.create((COSDictionary) a);
+ ao.setStructureElement(this);
+ attributes.addObject(ao, 0);
+ }
+ return attributes;
+ }
+
+ /**
+ * Sets the attributes together with their revision numbers (A).
+ *
+ * @param attributes the attributes
+ */
+ public void setAttributes(Revisions<PDAttributeObject> attributes)
+ {
+ COSName key = COSName.A;
+ if ((attributes.size() == 1) && (attributes.getRevisionNumber(0) == 0))
+ {
+ PDAttributeObject attributeObject = attributes.getObject(0);
+ attributeObject.setStructureElement(this);
+ this.getCOSDictionary().setItem(key, attributeObject);
+ return;
+ }
+ COSArray array = new COSArray();
+ for (int i = 0; i < attributes.size(); i++)
+ {
+ PDAttributeObject attributeObject = attributes.getObject(i);
+ attributeObject.setStructureElement(this);
+ int revisionNumber = attributes.getRevisionNumber(i);
+ if (revisionNumber < 0)
+ {
+ // TODO throw Exception because revision number must be > -1?
+ }
+ array.add(attributeObject);
+ array.add(COSInteger.get(revisionNumber));
+ }
+ this.getCOSDictionary().setItem(key, array);
+ }
+
+ /**
+ * Adds an attribute object.
+ *
+ * @param attributeObject the attribute object
+ */
+ public void addAttribute(PDAttributeObject attributeObject)
+ {
+ COSName key = COSName.A;
+ attributeObject.setStructureElement(this);
+ COSBase a = this.getCOSDictionary().getDictionaryObject(key);
+ COSArray array = null;
+ if (a instanceof COSArray)
+ {
+ array = (COSArray) a;
+ }
+ else
+ {
+ array = new COSArray();
+ if (a != null)
+ {
+ array.add(a);
+ array.add(COSInteger.get(0));
+ }
+ }
+ this.getCOSDictionary().setItem(key, array);
+ array.add(attributeObject);
+ array.add(COSInteger.get(this.getRevisionNumber()));
+ }
+
+ /**
+ * Removes an attribute object.
+ *
+ * @param attributeObject the attribute object
+ */
+ public void removeAttribute(PDAttributeObject attributeObject)
+ {
+ COSName key = COSName.A;
+ COSBase a = this.getCOSDictionary().getDictionaryObject(key);
+ if (a instanceof COSArray)
+ {
+ COSArray array = (COSArray) a;
+ array.remove(attributeObject.getCOSObject());
+ if ((array.size() == 2) && (array.getInt(1) == 0))
+ {
+ this.getCOSDictionary().setItem(key, array.getObject(0));
+ }
+ }
+ else
+ {
+ COSBase directA = a;
+ if (a instanceof COSObject)
+ {
+ directA = ((COSObject) a).getObject();
+ }
+ if (attributeObject.getCOSObject().equals(directA))
+ {
+ this.getCOSDictionary().setItem(key, null);
+ }
+ }
+ attributeObject.setStructureElement(null);
+ }
+
+ /**
+ * Updates the revision number for the given attribute object.
+ *
+ * @param attributeObject the attribute object
+ */
+ public void attributeChanged(PDAttributeObject attributeObject)
+ {
+ COSName key = COSName.A;
+ COSBase a = this.getCOSDictionary().getDictionaryObject(key);
+ if (a instanceof COSArray)
+ {
+ COSArray array = (COSArray) a;
+ for (int i = 0; i < array.size(); i++)
+ {
+ COSBase entry = array.getObject(i);
+ if (entry.equals(attributeObject.getCOSObject()))
+ {
+ COSBase next = array.get(i + 1);
+ if (next instanceof COSInteger)
+ {
+ array.set(i + 1, COSInteger.get(this.getRevisionNumber()));
+ }
+ }
+ }
+ }
+ else
+ {
+ COSArray array = new COSArray();
+ array.add(a);
+ array.add(COSInteger.get(this.getRevisionNumber()));
+ this.getCOSDictionary().setItem(key, array);
+ }
+ }
+
+ /**
+ * Returns the class names together with their revision numbers (C).
+ *
+ * @return the class names
+ */
+ public Revisions<String> getClassNames()
+ {
+ COSName key = COSName.C;
+ Revisions<String> classNames = new Revisions<String>();
+ COSBase c = this.getCOSDictionary().getDictionaryObject(key);
+ if (c instanceof COSName)
+ {
+ classNames.addObject(((COSName) c).getName(), 0);
+ }
+ if (c instanceof COSArray)
+ {
+ COSArray array = (COSArray) c;
+ Iterator<COSBase> it = array.iterator();
+ String className = null;
+ while (it.hasNext())
+ {
+ COSBase item = it.next();
+ if (item instanceof COSName)
+ {
+ className = ((COSName) item).getName();
+ classNames.addObject(className, 0);
+ }
+ else if (item instanceof COSInteger)
+ {
+ classNames.setRevisionNumber(className,
+ ((COSInteger) item).intValue());
+ }
+ }
+ }
+ return classNames;
+ }
+
+ /**
+ * Sets the class names together with their revision numbers (C).
+ *
+ * @param classNames the class names
+ */
+ public void setClassNames(Revisions<String> classNames)
+ {
+ if (classNames == null)
+ {
+ return;
+ }
+ COSName key = COSName.C;
+ if ((classNames.size() == 1) && (classNames.getRevisionNumber(0) == 0))
+ {
+ String className = classNames.getObject(0);
+ this.getCOSDictionary().setName(key, className);
+ return;
+ }
+ COSArray array = new COSArray();
+ for (int i = 0; i < classNames.size(); i++)
+ {
+ String className = classNames.getObject(i);
+ int revisionNumber = classNames.getRevisionNumber(i);
+ if (revisionNumber < 0)
+ {
+ // TODO throw Exception because revision number must be > -1?
+ }
+ array.add(COSName.getPDFName(className));
+ array.add(COSInteger.get(revisionNumber));
+ }
+ this.getCOSDictionary().setItem(key, array);
+ }
+
+ /**
+ * Adds a class name.
+ *
+ * @param className the class name
+ */
+ public void addClassName(String className)
+ {
+ if (className == null)
+ {
+ return;
+ }
+ COSName key = COSName.C;
+ COSBase c = this.getCOSDictionary().getDictionaryObject(key);
+ COSArray array = null;
+ if (c instanceof COSArray)
+ {
+ array = (COSArray) c;
+ }
+ else
+ {
+ array = new COSArray();
+ if (c != null)
+ {
+ array.add(c);
+ array.add(COSInteger.get(0));
+ }
+ }
+ this.getCOSDictionary().setItem(key, array);
+ array.add(COSName.getPDFName(className));
+ array.add(COSInteger.get(this.getRevisionNumber()));
+ }
+
+ /**
+ * Removes a class name.
+ *
+ * @param className the class name
+ */
+ public void removeClassName(String className)
+ {
+ if (className == null)
+ {
+ return;
+ }
+ COSName key = COSName.C;
+ COSBase c = this.getCOSDictionary().getDictionaryObject(key);
+ COSName name = COSName.getPDFName(className);
+ if (c instanceof COSArray)
+ {
+ COSArray array = (COSArray) c;
+ array.remove(name);
+ if ((array.size() == 2) && (array.getInt(1) == 0))
+ {
+ this.getCOSDictionary().setItem(key, array.getObject(0));
+ }
+ }
+ else
+ {
+ COSBase directC = c;
+ if (c instanceof COSObject)
+ {
+ directC = ((COSObject) c).getObject();
+ }
+ if (name.equals(directC))
+ {
+ this.getCOSDictionary().setItem(key, null);
+ }
+ }
+ }
+
+ /**
+ * Returns the revision number (R).
+ *
+ * @return the revision number
+ */
+ public int getRevisionNumber()
+ {
+ return this.getCOSDictionary().getInt(COSName.R, 0);
+ }
+
+ /**
+ * Sets the revision number (R).
+ *
+ * @param revisionNumber the revision number
+ */
+ public void setRevisionNumber(int revisionNumber)
+ {
+ if (revisionNumber < 0)
+ {
+ // TODO throw Exception because revision number must be > -1?
+ }
+ this.getCOSDictionary().setInt(COSName.R, revisionNumber);
+ }
+
+ /**
+ * Increments th revision number
+ */
+ public void incrementRevisionNumber()
+ {
+ this.setRevisionNumber(this.getRevisionNumber() + 1);
+ }
+
+ /**
+ * Returns the title (T).
+ *
+ * @return the title
+ */
+ public String getTitle()
+ {
+ return this.getCOSDictionary().getString(COSName.T);
+ }
+
+ /**
+ * Sets the title (T).
+ *
+ * @param title the title
+ */
+ public void setTitle(String title)
+ {
+ this.getCOSDictionary().setString(COSName.T, title);
+ }
+
+ /**
+ * Returns the language (Lang).
+ *
+ * @return the language
+ */
+ public String getLanguage()
+ {
+ return this.getCOSDictionary().getString(COSName.LANG);
+ }
+
+ /**
+ * Sets the language (Lang).
+ *
+ * @param language the language
+ */
+ public void setLanguage(String language)
+ {
+ this.getCOSDictionary().setString(COSName.LANG, language);
+ }
+
+ /**
+ * Returns the alternate description (Alt).
+ *
+ * @return the alternate description
+ */
+ public String getAlternateDescription()
+ {
+ return this.getCOSDictionary().getString(COSName.ALT);
+ }
+
+ /**
+ * Sets the alternate description (Alt).
+ *
+ * @param alternateDescription the alternate description
+ */
+ public void setAlternateDescription(String alternateDescription)
+ {
+ this.getCOSDictionary().setString(COSName.ALT, alternateDescription);
+ }
+
+ /**
+ * Returns the expanded form (E).
+ *
+ * @return the expanded form
+ */
+ public String getExpandedForm()
+ {
+ return this.getCOSDictionary().getString(COSName.E);
+ }
+
+ /**
+ * Sets the expanded form (E).
+ *
+ * @param expandedForm the expanded form
+ */
+ public void setExpandedForm(String expandedForm)
+ {
+ this.getCOSDictionary().setString(COSName.E, expandedForm);
+ }
+
+ /**
+ * Returns the actual text (ActualText).
+ *
+ * @return the actual text
+ */
+ public String getActualText()
+ {
+ return this.getCOSDictionary().getString(COSName.ACTUAL_TEXT);
+ }
+
+ /**
+ * Sets the actual text (ActualText).
+ *
+ * @param actualText the actual text
+ */
+ public void setActualText(String actualText)
+ {
+ this.getCOSDictionary().setString(COSName.ACTUAL_TEXT, actualText);
+ }
+
+ /**
+ * Returns the standard structure type, the actual structure type is mapped
+ * to in the role map.
+ *
+ * @return the standard structure type
+ */
+ public String getStandardStructureType()
+ {
+ String type = this.getStructureType();
+ String mappedType;
+ while (true)
+ {
+ mappedType = this.getRoleMap().get(type);
+ if ((mappedType == null) || type.equals(mappedType))
+ {
+ break;
+ }
+ type = mappedType;
+ }
+ return type;
+ }
+
+ /**
+ * Appends a marked-content sequence kid.
+ *
+ * @param markedContent the marked-content sequence
+ */
+ public void appendKid(PDMarkedContent markedContent)
+ {
+ if (markedContent == null)
+ {
+ return;
+ }
+ this.appendKid(COSInteger.get(markedContent.getMCID()));
+ }
+
+ /**
+ * Appends a marked-content reference kid.
+ *
+ * @param markedContentReference the marked-content reference
+ */
+ public void appendKid(PDMarkedContentReference markedContentReference)
+ {
+ this.appendObjectableKid(markedContentReference);
+ }
+
+ /**
+ * Appends an object reference kid.
+ *
+ * @param objectReference the object reference
+ */
+ public void appendKid(PDObjectReference objectReference)
+ {
+ this.appendObjectableKid(objectReference);
+ }
+
+ /**
+ * Inserts a marked-content identifier kid before a reference kid.
+ *
+ * @param markedContentIdentifier the marked-content identifier
+ * @param refKid the reference kid
+ */
+ public void insertBefore(COSInteger markedContentIdentifier, Object refKid)
+ {
+ this.insertBefore((COSBase) markedContentIdentifier, refKid);
+ }
+
+ /**
+ * Inserts a marked-content reference kid before a reference kid.
+ *
+ * @param markedContentReference the marked-content reference
+ * @param refKid the reference kid
+ */
+ public void insertBefore(PDMarkedContentReference markedContentReference,
+ Object refKid)
+ {
+ this.insertObjectableBefore(markedContentReference, refKid);
+ }
+
+ /**
+ * Inserts an object reference kid before a reference kid.
+ *
+ * @param objectReference the object reference
+ * @param refKid the reference kid
+ */
+ public void insertBefore(PDObjectReference objectReference, Object refKid)
+ {
+ this.insertObjectableBefore(objectReference, refKid);
+ }
+
+ /**
+ * Removes a marked-content identifier kid.
+ *
+ * @param markedContentIdentifier the marked-content identifier
+ */
+ public void removeKid(COSInteger markedContentIdentifier)
+ {
+ this.removeKid((COSBase) markedContentIdentifier);
+ }
+
+ /**
+ * Removes a marked-content reference kid.
+ *
+ * @param markedContentReference the marked-content reference
+ */
+ public void removeKid(PDMarkedContentReference markedContentReference)
+ {
+ this.removeObjectableKid(markedContentReference);
+ }
+
+ /**
+ * Removes an object reference kid.
+ *
+ * @param objectReference the object reference
+ */
+ public void removeKid(PDObjectReference objectReference)
+ {
+ this.removeObjectableKid(objectReference);
+ }
+
+
+ /**
+ * Returns the structure tree root.
+ *
+ * @return the structure tree root
+ */
+ private PDStructureTreeRoot getStructureTreeRoot()
+ {
+ PDStructureNode parent = this.getParent();
+ while (parent instanceof PDStructureElement)
+ {
+ parent = ((PDStructureElement) parent).getParent();
+ }
+ if (parent instanceof PDStructureTreeRoot)
+ {
+ return (PDStructureTreeRoot) parent;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the role map.
+ *
+ * @return the role map
+ */
+ private Map<String, String> getRoleMap()
+ {
+ PDStructureTreeRoot root = this.getStructureTreeRoot();
+ if (root != null)
+ {
+ return root.getRoleMap();
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+
+/**
+ * A node in the structure tree.
+ *
+ * @author Koch
+ * @version $Revision: $
+ */
+public abstract class PDStructureNode implements COSObjectable
+{
+
+ /**
+ * Creates a node in the structure tree. Can be either a structure tree root,
+ * or a structure element.
+ *
+ * @param node the node dictionary
+ * @return the structure node
+ */
+ public static PDStructureNode create(COSDictionary node)
+ {
+ String type = node.getNameAsString(COSName.TYPE);
+ if ("StructTreeRoot".equals(type))
+ {
+ return new PDStructureTreeRoot(node);
+ }
+ if ((type == null) || "StructElem".equals(type))
+ {
+ return new PDStructureElement(node);
+ }
+ throw new IllegalArgumentException("Dictionary must not include a Type entry with a value that is neither StructTreeRoot nor StructElem.");
+ }
+
+
+ private COSDictionary dictionary;
+
+ protected COSDictionary getCOSDictionary()
+ {
+ return dictionary;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param type the type
+ */
+ protected PDStructureNode(String type)
+ {
+ this.dictionary = new COSDictionary();
+ this.dictionary.setName(COSName.TYPE, type);
+ }
+
+ /**
+ * Constructor for an existing structure node.
+ *
+ * @param dictionary The existing dictionary.
+ */
+ protected PDStructureNode(COSDictionary dictionary)
+ {
+ this.dictionary = dictionary;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return this.dictionary;
+ }
+
+ /**
+ * Returns the type.
+ *
+ * @return the type
+ */
+ public String getType()
+ {
+ return this.getCOSDictionary().getNameAsString(COSName.TYPE);
+ }
+
+ /**
+ * Returns a list of objects for the kids (K).
+ *
+ * @return a list of objects for the kids
+ */
+ public List<Object> getKids()
+ {
+ List<Object> kidObjects = new ArrayList<Object>();
+ COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
+ if (k instanceof COSArray)
+ {
+ Iterator<COSBase> kids = ((COSArray) k).iterator();
+ while (kids.hasNext())
+ {
+ COSBase kid = kids.next();
+ Object kidObject = this.createObject(kid);
+ if (kidObject != null)
+ {
+ kidObjects.add(kidObject);
+ }
+ }
+ }
+ else
+ {
+ Object kidObject = this.createObject(k);
+ if (kidObject != null)
+ {
+ kidObjects.add(kidObject);
+ }
+ }
+ return kidObjects;
+ }
+
+ /**
+ * Sets the kids (K).
+ *
+ * @param kids the kids
+ */
+ public void setKids(List<Object> kids)
+ {
+ this.getCOSDictionary().setItem(COSName.K,
+ COSArrayList.converterToCOSArray(kids));
+ }
+
+ /**
+ * Appends a structure element kid.
+ *
+ * @param structureElement the structure element
+ */
+ public void appendKid(PDStructureElement structureElement)
+ {
+ this.appendObjectableKid(structureElement);
+ structureElement.setParent(this);
+ }
+
+ /**
+ * Appends an objectable kid.
+ *
+ * @param objectable the objectable
+ */
+ protected void appendObjectableKid(COSObjectable objectable)
+ {
+ if (objectable == null)
+ {
+ return;
+ }
+ this.appendKid(objectable.getCOSObject());
+ }
+
+ /**
+ * Appends a COS base kid.
+ *
+ * @param object the COS base
+ */
+ protected void appendKid(COSBase object)
+ {
+ if (object == null)
+ {
+ return;
+ }
+ COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
+ if (k == null)
+ {
+ // currently no kid: set new kid as kids
+ this.getCOSDictionary().setItem(COSName.K, object);
+ }
+ else if (k instanceof COSArray)
+ {
+ // currently more than one kid: add new kid to existing array
+ COSArray array = (COSArray) k;
+ array.add(object);
+ }
+ else
+ {
+ // currently one kid: put current and new kid into array and set array as kids
+ COSArray array = new COSArray();
+ array.add(k);
+ array.add(object);
+ this.getCOSDictionary().setItem(COSName.K, array);
+ }
+ }
+
+ /**
+ * Inserts a structure element kid before a reference kid.
+ *
+ * @param newKid the structure element
+ * @param refKid the reference kid
+ */
+ public void insertBefore(PDStructureElement newKid, Object refKid)
+ {
+ this.insertObjectableBefore(newKid, refKid);
+ }
+
+ /**
+ * Inserts an objectable kid before a reference kid.
+ *
+ * @param newKid the objectable
+ * @param refKid the reference kid
+ */
+ protected void insertObjectableBefore(COSObjectable newKid, Object refKid)
+ {
+ if (newKid == null)
+ {
+ return;
+ }
+ this.insertBefore(newKid.getCOSObject(), refKid);
+ }
+
+ /**
+ * Inserts an COS base kid before a reference kid.
+ *
+ * @param newKid the COS base
+ * @param refKid the reference kid
+ */
+ protected void insertBefore(COSBase newKid, Object refKid)
+ {
+ if ((newKid == null) || (refKid == null))
+ {
+ return;
+ }
+ COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
+ if (k == null)
+ {
+ return;
+ }
+ COSBase refKidBase = null;
+ if (refKid instanceof COSObjectable)
+ {
+ refKidBase = ((COSObjectable) refKid).getCOSObject();
+ }
+ else if (refKid instanceof COSInteger)
+ {
+ refKidBase = (COSInteger) refKid;
+ }
+ if (k instanceof COSArray)
+ {
+ COSArray array = (COSArray) k;
+ int refIndex = array.indexOfObject(refKidBase);
+ array.add(refIndex, newKid.getCOSObject());
+ }
+ else
+ {
+ boolean onlyKid = k.equals(refKidBase);
+ if (!onlyKid && (k instanceof COSObject))
+ {
+ COSBase kObj = ((COSObject) k).getObject();
+ onlyKid = kObj.equals(refKidBase);
+ }
+ if (onlyKid)
+ {
+ COSArray array = new COSArray();
+ array.add(newKid);
+ array.add(refKidBase);
+ this.getCOSDictionary().setItem(COSName.K, array);
+ }
+ }
+ }
+
+ /**
+ * Removes a structure element kid.
+ *
+ * @param structureElement the structure element
+ * @return <code>true</code> if the kid was removed, <code>false</code> otherwise
+ */
+ public boolean removeKid(PDStructureElement structureElement)
+ {
+ boolean removed = this.removeObjectableKid(structureElement);
+ if (removed)
+ {
+ structureElement.setParent(null);
+ }
+ return removed;
+ }
+
+ /**
+ * Removes an objectable kid.
+ *
+ * @param objectable the objectable
+ * @return <code>true</code> if the kid was removed, <code>false</code> otherwise
+ */
+ protected boolean removeObjectableKid(COSObjectable objectable)
+ {
+ if (objectable == null)
+ {
+ return false;
+ }
+ return this.removeKid(objectable.getCOSObject());
+ }
+
+ /**
+ * Removes a COS base kid.
+ *
+ * @param object the COS base
+ * @return <code>true</code> if the kid was removed, <code>false</code> otherwise
+ */
+ protected boolean removeKid(COSBase object)
+ {
+ if (object == null)
+ {
+ return false;
+ }
+ COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
+ if (k == null)
+ {
+ // no kids: objectable is not a kid
+ return false;
+ }
+ else if (k instanceof COSArray)
+ {
+ // currently more than one kid: remove kid from existing array
+ COSArray array = (COSArray) k;
+ boolean removed = array.removeObject(object);
+ // if now only one kid: set remaining kid as kids
+ if (array.size() == 1)
+ {
+ this.getCOSDictionary().setItem(COSName.K, array.getObject(0));
+ }
+ return removed;
+ }
+ else
+ {
+ // currently one kid: if current kid equals given object, remove kids entry
+ boolean onlyKid = k.equals(object);
+ if (!onlyKid && (k instanceof COSObject))
+ {
+ COSBase kObj = ((COSObject) k).getObject();
+ onlyKid = kObj.equals(object);
+ }
+ if (onlyKid)
+ {
+ this.getCOSDictionary().setItem(COSName.K, null);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Creates an object for a kid of this structure node.
+ * The type of object depends on the type of the kid. It can be
+ * <ul>
+ * <li>a {@link PDStructureElement},</li>
+ * <li>a {@link PDAnnotation},</li>
+ * <li>a {@link PDXObject},</li>
+ * <li>a {@link PDMarkedContentReference}</li>
+ * <li>a {@link Integer}</li>
+ * </ul>
+ *
+ * @param kid the kid
+ * @return the object
+ */
+ protected Object createObject(COSBase kid)
+ {
+ COSDictionary kidDic = null;
+ if (kid instanceof COSDictionary)
+ {
+ kidDic = (COSDictionary) kid;
+ }
+ else if (kid instanceof COSObject)
+ {
+ COSBase base = ((COSObject) kid).getObject();
+ if (base instanceof COSDictionary)
+ {
+ kidDic = (COSDictionary) base;
+ }
+ }
+ if (kidDic != null)
+ {
+ String type = kidDic.getNameAsString(COSName.TYPE);
+ if ((type == null) || PDStructureElement.TYPE.equals(type))
+ {
+ // A structure element dictionary denoting another structure
+ // element
+ return new PDStructureElement(kidDic);
+ }
+ else if (PDObjectReference.TYPE.equals(type))
+ {
+ // An object reference dictionary denoting a PDF object
+ return new PDObjectReference(kidDic);
+ }
+ else if (PDMarkedContentReference.TYPE.equals(type))
+ {
+ // A marked-content reference dictionary denoting a
+ // marked-content sequence
+ return new PDMarkedContentReference(kidDic);
+ }
+ }
+ else if (kid instanceof COSInteger)
+ {
+ // An integer marked-content identifier denoting a
+ // marked-content sequence
+ COSInteger mcid = (COSInteger) kid;
+ return mcid.intValue();
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSDictionaryMap;
+import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
+
+/**
+ * A root of a structure tree.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>,
+ * <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDStructureTreeRoot extends PDStructureNode
+{
+
+ public static final String TYPE = "StructTreeRoot";
+
+
+ /**
+ * Default Constructor.
+ *
+ */
+ public PDStructureTreeRoot()
+ {
+ super(TYPE);
+ }
+
+ /**
+ * Constructor for an existing structure element.
+ *
+ * @param dic The existing dictionary.
+ */
+ public PDStructureTreeRoot( COSDictionary dic )
+ {
+ super(dic);
+ }
+
+
+ /**
+ * Returns the ID tree.
+ *
+ * @return the ID tree
+ */
+ public PDNameTreeNode getIDTree()
+ {
+ COSDictionary idTreeDic = (COSDictionary) this.getCOSDictionary()
+ .getDictionaryObject(COSName.ID_TREE);
+ if (idTreeDic != null)
+ {
+ return new PDNameTreeNode(idTreeDic, PDStructureElement.class);
+ }
+ return null;
+ }
+
+ /**
+ * Sets the ID tree.
+ *
+ * @param idTree the ID tree
+ */
+ public void setIDTree(PDNameTreeNode idTree)
+ {
+ this.getCOSDictionary().setItem(COSName.ID_TREE, idTree);
+ }
+
+ /**
+ * Returns the next key in the parent tree.
+ *
+ * @return the next key in the parent tree
+ */
+ public int getParentTreeNextKey()
+ {
+ return this.getCOSDictionary().getInt(COSName.PARENT_TREE_NEXT_KEY);
+ }
+
+ /**
+ * Returns the role map.
+ *
+ * @return the role map
+ */
+ @SuppressWarnings("unchecked")
+ public Map<String, String> getRoleMap()
+ {
+ COSBase rm = this.getCOSDictionary().getDictionaryObject(COSName.ROLE_MAP);
+ if (rm instanceof COSDictionary)
+ {
+ try
+ {
+ return COSDictionaryMap.convertBasicTypesToMap((COSDictionary) rm);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ return new Hashtable<String, String>();
+ }
+
+ /**
+ * Sets the role map.
+ *
+ * @param roleMap the role map
+ */
+ public void setRoleMap(Map<String, String> roleMap)
+ {
+ COSDictionary rmDic = new COSDictionary();
+ for (String key : roleMap.keySet())
+ {
+ rmDic.setName(key, roleMap.get(key));
+ }
+ this.getCOSDictionary().setItem(COSName.ROLE_MAP, rmDic);
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * A User attribute object.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDUserAttributeObject extends PDAttributeObject
+{
+
+ /**
+ * Attribute owner for user properties
+ */
+ public static final String OWNER_USER_PROPERTIES = "UserProperties";
+
+
+ /**
+ * Default constructor
+ */
+ public PDUserAttributeObject()
+ {
+ this.setOwner(OWNER_USER_PROPERTIES);
+ }
+
+ /**
+ *
+ * @param dictionary the dictionary
+ */
+ public PDUserAttributeObject(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+
+ /**
+ * Returns the user properties.
+ *
+ * @return the user properties
+ */
+ public List<PDUserProperty> getOwnerUserProperties()
+ {
+ COSArray p = (COSArray) this.getCOSDictionary()
+ .getDictionaryObject(COSName.P);
+ List<PDUserProperty> properties = new ArrayList<PDUserProperty>(p.size());
+ for (int i = 0; i < p.size(); i++)
+ {
+ properties.add(
+ new PDUserProperty((COSDictionary) p.getObject(i), this));
+ }
+ return properties;
+ }
+
+ /**
+ * Sets the user properties.
+ *
+ * @param userProperties the user properties
+ */
+ public void setUserProperties(List<PDUserProperty> userProperties)
+ {
+ COSArray p = new COSArray();
+ for (PDUserProperty userProperty : userProperties)
+ {
+ p.add(userProperty);
+ }
+ this.getCOSDictionary().setItem(COSName.P, p);
+ }
+
+ /**
+ * Adds a user property.
+ *
+ * @param userProperty the user property
+ */
+ public void addUserProperty(PDUserProperty userProperty)
+ {
+ COSArray p = (COSArray) this.getCOSDictionary()
+ .getDictionaryObject(COSName.P);
+ p.add(userProperty);
+ this.notifyChanged();
+ }
+
+ /**
+ * Removes a user property.
+ *
+ * @param userProperty the user property
+ */
+ public void removeUserProperty(PDUserProperty userProperty)
+ {
+ if (userProperty == null)
+ {
+ return;
+ }
+ COSArray p = (COSArray) this.getCOSDictionary()
+ .getDictionaryObject(COSName.P);
+ p.remove(userProperty.getCOSObject());
+ this.notifyChanged();
+ }
+
+ public void userPropertyChanged(PDUserProperty userProperty)
+ {
+
+ }
+
+ @Override
+ public String toString()
+ {
+ return new StringBuilder().append(super.toString())
+ .append(", userProperties=")
+ .append(this.getOwnerUserProperties()).toString();
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDDictionaryWrapper;
+
+/**
+ * A user property.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDUserProperty extends PDDictionaryWrapper
+{
+
+ private final PDUserAttributeObject userAttributeObject;
+
+ /**
+ * Creates a new user property.
+ *
+ * @param the user attribute object
+ */
+ public PDUserProperty(PDUserAttributeObject userAttributeObject)
+ {
+ this.userAttributeObject = userAttributeObject;
+ }
+
+ /**
+ * Creates a user property with a given dictionary.
+ *
+ * @param dictionary the dictionary
+ * @param the user attribute object
+ */
+ public PDUserProperty(COSDictionary dictionary,
+ PDUserAttributeObject userAttributeObject)
+ {
+ super(dictionary);
+ this.userAttributeObject = userAttributeObject;
+ }
+
+
+ /**
+ * Returns the property name.
+ *
+ * @return the property name
+ */
+ public String getName()
+ {
+ return this.getCOSDictionary().getNameAsString(COSName.N);
+ }
+
+ /**
+ * Sets the property name.
+ *
+ * @param name the property name
+ */
+ public void setName(String name)
+ {
+ this.potentiallyNotifyChanged(this.getName(), name);
+ this.getCOSDictionary().setName(COSName.N, name);
+ }
+
+ /**
+ * Returns the property value.
+ *
+ * @return the property value
+ */
+ public COSBase getValue()
+ {
+ return this.getCOSDictionary().getDictionaryObject(COSName.V);
+ }
+
+ /**
+ * Sets the property value.
+ *
+ * @param value the property value
+ */
+ public void setValue(COSBase value)
+ {
+ this.potentiallyNotifyChanged(this.getValue(), value);
+ this.getCOSDictionary().setItem(COSName.V, value);
+ }
+
+ /**
+ * Returns the string for the property value.
+ *
+ * @return the string for the property value
+ */
+ public String getFormattedValue()
+ {
+ return this.getCOSDictionary().getString(COSName.F);
+ }
+
+ /**
+ * Sets the string for the property value.
+ *
+ * @param formattedValue the string for the property value
+ */
+ public void setFormattedValue(String formattedValue)
+ {
+ this.potentiallyNotifyChanged(this.getFormattedValue(), formattedValue);
+ this.getCOSDictionary().setString(COSName.F, formattedValue);
+ }
+
+ /**
+ * Shall the property be hidden?
+ *
+ * @return <code>true</code> if the property shall be hidden,
+ * <code>false</code> otherwise
+ */
+ public boolean isHidden()
+ {
+ return this.getCOSDictionary().getBoolean(COSName.H, false);
+ }
+
+ /**
+ * Specifies whether the property shall be hidden.
+ *
+ * @param hidden <code>true</code> if the property shall be hidden,
+ * <code>false</code> otherwise
+ */
+ public void setHidden(boolean hidden)
+ {
+ this.potentiallyNotifyChanged(this.isHidden(), hidden);
+ this.getCOSDictionary().setBoolean(COSName.H, hidden);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return new StringBuilder("Name=").append(this.getName())
+ .append(", Value=").append(this.getValue())
+ .append(", FormattedValue=").append(this.getFormattedValue())
+ .append(", Hidden=").append(this.isHidden()).toString();
+ }
+
+
+ /**
+ * Notifies the user attribute object if the user property is changed.
+ *
+ * @param oldEntry old entry
+ * @param newEntry new entry
+ */
+ private void potentiallyNotifyChanged(Object oldEntry, Object newEntry)
+ {
+ if (this.isEntryChanged(oldEntry, newEntry))
+ {
+ this.userAttributeObject.userPropertyChanged(this);
+ }
+ }
+
+ /**
+ * Is the value changed?
+ *
+ * @param oldEntry old entry
+ * @param newEntry new entry
+ * @return <code>true</code> if the entry is changed, <code>false</code>
+ * otherwise
+ */
+ private boolean isEntryChanged(Object oldEntry, Object newEntry)
+ {
+ if (oldEntry == null)
+ {
+ if (newEntry == null)
+ {
+ return false;
+ }
+ return true;
+ }
+ return !oldEntry.equals(newEntry);
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.logicalstructure;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Koch
+ * @version $Revision: $
+ *
+ * @param <T> the type of object to store the revision numbers with
+ */
+public class Revisions<T>
+{
+
+ private List<T> objects;
+ private List<Integer> revisionNumbers;
+
+ private List<T> getObjects()
+ {
+ if (this.objects == null)
+ {
+ this.objects = new ArrayList<T>();
+ }
+ return this.objects;
+ }
+
+ private List<Integer> getRevisionNumbers()
+ {
+ if (this.revisionNumbers == null)
+ {
+ this.revisionNumbers = new ArrayList<Integer>();
+ }
+ return this.revisionNumbers;
+ }
+
+
+ /**
+ *
+ */
+ public Revisions()
+ {
+ }
+
+
+ /**
+ * Returns the object at the specified position.
+ *
+ * @param index the position
+ * @return the object
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
+ public T getObject(int index) throws IndexOutOfBoundsException
+ {
+ return this.getObjects().get(index);
+ }
+
+ /**
+ * Returns the revision number at the specified position.
+ *
+ * @param index the position
+ * @return the revision number
+ * @throws IndexOutOfBoundsException if the index is out of range
+ */
+ public int getRevisionNumber(int index) throws IndexOutOfBoundsException
+ {
+ return this.getRevisionNumbers().get(index);
+ }
+
+ /**
+ * Adds an object with a specified revision number.
+ *
+ * @param object the object
+ * @param revisionNumber the revision number
+ */
+ public void addObject(T object, int revisionNumber)
+ {
+ this.getObjects().add(object);
+ this.getRevisionNumbers().add(revisionNumber);
+ }
+
+ /**
+ * Sets the revision number of a specified object.
+ *
+ * @param object the object
+ * @param revisionNumber the revision number
+ */
+ protected void setRevisionNumber(T object, int revisionNumber)
+ {
+ int index = this.getObjects().indexOf(object);
+ if (index > -1)
+ {
+ this.getRevisionNumbers().set(index, revisionNumber);
+ }
+ }
+
+ /**
+ * Returns the size.
+ *
+ * @return the size
+ */
+ public int size()
+ {
+ return this.getObjects().size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < this.getObjects().size(); i++)
+ {
+ if (i > 0)
+ {
+ sb.append("; ");
+ }
+ sb.append("object=").append(this.getObjects().get(i))
+ .append(", revisionNumber=").append(this.getRevisionNumber(i));
+ }
+ return sb.toString();
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The logical structure package provides a mechanism for incorporating
+structural information about a document's content into a PDF file.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.documentinterchange.markedcontent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.documentinterchange.taggedpdf.PDArtifactMarkedContent;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.util.TextPosition;
+
+/**
+ * A marked content.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDMarkedContent
+{
+
+ /**
+ * Creates a marked-content sequence.
+ *
+ * @param tag the tag
+ * @param properties the properties
+ * @return the marked-content sequence
+ */
+ public static PDMarkedContent create(COSName tag, COSDictionary properties)
+ {
+ if (COSName.ARTIFACT.equals(tag))
+ {
+ new PDArtifactMarkedContent(properties);
+ }
+ return new PDMarkedContent(tag, properties);
+ }
+
+
+ private String tag;
+ private COSDictionary properties;
+ private List<Object> contents;
+
+
+ /**
+ * Creates a new marked content object.
+ *
+ * @param tag the tag
+ * @param properties the properties
+ */
+ public PDMarkedContent(COSName tag, COSDictionary properties)
+ {
+ this.tag = tag == null ? null : tag.getName();
+ this.properties = properties;
+ this.contents = new ArrayList<Object>();
+ }
+
+
+ /**
+ * Gets the tag.
+ *
+ * @return the tag
+ */
+ public String getTag()
+ {
+ return this.tag;
+ }
+
+ /**
+ * Gets the properties.
+ *
+ * @return the properties
+ */
+ public COSDictionary getProperties()
+ {
+ return this.properties;
+ }
+
+ /**
+ * Gets the marked-content identifier.
+ *
+ * @return the marked-content identifier
+ */
+ public int getMCID()
+ {
+ return this.getProperties() == null ? null :
+ this.getProperties().getInt(COSName.MCID);
+ }
+
+ /**
+ * Gets the language (Lang).
+ *
+ * @return the language
+ */
+ public String getLanguage()
+ {
+ return this.getProperties() == null ? null :
+ this.getProperties().getNameAsString(COSName.LANG);
+ }
+
+ /**
+ * Gets the actual text (ActualText).
+ *
+ * @return the actual text
+ */
+ public String getActualText()
+ {
+ return this.getProperties() == null ? null :
+ this.getProperties().getString(COSName.ACTUAL_TEXT);
+ }
+
+ /**
+ * Gets the alternate description (Alt).
+ *
+ * @return the alternate description
+ */
+ public String getAlternateDescription()
+ {
+ return this.getProperties() == null ? null :
+ this.getProperties().getString(COSName.ALT);
+ }
+
+ /**
+ * Gets the expanded form (E).
+ *
+ * @return the expanded form
+ */
+ public String getExpandedForm()
+ {
+ return this.getProperties() == null ? null :
+ this.getProperties().getString(COSName.E);
+ }
+
+ /**
+ * Gets the contents of the marked content sequence. Can be
+ * <ul>
+ * <li>{@link TextPosition},</li>
+ * <li>{@link PDMarkedContent}, or</li>
+ * <li>{@link PDXObject}.</li>
+ * </ul>
+ *
+ * @return the contents of the marked content sequence
+ */
+ public List<Object> getContents()
+ {
+ return this.contents;
+ }
+
+ /**
+ * Adds a text position to the contents.
+ *
+ * @param text the text position
+ */
+ public void addText(TextPosition text)
+ {
+ this.getContents().add(text);
+ }
+
+ /**
+ * Adds a marked content to the contents.
+ *
+ * @param markedContent the marked content
+ */
+ public void addMarkedContent(PDMarkedContent markedContent)
+ {
+ this.getContents().add(markedContent);
+ }
+
+ /**
+ * Adds an XObject to the contents.
+ *
+ * @param xobject the XObject
+ */
+ public void addXObject(PDXObject xobject)
+ {
+ this.getContents().add(xobject);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder("tag=").append(this.tag)
+ .append(", properties=").append(this.properties);
+ sb.append(", contents=").append(this.contents);
+ return sb.toString();
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The marked content package provides a mechanism for modeling marked-content
+sequences.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.documentinterchange.prepress;
+
+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.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+
+/**
+ * The Box Style specifies visual characteristics for displaying box areas.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDBoxStyle implements COSObjectable
+{
+ /**
+ * Style for guideline.
+ */
+ public static final String GUIDELINE_STYLE_SOLID = "S";
+ /**
+ * Style for guideline.
+ */
+ public static final String GUIDELINE_STYLE_DASHED = "D";
+
+ private COSDictionary dictionary;
+
+ /**
+ * Default Constructor.
+ *
+ */
+ public PDBoxStyle()
+ {
+ dictionary = new COSDictionary();
+ }
+
+ /**
+ * Constructor for an existing BoxStyle element.
+ *
+ * @param dic The existing dictionary.
+ */
+ public PDBoxStyle( COSDictionary dic )
+ {
+ dictionary = dic;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return dictionary;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getDictionary()
+ {
+ return dictionary;
+ }
+
+ /**
+ * Get the color to be used for the guidelines. This is guaranteed to
+ * not return null. The color space will always be DeviceRGB and the
+ * default color is [0,0,0].
+ *
+ *@return The guideline color.
+ */
+ public PDColorState getGuidelineColor()
+ {
+ COSArray colorValues = (COSArray)dictionary.getDictionaryObject( "C" );
+ if( colorValues == null )
+ {
+ colorValues = new COSArray();
+ colorValues.add( COSInteger.ZERO );
+ colorValues.add( COSInteger.ZERO );
+ colorValues.add( COSInteger.ZERO );
+ dictionary.setItem( "C", colorValues );
+ }
+ PDColorState instance = new PDColorState( colorValues );
+ instance.setColorSpace( PDDeviceRGB.INSTANCE );
+ return instance;
+ }
+
+ /**
+ * Set the color space instance for this box style. This must be a
+ * PDDeviceRGB!
+ *
+ * @param color The new colorspace value.
+ */
+ public void setGuideLineColor( PDColorState color )
+ {
+ COSArray values = null;
+ if( color != null )
+ {
+ values = color.getCOSColorSpaceValue();
+ }
+ dictionary.setItem( "C", values );
+ }
+
+ /**
+ * Get the width of the of the guideline in default user space units.
+ * The default is 1.
+ *
+ * @return The width of the guideline.
+ */
+ public float getGuidelineWidth()
+ {
+ return dictionary.getFloat( "W", 1 );
+ }
+
+ /**
+ * Set the guideline width.
+ *
+ * @param width The width in default user space units.
+ */
+ public void setGuidelineWidth( float width )
+ {
+ dictionary.setFloat( "W", width );
+ }
+
+ /**
+ * Get the style for the guideline. The default is "S" for solid.
+ *
+ * @return The guideline style.
+ * @see PDBoxStyle#GUIDELINE_STYLE_DASHED
+ * @see PDBoxStyle#GUIDELINE_STYLE_SOLID
+ */
+ public String getGuidelineStyle()
+ {
+ return dictionary.getNameAsString( "S", GUIDELINE_STYLE_SOLID );
+ }
+
+ /**
+ * Set the style for the box.
+ *
+ * @param style The style for the box line.
+ * @see PDBoxStyle#GUIDELINE_STYLE_DASHED
+ * @see PDBoxStyle#GUIDELINE_STYLE_SOLID
+ */
+ public void setGuidelineStyle( String style )
+ {
+ dictionary.setName( "S", style );
+ }
+
+ /**
+ * Get the line dash pattern for this box style. This is guaranteed to not
+ * return null. The default is [3],0.
+ *
+ * @return The line dash pattern.
+ */
+ public PDLineDashPattern getLineDashPattern()
+ {
+ PDLineDashPattern pattern = null;
+ COSArray d = (COSArray)dictionary.getDictionaryObject( "D" );
+ if( d == null )
+ {
+ d = new COSArray();
+ d.add( COSInteger.THREE );
+ dictionary.setItem( "D", d );
+ }
+ COSArray lineArray = new COSArray();
+ lineArray.add( d );
+ //dash phase is not specified and assumed to be zero.
+ lineArray.add( COSInteger.ZERO );
+ pattern = new PDLineDashPattern( lineArray );
+ return pattern;
+ }
+
+ /**
+ * Set the line dash pattern associated with this box style.
+ *
+ * @param pattern The patter for this box style.
+ */
+ public void setLineDashPattern( PDLineDashPattern pattern )
+ {
+ COSArray array = null;
+ if( pattern != null )
+ {
+ array = pattern.getCOSDashPattern();
+ }
+ dictionary.setItem( "D", array );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains classes for prepress support in PDFBox.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDMarkedContent;
+
+/**
+ * An artifact marked content.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ *
+ */
+public class PDArtifactMarkedContent extends PDMarkedContent
+{
+
+ public PDArtifactMarkedContent(COSDictionary properties)
+ {
+ super(COSName.ARTIFACT, properties);
+ }
+
+
+ /**
+ * Gets the type (Type).
+ *
+ * @return the type
+ */
+ public String getType()
+ {
+ return this.getProperties().getNameAsString(COSName.TYPE);
+ }
+
+ /**
+ * Gets the artifact's bounding box (BBox).
+ *
+ * @return the artifact's bounding box
+ */
+ public PDRectangle getBBox()
+ {
+ PDRectangle retval = null;
+ COSArray a = (COSArray) this.getProperties().getDictionaryObject(
+ COSName.BBOX);
+ if (a != null)
+ {
+ retval = new PDRectangle(a);
+ }
+ return retval;
+ }
+
+ /**
+ * Is the artifact attached to the top edge?
+ *
+ * @return <code>true</code> if the artifact is attached to the top edge,
+ * <code>false</code> otherwise
+ */
+ public boolean isTopAttached()
+ {
+ return this.isAttached("Top");
+ }
+
+ /**
+ * Is the artifact attached to the bottom edge?
+ *
+ * @return <code>true</code> if the artifact is attached to the bottom edge,
+ * <code>false</code> otherwise
+ */
+ public boolean isBottomAttached()
+ {
+ return this.isAttached("Bottom");
+ }
+
+ /**
+ * Is the artifact attached to the left edge?
+ *
+ * @return <code>true</code> if the artifact is attached to the left edge,
+ * <code>false</code> otherwise
+ */
+ public boolean isLeftAttached()
+ {
+ return this.isAttached("Left");
+ }
+
+ /**
+ * Is the artifact attached to the right edge?
+ *
+ * @return <code>true</code> if the artifact is attached to the right edge,
+ * <code>false</code> otherwise
+ */
+ public boolean isRightAttached()
+ {
+ return this.isAttached("Right");
+ }
+
+ /**
+ * Gets the subtype (Subtype).
+ *
+ * @return the subtype
+ */
+ public String getSubtype()
+ {
+ return this.getProperties().getNameAsString(COSName.SUBTYPE);
+ }
+
+
+ /**
+ * Is the artifact attached to the given edge?
+ *
+ * @param edge the edge
+ * @return <code>true</code> if the artifact is attached to the given edge,
+ * <code>false</code> otherwise
+ */
+ private boolean isAttached(String edge)
+ {
+ COSArray a = (COSArray) this.getProperties().getDictionaryObject(
+ COSName.ATTACHED);
+ if (a != null)
+ {
+ for (int i = 0; i < a.size(); i++)
+ {
+ if (edge.equals(a.getName(i)))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
+
+/**
+ * An Export Format attribute object.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDExportFormatAttributeObject extends PDLayoutAttributeObject
+{
+
+ /**
+ * standard attribute owner: XML-1.00
+ */
+ public static final String OWNER_XML_1_00 = "XML-1.00";
+ /**
+ * standard attribute owner: HTML-3.2
+ */
+ public static final String OWNER_HTML_3_20 = "HTML-3.2";
+ /**
+ * standard attribute owner: HTML-4.01
+ */
+ public static final String OWNER_HTML_4_01 = "HTML-4.01";
+ /**
+ * standard attribute owner: OEB-1.00
+ */
+ public static final String OWNER_OEB_1_00 = "OEB-1.00";
+ /**
+ * standard attribute owner: RTF-1.05
+ */
+ public static final String OWNER_RTF_1_05 = "RTF-1.05";
+ /**
+ * standard attribute owner: CSS-1.00
+ */
+ public static final String OWNER_CSS_1_00 = "CSS-1.00";
+ /**
+ * standard attribute owner: CSS-2.00
+ */
+ public static final String OWNER_CSS_2_00 = "CSS-2.00";
+
+
+ /**
+ * Default constructor.
+ */
+ public PDExportFormatAttributeObject(String owner)
+ {
+ this.setOwner(owner);
+ }
+
+ /**
+ * Creates a new ExportFormat attribute object with a given dictionary.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDExportFormatAttributeObject(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+
+ /**
+ * Gets the list numbering (ListNumbering). The default value is
+ * {@link #LIST_NUMBERING_NONE}.
+ *
+ * @return the list numbering
+ */
+ public String getListNumbering()
+ {
+ return this.getName(PDListAttributeObject.LIST_NUMBERING,
+ PDListAttributeObject.LIST_NUMBERING_NONE);
+ }
+
+ /**
+ * Sets the list numbering (ListNumbering). The value shall be one of the
+ * following:
+ * <ul>
+ * <li>{@link #LIST_NUMBERING_NONE},</li>
+ * <li>{@link #LIST_NUMBERING_DISC},</li>
+ * <li>{@link #LIST_NUMBERING_CIRCLE},</li>
+ * <li>{@link #LIST_NUMBERING_SQUARE},</li>
+ * <li>{@link #LIST_NUMBERING_DECIMAL},</li>
+ * <li>{@link #LIST_NUMBERING_UPPER_ROMAN},</li>
+ * <li>{@link #LIST_NUMBERING_LOWER_ROMAN},</li>
+ * <li>{@link #LIST_NUMBERING_UPPER_ALPHA},</li>
+ * <li>{@link #LIST_NUMBERING_LOWER_ALPHA}.</li>
+ * </ul>
+ *
+ * @param listNumbering the list numbering
+ */
+ public void setListNumbering(String listNumbering)
+ {
+ this.setName(PDListAttributeObject.LIST_NUMBERING, listNumbering);
+ }
+
+ /**
+ * Gets the number of rows in the enclosing table that shall be spanned by
+ * the cell (RowSpan). The default value is 1.
+ *
+ * @return the row span
+ */
+ public int getRowSpan()
+ {
+ return this.getInteger(PDTableAttributeObject.ROW_SPAN, 1);
+ }
+
+ /**
+ * Sets the number of rows in the enclosing table that shall be spanned by
+ * the cell (RowSpan).
+ *
+ * @param rowSpan the row span
+ */
+ public void setRowSpan(int rowSpan)
+ {
+ this.setInteger(PDTableAttributeObject.ROW_SPAN, rowSpan);
+ }
+
+ /**
+ * Gets the number of columns in the enclosing table that shall be spanned
+ * by the cell (ColSpan). The default value is 1.
+ *
+ * @return the column span
+ */
+ public int getColSpan()
+ {
+ return this.getInteger(PDTableAttributeObject.COL_SPAN, 1);
+ }
+
+ /**
+ * Sets the number of columns in the enclosing table that shall be spanned
+ * by the cell (ColSpan).
+ *
+ * @param colSpan the column span
+ */
+ public void setColSpan(int colSpan)
+ {
+ this.setInteger(PDTableAttributeObject.COL_SPAN, colSpan);
+ }
+
+ /**
+ * Gets the headers (Headers). An array of byte strings, where each string
+ * shall be the element identifier (see the
+ * {@link PDStructureElement#getElementIdentifier()}) for a TH structure
+ * element that shall be used as a header associated with this cell.
+ *
+ * @return the headers.
+ */
+ public String[] getHeaders()
+ {
+ return this.getArrayOfString(PDTableAttributeObject.HEADERS);
+ }
+
+ /**
+ * Sets the headers (Headers). An array of byte strings, where each string
+ * shall be the element identifier (see the
+ * {@link PDStructureElement#getElementIdentifier()}) for a TH structure
+ * element that shall be used as a header associated with this cell.
+ *
+ * @param headers the headers
+ */
+ public void setHeaders(String[] headers)
+ {
+ this.setArrayOfString(PDTableAttributeObject.HEADERS, headers);
+ }
+
+ /**
+ * Gets the scope (Scope). It shall reflect whether the header cell applies
+ * to the rest of the cells in the row that contains it, the column that
+ * contains it, or both the row and the column that contain it.
+ *
+ * @return the scope
+ */
+ public String getScope()
+ {
+ return this.getName(PDTableAttributeObject.SCOPE);
+ }
+
+ /**
+ * Sets the scope (Scope). It shall reflect whether the header cell applies
+ * to the rest of the cells in the row that contains it, the column that
+ * contains it, or both the row and the column that contain it. The value
+ * shall be one of the following:
+ * <ul>
+ * <li>{@link #SCOPE_ROW},</li>
+ * <li>{@link #SCOPE_COLUMN}, or</li>
+ * <li>{@link #SCOPE_BOTH}.</li>
+ * </ul>
+ *
+ * @param scope the scope
+ */
+ public void setScope(String scope)
+ {
+ this.setName(PDTableAttributeObject.SCOPE, scope);
+ }
+
+ /**
+ * Gets the summary of the table’s purpose and structure.
+ *
+ * @return the summary
+ */
+ public String getSummary()
+ {
+ return this.getString(PDTableAttributeObject.SUMMARY);
+ }
+
+ /**
+ * Sets the summary of the table’s purpose and structure.
+ *
+ * @param summary the summary
+ */
+ public void setSummary(String summary)
+ {
+ this.setString(PDTableAttributeObject.SUMMARY, summary);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder().append(super.toString());
+ if (this.isSpecified(PDListAttributeObject.LIST_NUMBERING))
+ {
+ sb.append(", ListNumbering=").append(this.getListNumbering());
+ }
+ if (this.isSpecified(PDTableAttributeObject.ROW_SPAN))
+ {
+ sb.append(", RowSpan=").append(String.valueOf(this.getRowSpan()));
+ }
+ if (this.isSpecified(PDTableAttributeObject.COL_SPAN))
+ {
+ sb.append(", ColSpan=").append(String.valueOf(this.getColSpan()));
+ }
+ if (this.isSpecified(PDTableAttributeObject.HEADERS))
+ {
+ sb.append(", Headers=").append(arrayToString(this.getHeaders()));
+ }
+ if (this.isSpecified(PDTableAttributeObject.SCOPE))
+ {
+ sb.append(", Scope=").append(this.getScope());
+ }
+ if (this.isSpecified(PDTableAttributeObject.SUMMARY))
+ {
+ sb.append(", Summary=").append(this.getSummary());
+ }
+ return sb.toString();
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+
+/**
+ * An object for four colours.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDFourColours implements COSObjectable
+{
+
+ private COSArray array;
+
+ public PDFourColours()
+ {
+ this.array = new COSArray();
+ this.array.add(COSNull.NULL);
+ this.array.add(COSNull.NULL);
+ this.array.add(COSNull.NULL);
+ this.array.add(COSNull.NULL);
+ }
+
+ public PDFourColours(COSArray array)
+ {
+ this.array = array;
+ // ensure that array has 4 items
+ if (this.array.size() < 4)
+ {
+ for (int i = (this.array.size() - 1); i < 4; i++)
+ {
+ this.array.add(COSNull.NULL);
+ }
+ }
+ }
+
+
+ /**
+ * Gets the colour for the before edge.
+ *
+ * @return the colour for the before edge
+ */
+ public PDGamma getBeforeColour()
+ {
+ return this.getColourByIndex(0);
+ }
+
+ /**
+ * Sets the colour for the before edge.
+ *
+ * @param colour the colour for the before edge
+ */
+ public void setBeforeColour(PDGamma colour)
+ {
+ this.setColourByIndex(0, colour);
+ }
+
+ /**
+ * Gets the colour for the after edge.
+ *
+ * @return the colour for the after edge
+ */
+ public PDGamma getAfterColour()
+ {
+ return this.getColourByIndex(1);
+ }
+
+ /**
+ * Sets the colour for the after edge.
+ *
+ * @param colour the colour for the after edge
+ */
+ public void setAfterColour(PDGamma colour)
+ {
+ this.setColourByIndex(1, colour);
+ }
+
+ /**
+ * Gets the colour for the start edge.
+ *
+ * @return the colour for the start edge
+ */
+ public PDGamma getStartColour()
+ {
+ return this.getColourByIndex(2);
+ }
+
+ /**
+ * Sets the colour for the start edge.
+ *
+ * @param colour the colour for the start edge
+ */
+ public void setStartColour(PDGamma colour)
+ {
+ this.setColourByIndex(2, colour);
+ }
+
+ /**
+ * Gets the colour for the end edge.
+ *
+ * @return the colour for the end edge
+ */
+ public PDGamma getEndColour()
+ {
+ return this.getColourByIndex(3);
+ }
+
+ /**
+ * Sets the colour for the end edge.
+ *
+ * @param colour the colour for the end edge
+ */
+ public void setEndColour(PDGamma colour)
+ {
+ this.setColourByIndex(3, colour);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return this.array;
+ }
+
+
+ /**
+ * Gets the colour by edge index.
+ *
+ * @param index edge index
+ * @return the colour
+ */
+ private PDGamma getColourByIndex(int index)
+ {
+ PDGamma retval = null;
+ COSBase item = this.array.getObject(index);
+ if (item instanceof COSArray)
+ {
+ retval = new PDGamma((COSArray) item);
+ }
+ return retval;
+ }
+
+ /**
+ * Sets the colour by edge index.
+ *
+ * @param index the edge index
+ * @param colour the colour
+ */
+ private void setColourByIndex(int index, PDGamma colour)
+ {
+ COSBase base;
+ if (colour == null)
+ {
+ base = COSNull.NULL;
+ }
+ else
+ {
+ base = colour.getCOSArray();
+ }
+ this.array.set(index, base);
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+
+/**
+ * A Layout attribute object.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDLayoutAttributeObject extends PDStandardAttributeObject
+{
+
+ /**
+ * standard attribute owner: Layout
+ */
+ public static final String OWNER_LAYOUT = "Layout";
+
+ private static final String PLACEMENT = "Placement";
+ private static final String WRITING_MODE = "WritingMode";
+ private static final String BACKGROUND_COLOR = "BackgroundColor";
+ private static final String BORDER_COLOR = "BorderColor";
+ private static final String BORDER_STYLE = "BorderStyle";
+ private static final String BORDER_THICKNESS = "BorderThickness";
+ private static final String PADDING = "Padding";
+ private static final String COLOR = "Color";
+ private static final String SPACE_BEFORE = "SpaceBefore";
+ private static final String SPACE_AFTER = "SpaceAfter";
+ private static final String START_INDENT = "StartIndent";
+ private static final String END_INDENT = "EndIndent";
+ private static final String TEXT_INDENT = "TextIndent";
+ private static final String TEXT_ALIGN = "TextAlign";
+ private static final String BBOX = "BBox";
+ private static final String WIDTH = "Width";
+ private static final String HEIGHT = "Height";
+ private static final String BLOCK_ALIGN = "BlockAlign";
+ private static final String INLINE_ALIGN = "InlineAlign";
+ private static final String T_BORDER_STYLE = "TBorderStyle";
+ private static final String T_PADDING = "TPadding";
+ private static final String BASELINE_SHIFT = "BaselineShift";
+ private static final String LINE_HEIGHT = "LineHeight";
+ private static final String TEXT_DECORATION_COLOR = "TextDecorationColor";
+ private static final String TEXT_DECORATION_THICKNESS = "TextDecorationThickness";
+ private static final String TEXT_DECORATION_TYPE = "TextDecorationType";
+ private static final String RUBY_ALIGN = "RubyAlign";
+ private static final String RUBY_POSITION = "RubyPosition";
+ private static final String GLYPH_ORIENTATION_VERTICAL = "GlyphOrientationVertical";
+ private static final String COLUMN_COUNT = "ColumnCount";
+ private static final String COLUMN_GAP = "ColumnGap";
+ private static final String COLUMN_WIDTHS = "ColumnWidths";
+
+ /**
+ * Placement: Block: Stacked in the block-progression direction within an
+ * enclosing reference area or parent BLSE.
+ */
+ public static final String PLACEMENT_BLOCK = "Block";
+ /**
+ * Placement: Inline: Packed in the inline-progression direction within an
+ * enclosing BLSE.
+ */
+ public static final String PLACEMENT_INLINE = "Inline";
+ /**
+ * Placement: Before: Placed so that the before edge of the element’s
+ * allocation rectangle coincides with that of the nearest enclosing
+ * reference area. The element may float, if necessary, to achieve the
+ * specified placement. The element shall be treated as a block occupying
+ * the full extent of the enclosing reference area in the inline direction.
+ * Other content shall be stacked so as to begin at the after edge of the
+ * element’s allocation rectangle.
+ */
+ public static final String PLACEMENT_BEFORE = "Before";
+ /**
+ * Placement: Start: Placed so that the start edge of the element’s
+ * allocation rectangle coincides with that of the nearest enclosing
+ * reference area. The element may float, if necessary, to achieve the
+ * specified placement. Other content that would intrude into the element’s
+ * allocation rectangle shall be laid out as a runaround.
+ */
+ public static final String PLACEMENT_START = "Start";
+ /**
+ * Placement: End: Placed so that the end edge of the element’s allocation
+ * rectangle coincides with that of the nearest enclosing reference area.
+ * The element may float, if necessary, to achieve the specified placement.
+ * Other content that would intrude into the element’s allocation rectangle
+ * shall be laid out as a runaround.
+ */
+ public static final String PLACEMENT_END = "End";
+ /**
+ * WritingMode: LrTb: Inline progression from left to right; block
+ * progression from top to bottom. This is the typical writing mode for
+ * Western writing systems.
+ */
+ public static final String WRITING_MODE_LRTB = "LrTb";
+ /**
+ * WritingMode: RlTb: Inline progression from right to left; block
+ * progression from top to bottom. This is the typical writing mode for
+ * Arabic and Hebrew writing systems.
+ */
+ public static final String WRITING_MODE_RLTB = "RlTb";
+ /**
+ * WritingMode: TbRl: Inline progression from top to bottom; block
+ * progression from right to left. This is the typical writing mode for
+ * Chinese and Japanese writing systems.
+ */
+ public static final String WRITING_MODE_TBRL = "TbRl";
+ /**
+ * BorderStyle: None: No border. Forces the computed value of
+ * BorderThickness to be 0.
+ */
+ public static final String BORDER_STYLE_NONE = "None";
+ /**
+ * BorderStyle: Hidden: Same as {@link #BORDER_STYLE_NONE}, except in terms
+ * of border conflict resolution for table elements.
+ */
+ public static final String BORDER_STYLE_HIDDEN = "Hidden";
+ /**
+ * BorderStyle: Dotted: The border is a series of dots.
+ */
+ public static final String BORDER_STYLE_DOTTED = "Dotted";
+ /**
+ * BorderStyle: Dashed: The border is a series of short line segments.
+ */
+ public static final String BORDER_STYLE_DASHED = "Dashed";
+ /**
+ * BorderStyle: Solid: The border is a single line segment.
+ */
+ public static final String BORDER_STYLE_SOLID = "Solid";
+ /**
+ * BorderStyle: Double: The border is two solid lines. The sum of the two
+ * lines and the space between them equals the value of BorderThickness.
+ */
+ public static final String BORDER_STYLE_DOUBLE = "Double";
+ /**
+ * BorderStyle: Groove: The border looks as though it were carved into the
+ * canvas.
+ */
+ public static final String BORDER_STYLE_GROOVE = "Groove";
+ /**
+ * BorderStyle: Ridge: The border looks as though it were coming out of the
+ * canvas (the opposite of {@link #BORDER_STYLE_GROOVE}).
+ */
+ public static final String BORDER_STYLE_RIDGE = "Ridge";
+ /**
+ * BorderStyle: Inset: The border makes the entire box look as though it
+ * were embedded in the canvas.
+ */
+ public static final String BORDER_STYLE_INSET = "Inset";
+ /**
+ * BorderStyle: Outset: The border makes the entire box look as though it
+ * were coming out of the canvas (the opposite of {@link #BORDER_STYLE_INSET}.
+ */
+ public static final String BORDER_STYLE_OUTSET = "Outset";
+ /**
+ * TextAlign: Start: Aligned with the start edge.
+ */
+ public static final String TEXT_ALIGN_START = "Start";
+ /**
+ * TextAlign: Center: Centered between the start and end edges.
+ */
+ public static final String TEXT_ALIGN_CENTER = "Center";
+ /**
+ * TextAlign: End: Aligned with the end edge.
+ */
+ public static final String TEXT_ALIGN_END = "End";
+ /**
+ * TextAlign: Justify: Aligned with both the start and end edges, with
+ * internal spacing within each line expanded, if necessary, to achieve such
+ * alignment. The last (or only) line shall be aligned with the start edge
+ * only.
+ */
+ public static final String TEXT_ALIGN_JUSTIFY = "Justify";
+ /**
+ * Width: Auto
+ */
+ public static final String WIDTH_AUTO = "Auto";
+ /**
+ * Height: Auto
+ */
+ public static final String HEIGHT_AUTO = "Auto";
+ /**
+ * BlockAlign: Before: Before edge of the first child’s allocation rectangle
+ * aligned with that of the table cell’s content rectangle.
+ */
+ public static final String BLOCK_ALIGN_BEFORE = "Before";
+ /**
+ * BlockAlign: Middle: Children centered within the table cell. The distance
+ * between the before edge of the first child’s allocation rectangle and
+ * that of the table cell’s content rectangle shall be the same as the
+ * distance between the after edge of the last child’s allocation rectangle
+ * and that of the table cell’s content rectangle.
+ */
+ public static final String BLOCK_ALIGN_MIDDLE = "Middle";
+ /**
+ * BlockAlign: After: After edge of the last child’s allocation rectangle
+ * aligned with that of the table cell’s content rectangle.
+ */
+ public static final String BLOCK_ALIGN_AFTER = "After";
+ /**
+ * BlockAlign: Justify: Children aligned with both the before and after
+ * edges of the table cell’s content rectangle. The first child shall be
+ * placed as described for {@link #BLOCK_ALIGN_BEFORE} and the last child as
+ * described for {@link #BLOCK_ALIGN_AFTER}, with equal spacing between the
+ * children. If there is only one child, it shall be aligned with the before
+ * edge only, as for {@link #BLOCK_ALIGN_BEFORE}.
+ */
+ public static final String BLOCK_ALIGN_JUSTIFY = "Justify";
+ /**
+ * InlineAlign: Start: Start edge of each child’s allocation rectangle
+ * aligned with that of the table cell’s content rectangle.
+ */
+ public static final String INLINE_ALIGN_START = "Start";
+ /**
+ * InlineAlign: Center: Each child centered within the table cell. The
+ * distance between the start edges of the child’s allocation rectangle and
+ * the table cell’s content rectangle shall be the same as the distance
+ * between their end edges.
+ */
+ public static final String INLINE_ALIGN_CENTER = "Center";
+ /**
+ * InlineAlign: End: End edge of each child’s allocation rectangle aligned
+ * with that of the table cell’s content rectangle.
+ */
+ public static final String INLINE_ALIGN_END = "End";
+ /**
+ * LineHeight: NormalAdjust the line height to include any nonzero value
+ * specified for BaselineShift.
+ */
+ public static final String LINE_HEIGHT_NORMAL = "Normal";
+ /**
+ * LineHeight: Auto: Adjustment for the value of BaselineShift shall not be
+ * made.
+ */
+ public static final String LINE_HEIGHT_AUTO = "Auto";
+ /**
+ * TextDecorationType: None: No text decoration
+ */
+ public static final String TEXT_DECORATION_TYPE_NONE = "None";
+ /**
+ * TextDecorationType: Underline: A line below the text
+ */
+ public static final String TEXT_DECORATION_TYPE_UNDERLINE = "Underline";
+ /**
+ * TextDecorationType: Overline: A line above the text
+ */
+ public static final String TEXT_DECORATION_TYPE_OVERLINE = "Overline";
+ /**
+ * TextDecorationType: LineThrough: A line through the middle of the text
+ */
+ public static final String TEXT_DECORATION_TYPE_LINE_THROUGH = "LineThrough";
+ /**
+ * RubyAlign: Start: The content shall be aligned on the start edge in the
+ * inline-progression direction.
+ */
+ public static final String RUBY_ALIGN_START = "Start";
+ /**
+ * RubyAlign: Center: The content shall be centered in the
+ * inline-progression direction.
+ */
+ public static final String RUBY_ALIGN_CENTER = "Center";
+ /**
+ * RubyAlign: End: The content shall be aligned on the end edge in the
+ * inline-progression direction.
+ */
+ public static final String RUBY_ALIGN_END = "End";
+ /**
+ * RubyAlign: Justify: The content shall be expanded to fill the available
+ * width in the inline-progression direction.
+ */
+ public static final String RUBY_ALIGN_JUSTIFY = "Justify";
+ /**
+ * RubyAlign: Distribute: The content shall be expanded to fill the
+ * available width in the inline-progression direction. However, space shall
+ * also be inserted at the start edge and end edge of the text. The spacing
+ * shall be distributed using a 1:2:1 (start:infix:end) ratio. It shall be
+ * changed to a 0:1:1 ratio if the ruby appears at the start of a text line
+ * or to a 1:1:0 ratio if the ruby appears at the end of the text line.
+ */
+ public static final String RUBY_ALIGN_DISTRIBUTE = "Distribute";
+ /**
+ * RubyPosition: Before: The RT content shall be aligned along the before
+ * edge of the element.
+ */
+ public static final String RUBY_POSITION_BEFORE = "Before";
+ /**
+ * RubyPosition: After: The RT content shall be aligned along the after edge
+ * of the element.
+ */
+ public static final String RUBY_POSITION_AFTER = "After";
+ /**
+ * RubyPosition: Warichu: The RT and associated RP elements shall be
+ * formatted as a warichu, following the RB element.
+ */
+ public static final String RUBY_POSITION_WARICHU = "Warichu";
+ /**
+ * RubyPosition: Inline: The RT and associated RP elements shall be
+ * formatted as a parenthesis comment, following the RB element.
+ */
+ public static final String RUBY_POSITION_INLINE = "Inline";
+ /**
+ * GlyphOrientationVertical: Auto
+ */
+ public static final String GLYPH_ORIENTATION_VERTICAL_AUTO = "Auto";
+ /**
+ * GlyphOrientationVertical: -180°
+ */
+ public static final String GLYPH_ORIENTATION_VERTICAL_MINUS_180_DEGREES = "-180";
+ /**
+ * GlyphOrientationVertical: -90°
+ */
+ public static final String GLYPH_ORIENTATION_VERTICAL_MINUS_90_DEGREES = "-90";
+ /**
+ * GlyphOrientationVertical: 0°
+ */
+ public static final String GLYPH_ORIENTATION_VERTICAL_ZERO_DEGREES = "0";
+ /**
+ * GlyphOrientationVertical: 90°
+ */
+ public static final String GLYPH_ORIENTATION_VERTICAL_90_DEGREES = "90";
+ /**
+ * GlyphOrientationVertical: 180°
+ */
+ public static final String GLYPH_ORIENTATION_VERTICAL_180_DEGREES = "180";
+ /**
+ * GlyphOrientationVertical: 270°
+ */
+ public static final String GLYPH_ORIENTATION_VERTICAL_270_DEGREES = "270";
+ /**
+ * GlyphOrientationVertical: 360°
+ */
+ public static final String GLYPH_ORIENTATION_VERTICAL_360_DEGREES = "360";
+
+
+ /**
+ * Default constructor.
+ */
+ public PDLayoutAttributeObject()
+ {
+ this.setOwner(OWNER_LAYOUT);
+ }
+
+ /**
+ * Creates a new Layout attribute object with a given dictionary.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDLayoutAttributeObject(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+
+ /**
+ * Gets the positioning of the element with respect to the enclosing
+ * reference area and other content (Placement). The default value is
+ * {@link #PLACEMENT_INLINE}.
+ *
+ * @return the placement
+ */
+ public String getPlacement()
+ {
+ return this.getName(PLACEMENT, PLACEMENT_INLINE);
+ }
+
+ /**
+ * Sets the positioning of the element with respect to the enclosing
+ * reference area and other content (Placement). The value should be one of:
+ * <ul>
+ * <li>{@link #PLACEMENT_BLOCK},</li>
+ * <li>{@link #PLACEMENT_INLINE},</li>
+ * <li>{@link #PLACEMENT_BEFORE},</li>
+ * <li>{@link #PLACEMENT_START},</li>
+ * <li>{@link #PLACEMENT_END}.</li>
+ * <ul>
+ *
+ * @param placement the placement
+ */
+ public void setPlacement(String placement)
+ {
+ this.setName(PLACEMENT, placement);
+ }
+
+ /**
+ * Gets the writing mode (WritingMode). The default value is
+ * {@link #WRITING_MODE_LRTB}.
+ *
+ * @return the writing mode
+ */
+ public String getWritingMode()
+ {
+ return this.getName(WRITING_MODE, WRITING_MODE_LRTB);
+ }
+
+ /**
+ * Sets the writing mode (WritingMode). The value should be one of:
+ * <ul>
+ * <li>{@link #WRITING_MODE_LRTB},</li>
+ * <li>{@link #WRITING_MODE_RLTB},</li>
+ * <li>{@link #WRITING_MODE_TBRL}.</li>
+ * </ul>
+ *
+ * @param writingMode the writing mode
+ */
+ public void setWritingMode(String writingMode)
+ {
+ this.setName(WRITING_MODE, writingMode);
+ }
+
+ /**
+ * Gets the background colour (BackgroundColor).
+ *
+ * @return the background colour
+ */
+ public PDGamma getBackgroundColor()
+ {
+ return this.getColor(BACKGROUND_COLOR);
+ }
+
+ /**
+ * Sets the background colour (BackgroundColor).
+ *
+ * @param backgroundColor the background colour
+ */
+ public void setBackgroundColor(PDGamma backgroundColor)
+ {
+ this.setColor(BACKGROUND_COLOR, backgroundColor);
+ }
+
+ /**
+ * Gets the border colour (BorderColor).
+ *
+ * @return a single border colour ({@link PDGamma}) or four border colours
+ * ({@link PDFourColours})
+ */
+ public Object getBorderColors()
+ {
+ return this.getColorOrFourColors(BORDER_COLOR);
+ }
+
+ /**
+ * Sets the same border colour for all four sides (BorderColor).
+ *
+ * @param borderColor the border colour
+ */
+ public void setAllBorderColors(PDGamma borderColor)
+ {
+ this.setColor(BORDER_COLOR, borderColor);
+ }
+
+ /**
+ * Sets the border colours for four sides separately (BorderColor).
+ *
+ * @param borderColors the border colours
+ */
+ public void setBorderColors(PDFourColours borderColors)
+ {
+ this.setFourColors(BORDER_COLOR, borderColors);
+ }
+
+ /**
+ * Gets the border style (BorderStyle). The default value is
+ * {@link #BORDER_STYLE_NONE}.
+ *
+ * @return the border styles (a String or an array of four Strings)
+ */
+ public Object getBorderStyle()
+ {
+ return this.getNameOrArrayOfName(BORDER_STYLE, BORDER_STYLE_NONE);
+ }
+
+ /**
+ * Sets the same border style for all four sides (BorderStyle). The value
+ * should be one of:
+ * <ul>
+ * <li>{@link #BORDER_STYLE_NONE},</li>
+ * <li>{@link #BORDER_STYLE_HIDDEN},</li>
+ * <li>{@link #BORDER_STYLE_DOTTED},</li>
+ * <li>{@link #BORDER_STYLE_DASHED},</li>
+ * <li>{@link #BORDER_STYLE_SOLID},</li>
+ * <li>{@link #BORDER_STYLE_DOUBLE},</li>
+ * <li>{@link #BORDER_STYLE_GROOVE},</li>
+ * <li>{@link #BORDER_STYLE_RIDGE},</li>
+ * <li>{@link #BORDER_STYLE_INSET},</li>
+ * <li>{@link #BORDER_STYLE_OUTSET}.</li>
+ * </ul>
+ *
+ * @param borderStyle the border style
+ */
+ public void setAllBorderStyles(String borderStyle)
+ {
+ this.setName(BORDER_STYLE, borderStyle);
+ }
+
+ /**
+ * Sets the border styles for four sides separately (BorderStyle). The
+ * values should be of:
+ * <ul>
+ * <li>{@link #BORDER_STYLE_NONE},</li>
+ * <li>{@link #BORDER_STYLE_HIDDEN},</li>
+ * <li>{@link #BORDER_STYLE_DOTTED},</li>
+ * <li>{@link #BORDER_STYLE_DASHED},</li>
+ * <li>{@link #BORDER_STYLE_SOLID},</li>
+ * <li>{@link #BORDER_STYLE_DOUBLE},</li>
+ * <li>{@link #BORDER_STYLE_GROOVE},</li>
+ * <li>{@link #BORDER_STYLE_RIDGE},</li>
+ * <li>{@link #BORDER_STYLE_INSET},</li>
+ * <li>{@link #BORDER_STYLE_OUTSET}.</li>
+ * </ul>
+ *
+ * @param borderStyles the border styles (an array of four Strings)
+ */
+ public void setBorderStyles(String[] borderStyles)
+ {
+ this.setArrayOfName(BORDER_STYLE, borderStyles);
+ }
+
+ /**
+ * Gets the border thickness (BorderThickness).
+ *
+ * @return the border thickness (a Float or an array of four floats)
+ */
+ public Object getBorderThickness()
+ {
+ return this.getNumberOrArrayOfNumber(BORDER_THICKNESS, UNSPECIFIED);
+ }
+
+ /**
+ * Sets the same border thickness for all four sides (BorderThickness).
+ *
+ * @param borderThickness the border thickness
+ */
+ public void setAllBorderThicknesses(float borderThickness)
+ {
+ this.setNumber(BORDER_THICKNESS, borderThickness);
+ }
+
+ /**
+ * Sets the same border thickness for all four sides (BorderThickness).
+ *
+ * @param borderThickness the border thickness
+ */
+ public void setAllBorderThicknesses(int borderThickness)
+ {
+ this.setNumber(BORDER_THICKNESS, borderThickness);
+ }
+
+ /**
+ * Sets the border thicknesses for four sides separately (BorderThickness).
+ *
+ * @param borderThicknesses the border thickness (an array of four floats)
+ */
+ public void setBorderThicknesses(float[] borderThicknesses)
+ {
+ this.setArrayOfNumber(BORDER_THICKNESS, borderThicknesses);
+ }
+
+ /**
+ * Gets the padding (Padding). The default value is 0.
+ *
+ * @return the padding (a Float or an array of float)
+ */
+ public Object getPadding()
+ {
+ return this.getNumberOrArrayOfNumber(PADDING, 0.f);
+ }
+
+ /**
+ * Sets the same padding for all four sides (Padding).
+ *
+ * @param padding the padding
+ */
+ public void setAllPaddings(float padding)
+ {
+ this.setNumber(PADDING, padding);
+ }
+
+ /**
+ * Sets the same padding for all four sides (Padding).
+ *
+ * @param padding the padding
+ */
+ public void setAllPaddings(int padding)
+ {
+ this.setNumber(PADDING, padding);
+ }
+
+ /**
+ * Sets the paddings for four sides separately (Padding).
+ *
+ * @param paddings the paddings (an array of four floats)
+ */
+ public void setPaddings(float[] paddings)
+ {
+ this.setArrayOfNumber(PADDING, paddings);
+ }
+
+ /**
+ * Gets the color to be used for drawing text and the default value for the
+ * colour of table borders and text decorations (Color).
+ *
+ * @return the colour
+ */
+ public PDGamma getColor()
+ {
+ return this.getColor(COLOR);
+ }
+
+ /**
+ * Sets the color to be used for drawing text and the default value for the
+ * colour of table borders and text decorations (Color).
+ *
+ * @param color the colour
+ */
+ public void setColor(PDGamma color)
+ {
+ this.setColor(COLOR, color);
+ }
+
+ /**
+ * Gets the amount of extra space preceding the before edge of the BLSE in
+ * the block-progression direction (SpaceBefore). The default value is 0.
+ *
+ * @return the space before
+ */
+ public float getSpaceBefore()
+ {
+ return this.getNumber(SPACE_BEFORE, 0.f);
+ }
+
+ /**
+ * Sets the amount of extra space preceding the before edge of the BLSE in
+ * the block-progression direction (SpaceBefore).
+ *
+ * @param spaceBefore the space before
+ */
+ public void setSpaceBefore(float spaceBefore)
+ {
+ this.setNumber(SPACE_BEFORE, spaceBefore);
+ }
+
+ /**
+ * Sets the amount of extra space preceding the before edge of the BLSE in
+ * the block-progression direction (SpaceBefore).
+ *
+ * @param spaceBefore the space before
+ */
+ public void setSpaceBefore(int spaceBefore)
+ {
+ this.setNumber(SPACE_BEFORE, spaceBefore);
+ }
+
+ /**
+ * Gets the amount of extra space following the after edge of the BLSE in
+ * the block-progression direction (SpaceAfter). The default value is 0.
+ *
+ * @return the space after
+ */
+ public float getSpaceAfter()
+ {
+ return this.getNumber(SPACE_AFTER, 0.f);
+ }
+
+ /**
+ * Sets the amount of extra space following the after edge of the BLSE in
+ * the block-progression direction (SpaceAfter).
+ *
+ * @param spaceAfter the space after
+ */
+ public void setSpaceAfter(float spaceAfter)
+ {
+ this.setNumber(SPACE_AFTER, spaceAfter);
+ }
+
+ /**
+ * Sets the amount of extra space following the after edge of the BLSE in
+ * the block-progression direction (SpaceAfter).
+ *
+ * @param spaceAfter the space after
+ */
+ public void setSpaceAfter(int spaceAfter)
+ {
+ this.setNumber(SPACE_AFTER, spaceAfter);
+ }
+
+ /**
+ * Gets the distance from the start edge of the reference area to that of
+ * the BLSE in the inline-progression direction (StartIndent). The default value is 0.
+ *
+ * @return the start indent
+ */
+ public float getStartIndent()
+ {
+ return this.getNumber(START_INDENT, 0.f);
+ }
+
+ /**
+ * Sets the distance from the start edge of the reference area to that of
+ * the BLSE in the inline-progression direction (StartIndent).
+ *
+ * @param startIndent the start indent
+ */
+ public void setStartIndent(float startIndent)
+ {
+ this.setNumber(START_INDENT, startIndent);
+ }
+
+ /**
+ * Sets the distance from the start edge of the reference area to that of
+ * the BLSE in the inline-progression direction (StartIndent).
+ *
+ * @param startIndent the start indent
+ */
+ public void setStartIndent(int startIndent)
+ {
+ this.setNumber(START_INDENT, startIndent);
+ }
+
+ /**
+ * Gets the distance from the end edge of the BLSE to that of the reference
+ * area in the inline-progression direction (EndIndent). The default value
+ * is 0.
+ *
+ * @return the end indent
+ */
+ public float getEndIndent()
+ {
+ return this.getNumber(END_INDENT, 0.f);
+ }
+
+ /**
+ * Sets the distance from the end edge of the BLSE to that of the reference
+ * area in the inline-progression direction (EndIndent).
+ *
+ * @param endIndent the end indent
+ */
+ public void setEndIndent(float endIndent)
+ {
+ this.setNumber(END_INDENT, endIndent);
+ }
+
+ /**
+ * Sets the distance from the end edge of the BLSE to that of the reference
+ * area in the inline-progression direction (EndIndent).
+ *
+ * @param endIndent the end indent
+ */
+ public void setEndIndent(int endIndent)
+ {
+ this.setNumber(END_INDENT, endIndent);
+ }
+
+ /**
+ * Gets the additional distance in the inline-progression direction from the
+ * start edge of the BLSE, as specified by StartIndent, to that of the first
+ * line of text (TextIndent). The default value is 0.
+ *
+ * @return the text indent
+ */
+ public float getTextIndent()
+ {
+ return this.getNumber(TEXT_INDENT, 0.f);
+ }
+
+ /**
+ * Sets the additional distance in the inline-progression direction from the
+ * start edge of the BLSE, as specified by StartIndent, to that of the first
+ * line of text (TextIndent).
+ *
+ * @param textIndent the text indent
+ */
+ public void setTextIndent(float textIndent)
+ {
+ this.setNumber(TEXT_INDENT, textIndent);
+ }
+
+ /**
+ * Sets the additional distance in the inline-progression direction from the
+ * start edge of the BLSE, as specified by StartIndent, to that of the first
+ * line of text (TextIndent).
+ *
+ * @param textIndent the text indent
+ */
+ public void setTextIndent(int textIndent)
+ {
+ this.setNumber(TEXT_INDENT, textIndent);
+ }
+
+ /**
+ * Gets the alignment, in the inline-progression direction, of text and
+ * other content within lines of the BLSE (TextAlign). The default value is
+ * {@link #TEXT_ALIGN_START}.
+ *
+ * @return the text alignment
+ */
+ public String getTextAlign()
+ {
+ return this.getName(TEXT_ALIGN, TEXT_ALIGN_START);
+ }
+
+ /**
+ * Sets the alignment, in the inline-progression direction, of text and
+ * other content within lines of the BLSE (TextAlign). The value should be
+ * one of:
+ * <ul>
+ * <li>{@link #TEXT_ALIGN_START},</li>
+ * <li>{@link #TEXT_ALIGN_CENTER},</li>
+ * <li>{@link #TEXT_ALIGN_END},</li>
+ * <li>{@link #TEXT_ALIGN_JUSTIFY}.</li>
+ * </ul>
+ *
+ * @param textIndent the text alignment
+ */
+ public void setTextAlign(String textIndent)
+ {
+ this.setName(TEXT_ALIGN, textIndent);
+ }
+
+ /**
+ * Gets the bounding box.
+ *
+ * @return the bounding box.
+ */
+ public PDRectangle getBBox()
+ {
+ COSArray array =
+ (COSArray) this.getCOSDictionary().getDictionaryObject(BBOX);
+ if (array != null)
+ {
+ return new PDRectangle(array);
+ }
+ return null;
+ }
+
+ /**
+ * Sets the bounding box.
+ *
+ * @param bbox the bounding box
+ */
+ public void setBBox(PDRectangle bbox)
+ {
+ String name = BBOX;
+ COSBase oldValue = this.getCOSDictionary().getDictionaryObject(name);
+ this.getCOSDictionary().setItem(name, bbox);
+ COSBase newValue = bbox == null ? null : bbox.getCOSObject();
+ this.potentiallyNotifyChanged(oldValue, newValue);
+ }
+
+ /**
+ * Gets the width of the element’s content rectangle in the
+ * inline-progression direction (Width). The default value is
+ * {@link #WIDTH_AUTO}.
+ *
+ * @return the width (a Float or a String)
+ */
+ public Object getWidth()
+ {
+ return this.getNumberOrName(WIDTH, WIDTH_AUTO);
+ }
+
+ /**
+ * Sets the width of the element’s content rectangle in the
+ * inline-progression direction (Width) to {@link #WIDTH_AUTO}.
+ */
+ public void setWidthAuto()
+ {
+ this.setName(WIDTH, WIDTH_AUTO);
+ }
+
+ /**
+ * Sets the width of the element’s content rectangle in the
+ * inline-progression direction (Width).
+ *
+ * @param width the width
+ */
+ public void setWidth(float width)
+ {
+ this.setNumber(WIDTH, width);
+ }
+
+ /**
+ * Sets the width of the element’s content rectangle in the
+ * inline-progression direction (Width).
+ *
+ * @param width the width
+ */
+ public void setWidth(int width)
+ {
+ this.setNumber(WIDTH, width);
+ }
+
+ /**
+ * Gets the height of the element’s content rectangle in the
+ * block-progression direction (Height). The default value is
+ * {@link #HEIGHT_AUTO}.
+ *
+ * @return the height (a Float or a String)
+ */
+ public Object getHeight()
+ {
+ return this.getNumberOrName(HEIGHT, HEIGHT_AUTO);
+ }
+
+ /**
+ * Sets the height of the element’s content rectangle in the
+ * block-progression direction (Height) to {@link #HEIGHT_AUTO}.
+ */
+ public void setHeightAuto()
+ {
+ this.setName(HEIGHT, HEIGHT_AUTO);
+ }
+
+ /**
+ * Sets the height of the element’s content rectangle in the
+ * block-progression direction (Height).
+ *
+ * @param height the height
+ */
+ public void setHeight(float height)
+ {
+ this.setNumber(HEIGHT, height);
+ }
+
+ /**
+ * Sets the height of the element’s content rectangle in the
+ * block-progression direction (Height).
+ *
+ * @param height the height
+ */
+ public void setHeight(int height)
+ {
+ this.setNumber(HEIGHT, height);
+ }
+
+ /**
+ * Gets the alignment, in the block-progression direction, of content within
+ * the table cell (BlockAlign). The default value is
+ * {@link #BLOCK_ALIGN_BEFORE}.
+ *
+ * @return the block alignment
+ */
+ public String getBlockAlign()
+ {
+ return this.getName(BLOCK_ALIGN, BLOCK_ALIGN_BEFORE);
+ }
+
+ /**
+ * Sets the alignment, in the block-progression direction, of content within
+ * the table cell (BlockAlign). The value should be one of:
+ * <ul>
+ * <li>{@link #BLOCK_ALIGN_BEFORE},</li>
+ * <li>{@link #BLOCK_ALIGN_MIDDLE},</li>
+ * <li>{@link #BLOCK_ALIGN_AFTER},</li>
+ * <li>{@link #BLOCK_ALIGN_JUSTIFY}.</li>
+ * </ul>
+ *
+ * @param blockAlign the block alignment
+ */
+ public void setBlockAlign(String blockAlign)
+ {
+ this.setName(BLOCK_ALIGN, blockAlign);
+ }
+
+ /**
+ * Gets the alignment, in the inline-progression direction, of content
+ * within the table cell (InlineAlign). The default value is
+ * {@link #INLINE_ALIGN_START}.
+ *
+ * @return the inline alignment
+ */
+ public String getInlineAlign()
+ {
+ return this.getName(INLINE_ALIGN, INLINE_ALIGN_START);
+ }
+
+ /**
+ * Sets the alignment, in the inline-progression direction, of content
+ * within the table cell (InlineAlign). The value should be one of
+ * <ul>
+ * <li>{@link #INLINE_ALIGN_START},</li>
+ * <li>{@link #INLINE_ALIGN_CENTER},</li>
+ * <li>{@link #INLINE_ALIGN_END}.</li>
+ * </ul>
+ *
+ * @param inlineAlign the inline alignment
+ */
+ public void setInlineAlign(String inlineAlign)
+ {
+ this.setName(INLINE_ALIGN, inlineAlign);
+ }
+
+ /**
+ * Gets the style of the border drawn on each edge of a table cell
+ * (TBorderStyle).
+ *
+ * @return
+ */
+ public Object getTBorderStyle()
+ {
+ return this.getNameOrArrayOfName(T_BORDER_STYLE, BORDER_STYLE_NONE);
+ }
+
+ /**
+ * Sets the same table border style for all four sides (TBorderStyle). The
+ * value should be one of:
+ * <ul>
+ * <li>{@link #BORDER_STYLE_NONE},</li>
+ * <li>{@link #BORDER_STYLE_HIDDEN},</li>
+ * <li>{@link #BORDER_STYLE_DOTTED},</li>
+ * <li>{@link #BORDER_STYLE_DASHED},</li>
+ * <li>{@link #BORDER_STYLE_SOLID},</li>
+ * <li>{@link #BORDER_STYLE_DOUBLE},</li>
+ * <li>{@link #BORDER_STYLE_GROOVE},</li>
+ * <li>{@link #BORDER_STYLE_RIDGE},</li>
+ * <li>{@link #BORDER_STYLE_INSET},</li>
+ * <li>{@link #BORDER_STYLE_OUTSET}.</li>
+ * </ul>
+ *
+ * @param tBorderStyle the table border style
+ */
+ public void setAllTBorderStyles(String tBorderStyle)
+ {
+ this.setName(T_BORDER_STYLE, tBorderStyle);
+ }
+
+ /**
+ * Sets the style of the border drawn on each edge of a table cell
+ * (TBorderStyle). The values should be of:
+ * <ul>
+ * <li>{@link #BORDER_STYLE_NONE},</li>
+ * <li>{@link #BORDER_STYLE_HIDDEN},</li>
+ * <li>{@link #BORDER_STYLE_DOTTED},</li>
+ * <li>{@link #BORDER_STYLE_DASHED},</li>
+ * <li>{@link #BORDER_STYLE_SOLID},</li>
+ * <li>{@link #BORDER_STYLE_DOUBLE},</li>
+ * <li>{@link #BORDER_STYLE_GROOVE},</li>
+ * <li>{@link #BORDER_STYLE_RIDGE},</li>
+ * <li>{@link #BORDER_STYLE_INSET},</li>
+ * <li>{@link #BORDER_STYLE_OUTSET}.</li>
+ * </ul>
+ *
+ * @param tBorderStyles
+ */
+ public void setTBorderStyles(String[] tBorderStyles)
+ {
+ this.setArrayOfName(T_BORDER_STYLE, tBorderStyles);
+ }
+
+ /**
+ * Gets the offset to account for the separation between the table cell’s
+ * content rectangle and the surrounding border (TPadding). The default
+ * value is 0.
+ *
+ * @return the table padding (a Float or an array of float)
+ */
+ public Object getTPadding()
+ {
+ return this.getNumberOrArrayOfNumber(T_PADDING, 0.f);
+ }
+
+ /**
+ * Sets the same table padding for all four sides (TPadding).
+ *
+ * @param tPadding the table padding
+ */
+ public void setAllTPaddings(float tPadding)
+ {
+ this.setNumber(T_PADDING, tPadding);
+ }
+
+ /**
+ * Sets the same table padding for all four sides (TPadding).
+ *
+ * @param tPadding the table padding
+ */
+ public void setAllTPaddings(int tPadding)
+ {
+ this.setNumber(T_PADDING, tPadding);
+ }
+
+ /**
+ * Sets the table paddings for four sides separately (TPadding).
+ *
+ * @param tPaddings the table paddings (an array of four floats)
+ */
+ public void setTPaddings(float[] tPaddings)
+ {
+ this.setArrayOfNumber(T_PADDING, tPaddings);
+ }
+
+ /**
+ * Gets the distance by which the element’s baseline shall be shifted
+ * relative to that of its parent element (BaselineShift). The default value
+ * is 0.
+ *
+ * @return the baseline shift
+ */
+ public float getBaselineShift()
+ {
+ return this.getNumber(BASELINE_SHIFT, 0.f);
+ }
+
+ /**
+ * Sets the distance by which the element’s baseline shall be shifted
+ * relative to that of its parent element (BaselineShift).
+ *
+ * @param baselineShift the baseline shift
+ */
+ public void setBaselineShift(float baselineShift)
+ {
+ this.setNumber(BASELINE_SHIFT, baselineShift);
+ }
+
+ /**
+ * Sets the distance by which the element’s baseline shall be shifted
+ * relative to that of its parent element (BaselineShift).
+ *
+ * @param baselineShift the baseline shift
+ */
+ public void setBaselineShift(int baselineShift)
+ {
+ this.setNumber(BASELINE_SHIFT, baselineShift);
+ }
+
+ /**
+ * Gets the element’s preferred height in the block-progression direction
+ * (LineHeight). The default value is {@link #LINE_HEIGHT_NORMAL}.
+ *
+ * @return the line height (a Float or a String)
+ */
+ public Object getLineHeight()
+ {
+ return this.getNumberOrName(LINE_HEIGHT, LINE_HEIGHT_NORMAL);
+ }
+
+ /**
+ * Sets the element’s preferred height in the block-progression direction
+ * (LineHeight) to {@link #LINE_HEIGHT_NORMAL}.
+ */
+ public void setLineHeightNormal()
+ {
+ this.setName(LINE_HEIGHT, LINE_HEIGHT_NORMAL);
+ }
+
+ /**
+ * Sets the element’s preferred height in the block-progression direction
+ * (LineHeight) to {@link #LINE_HEIGHT_AUTO}.
+ */
+ public void setLineHeightAuto()
+ {
+ this.setName(LINE_HEIGHT, LINE_HEIGHT_AUTO);
+ }
+
+ /**
+ * Sets the element’s preferred height in the block-progression direction
+ * (LineHeight).
+ *
+ * @param lineHeight the line height
+ */
+ public void setLineHeight(float lineHeight)
+ {
+ this.setNumber(LINE_HEIGHT, lineHeight);
+ }
+
+ /**
+ * Sets the element’s preferred height in the block-progression direction
+ * (LineHeight).
+ *
+ * @param lineHeight the line height
+ */
+ public void setLineHeight(int lineHeight)
+ {
+ this.setNumber(LINE_HEIGHT, lineHeight);
+ }
+
+ /**
+ * Gets the colour to be used for drawing text decorations
+ * (TextDecorationColor).
+ *
+ * @return the text decoration colour
+ */
+ public PDGamma getTextDecorationColor()
+ {
+ return this.getColor(TEXT_DECORATION_COLOR);
+ }
+
+ /**
+ * Sets the colour to be used for drawing text decorations
+ * (TextDecorationColor).
+ *
+ * @param textDecorationColor the text decoration colour
+ */
+ public void setTextDecorationColor(PDGamma textDecorationColor)
+ {
+ this.setColor(TEXT_DECORATION_COLOR, textDecorationColor);
+ }
+
+ /**
+ * Gets the thickness of each line drawn as part of the text decoration
+ * (TextDecorationThickness).
+ *
+ * @return the text decoration thickness
+ */
+ public float getTextDecorationThickness()
+ {
+ return this.getNumber(TEXT_DECORATION_THICKNESS);
+ }
+
+ /**
+ * Sets the thickness of each line drawn as part of the text decoration
+ * (TextDecorationThickness).
+ *
+ * @param textDecorationThickness the text decoration thickness
+ */
+ public void setTextDecorationThickness(float textDecorationThickness)
+ {
+ this.setNumber(TEXT_DECORATION_THICKNESS, textDecorationThickness);
+ }
+
+ /**
+ * Sets the thickness of each line drawn as part of the text decoration
+ * (TextDecorationThickness).
+ *
+ * @param textDecorationThickness the text decoration thickness
+ */
+ public void setTextDecorationThickness(int textDecorationThickness)
+ {
+ this.setNumber(TEXT_DECORATION_THICKNESS, textDecorationThickness);
+ }
+
+ /**
+ * Gets the type of text decoration (TextDecorationType). The default value
+ * is {@link #TEXT_DECORATION_TYPE_NONE}.
+ *
+ * @return the type of text decoration
+ */
+ public String getTextDecorationType()
+ {
+ return this.getName(TEXT_DECORATION_TYPE, TEXT_DECORATION_TYPE_NONE);
+ }
+
+ /**
+ * Sets the type of text decoration (TextDecorationType). The value should
+ * be one of:
+ * <ul>
+ * <li>{@link #TEXT_DECORATION_TYPE_NONE},</li>
+ * <li>{@link #TEXT_DECORATION_TYPE_UNDERLINE},</li>
+ * <li>{@link #TEXT_DECORATION_TYPE_OVERLINE},</li>
+ * <li>{@link #TEXT_DECORATION_TYPE_LINE_THROUGH}.</li>
+ * </ul>
+ *
+ * @param textDecorationType the type of text decoration
+ */
+ public void setTextDecorationType(String textDecorationType)
+ {
+ this.setName(TEXT_DECORATION_TYPE, textDecorationType);
+ }
+
+ /**
+ * Gets the justification of the lines within a ruby assembly (RubyAlign).
+ * The default value is {@link #RUBY_ALIGN_DISTRIBUTE}.
+ *
+ * @return the ruby alignment
+ */
+ public String getRubyAlign()
+ {
+ return this.getName(RUBY_ALIGN, RUBY_ALIGN_DISTRIBUTE);
+ }
+
+ /**
+ * Sets the justification of the lines within a ruby assembly (RubyAlign).
+ * The value should be one of:
+ * <ul>
+ * <li>{@link #RUBY_ALIGN_START},</li>
+ * <li>{@link #RUBY_ALIGN_CENTER},</li>
+ * <li>{@link #RUBY_ALIGN_END},</li>
+ * <li>{@link #RUBY_ALIGN_JUSTIFY},</li>
+ * <li>{@link #RUBY_ALIGN_DISTRIBUTE},</li>
+ * </ul>
+ *
+ * @param rubyAlign the ruby alignment
+ */
+ public void setRubyAlign(String rubyAlign)
+ {
+ this.setName(RUBY_ALIGN, rubyAlign);
+ }
+
+ /**
+ * Gets the placement of the RT structure element relative to the RB element
+ * in a ruby assembly (RubyPosition). The default value is
+ * {@link #RUBY_POSITION_BEFORE}.
+ *
+ * @return the ruby position
+ */
+ public String getRubyPosition()
+ {
+ return this.getName(RUBY_POSITION, RUBY_POSITION_BEFORE);
+ }
+
+ /**
+ * Sets the placement of the RT structure element relative to the RB element
+ * in a ruby assembly (RubyPosition). The value should be one of:
+ * <ul>
+ * <li>{@link #RUBY_POSITION_BEFORE},</li>
+ * <li>{@link #RUBY_POSITION_AFTER},</li>
+ * <li>{@link #RUBY_POSITION_WARICHU},</li>
+ * <li>{@link #RUBY_POSITION_INLINE}.</li>
+ * </ul>
+ *
+ * @param rubyPosition the ruby position
+ */
+ public void setRubyPosition(String rubyPosition)
+ {
+ this.setName(RUBY_POSITION, rubyPosition);
+ }
+
+ /**
+ * Gets the orientation of glyphs when the inline-progression direction is
+ * top to bottom or bottom to top (GlyphOrientationVertical). The default
+ * value is {@link #GLYPH_ORIENTATION_VERTICAL_AUTO}.
+ *
+ * @return the vertical glyph orientation
+ */
+ public String getGlyphOrientationVertical()
+ {
+ return this.getName(GLYPH_ORIENTATION_VERTICAL,
+ GLYPH_ORIENTATION_VERTICAL_AUTO);
+ }
+
+ /**
+ * Sets the orientation of glyphs when the inline-progression direction is
+ * top to bottom or bottom to top (GlyphOrientationVertical). The value
+ * should be one of:
+ * <ul>
+ * <li>{@link #GLYPH_ORIENTATION_VERTICAL_AUTO},</li>
+ * <li>{@link #GLYPH_ORIENTATION_VERTICAL_MINUS_180_DEGREES},</li>
+ * <li>{@link #GLYPH_ORIENTATION_VERTICAL_MINUS_90_DEGREES},</li>
+ * <li>{@link #GLYPH_ORIENTATION_VERTICAL_ZERO_DEGREES},</li>
+ * <li>{@link #GLYPH_ORIENTATION_VERTICAL_90_DEGREES},</li>
+ * <li>{@link #GLYPH_ORIENTATION_VERTICAL_180_DEGREES},</li>
+ * <li>{@link #GLYPH_ORIENTATION_VERTICAL_270_DEGREES},</li>
+ * <li>{@link #GLYPH_ORIENTATION_VERTICAL_360_DEGREES}.</li>
+ * </ul>
+ *
+ * @param glyphOrientationVertical the vertical glyph orientation
+ */
+ public void setGlyphOrientationVertical(String glyphOrientationVertical)
+ {
+ this.setName(GLYPH_ORIENTATION_VERTICAL, glyphOrientationVertical);
+ }
+
+ /**
+ * Gets the number of columns in the content of the grouping element
+ * (ColumnCount). The default value is 1.
+ *
+ * @return the column count
+ */
+ public int getColumnCount()
+ {
+ return this.getInteger(COLUMN_COUNT, 1);
+ }
+
+ /**
+ * Sets the number of columns in the content of the grouping element
+ * (ColumnCount).
+ *
+ * @param columnCount the column count
+ */
+ public void setColumnCount(int columnCount)
+ {
+ this.setInteger(COLUMN_COUNT, columnCount);
+ }
+
+ /**
+ * Gets the desired space between adjacent columns in the inline-progression
+ * direction (ColumnGap).
+ *
+ * @return the column gap (FLoat or array of floats)
+ */
+ public Object getColumnGap()
+ {
+ return this.getNumberOrArrayOfNumber(COLUMN_GAP, UNSPECIFIED);
+ }
+
+ /**
+ * Sets the desired space between all columns in the inline-progression
+ * direction (ColumnGap).
+ *
+ * @param columnGap the column gap
+ */
+ public void setColumnGap(float columnGap)
+ {
+ this.setNumber(COLUMN_GAP, columnGap);
+ }
+
+ /**
+ * Sets the desired space between all columns in the inline-progression
+ * direction (ColumnGap).
+ *
+ * @param columnGap the column gap
+ */
+ public void setColumnGap(int columnGap)
+ {
+ this.setNumber(COLUMN_GAP, columnGap);
+ }
+
+ /**
+ * Sets the desired space between adjacent columns in the inline-progression
+ * direction (ColumnGap), the first element specifying the space between the
+ * first and second columns, the second specifying the space between the
+ * second and third columns, and so on.
+ *
+ * @param columnGaps the column gaps
+ */
+ public void setColumnGaps(float[] columnGaps)
+ {
+ this.setArrayOfNumber(COLUMN_GAP, columnGaps);
+ }
+
+ /**
+ * Gets the desired width of the columns, measured in default user space
+ * units in the inline-progression direction (ColumnWidths).
+ *
+ * @return the column widths (Float or array of floats)
+ */
+ public Object getColumnWidths()
+ {
+ return this.getNumberOrArrayOfNumber(COLUMN_WIDTHS, UNSPECIFIED);
+ }
+
+ /**
+ * Sets the same column width for all columns (ColumnWidths).
+ *
+ * @param columnWidth the column width
+ */
+ public void setAllColumnWidths(float columnWidth)
+ {
+ this.setNumber(COLUMN_WIDTHS, columnWidth);
+ }
+
+ /**
+ * Sets the same column width for all columns (ColumnWidths).
+ *
+ * @param columnWidth the column width
+ */
+ public void setAllColumnWidths(int columnWidth)
+ {
+ this.setNumber(COLUMN_WIDTHS, columnWidth);
+ }
+
+ /**
+ * Sets the column widths for the columns separately (ColumnWidths).
+ *
+ * @param columnWidths the column widths
+ */
+ public void setColumnWidths(float[] columnWidths)
+ {
+ this.setArrayOfNumber(COLUMN_WIDTHS, columnWidths);
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder().append(super.toString());
+ if (this.isSpecified(PLACEMENT))
+ {
+ sb.append(", Placement=").append(this.getPlacement());
+ }
+ if (this.isSpecified(WRITING_MODE))
+ {
+ sb.append(", WritingMode=").append(this.getWritingMode());
+ }
+ if (this.isSpecified(BACKGROUND_COLOR))
+ {
+ sb.append(", BackgroundColor=").append(this.getBackgroundColor());
+ }
+ if (this.isSpecified(BORDER_COLOR))
+ {
+ sb.append(", BorderColor=").append(this.getBorderColors());
+ }
+ if (this.isSpecified(BORDER_STYLE))
+ {
+ Object borderStyle = this.getBorderStyle();
+ sb.append(", BorderStyle=");
+ if (borderStyle instanceof String[])
+ {
+ sb.append(arrayToString((String[]) borderStyle));
+ }
+ else
+ {
+ sb.append(borderStyle);
+ }
+ }
+ if (this.isSpecified(BORDER_THICKNESS))
+ {
+ Object borderThickness = this.getBorderThickness();
+ sb.append(", BorderThickness=");
+ if (borderThickness instanceof float[])
+ {
+ sb.append(arrayToString((float[]) borderThickness));
+ }
+ else
+ {
+ sb.append(String.valueOf((Float) borderThickness));
+ }
+ }
+ if (this.isSpecified(PADDING))
+ {
+ Object padding = this.getPadding();
+ sb.append(", Padding=");
+ if (padding instanceof float[])
+ {
+ sb.append(arrayToString((float[]) padding));
+ }
+ else
+ {
+ sb.append(String.valueOf((Float) padding));
+ }
+ }
+ if (this.isSpecified(COLOR))
+ {
+ sb.append(", Color=").append(this.getColor());
+ }
+ if (this.isSpecified(SPACE_BEFORE))
+ {
+ sb.append(", SpaceBefore=")
+ .append(String.valueOf(this.getSpaceBefore()));
+ }
+ if (this.isSpecified(SPACE_AFTER))
+ {
+ sb.append(", SpaceAfter=")
+ .append(String.valueOf(this.getSpaceAfter()));
+ }
+ if (this.isSpecified(START_INDENT))
+ {
+ sb.append(", StartIndent=")
+ .append(String.valueOf(this.getStartIndent()));
+ }
+ if (this.isSpecified(END_INDENT))
+ {
+ sb.append(", EndIndent=")
+ .append(String.valueOf(this.getEndIndent()));
+ }
+ if (this.isSpecified(TEXT_INDENT))
+ {
+ sb.append(", TextIndent=")
+ .append(String.valueOf(this.getTextIndent()));
+ }
+ if (this.isSpecified(TEXT_ALIGN))
+ {
+ sb.append(", TextAlign=").append(this.getTextAlign());
+ }
+ if (this.isSpecified(BBOX))
+ {
+ sb.append(", BBox=").append(this.getBBox());
+ }
+ if (this.isSpecified(WIDTH))
+ {
+ Object width = this.getWidth();
+ sb.append(", Width=");
+ if (width instanceof Float)
+ {
+ sb.append(String.valueOf((Float) width));
+ }
+ else
+ {
+ sb.append(width);
+ }
+ }
+ if (this.isSpecified(HEIGHT))
+ {
+ Object height = this.getHeight();
+ sb.append(", Height=");
+ if (height instanceof Float)
+ {
+ sb.append(String.valueOf((Float) height));
+ }
+ else
+ {
+ sb.append(height);
+ }
+ }
+ if (this.isSpecified(BLOCK_ALIGN))
+ {
+ sb.append(", BlockAlign=").append(this.getBlockAlign());
+ }
+ if (this.isSpecified(INLINE_ALIGN))
+ {
+ sb.append(", InlineAlign=").append(this.getInlineAlign());
+ }
+ if (this.isSpecified(T_BORDER_STYLE))
+ {
+ Object tBorderStyle = this.getTBorderStyle();
+ sb.append(", TBorderStyle=");
+ if (tBorderStyle instanceof String[])
+ {
+ sb.append(arrayToString((String[]) tBorderStyle));
+ }
+ else
+ {
+ sb.append(tBorderStyle);
+ }
+ }
+ if (this.isSpecified(T_PADDING))
+ {
+ Object tPadding = this.getTPadding();
+ sb.append(", TPadding=");
+ if (tPadding instanceof float[])
+ {
+ sb.append(arrayToString((float[]) tPadding));
+ }
+ else
+ {
+ sb.append(String.valueOf((Float) tPadding));
+ }
+ }
+ if (this.isSpecified(BASELINE_SHIFT))
+ {
+ sb.append(", BaselineShift=")
+ .append(String.valueOf(this.getBaselineShift()));
+ }
+ if (this.isSpecified(LINE_HEIGHT))
+ {
+ Object lineHeight = this.getLineHeight();
+ sb.append(", LineHeight=");
+ if (lineHeight instanceof Float)
+ {
+ sb.append(String.valueOf((Float) lineHeight));
+ }
+ else
+ {
+ sb.append(lineHeight);
+ }
+ }
+ if (this.isSpecified(TEXT_DECORATION_COLOR))
+ {
+ sb.append(", TextDecorationColor=")
+ .append(this.getTextDecorationColor());
+ }
+ if (this.isSpecified(TEXT_DECORATION_THICKNESS))
+ {
+ sb.append(", TextDecorationThickness=")
+ .append(String.valueOf(this.getTextDecorationThickness()));
+ }
+ if (this.isSpecified(TEXT_DECORATION_TYPE))
+ {
+ sb.append(", TextDecorationType=")
+ .append(this.getTextDecorationType());
+ }
+ if (this.isSpecified(RUBY_ALIGN))
+ {
+ sb.append(", RubyAlign=").append(this.getRubyAlign());
+ }
+ if (this.isSpecified(RUBY_POSITION))
+ {
+ sb.append(", RubyPosition=").append(this.getRubyPosition());
+ }
+ if (this.isSpecified(GLYPH_ORIENTATION_VERTICAL))
+ {
+ sb.append(", GlyphOrientationVertical=")
+ .append(this.getGlyphOrientationVertical());
+ }
+ if (this.isSpecified(COLUMN_COUNT))
+ {
+ sb.append(", ColumnCount=")
+ .append(String.valueOf(this.getColumnCount()));
+ }
+ if (this.isSpecified(COLUMN_GAP))
+ {
+ Object columnGap = this.getColumnGap();
+ sb.append(", ColumnGap=");
+ if (columnGap instanceof float[])
+ {
+ sb.append(arrayToString((float[]) columnGap));
+ }
+ else
+ {
+ sb.append(String.valueOf((Float) columnGap));
+ }
+ }
+ if (this.isSpecified(COLUMN_WIDTHS))
+ {
+ Object columnWidth = this.getColumnWidths();
+ sb.append(", ColumnWidths=");
+ if (columnWidth instanceof float[])
+ {
+ sb.append(arrayToString((float[]) columnWidth));
+ }
+ else
+ {
+ sb.append(String.valueOf((Float) columnWidth));
+ }
+ }
+ return sb.toString();
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * A List attribute object.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDListAttributeObject extends PDStandardAttributeObject
+{
+
+ /**
+ * standard attribute owner: List
+ */
+ public static final String OWNER_LIST = "List";
+
+ protected static final String LIST_NUMBERING = "ListNumbering";
+
+ /**
+ * ListNumbering: Circle: Open circular bullet
+ */
+ public static final String LIST_NUMBERING_CIRCLE = "Circle";
+ /**
+ * ListNumbering: Decimal: Decimal arabic numerals (1–9, 10–99, …)
+ */
+ public static final String LIST_NUMBERING_DECIMAL = "Decimal";
+ /**
+ * ListNumbering: Disc: Solid circular bullet
+ */
+ public static final String LIST_NUMBERING_DISC = "Disc";
+ /**
+ * ListNumbering: LowerAlpha: Lowercase letters (a, b, c, …)
+ */
+ public static final String LIST_NUMBERING_LOWER_ALPHA = "LowerAlpha";
+ /**
+ * ListNumbering: LowerRoman: Lowercase roman numerals (i, ii, iii, iv, …)
+ */
+ public static final String LIST_NUMBERING_LOWER_ROMAN = "LowerRoman";
+ /**
+ * ListNumbering: None: No autonumbering; Lbl elements (if present) contain arbitrary text
+ * not subject to any numbering scheme
+ */
+ public static final String LIST_NUMBERING_NONE = "None";
+ /**
+ * ListNumbering: Square: Solid square bullet
+ */
+ public static final String LIST_NUMBERING_SQUARE = "Square";
+ /**
+ * ListNumbering: UpperAlpha: Uppercase letters (A, B, C, …)
+ */
+ public static final String LIST_NUMBERING_UPPER_ALPHA = "UpperAlpha";
+ /**
+ * ListNumbering: UpperRoman: Uppercase roman numerals (I, II, III, IV, …)
+ */
+ public static final String LIST_NUMBERING_UPPER_ROMAN = "UpperRoman";
+
+
+ /**
+ * Default constructor.
+ */
+ public PDListAttributeObject()
+ {
+ this.setOwner(OWNER_LIST);
+ }
+
+ /**
+ * Creates a new List attribute object with a given dictionary.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDListAttributeObject(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+
+ /**
+ * Gets the list numbering (ListNumbering). The default value is
+ * {@link #LIST_NUMBERING_NONE}.
+ *
+ * @return the list numbering
+ */
+ public String getListNumbering()
+ {
+ return this.getName(LIST_NUMBERING, LIST_NUMBERING_NONE);
+ }
+
+ /**
+ * Sets the list numbering (ListNumbering). The value shall be one of the
+ * following:
+ * <ul>
+ * <li>{@link #LIST_NUMBERING_NONE},</li>
+ * <li>{@link #LIST_NUMBERING_DISC},</li>
+ * <li>{@link #LIST_NUMBERING_CIRCLE},</li>
+ * <li>{@link #LIST_NUMBERING_SQUARE},</li>
+ * <li>{@link #LIST_NUMBERING_DECIMAL},</li>
+ * <li>{@link #LIST_NUMBERING_UPPER_ROMAN},</li>
+ * <li>{@link #LIST_NUMBERING_LOWER_ROMAN},</li>
+ * <li>{@link #LIST_NUMBERING_UPPER_ALPHA},</li>
+ * <li>{@link #LIST_NUMBERING_LOWER_ALPHA}.</li>
+ * </ul>
+ *
+ * @param listNumbering the list numbering
+ */
+ public void setListNumbering(String listNumbering)
+ {
+ this.setName(LIST_NUMBERING, listNumbering);
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder().append(super.toString());
+ if (this.isSpecified(LIST_NUMBERING))
+ {
+ sb.append(", ListNumbering=").append(this.getListNumbering());
+ }
+ return sb.toString();
+ }
+
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * A PrintField attribute object.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDPrintFieldAttributeObject extends PDStandardAttributeObject
+{
+
+ /**
+ * standard attribute owner: PrintField
+ */
+ public static final String OWNER_PRINT_FIELD = "PrintField";
+
+ private static final String ROLE = "Role";
+ private static final String CHECKED = "checked";
+ private static final String DESC = "Desc";
+
+ /**
+ * role: rb: Radio button
+ */
+ public static final String ROLE_RB = "rb";
+ /**
+ * role: cb: Check box
+ */
+ public static final String ROLE_CB = "cb";
+ /**
+ * role: pb: Push button
+ */
+ public static final String ROLE_PB = "pb";
+ /**
+ * role: tv: Text-value field
+ */
+ public static final String ROLE_TV = "tv";
+ /**
+ * checked state: on
+ */
+ public static final String CHECKED_STATE_ON = "on";
+ /**
+ * checked state: off
+ */
+ public static final String CHECKED_STATE_OFF = "off";
+ /**
+ * checked state: neutral
+ */
+ public static final String CHECKED_STATE_NEUTRAL = "neutral";
+
+
+ /**
+ * Default constructor.
+ */
+ public PDPrintFieldAttributeObject()
+ {
+ this.setOwner(OWNER_PRINT_FIELD);
+ }
+
+ /**
+ * Creates a new PrintField attribute object with a given dictionary.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDPrintFieldAttributeObject(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+
+ /**
+ * Gets the role.
+ *
+ * @return the role
+ */
+ public String getRole()
+ {
+ return this.getName(ROLE);
+ }
+
+ /**
+ * Sets the role. The value of Role shall be one of the following:
+ * <ul>
+ * <li>{@link #ROLE_RB},</li>
+ * <li>{@link #ROLE_CB},</li>
+ * <li>{@link #ROLE_PB},</li>
+ * <li>{@link #ROLE_TV}.</li>
+ * </ul>
+ *
+ * @param role the role
+ */
+ public void setRole(String role)
+ {
+ this.setName(ROLE, role);
+ }
+
+ /**
+ * Gets the checked state. The default value is {@link #CHECKED_STATE_OFF}.
+ *
+ * @return the checked state
+ */
+ public String getCheckedState()
+ {
+ return this.getName(CHECKED, CHECKED_STATE_OFF);
+ }
+
+ /**
+ * Sets the checked state. The value shall be one of:
+ * <ul>
+ * <li>{@link #CHECKED_STATE_ON},</li>
+ * <li>{@link #CHECKED_STATE_OFF} (default), or</li>
+ * <li>{@link #CHECKED_STATE_NEUTRAL}.</li>
+ * </ul>
+ *
+ * @param checkedState the checked state
+ */
+ public void setCheckedState(String checkedState)
+ {
+ this.setName(CHECKED, checkedState);
+ }
+
+ /**
+ * Gets the alternate name of the field (Desc).
+ *
+ * @return the alternate name of the field
+ */
+ public String getAlternateName()
+ {
+ return this.getString(DESC);
+ }
+
+ /**
+ * Sets the alternate name of the field (Desc).
+ *
+ * @param alternateName the alternate name of the field
+ */
+ public void setAlternateName(String alternateName)
+ {
+ this.setString(DESC, alternateName);
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder().append(super.toString());
+ if (this.isSpecified(ROLE))
+ {
+ sb.append(", Role=").append(this.getRole());
+ }
+ if (this.isSpecified(CHECKED))
+ {
+ sb.append(", Checked=").append(this.getCheckedState());
+ }
+ if (this.isSpecified(DESC))
+ {
+ sb.append(", Desc=").append(this.getAlternateName());
+ }
+ return sb.toString();
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+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.COSString;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDAttributeObject;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+
+/**
+ * A standard attribute object.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public abstract class PDStandardAttributeObject extends PDAttributeObject
+{
+
+ /**
+ * Default constructor.
+ */
+ public PDStandardAttributeObject()
+ {
+ }
+
+ /**
+ * Creates a new standard attribute object with a given dictionary.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDStandardAttributeObject(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+
+ /**
+ * Is the attribute with the given name specified in this attribute object?
+ *
+ * @param name the attribute name
+ * @return <code>true</code> if the attribute is specified,
+ * <code>false</code> otherwise
+ */
+ public boolean isSpecified(String name)
+ {
+ return this.getCOSDictionary().getDictionaryObject(name) != null;
+ }
+
+
+ /**
+ * Gets a string attribute value.
+ *
+ * @param name the attribute name
+ * @return the string attribute value
+ */
+ protected String getString(String name)
+ {
+ return this.getCOSDictionary().getString(name);
+ }
+
+ /**
+ * Sets a string attribute value.
+ *
+ * @param name the attribute name
+ * @param value the string attribute value
+ */
+ protected void setString(String name, String value)
+ {
+ COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.getCOSDictionary().setString(name, value);
+ COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.potentiallyNotifyChanged(oldBase, newBase);
+ }
+
+ /**
+ * Gets an array of strings.
+ *
+ * @param name the attribute name
+ * @return the array of strings
+ */
+ protected String[] getArrayOfString(String name)
+ {
+ COSBase v = this.getCOSDictionary().getDictionaryObject(name);
+ if (v instanceof COSArray)
+ {
+ COSArray array = (COSArray) v;
+ String[] strings = new String[array.size()];
+ for (int i = 0; i < array.size(); i++)
+ {
+ strings[i] = ((COSName) array.getObject(i)).getName();
+ }
+ return strings;
+ }
+ return null;
+ }
+
+ /**
+ * Sets an array of strings.
+ *
+ * @param name the attribute name
+ * @param values the array of strings
+ */
+ protected void setArrayOfString(String name, String[] values)
+ {
+ COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+ COSArray array = new COSArray();
+ for (int i = 0; i < values.length; i++)
+ {
+ array.add(new COSString(values[i]));
+ }
+ this.getCOSDictionary().setItem(name, array);
+ COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.potentiallyNotifyChanged(oldBase, newBase);
+ }
+
+ /**
+ * Gets a name value.
+ *
+ * @param name the attribute name
+ * @return the name value
+ */
+ protected String getName(String name)
+ {
+ return this.getCOSDictionary().getNameAsString(name);
+ }
+
+ /**
+ * Gets a name value.
+ *
+ * @param name the attribute name
+ * @param defaultValue the default value
+ * @return the name value
+ */
+ protected String getName(String name, String defaultValue)
+ {
+ return this.getCOSDictionary().getNameAsString(name, defaultValue);
+ }
+
+ /**
+ * Gets a name value or array of name values.
+ *
+ * @param name the attribute name
+ * @param defaultValue the default value
+ * @return a String or array of Strings
+ */
+ protected Object getNameOrArrayOfName(String name, String defaultValue)
+ {
+ COSBase v = this.getCOSDictionary().getDictionaryObject(name);
+ if (v instanceof COSArray)
+ {
+ COSArray array = (COSArray) v;
+ String[] names = new String[array.size()];
+ for (int i = 0; i < array.size(); i++)
+ {
+ COSBase item = array.getObject(i);
+ if (item instanceof COSName)
+ {
+ names[i] = ((COSName) item).getName();
+ }
+ }
+ return names;
+ }
+ if (v instanceof COSName)
+ {
+ return ((COSName) v).getName();
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Sets a name value.
+ *
+ * @param name the attribute name
+ * @param value the name value
+ */
+ protected void setName(String name, String value)
+ {
+ COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.getCOSDictionary().setName(name, value);
+ COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.potentiallyNotifyChanged(oldBase, newBase);
+ }
+
+ /**
+ * Sets an array of name values.
+ *
+ * @param name the attribute name
+ * @param values the array of name values
+ */
+ protected void setArrayOfName(String name, String[] values)
+ {
+ COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+ COSArray array = new COSArray();
+ for (int i = 0; i < values.length; i++)
+ {
+ array.add(COSName.getPDFName(values[i]));
+ }
+ this.getCOSDictionary().setItem(name, array);
+ COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.potentiallyNotifyChanged(oldBase, newBase);
+ }
+
+ /**
+ * Gets a number or a name value.
+ *
+ * @param name the attribute name
+ * @param defaultValue the default name
+ * @return a Float or a String
+ */
+ protected Object getNumberOrName(String name, String defaultValue)
+ {
+ COSBase value = this.getCOSDictionary().getDictionaryObject(name);
+ if (value instanceof COSNumber)
+ {
+ return ((COSNumber) value).floatValue();
+ }
+ if (value instanceof COSName)
+ {
+ return ((COSName) value).getName();
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Gets an integer.
+ *
+ * @param name the attribute name
+ * @param defaultValue the default value
+ * @return the integer
+ */
+ protected int getInteger(String name, int defaultValue)
+ {
+ return this.getCOSDictionary().getInt(name, defaultValue);
+ }
+
+ /**
+ * Sets an integer.
+ *
+ * @param name the attribute name
+ * @param value the integer
+ */
+ protected void setInteger(String name, int value)
+ {
+ COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.getCOSDictionary().setInt(name, value);
+ COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.potentiallyNotifyChanged(oldBase, newBase);
+ }
+
+ /**
+ * Gets a number value.
+ *
+ * @param name the attribute name
+ * @param defaultValue the default value
+ * @return the number value
+ */
+ protected float getNumber(String name, float defaultValue)
+ {
+ return this.getCOSDictionary().getFloat(name, defaultValue);
+ }
+
+ /**
+ * Gets a number value.
+ *
+ * @param name the attribute name
+ * @return the number value
+ */
+ protected float getNumber(String name)
+ {
+ return this.getCOSDictionary().getFloat(name);
+ }
+
+ /**
+ * An "unspecified" default float value.
+ */
+ protected static final float UNSPECIFIED = -1.f;
+
+ /**
+ * Gets a number or an array of numbers.
+ *
+ * @param name the attribute name
+ * @param defaultValue the default value
+ * @return a Float or an array of floats
+ */
+ protected Object getNumberOrArrayOfNumber(String name, float defaultValue)
+ {
+ COSBase v = this.getCOSDictionary().getDictionaryObject(name);
+ if (v instanceof COSArray)
+ {
+ COSArray array = (COSArray) v;
+ float[] values = new float[array.size()];
+ for (int i = 0; i < array.size(); i++)
+ {
+ COSBase item = array.getObject(i);
+ if (item instanceof COSNumber)
+ {
+ values[i] = ((COSNumber) item).floatValue();
+ }
+ }
+ return values;
+ }
+ if (v instanceof COSNumber)
+ {
+ return ((COSNumber) v).floatValue();
+ }
+ if (defaultValue == UNSPECIFIED)
+ {
+ return null;
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Sets a float number.
+ *
+ * @param name the attribute name
+ * @param value the float number
+ */
+ protected void setNumber(String name, float value)
+ {
+ COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.getCOSDictionary().setFloat(name, value);
+ COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.potentiallyNotifyChanged(oldBase, newBase);
+ }
+
+ /**
+ * Sets an integer number.
+ *
+ * @param name the attribute name
+ * @param value the integer number
+ */
+ protected void setNumber(String name, int value)
+ {
+ COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.getCOSDictionary().setInt(name, value);
+ COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.potentiallyNotifyChanged(oldBase, newBase);
+ }
+
+ /**
+ * Sets an array of float numbers.
+ *
+ * @param name the attribute name
+ * @param values the float numbers
+ */
+ protected void setArrayOfNumber(String name, float[] values)
+ {
+ COSArray array = new COSArray();
+ for (int i = 0; i < values.length; i++)
+ {
+ array.add(new COSFloat(values[i]));
+ }
+ COSBase oldBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.getCOSDictionary().setItem(name, array);
+ COSBase newBase = this.getCOSDictionary().getDictionaryObject(name);
+ this.potentiallyNotifyChanged(oldBase, newBase);
+ }
+
+ /**
+ * Gets a colour.
+ *
+ * @param name the attribute name
+ * @return the colour
+ */
+ protected PDGamma getColor(String name)
+ {
+ COSArray c = (COSArray) this.getCOSDictionary().getDictionaryObject(name);
+ if (c != null)
+ {
+ return new PDGamma(c);
+ }
+ return null;
+ }
+
+ /**
+ * Gets a single colour or four colours.
+ *
+ * @param name the attribute name
+ * @return the single ({@link PDGamma}) or a ({@link PDFourColours})
+ */
+ protected Object getColorOrFourColors(String name)
+ {
+ COSArray array =
+ (COSArray) this.getCOSDictionary().getDictionaryObject(name);
+ if (array == null)
+ {
+ return null;
+ }
+ if (array.size() == 3)
+ {
+ // only one colour
+ return new PDGamma(array);
+ }
+ else if (array.size() == 4)
+ {
+ return new PDFourColours(array);
+ }
+ return null;
+ }
+
+ /**
+ * Sets a colour.
+ *
+ * @param name the attribute name
+ * @param value the colour
+ */
+ protected void setColor(String name, PDGamma value)
+ {
+ COSBase oldValue = this.getCOSDictionary().getDictionaryObject(name);
+ this.getCOSDictionary().setItem(name, value);
+ COSBase newValue = value == null ? null : value.getCOSObject();
+ this.potentiallyNotifyChanged(oldValue, newValue);
+ }
+
+ /**
+ * Sets four colours.
+ *
+ * @param name the attribute name
+ * @param value the four colours
+ */
+ protected void setFourColors(String name, PDFourColours value)
+ {
+ COSBase oldValue = this.getCOSDictionary().getDictionaryObject(name);
+ this.getCOSDictionary().setItem(name, value);
+ COSBase newValue = value == null ? null : value.getCOSObject();
+ this.potentiallyNotifyChanged(oldValue, newValue);
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.taggedpdf;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
+
+/**
+ * A Table attribute object.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class PDTableAttributeObject extends PDStandardAttributeObject
+{
+
+ /**
+ * standard attribute owner: Table
+ */
+ public static final String OWNER_TABLE = "Table";
+
+ protected static final String ROW_SPAN = "RowSpan";
+ protected static final String COL_SPAN = "ColSpan";
+ protected static final String HEADERS = "Headers";
+ protected static final String SCOPE = "Scope";
+ protected static final String SUMMARY = "Summary";
+
+ /**
+ * Scope: Both
+ */
+ public static final String SCOPE_BOTH = "Both";
+ /**
+ * Scope: Column
+ */
+ public static final String SCOPE_COLUMN = "Column";
+ /**
+ * Scope: Row
+ */
+ public static final String SCOPE_ROW = "Row";
+
+
+ /**
+ * Default constructor.
+ */
+ public PDTableAttributeObject()
+ {
+ this.setOwner(OWNER_TABLE);
+ }
+
+ /**
+ * Creates a new Table attribute object with a given dictionary.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDTableAttributeObject(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+
+ /**
+ * Gets the number of rows in the enclosing table that shall be spanned by
+ * the cell (RowSpan). The default value is 1.
+ *
+ * @return the row span
+ */
+ public int getRowSpan()
+ {
+ return this.getInteger(ROW_SPAN, 1);
+ }
+
+ /**
+ * Sets the number of rows in the enclosing table that shall be spanned by
+ * the cell (RowSpan).
+ *
+ * @param rowSpan the row span
+ */
+ public void setRowSpan(int rowSpan)
+ {
+ this.setInteger(ROW_SPAN, rowSpan);
+ }
+
+ /**
+ * Gets the number of columns in the enclosing table that shall be spanned
+ * by the cell (ColSpan). The default value is 1.
+ *
+ * @return the column span
+ */
+ public int getColSpan()
+ {
+ return this.getInteger(COL_SPAN, 1);
+ }
+
+ /**
+ * Sets the number of columns in the enclosing table that shall be spanned
+ * by the cell (ColSpan).
+ *
+ * @param colSpan the column span
+ */
+ public void setColSpan(int colSpan)
+ {
+ this.setInteger(COL_SPAN, colSpan);
+ }
+
+ /**
+ * Gets the headers (Headers). An array of byte strings, where each string
+ * shall be the element identifier (see the
+ * {@link PDStructureElement#getElementIdentifier()}) for a TH structure
+ * element that shall be used as a header associated with this cell.
+ *
+ * @return the headers.
+ */
+ public String[] getHeaders()
+ {
+ return this.getArrayOfString(HEADERS);
+ }
+
+ /**
+ * Sets the headers (Headers). An array of byte strings, where each string
+ * shall be the element identifier (see the
+ * {@link PDStructureElement#getElementIdentifier()}) for a TH structure
+ * element that shall be used as a header associated with this cell.
+ *
+ * @param headers the headers
+ */
+ public void setHeaders(String[] headers)
+ {
+ this.setArrayOfString(HEADERS, headers);
+ }
+
+ /**
+ * Gets the scope (Scope). It shall reflect whether the header cell applies
+ * to the rest of the cells in the row that contains it, the column that
+ * contains it, or both the row and the column that contain it.
+ *
+ * @return the scope
+ */
+ public String getScope()
+ {
+ return this.getName(SCOPE);
+ }
+
+ /**
+ * Sets the scope (Scope). It shall reflect whether the header cell applies
+ * to the rest of the cells in the row that contains it, the column that
+ * contains it, or both the row and the column that contain it. The value
+ * shall be one of the following:
+ * <ul>
+ * <li>{@link #SCOPE_ROW},</li>
+ * <li>{@link #SCOPE_COLUMN}, or</li>
+ * <li>{@link #SCOPE_BOTH}.</li>
+ * </ul>
+ *
+ * @param scope the scope
+ */
+ public void setScope(String scope)
+ {
+ this.setName(SCOPE, scope);
+ }
+
+ /**
+ * Gets the summary of the table’s purpose and structure.
+ *
+ * @return the summary
+ */
+ public String getSummary()
+ {
+ return this.getString(SUMMARY);
+ }
+
+ /**
+ * Sets the summary of the table’s purpose and structure.
+ *
+ * @param summary the summary
+ */
+ public void setSummary(String summary)
+ {
+ this.setString(SUMMARY, summary);
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder().append(super.toString());
+ if (this.isSpecified(ROW_SPAN))
+ {
+ sb.append(", RowSpan=").append(String.valueOf(this.getRowSpan()));
+ }
+ if (this.isSpecified(COL_SPAN))
+ {
+ sb.append(", ColSpan=").append(String.valueOf(this.getColSpan()));
+ }
+ if (this.isSpecified(HEADERS))
+ {
+ sb.append(", Headers=").append(arrayToString(this.getHeaders()));
+ }
+ if (this.isSpecified(SCOPE))
+ {
+ sb.append(", Scope=").append(this.getScope());
+ }
+ if (this.isSpecified(SUMMARY))
+ {
+ sb.append(", Summary=").append(this.getSummary());
+ }
+ return sb.toString();
+ }
+
+}
--- /dev/null
+/*
+ * 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.documentinterchange.taggedpdf;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The standard structure types.
+ *
+ * @author <a href="mailto:Johannes%20Koch%20%3Ckoch@apache.org%3E">Johannes Koch</a>
+ * @version $Revision: $
+ */
+public class StandardStructureTypes
+{
+
+ private StandardStructureTypes()
+ {
+ }
+
+
+ // Grouping Elements
+ /**
+ * Document
+ */
+ public static final String DOCUMENT = "Document";
+
+ /**
+ * Part
+ */
+ public static final String PART = "Part";
+
+ /**
+ * Art
+ */
+ public static final String ART = "Art";
+
+ /**
+ * Sect
+ */
+ public static final String SECT = "Sect";
+
+ /**
+ * Div
+ */
+ public static final String DIV = "Div";
+
+ /**
+ * BlockQuote
+ */
+ public static final String BLOCK_QUOTE = "BlockQuote";
+
+ /**
+ * Caption
+ */
+ public static final String CAPTION = "Caption";
+
+ /**
+ * TOC
+ */
+ public static final String TOC = "TOC";
+
+ /**
+ * TOCI
+ */
+ public static final String TOCI = "TOCI";
+
+ /**
+ * Index
+ */
+ public static final String INDEX = "Index";
+
+ /**
+ * NonStruct
+ */
+ public static final String NON_STRUCT = "NonStruct";
+
+ /**
+ * Private
+ */
+ public static final String PRIVATE = "Private";
+
+
+ // Block-Level Structure Elements
+ /**
+ * P
+ */
+ public static final String P = "P";
+
+ /**
+ * H
+ */
+ public static final String H = "H";
+
+ /**
+ * H1
+ */
+ public static final String H1 = "H1";
+
+ /**
+ * H2
+ */
+ public static final String H2 = "H2";
+
+ /**
+ * H3
+ */
+ public static final String H3 = "H3";
+
+ /**
+ * H4
+ */
+ public static final String H4 = "H4";
+
+ /**
+ * H5
+ */
+ public static final String H5 = "H5";
+
+ /**
+ * H6
+ */
+ public static final String H6 = "H6";
+
+ /**
+ * L
+ */
+ public static final String L = "L";
+
+ /**
+ * LI
+ */
+ public static final String LI = "LI";
+
+ /**
+ * Lbl
+ */
+ public static final String LBL = "Lbl";
+
+ /**
+ * LBody
+ */
+ public static final String L_BODY = "LBody";
+
+ /**
+ * Table
+ */
+ public static final String TABLE = "Table";
+
+ /**
+ * TR
+ */
+ public static final String TR = "TR";
+
+ /**
+ * TH
+ */
+ public static final String TH = "TH";
+
+ /**
+ * TD
+ */
+ public static final String TD = "TD";
+
+ /**
+ * THead
+ */
+ public static final String T_HEAD = "THead";
+
+ /**
+ * TBody
+ */
+ public static final String T_BODY = "TBody";
+
+ /**
+ * TFoot
+ */
+ public static final String T_FOOT = "TFoot";
+
+
+ // Inline-Level Structure Elements
+ /**
+ * Span
+ */
+ public static final String SPAN = "Span";
+
+ /**
+ * Quote
+ */
+ public static final String QUOTE = "Quote";
+
+ /**
+ * Note
+ */
+ public static final String NOTE = "Note";
+
+ /**
+ * Reference
+ */
+ public static final String REFERENCE = "Reference";
+
+ /**
+ * BibEntry
+ */
+ public static final String BIB_ENTRY = "BibEntry";
+
+ /**
+ * Code
+ */
+ public static final String CODE = "Code";
+
+ /**
+ * Link
+ */
+ public static final String LINK = "Link";
+
+ /**
+ * Annot
+ */
+ public static final String ANNOT = "Annot";
+
+ /**
+ * Ruby
+ */
+ public static final String RUBY = "Ruby";
+
+ /**
+ * RB
+ */
+ public static final String RB = "RB";
+
+ /**
+ * RT
+ */
+ public static final String RT = "RT";
+
+ /**
+ * RP
+ */
+ public static final String RP = "RP";
+
+ /**
+ * Warichu
+ */
+ public static final String WARICHU = "Warichu";
+
+ /**
+ * WT
+ */
+ public static final String WT = "WT";
+
+ /**
+ * WP
+ */
+ public static final String WP = "WP";
+
+
+ // Illustration Elements
+ /**
+ * Figure
+ */
+ public static final String Figure = "Figure";
+
+ /**
+ * Formula
+ */
+ public static final String FORMULA = "Formula";
+
+ /**
+ * Form
+ */
+ public static final String FORM = "Form";
+
+ /**
+ * All standard structure types.
+ */
+ public static List<String> types = new ArrayList<String>();
+
+ static
+ {
+ Field[] fields = StandardStructureTypes.class.getFields();
+ for (int i = 0; i < fields.length; i++)
+ {
+ if (Modifier.isFinal(fields[i].getModifiers()))
+ {
+ try
+ {
+ types.add(fields[i].get(null).toString());
+ }
+ catch (IllegalArgumentException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IllegalAccessException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ Collections.sort(types);
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The tagged PDF package provides a mechanism for incorporating "tags" (standard
+structure types and attributes) into a PDF file.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.edit;
+
+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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.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;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDPattern;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+import org.apache.pdfbox.util.MapUtil;
+
+
+/**
+ * This class will is a convenience for creating page content streams. You MUST
+ * call close() when you are finished with this object.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.19 $
+ */
+public class PDPageContentStream
+{
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDPageContentStream.class);
+
+ private PDPage page;
+ private OutputStream output;
+ private boolean inTextMode = false;
+ private Map<PDFont,String> fontMappings;
+ private Map<PDXObject,String> xobjectMappings;
+ private PDResources resources;
+ private Map fonts;
+ private Map xobjects;
+
+ private PDColorSpace currentStrokingColorSpace = new PDDeviceGray();
+ private PDColorSpace currentNonStrokingColorSpace = new PDDeviceGray();
+
+ //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 SET_TEXT_MATRIX = "Tm\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 RECTANGLE = "re\n";
+ private static final String FILL_NON_ZERO = "f\n";
+ private static final String FILL_EVEN_ODD = "f*\n";
+ private static final String LINE_TO = "l\n";
+ private static final String MOVE_TO = "m\n";
+ private static final String CLOSE_STROKE = "s\n";
+ private static final String STROKE = "S\n";
+ private static final String LINE_WIDTH = "w\n";
+ private static final String CLOSE_SUBPATH = "h\n";
+ private static final String CLIP_PATH_NON_ZERO = "W\n";
+ private static final String CLIP_PATH_EVEN_ODD = "W*\n";
+ private static final String NOP = "n\n";
+ 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";
+
+ private static final String SET_STROKING_COLOR_SIMPLE="SC\n";
+ private static final String SET_STROKING_COLOR_COMPLEX="SCN\n";
+ private static final String SET_NON_STROKING_COLOR_SIMPLE="sc\n";
+ private static final String SET_NON_STROKING_COLOR_COMPLEX="scn\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
+ {
+ this(document,sourcePage,appendContent,compress,false);
+ }
+ /**
+ * 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.
+ * @param resetContext Tell if the graphic context should be reseted.
+ * @throws IOException If there is an error writing to the page contents.
+ */
+ public PDPageContentStream( PDDocument document, PDPage sourcePage, boolean appendContent, boolean compress, boolean resetContext )
+ throws IOException
+ {
+
+ page = sourcePage;
+ resources = page.getResources();
+ if( resources == null )
+ {
+ resources = new PDResources();
+ page.setResources( resources );
+ }
+
+ //Fonts including reverse lookup
+ fonts = resources.getFonts();
+ fontMappings = reverseMap(fonts, PDFont.class);
+
+ //XObjects including reverse lookup
+ xobjects = resources.getXObjects();
+ xobjectMappings = reverseMap(xobjects, PDXObject.class);
+
+ // Get the pdstream from the source page instead of creating a new one
+ PDStream contents = sourcePage.getContents();
+ boolean hasContent = contents != null;
+
+ // If request specifies the need to append to the document
+ if(appendContent && hasContent)
+ {
+
+ // 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<COSName> filters = new ArrayList<COSName>();
+ filters.add( COSName.FLATE_DECODE );
+ contentsToAppend.setFilters( filters );
+ }
+
+ if (resetContext)
+ {
+ // create a new stream to encapsulate the existing stream
+ PDStream saveGraphics = new PDStream( document );
+ output = saveGraphics.createOutputStream();
+ // save the initial/unmodified graphics context
+ saveGraphicsState();
+ close();
+ if( compress )
+ {
+ List<COSName> filters = new ArrayList<COSName>();
+ filters.add( COSName.FLATE_DECODE );
+ saveGraphics.setFilters( filters );
+ }
+ // insert the new stream at the beginning
+ compoundStream.insertCOSStream(saveGraphics);
+ }
+
+ // Sets the compoundStream as page contents
+ sourcePage.setContents( new PDStream(compoundStream) );
+ output = contentsToAppend.createOutputStream();
+ if (resetContext)
+ {
+ // restore the initial/unmodified graphics context
+ restoreGraphicsState();
+ }
+ }
+ else
+ {
+ if (hasContent)
+ {
+ log.warn("You are overwriting an existing content, you should use the append mode");
+ }
+ contents = new PDStream( document );
+ if( compress )
+ {
+ List<COSName> filters = new ArrayList<COSName>();
+ filters.add( COSName.FLATE_DECODE );
+ contents.setFilters( filters );
+ }
+ sourcePage.setContents( contents );
+ output = contents.createOutputStream();
+ }
+ formatDecimal.setMaximumFractionDigits( 10 );
+ 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.
+ *
+ * @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 = fontMappings.get( font );
+ if( fontMapping == null )
+ {
+ fontMapping = MapUtil.getNextUniqueKey( fonts, "F" );
+ fontMappings.put( font, fontMapping );
+ fonts.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, float x, float y ) throws IOException
+ {
+ drawXObject( image, x, y, image.getWidth(), image.getHeight() );
+ }
+
+ /**
+ * Draw an xobject(form or image) at the x,y coordinates and a certain width and height.
+ *
+ * @param xobject The xobject 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 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 )
+ {
+ xObjectPrefix = "Im";
+ }
+ else
+ {
+ xObjectPrefix = "Form";
+ }
+
+ String objMapping = xobjectMappings.get( xobject );
+ if( objMapping == null )
+ {
+ objMapping = MapUtil.getNextUniqueKey( xobjects, xObjectPrefix );
+ xobjectMappings.put( xobject, objMapping );
+ xobjects.put( objMapping, xobject );
+ }
+ saveGraphicsState();
+ appendRawCommands( SPACE );
+ concatenate2CTM(transform);
+ appendRawCommands( SPACE );
+ appendRawCommands( "/" );
+ appendRawCommands( objMapping );
+ appendRawCommands( SPACE );
+ appendRawCommands( XOBJECT_DO );
+ restoreGraphicsState();
+ }
+
+
+ /**
+ * The Td operator.
+ * 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.
+ */
+ 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 );
+ }
+
+ /**
+ * The Tm operator. Sets the text matrix to the given values.
+ * 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.
+ * @param d The d value of the matrix.
+ * @param e The e value of the matrix.
+ * @param f The f value of the matrix.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void setTextMatrix( double a, double b, double c, double d, double e, double f ) throws IOException
+ {
+ if( !inTextMode )
+ {
+ throw new IOException( "Error: must call beginText() before setTextMatrix");
+ }
+ appendRawCommands( formatDecimal.format( a ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( b ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( c ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( e ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( f ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( SET_TEXT_MATRIX );
+ }
+
+ /**
+ * 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.
+ * @param sx The scaling factor in x-direction.
+ * @param sy The scaling factor in y-direction.
+ * @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.
+ */
+ public void setTextScaling( double sx, double sy, double tx, double ty ) throws IOException
+ {
+ setTextMatrix(sx, 0, 0, sy, tx, ty);
+ }
+
+ /**
+ * The Tm operator. Sets the text matrix to the given translation values.
+ * 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.
+ */
+ public void setTextTranslation( double tx, double ty ) throws IOException
+ {
+ setTextMatrix(1, 0, 0, 1, tx, ty);
+ }
+
+ /**
+ * 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.
+ * @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.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void setTextRotation( double angle, double tx, double ty ) throws IOException
+ {
+ double angleCos = Math.cos(angle);
+ double angleSin = Math.sin(angle);
+ setTextMatrix( angleCos, angleSin, -angleSin, angleCos, tx, ty);
+ }
+
+ /**
+ * The Cm operator. Concatenates the current transformation matrix with the given values.
+ * @param a The a value of the matrix.
+ * @param b The b value of the matrix.
+ * @param c The c value of the matrix.
+ * @param d The d value of the matrix.
+ * @param e The e value of the matrix.
+ * @param f The f value of the matrix.
+ * @throws IOException If there is an error writing to the stream.
+ */
+ public void concatenate2CTM( double a, double b, double c, double d, double e, double f ) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( a ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( b ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( c ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( d ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( e ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( f ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( CONCATENATE_MATRIX );
+ }
+
+ /**
+ * 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.
+ * @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 space. This will add the colorspace to the PDResources
+ * if necessary.
+ *
+ * @param colorSpace The colorspace to write.
+ * @throws IOException If there is an error writing the colorspace.
+ */
+ public void setStrokingColorSpace( PDColorSpace colorSpace ) throws IOException
+ {
+ currentStrokingColorSpace = colorSpace;
+ writeColorSpace( colorSpace );
+ appendRawCommands( SET_STROKING_COLORSPACE );
+ }
+
+ /**
+ * Set the stroking color space. This will add the colorspace to the PDResources
+ * if necessary.
+ *
+ * @param colorSpace The colorspace to write.
+ * @throws IOException If there is an error writing the colorspace.
+ */
+ public void setNonStrokingColorSpace( PDColorSpace colorSpace ) throws IOException
+ {
+ currentNonStrokingColorSpace = colorSpace;
+ writeColorSpace( colorSpace );
+ appendRawCommands( SET_NON_STROKING_COLORSPACE );
+ }
+
+ private void writeColorSpace( PDColorSpace colorSpace ) throws IOException
+ {
+ COSName key = null;
+ if( colorSpace instanceof PDDeviceGray ||
+ colorSpace instanceof PDDeviceRGB ||
+ colorSpace instanceof PDDeviceCMYK )
+ {
+ key = COSName.getPDFName( colorSpace.getName() );
+ }
+ else
+ {
+ COSDictionary colorSpaces =
+ (COSDictionary)resources.getCOSDictionary().getDictionaryObject(COSName.COLORSPACE);
+ if( colorSpaces == null )
+ {
+ colorSpaces = new COSDictionary();
+ resources.getCOSDictionary().setItem( COSName.COLORSPACE, colorSpaces );
+ }
+ key = colorSpaces.getKeyForValue( colorSpace.getCOSObject() );
+
+ if( key == null )
+ {
+ int counter = 0;
+ String csName = "CS";
+ while( colorSpaces.containsValue( csName + counter ) )
+ {
+ counter++;
+ }
+ key = COSName.getPDFName( csName + counter );
+ colorSpaces.setItem( key, colorSpace );
+ }
+ }
+ key.writePDF( output );
+ appendRawCommands( SPACE );
+ }
+
+ /**
+ * Set the color components of current stroking colorspace.
+ *
+ * @param components The components to set for the current color.
+ * @throws IOException If there is an error while writing to the stream.
+ */
+ public void setStrokingColor( float[] components ) throws IOException
+ {
+ for( int i=0; i< components.length; i++ )
+ {
+ appendRawCommands( formatDecimal.format( components[i] ) );
+ appendRawCommands( SPACE );
+ }
+ if( currentStrokingColorSpace instanceof PDSeparation ||
+ currentStrokingColorSpace instanceof PDPattern ||
+ currentStrokingColorSpace instanceof PDDeviceN ||
+ currentStrokingColorSpace instanceof PDICCBased )
+ {
+ appendRawCommands( SET_STROKING_COLOR_COMPLEX );
+ }
+ else
+ {
+ appendRawCommands( SET_STROKING_COLOR_SIMPLE );
+ }
+ }
+
+ /**
+ * 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 color components of current non stroking colorspace.
+ *
+ * @param components The components to set for the current color.
+ * @throws IOException If there is an error while writing to the stream.
+ */
+ public void setNonStrokingColor( float[] components ) throws IOException
+ {
+ for( int i=0; i< components.length; i++ )
+ {
+ appendRawCommands( formatDecimal.format( components[i] ) );
+ appendRawCommands( SPACE );
+ }
+ if( currentNonStrokingColorSpace instanceof PDSeparation ||
+ currentNonStrokingColorSpace instanceof PDPattern ||
+ currentNonStrokingColorSpace instanceof PDDeviceN ||
+ currentNonStrokingColorSpace instanceof PDICCBased )
+ {
+ appendRawCommands( SET_NON_STROKING_COLOR_COMPLEX );
+ }
+ else
+ {
+ appendRawCommands( SET_NON_STROKING_COLOR_SIMPLE );
+ }
+ }
+
+ /**
+ * 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 );
+ }
+
+ /**
+ * Add a rectangle to the current path.
+ *
+ * @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 addRect( 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( RECTANGLE );
+ }
+
+ /**
+ * 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
+ {
+ addRect(x, y, width, height);
+ fill(PathIterator.WIND_NON_ZERO);
+ }
+
+ /**
+ * 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
+ * @param x2 x coordinate of the point 2
+ * @param y2 y coordinate of the point 2
+ * @param x3 x coordinate of the point 3
+ * @param y3 y coordinate of the point 3
+ * @throws IOException If there is an error while adding the .
+ */
+ public void addBezier312(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( x1) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y1) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( x2) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y2) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( x3) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y3) );
+ appendRawCommands( SPACE );
+ appendRawCommands( BEZIER_312 );
+ }
+
+ /**
+ * 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
+ * @param x3 x coordinate of the point 3
+ * @param y3 y coordinate of the point 3
+ * @throws IOException If there is an error while adding the .
+ */
+ public void addBezier32(float x2, float y2, float x3, float y3) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( x2) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y2) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( x3) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y3) );
+ appendRawCommands( SPACE );
+ appendRawCommands( BEZIER_32 );
+ }
+
+ /**
+ * 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
+ * @param x3 x coordinate of the point 3
+ * @param y3 y coordinate of the point 3
+ * @throws IOException If there is an error while adding the .
+ */
+ public void addBezier31(float x1, float y1, float x3, float y3) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( x1) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y1) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( x3) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y3) );
+ appendRawCommands( SPACE );
+ appendRawCommands( BEZIER_313 );
+ }
+
+
+ /**
+ * Add a line to the given coordinate.
+ *
+ * @param x The x coordinate.
+ * @param y The y coordinate.
+ * @throws IOException If there is an error while adding the line.
+ */
+ public void moveTo( float x, float y) throws IOException
+ {
+ // moveTo
+ appendRawCommands( formatDecimal.format( x) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y) );
+ appendRawCommands( SPACE );
+ appendRawCommands( MOVE_TO );
+ }
+
+ /**
+ * Add a move to the given coordinate.
+ *
+ * @param x The x coordinate.
+ * @param y The y coordinate.
+ * @throws IOException If there is an error while adding the line.
+ */
+ public void lineTo( float x, float y) throws IOException
+ {
+ // moveTo
+ appendRawCommands( formatDecimal.format( x) );
+ appendRawCommands( SPACE );
+ appendRawCommands( formatDecimal.format( y) );
+ appendRawCommands( SPACE );
+ appendRawCommands( LINE_TO );
+ }
+ /**
+ * add a line to the current path.
+ *
+ * @param xStart The start x coordinate.
+ * @param yStart The start y coordinate.
+ * @param xEnd The end x coordinate.
+ * @param yEnd The end y coordinate.
+ * @throws IOException If there is an error while adding the line.
+ */
+ public void addLine( float xStart, float yStart, float xEnd, float yEnd ) throws IOException
+ {
+ // moveTo
+ moveTo(xStart, yStart);
+ // lineTo
+ lineTo(xEnd, yEnd);
+ }
+
+ /**
+ * Draw a line on the page using the current non stroking color and the current line width.
+ *
+ * @param xStart The start x coordinate.
+ * @param yStart The start y coordinate.
+ * @param xEnd The end x coordinate.
+ * @param yEnd The end y coordinate.
+ * @throws IOException If there is an error while drawing on the screen.
+ */
+ public void drawLine( float xStart, float yStart, float xEnd, float yEnd ) throws IOException
+ {
+ addLine(xStart, yStart, xEnd, yEnd);
+ // stroke
+ stroke();
+ }
+
+ /**
+ * Add a polygon to the current path.
+ * @param x x coordinate of each points
+ * @param y y coordinate of each points
+ * @throws IOException If there is an error while drawing on the screen.
+ */
+ public void addPolygon(float[] x, float[] y) throws IOException
+ {
+ if (x.length != y.length)
+ {
+ throw new IOException( "Error: some points are missing coordinate" );
+ }
+ for (int i = 0; i < x.length; i++)
+ {
+ if (i == 0)
+ {
+ moveTo(x[i], y[i]);
+ }
+ else
+ {
+ lineTo(x[i], y[i]);
+ }
+ }
+ closeSubPath();
+ }
+
+ /**
+ * Draw a polygon on the page using the current non stroking color.
+ * @param x x coordinate of each points
+ * @param y y coordinate of each points
+ * @throws IOException If there is an error while drawing on the screen.
+ */
+ public void drawPolygon(float[] x, float[] y) throws IOException
+ {
+ addPolygon(x, y);
+ stroke();
+ }
+
+ /**
+ * Draw and fill a polygon on the page using the current non stroking color.
+ * @param x x coordinate of each points
+ * @param y y coordinate of each points
+ * @throws IOException If there is an error while drawing on the screen.
+ */
+ public void fillPolygon(float[] x, float[] y) throws IOException
+ {
+ addPolygon(x, y);
+ fill(PathIterator.WIND_NON_ZERO);
+ }
+
+ /**
+ * Stroke the path.
+ */
+ public void stroke() throws IOException
+ {
+ appendRawCommands( STROKE );
+ }
+
+ /**
+ * Close and stroke the path.
+ */
+ public void closeAndStroke() throws IOException
+ {
+ appendRawCommands( CLOSE_STROKE );
+ }
+
+ /**
+ * Fill the path.
+ */
+ public void fill(int windingRule) throws IOException
+ {
+ if (windingRule == PathIterator.WIND_NON_ZERO)
+ {
+ appendRawCommands( FILL_NON_ZERO );
+ }
+ else if (windingRule == PathIterator.WIND_EVEN_ODD)
+ {
+ appendRawCommands( FILL_EVEN_ODD );
+ }
+ else
+ {
+ throw new IOException( "Error: unknown value for winding rule" );
+ }
+
+ }
+
+ /**
+ * Close subpath.
+ */
+ public void closeSubPath() throws IOException
+ {
+ appendRawCommands( CLOSE_SUBPATH );
+ }
+
+ /**
+ * Clip path.
+ */
+ public void clipPath(int windingRule) throws IOException
+ {
+ if (windingRule == PathIterator.WIND_NON_ZERO)
+ {
+ appendRawCommands( CLIP_PATH_NON_ZERO );
+ appendRawCommands( NOP );
+ }
+ else if (windingRule == PathIterator.WIND_EVEN_ODD)
+ {
+ appendRawCommands( CLIP_PATH_EVEN_ODD );
+ appendRawCommands( NOP );
+ }
+ else
+ {
+ throw new IOException( "Error: unknown value for winding rule" );
+ }
+ }
+
+ /**
+ * Set linewidth to the given value.
+ *
+ * @param lineWidth The width which is used for drwaing.
+ * @throws IOException If there is an error while drawing on the screen.
+ */
+ public void setLineWidth(float lineWidth) throws IOException
+ {
+ appendRawCommands( formatDecimal.format( lineWidth ) );
+ appendRawCommands( SPACE );
+ appendRawCommands( LINE_WIDTH );
+ }
+
+ /**
+ * 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
+ {
+ appendRawCommands( SAVE_GRAPHICS_STATE);
+ }
+
+ /**
+ * Q operator. Restores the current graphics state.
+ * @throws IOException If an error occurs while writing to the stream.
+ */
+ public void restoreGraphicsState() throws IOException
+ {
+ appendRawCommands( RESTORE_GRAPHICS_STATE );
+ }
+
+ /**
+ * 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 );
+ }
+
+ /**
+ * 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.
+ */
+ public void close() throws IOException
+ {
+ output.close();
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The PDModel edit package will be used to store classes for creating page content.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.encryption;
+
+/**
+ * This class represents the access permissions to a document.
+ * These permissions are specified in the PDF format specifications, they include:
+ * <ul>
+ * <li>print the document</li>
+ * <li>modify the content of the document</li>
+ * <li>copy or extract content of the document</li>
+ * <li>add or modify annotations</li>
+ * <li>fill in interactive form fields</li>
+ * <li>extract text and graphics for accessibility to visually impaired people</li>
+ * <li>assemble the document</li>
+ * <li>print in degraded quality</li>
+ * </ul>
+ *
+ * This class can be used to protect a document by assigning access permissions to recipients.
+ * In this case, it must be used with a specific ProtectionPolicy.
+ *
+ *
+ * When a document is decrypted, it has a currentAccessPermission property which is the access permissions
+ * granted to the user who decrypted the document.
+ *
+ * @see ProtectionPolicy
+ * @see org.apache.pdfbox.pdmodel.PDDocument#getCurrentAccessPermission()
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.4 $
+ */
+
+public class AccessPermission
+{
+
+ private static final int DEFAULT_PERMISSIONS = 0xFFFFFFFF ^ 3;//bits 0 & 1 need to be zero
+ private static final int PRINT_BIT = 3;
+ private static final int MODIFICATION_BIT = 4;
+ private static final int EXTRACT_BIT = 5;
+ private static final int MODIFY_ANNOTATIONS_BIT = 6;
+ private static final int FILL_IN_FORM_BIT = 9;
+ private static final int EXTRACT_FOR_ACCESSIBILITY_BIT = 10;
+ private static final int ASSEMBLE_DOCUMENT_BIT = 11;
+ private static final int DEGRADED_PRINT_BIT = 12;
+
+ private int bytes = DEFAULT_PERMISSIONS;
+
+ private boolean readOnly = false;
+
+ /**
+ * Create a new access permission object.
+ * By default, all permissions are granted.
+ */
+ public AccessPermission()
+ {
+ bytes = DEFAULT_PERMISSIONS;
+ }
+
+ /**
+ * Create a new access permission object from a byte array.
+ * Bytes are ordered most significant byte first.
+ *
+ * @param b the bytes as defined in PDF specs
+ */
+
+ public AccessPermission(byte[] b)
+ {
+ bytes = 0;
+ bytes |= b[0] & 0xFF;
+ bytes <<= 8;
+ bytes |= b[1] & 0xFF;
+ bytes <<= 8;
+ bytes |= b[2] & 0xFF;
+ bytes <<= 8;
+ bytes |= b[3] & 0xFF;
+ }
+
+ /**
+ * Creates a new access permission object from a single integer.
+ *
+ * @param permissions The permission bits.
+ */
+ public AccessPermission( int permissions )
+ {
+ bytes = permissions;
+ }
+
+ private boolean isPermissionBitOn( int bit )
+ {
+ return (bytes & (1 << (bit-1))) != 0;
+ }
+
+ private boolean setPermissionBit( int bit, boolean value )
+ {
+ int permissions = bytes;
+ if( value )
+ {
+ permissions = permissions | (1 << (bit-1));
+ }
+ else
+ {
+ permissions = permissions & (0xFFFFFFFF ^ (1 << (bit-1)));
+ }
+ bytes = permissions;
+
+ return (bytes & (1 << (bit-1))) != 0;
+ }
+
+
+
+
+ /**
+ * This will tell if the access permission corresponds to owner
+ * access permission (no restriction).
+ *
+ * @return true if the access permission does not restrict the use of the document
+ */
+ public boolean isOwnerPermission()
+ {
+ return (this.canAssembleDocument()
+ && this.canExtractContent()
+ && this.canExtractForAccessibility()
+ && this.canFillInForm()
+ && this.canModify()
+ && this.canModifyAnnotations()
+ && this.canPrint()
+ && this.canPrintDegraded()
+ );
+ }
+
+ /**
+ * returns an access permission object for a document owner.
+ *
+ * @return A standard owner access permission set.
+ */
+
+ public static AccessPermission getOwnerAccessPermission()
+ {
+ AccessPermission ret = new AccessPermission();
+ ret.setCanAssembleDocument(true);
+ ret.setCanExtractContent(true);
+ ret.setCanExtractForAccessibility(true);
+ ret.setCanFillInForm(true);
+ ret.setCanModify(true);
+ ret.setCanModifyAnnotations(true);
+ ret.setCanPrint(true);
+ ret.setCanPrintDegraded(true);
+ return ret;
+ }
+
+ /**
+ * This returns an integer representing the access permissions.
+ * This integer can be used for public key encryption. This format
+ * is not documented in the PDF specifications but is necessary for compatibility
+ * with Adobe Acrobat and Adobe Reader.
+ *
+ * @return the integer representing access permissions
+ */
+
+ public int getPermissionBytesForPublicKey()
+ {
+ setPermissionBit(1, true);
+ setPermissionBit(7, false);
+ setPermissionBit(8, false);
+ for(int i=13; i<=32; i++)
+ {
+ setPermissionBit(i, false);
+ }
+ return bytes;
+ }
+
+ /**
+ * The returns an integer representing the access permissions.
+ * This integer can be used for standard PDF encryption as specified
+ * in the PDF specifications.
+ *
+ * @return the integer representing the access permissions
+ */
+ public int getPermissionBytes()
+ {
+ return bytes;
+ }
+
+ /**
+ * This will tell if the user can print.
+ *
+ * @return true If supplied with the user password they are allowed to print.
+ */
+ public boolean canPrint()
+ {
+ return isPermissionBitOn( PRINT_BIT );
+ }
+
+ /**
+ * Set if the user can print.
+ * This method will have no effect if the object is in read only mode
+ *
+ * @param allowPrinting A boolean determining if the user can print.
+ */
+ public void setCanPrint( boolean allowPrinting )
+ {
+ if(!readOnly)
+ {
+ setPermissionBit( PRINT_BIT, allowPrinting );
+ }
+ }
+
+ /**
+ * This will tell if the user can modify contents of the document.
+ *
+ * @return true If supplied with the user password they are allowed to modify the document
+ */
+ public boolean canModify()
+ {
+ return isPermissionBitOn( MODIFICATION_BIT );
+ }
+
+ /**
+ * Set if the user can modify the document.
+ * This method will have no effect if the object is in read only mode
+ *
+ * @param allowModifications A boolean determining if the user can modify the document.
+ */
+ public void setCanModify( boolean allowModifications )
+ {
+ if(!readOnly)
+ {
+ setPermissionBit( MODIFICATION_BIT, allowModifications );
+ }
+ }
+
+ /**
+ * This will tell if the user can extract text and images from the PDF document.
+ *
+ * @return true If supplied with the user password they are allowed to extract content
+ * from the PDF document
+ */
+ public boolean canExtractContent()
+ {
+ return isPermissionBitOn( EXTRACT_BIT );
+ }
+
+ /**
+ * Set if the user can extract content from the document.
+ * This method will have no effect if the object is in read only mode
+ *
+ * @param allowExtraction A boolean determining if the user can extract content
+ * from the document.
+ */
+ public void setCanExtractContent( boolean allowExtraction )
+ {
+ if(!readOnly)
+ {
+ setPermissionBit( EXTRACT_BIT, allowExtraction );
+ }
+ }
+
+ /**
+ * This will tell if the user can add/modify text annotations, fill in interactive forms fields.
+ *
+ * @return true If supplied with the user password they are allowed to modify annotations.
+ */
+ public boolean canModifyAnnotations()
+ {
+ return isPermissionBitOn( MODIFY_ANNOTATIONS_BIT );
+ }
+
+ /**
+ * Set if the user can modify annotations.
+ * This method will have no effect if the object is in read only mode
+ *
+ * @param allowAnnotationModification A boolean determining if the user can modify annotations.
+ */
+ public void setCanModifyAnnotations( boolean allowAnnotationModification )
+ {
+ if(!readOnly)
+ {
+ setPermissionBit( MODIFY_ANNOTATIONS_BIT, allowAnnotationModification );
+ }
+ }
+
+ /**
+ * This will tell if the user can fill in interactive forms.
+ *
+ * @return true If supplied with the user password they are allowed to fill in form fields.
+ */
+ public boolean canFillInForm()
+ {
+ return isPermissionBitOn( FILL_IN_FORM_BIT );
+ }
+
+ /**
+ * Set if the user can fill in interactive forms.
+ * This method will have no effect if the object is in read only mode
+ *
+ * @param allowFillingInForm A boolean determining if the user can fill in interactive forms.
+ */
+ public void setCanFillInForm( boolean allowFillingInForm )
+ {
+ if(!readOnly)
+ {
+ setPermissionBit( FILL_IN_FORM_BIT, allowFillingInForm );
+ }
+ }
+
+ /**
+ * This will tell if the user can extract text and images from the PDF document
+ * for accessibility purposes.
+ *
+ * @return true If supplied with the user password they are allowed to extract content
+ * from the PDF document
+ */
+ public boolean canExtractForAccessibility()
+ {
+ return isPermissionBitOn( EXTRACT_FOR_ACCESSIBILITY_BIT );
+ }
+
+ /**
+ * Set if the user can extract content from the document for accessibility purposes.
+ * This method will have no effect if the object is in read only mode
+ *
+ * @param allowExtraction A boolean determining if the user can extract content
+ * from the document.
+ */
+ public void setCanExtractForAccessibility( boolean allowExtraction )
+ {
+ if(!readOnly)
+ {
+ setPermissionBit( EXTRACT_FOR_ACCESSIBILITY_BIT, allowExtraction );
+ }
+ }
+
+ /**
+ * This will tell if the user can insert/rotate/delete pages.
+ *
+ * @return true If supplied with the user password they are allowed to extract content
+ * from the PDF document
+ */
+ public boolean canAssembleDocument()
+ {
+ return isPermissionBitOn( ASSEMBLE_DOCUMENT_BIT );
+ }
+
+ /**
+ * Set if the user can insert/rotate/delete pages.
+ * This method will have no effect if the object is in read only mode
+ *
+ * @param allowAssembly A boolean determining if the user can assemble the document.
+ */
+ public void setCanAssembleDocument( boolean allowAssembly )
+ {
+ if(!readOnly)
+ {
+ setPermissionBit( ASSEMBLE_DOCUMENT_BIT, allowAssembly );
+ }
+ }
+
+ /**
+ * This will tell if the user can print the document in a degraded format.
+ *
+ * @return true If supplied with the user password they are allowed to print the
+ * document in a degraded format.
+ */
+ public boolean canPrintDegraded()
+ {
+ return isPermissionBitOn( DEGRADED_PRINT_BIT );
+ }
+
+ /**
+ * Set if the user can print the document in a degraded format.
+ * This method will have no effect if the object is in read only mode
+ *
+ * @param allowAssembly A boolean determining if the user can print the
+ * document in a degraded format.
+ */
+ public void setCanPrintDegraded( boolean allowAssembly )
+ {
+ if(!readOnly)
+ {
+ setPermissionBit( DEGRADED_PRINT_BIT, allowAssembly );
+ }
+ }
+
+ /**
+ * Locks the access permission read only (ie, the setters will have no effects).
+ * After that, the object cannot be unlocked.
+ * This method is used for the currentAccessPermssion of a document to avoid
+ * users to change access permission.
+ */
+ public void setReadOnly()
+ {
+ readOnly = true;
+ }
+
+ /**
+ * This will tell if the object has been set as read only.
+ *
+ * @return true if the object is in read only mode.
+ */
+
+ public boolean isReadOnly()
+ {
+ return readOnly;
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+/**
+ * This exception can be thrown by the SecurityHandlersManager class when
+ * a document required an unimplemented security handler to be opened.
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.2 $
+ */
+
+public class BadSecurityHandlerException extends Exception
+{
+ /**
+ * Default Constructor.
+ */
+ public BadSecurityHandlerException()
+ {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param e A sub exception.
+ */
+ public BadSecurityHandlerException(Exception e)
+ {
+ super(e);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param msg Message describing exception.
+ */
+ public BadSecurityHandlerException(String msg)
+ {
+ super(msg);
+ }
+
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+/**
+ * This class represents data required to decrypt PDF documents. This can
+ * be a password for standard security or a X509 certificate with a private
+ * key for public key security.
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.2 $
+ */
+public abstract class DecryptionMaterial
+{
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This class is a specialized view of the crypt filter dictionary of a PDF document.
+ * It contains a low level dictionary (COSDictionary) and provides the methods to
+ * manage its fields.
+ *
+ *
+ * @version $Revision: 1.0 $
+ */
+public class PDCryptFilterDictionary
+{
+
+ /**
+ * COS crypt filter dictionary.
+ */
+ protected COSDictionary cryptFilterDictionary = null;
+
+ /**
+ * creates a new empty crypt filter dictionary.
+ */
+ public PDCryptFilterDictionary()
+ {
+ cryptFilterDictionary = new COSDictionary();
+ }
+
+ /**
+ * creates a new crypt filter dictionary from the low level dictionary provided.
+ * @param d the low level dictionary that will be managed by the newly created object
+ */
+ public PDCryptFilterDictionary(COSDictionary d)
+ {
+ cryptFilterDictionary = d;
+ }
+
+ /**
+ * This will get the dictionary associated with this crypt filter dictionary.
+ *
+ * @return The COS dictionary that this object wraps.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return cryptFilterDictionary;
+ }
+
+ /**
+ * This will set the number of bits to use for the crypt filter algorithm.
+ *
+ * @param length The new key length.
+ */
+ public void setLength(int length)
+ {
+ cryptFilterDictionary.setInt(COSName.LENGTH, length);
+ }
+
+ /**
+ * This will return the Length entry of the crypt filter dictionary.<br /><br />
+ * The length in <b>bits</b> for the crypt filter algorithm. This will return a multiple of 8.
+ *
+ * @return The length in bits for the encryption algorithm
+ */
+ public int getLength()
+ {
+ return cryptFilterDictionary.getInt( COSName.LENGTH, 40 );
+ }
+
+ /**
+ * This will set the crypt filter method.
+ * Allowed values are: NONE, V2, AESV2
+ *
+ * @param cfm name of the crypt filter method.
+ *
+ * @throws IOException If there is an error setting the data.
+ */
+ public void setCryptFilterMethod(COSName cfm) throws IOException
+ {
+ cryptFilterDictionary.setItem( COSName.CFM, cfm );
+ }
+
+ /**
+ * This will return the crypt filter method.
+ * Allowed values are: NONE, V2, AESV2
+ *
+ * @return the name of the crypt filter method.
+ *
+ * @throws IOException If there is an error accessing the data.
+ */
+ public COSName getCryptFilterMethod() throws IOException
+ {
+ return (COSName)cryptFilterDictionary.getDictionaryObject( COSName.CFM );
+ }
+
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * This class is a specialized view of the encryption dictionary of a PDF document.
+ * It contains a low level dictionary (COSDictionary) and provides the methods to
+ * manage its fields.
+ *
+ * The available fields are the ones who are involved by standard security handler
+ * and public key security handler.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.7 $
+ */
+public class PDEncryptionDictionary
+{
+ /**
+ * See PDF Reference 1.4 Table 3.13.
+ */
+ public static final int VERSION0_UNDOCUMENTED_UNSUPPORTED = 0;
+ /**
+ * See PDF Reference 1.4 Table 3.13.
+ */
+ public static final int VERSION1_40_BIT_ALGORITHM = 1;
+ /**
+ * See PDF Reference 1.4 Table 3.13.
+ */
+ public static final int VERSION2_VARIABLE_LENGTH_ALGORITHM = 2;
+ /**
+ * See PDF Reference 1.4 Table 3.13.
+ */
+ public static final int VERSION3_UNPUBLISHED_ALGORITHM = 3;
+ /**
+ * See PDF Reference 1.4 Table 3.13.
+ */
+ public static final int VERSION4_SECURITY_HANDLER = 4;
+
+ /**
+ * The default security handler.
+ */
+ public static final String DEFAULT_NAME = "Standard";
+
+ /**
+ * The default length for the encryption key.
+ */
+ public static final int DEFAULT_LENGTH = 40;
+
+ /**
+ * The default version, according to the PDF Reference.
+ */
+ public static final int DEFAULT_VERSION = VERSION0_UNDOCUMENTED_UNSUPPORTED;
+
+ /**
+ * COS encryption dictionary.
+ */
+ protected COSDictionary encryptionDictionary = null;
+
+ /**
+ * creates a new empty encryption dictionary.
+ */
+ public PDEncryptionDictionary()
+ {
+ encryptionDictionary = new COSDictionary();
+ }
+
+ /**
+ * creates a new encryption dictionary from the low level dictionary provided.
+ * @param d the low level dictionary that will be managed by the newly created object
+ */
+ public PDEncryptionDictionary(COSDictionary d)
+ {
+ encryptionDictionary = d;
+ }
+
+ /**
+ * This will get the dictionary associated with this encryption dictionary.
+ *
+ * @return The COS dictionary that this object wraps.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return encryptionDictionary;
+ }
+
+ /**
+ * Sets the filter entry of the encryption dictionary.
+ *
+ * @param filter The filter name.
+ */
+ public void setFilter(String filter)
+ {
+ encryptionDictionary.setItem( COSName.FILTER, COSName.getPDFName( filter ) );
+ }
+
+ /**
+ * Get the name of the filter.
+ *
+ * @return The filter name contained in this encryption dictionary.
+ */
+ public String getFilter()
+ {
+ return encryptionDictionary.getNameAsString( COSName.FILTER );
+ }
+
+ /**
+ * Get the name of the subfilter.
+ *
+ * @return The subfilter name contained in this encryption dictionary.
+ */
+ public String getSubFilter()
+ {
+ return encryptionDictionary.getNameAsString( COSName.SUB_FILTER );
+ }
+
+ /**
+ * Set the subfilter entry of the encryption dictionary.
+ *
+ * @param subfilter The value of the subfilter field.
+ */
+ public void setSubFilter(String subfilter)
+ {
+ encryptionDictionary.setName( COSName.SUB_FILTER, subfilter );
+ }
+
+ /**
+ * This will set the V entry of the encryption dictionary.<br /><br />
+ * See PDF Reference 1.4 Table 3.13. <br /><br/>
+ * <b>Note: This value is used to decrypt the pdf document. If you change this when
+ * the document is encrypted then decryption will fail!.</b>
+ *
+ * @param version The new encryption version.
+ */
+ public void setVersion(int version)
+ {
+ encryptionDictionary.setInt( COSName.V, version );
+ }
+
+ /**
+ * This will return the V entry of the encryption dictionary.<br /><br />
+ * See PDF Reference 1.4 Table 3.13.
+ *
+ * @return The encryption version to use.
+ */
+ public int getVersion()
+ {
+ return encryptionDictionary.getInt( COSName.V, 0 );
+ }
+
+ /**
+ * This will set the number of bits to use for the encryption algorithm.
+ *
+ * @param length The new key length.
+ */
+ public void setLength(int length)
+ {
+ encryptionDictionary.setInt(COSName.LENGTH, length);
+ }
+
+ /**
+ * This will return the Length entry of the encryption dictionary.<br /><br />
+ * The length in <b>bits</b> for the encryption algorithm. This will return a multiple of 8.
+ *
+ * @return The length in bits for the encryption algorithm
+ */
+ public int getLength()
+ {
+ return encryptionDictionary.getInt( COSName.LENGTH, 40 );
+ }
+
+ /**
+ * This will set the R entry of the encryption dictionary.<br /><br />
+ * See PDF Reference 1.4 Table 3.14. <br /><br/>
+ *
+ * <b>Note: This value is used to decrypt the pdf document. If you change this when
+ * the document is encrypted then decryption will fail!.</b>
+ *
+ * @param revision The new encryption version.
+ */
+ public void setRevision(int revision)
+ {
+ encryptionDictionary.setInt( COSName.R, revision );
+ }
+
+ /**
+ * This will return the R entry of the encryption dictionary.<br /><br />
+ * See PDF Reference 1.4 Table 3.14.
+ *
+ * @return The encryption revision to use.
+ */
+ public int getRevision()
+ {
+ return encryptionDictionary.getInt( COSName.R, DEFAULT_VERSION );
+ }
+
+ /**
+ * This will set the O entry in the standard encryption dictionary.
+ *
+ * @param o A 32 byte array or null if there is no owner key.
+ *
+ * @throws IOException If there is an error setting the data.
+ */
+ public void setOwnerKey(byte[] o) throws IOException
+ {
+ COSString owner = new COSString();
+ owner.append( o );
+ encryptionDictionary.setItem( COSName.O, owner );
+ }
+
+ /**
+ * This will get the O entry in the standard encryption dictionary.
+ *
+ * @return A 32 byte array or null if there is no owner key.
+ *
+ * @throws IOException If there is an error accessing the data.
+ */
+ public byte[] getOwnerKey() throws IOException
+ {
+ byte[] o = null;
+ COSString owner = (COSString)encryptionDictionary.getDictionaryObject( COSName.O );
+ if( owner != null )
+ {
+ o = owner.getBytes();
+ }
+ return o;
+ }
+
+ /**
+ * This will set the U entry in the standard encryption dictionary.
+ *
+ * @param u A 32 byte array.
+ *
+ * @throws IOException If there is an error setting the data.
+ */
+ public void setUserKey(byte[] u) throws IOException
+ {
+ COSString user = new COSString();
+ user.append( u );
+ encryptionDictionary.setItem( COSName.U, user );
+ }
+
+ /**
+ * This will get the U entry in the standard encryption dictionary.
+ *
+ * @return A 32 byte array or null if there is no user key.
+ *
+ * @throws IOException If there is an error accessing the data.
+ */
+ public byte[] getUserKey() throws IOException
+ {
+ byte[] u = null;
+ COSString user = (COSString)encryptionDictionary.getDictionaryObject( COSName.U );
+ if( user != null )
+ {
+ u = user.getBytes();
+ }
+ return u;
+ }
+
+ /**
+ * This will set the permissions bit mask.
+ *
+ * @param permissions The new permissions bit mask
+ */
+ public void setPermissions(int permissions)
+ {
+ encryptionDictionary.setInt( COSName.P, permissions );
+ }
+
+ /**
+ * This will get the permissions bit mask.
+ *
+ * @return The permissions bit mask.
+ */
+ public int getPermissions()
+ {
+ return encryptionDictionary.getInt( COSName.P, 0 );
+ }
+
+ /**
+ * Will get the EncryptMetaData dictionary info.
+ *
+ * @return true if EncryptMetaData is explicitly set to false (the default is true)
+ */
+ public boolean isEncryptMetaData()
+ {
+ // default is true (see 7.6.3.2 Standard Encryption Dictionary PDF 32000-1:2008)
+ boolean encryptMetaData = true;
+
+ COSBase value = encryptionDictionary.getDictionaryObject(COSName.ENCRYPT_META_DATA);
+
+ if (value instanceof COSBoolean) {
+ encryptMetaData = ((COSBoolean)value).getValue();
+ }
+
+ return encryptMetaData;
+ }
+
+ /**
+ * This will set the Recipients field of the dictionary. This field contains an array
+ * of string.
+ * @param recipients the array of bytes arrays to put in the Recipients field.
+ * @throws IOException If there is an error setting the data.
+ */
+ public void setRecipients(byte[][] recipients) throws IOException
+ {
+ COSArray array = new COSArray();
+ for(int i=0; i<recipients.length; i++)
+ {
+ COSString recip = new COSString();
+ recip.append(recipients[i]);
+ recip.setForceLiteralForm(true);
+ array.add(recip);
+ }
+ encryptionDictionary.setItem(COSName.RECIPIENTS, array);
+ }
+
+ /**
+ * Returns the number of recipients contained in the Recipients field of the dictionary.
+ *
+ * @return the number of recipients contained in the Recipients field.
+ */
+ public int getRecipientsLength()
+ {
+ COSArray array = (COSArray)encryptionDictionary.getItem(COSName.RECIPIENTS);
+ return array.size();
+ }
+
+ /**
+ * returns the COSString contained in the Recipients field at position i.
+ *
+ * @param i the position in the Recipients field array.
+ *
+ * @return a COSString object containing information about the recipient number i.
+ */
+ public COSString getRecipientStringAt(int i)
+ {
+ COSArray array = (COSArray)encryptionDictionary.getItem(COSName.RECIPIENTS);
+ return (COSString)array.get(i);
+ }
+
+ /**
+ * Returns the standard crypt filter.
+ *
+ * @return the standard crypt filter if available.
+ */
+ public PDCryptFilterDictionary getStdCryptFilterDictionary()
+ {
+ return getCryptFilterDictionary(COSName.STD_CF);
+ }
+
+ /**
+ * Returns the crypt filter with the given name.
+ *
+ * @param cryptFilterName the name of the crypt filter
+ *
+ * @return the crypt filter with the given name if available
+ */
+ public PDCryptFilterDictionary getCryptFilterDictionary(COSName cryptFilterName)
+ {
+ COSDictionary cryptFilterDictionary = (COSDictionary)encryptionDictionary.getDictionaryObject( COSName.CF );
+ if (cryptFilterDictionary != null)
+ {
+ COSDictionary stdCryptFilterDictionary = (COSDictionary)cryptFilterDictionary.getDictionaryObject(cryptFilterName);
+ if (stdCryptFilterDictionary != null)
+ {
+ return new PDCryptFilterDictionary(stdCryptFilterDictionary);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the name of the filter which is used for de/encrypting streams.
+ * Default value is "Identity".
+ *
+ * @return the name of the filter
+ */
+ public COSName getStreamFilterName()
+ {
+ COSName stmF = (COSName)encryptionDictionary.getDictionaryObject( COSName.STM_F );
+ if (stmF == null)
+ {
+ stmF = COSName.IDENTITY;
+ }
+ return stmF;
+ }
+
+ /**
+ * Returns the name of the filter which is used for de/encrypting strings.
+ * Default value is "Identity".
+ *
+ * @return the name of the filter
+ */
+ public COSName getStringFilterName()
+ {
+ COSName strF = (COSName)encryptionDictionary.getDictionaryObject( COSName.STR_F );
+ if (strF == null)
+ {
+ strF = COSName.IDENTITY;
+ }
+ return strF;
+ }
+
+}
+
+
\ No newline at end of file
--- /dev/null
+/*
+ * 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.encryption;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import java.io.IOException;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class will handle loading of the different security handlers.
+ *
+ * See PDF Reference 1.4 section "3.5 Encryption"
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ * @deprecated Made deprecated by the new security layer of PDFBox. Use SecurityHandlers instead.
+ */
+
+public class PDEncryptionManager
+{
+ private static Map handlerMap = Collections.synchronizedMap( new HashMap() );
+
+ static
+ {
+ registerSecurityHandler( PDStandardEncryption.FILTER_NAME, PDStandardEncryption.class );
+ }
+
+ private PDEncryptionManager()
+ {
+ }
+
+ /**
+ * This will allow the user to register new security handlers when unencrypting a
+ * document.
+ *
+ * @param filterName As described in the encryption dictionary.
+ * @param handlerClass A subclass of PDEncryptionDictionary that has a constructor that takes
+ * a COSDictionary.
+ */
+ public static void registerSecurityHandler( String filterName, Class handlerClass )
+ {
+ handlerMap.put( COSName.getPDFName( filterName ), handlerClass );
+ }
+
+ /**
+ * This will get the correct security handler for the encryption dictionary.
+ *
+ * @param dictionary The encryption dictionary.
+ *
+ * @return An implementation of PDEncryptionDictionary(PDStandardEncryption for most cases).
+ *
+ * @throws IOException If a security handler could not be found.
+ */
+ public static PDEncryptionDictionary getEncryptionDictionary( COSDictionary dictionary )
+ throws IOException
+ {
+ Object retval = null;
+ if( dictionary != null )
+ {
+ COSName filter = (COSName)dictionary.getDictionaryObject( COSName.FILTER );
+ Class handlerClass = (Class)handlerMap.get( filter );
+ if( handlerClass == null )
+ {
+ throw new IOException( "No handler for security handler '" + filter.getName() + "'" );
+ }
+ else
+ {
+ try
+ {
+ Constructor ctor = handlerClass.getConstructor( new Class[] {
+ COSDictionary.class
+ } );
+ retval = ctor.newInstance( new Object[] {
+ dictionary
+ } );
+ }
+ catch( NoSuchMethodException e )
+ {
+ throw new IOException( e.getMessage() );
+ }
+ catch( InstantiationException e )
+ {
+ throw new IOException( e.getMessage() );
+ }
+ catch( IllegalAccessException e )
+ {
+ throw new IOException( e.getMessage() );
+ }
+ catch( InvocationTargetException e )
+ {
+ throw new IOException( e.getMessage() );
+ }
+ }
+ }
+ return (PDEncryptionDictionary)retval;
+
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+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.COSString;
+
+import java.io.IOException;
+
+/**
+ * This class holds information that is related to the standard PDF encryption.
+ *
+ * See PDF Reference 1.4 section "3.5 Encryption"
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ * @deprecated Made deprecated by the new security layer of PDFBox. Use SecurityHandlers instead.
+ */
+public class PDStandardEncryption extends PDEncryptionDictionary
+{
+ /**
+ * The 'Filter' name for this security handler.
+ */
+ public static final String FILTER_NAME = "Standard";
+
+ /**
+ * The default revision of one is not specified.
+ */
+ public static final int DEFAULT_REVISION = 3;
+
+ /**
+ * Encryption revision 2.
+ */
+ public static final int REVISION2 = 2;
+ /**
+ * Encryption revision 3.
+ */
+ public static final int REVISION3 = 3;
+ /**
+ * Encryption revision 4.
+ */
+ public static final int REVISION4 = 4;
+
+ /**
+ * The default set of permissions which is to allow all.
+ */
+ public static final int DEFAULT_PERMISSIONS = 0xFFFFFFFF ^ 3;//bits 0 & 1 need to be zero
+
+ private static final int PRINT_BIT = 3;
+ private static final int MODIFICATION_BIT = 4;
+ private static final int EXTRACT_BIT = 5;
+ private static final int MODIFY_ANNOTATIONS_BIT = 6;
+ private static final int FILL_IN_FORM_BIT = 9;
+ private static final int EXTRACT_FOR_ACCESSIBILITY_BIT = 10;
+ private static final int ASSEMBLE_DOCUMENT_BIT = 11;
+ private static final int DEGRADED_PRINT_BIT = 12;
+
+ /**
+ * Default constructor that uses Version 2, Revision 3, 40 bit encryption,
+ * all permissions allowed.
+ */
+ public PDStandardEncryption()
+ {
+ super();
+ encryptionDictionary.setItem( COSName.FILTER, COSName.getPDFName( FILTER_NAME ) );
+ setVersion( PDEncryptionDictionary.VERSION1_40_BIT_ALGORITHM );
+ setRevision( PDStandardEncryption.REVISION2 );
+ setPermissions( DEFAULT_PERMISSIONS );
+ }
+
+ /**
+ * Constructor from existing dictionary.
+ *
+ * @param dict The existing encryption dictionary.
+ */
+ public PDStandardEncryption( COSDictionary dict )
+ {
+ super( dict );
+ }
+
+ /**
+ * This will return the R entry of the encryption dictionary.<br /><br />
+ * See PDF Reference 1.4 Table 3.14.
+ *
+ * @return The encryption revision to use.
+ */
+ public int getRevision()
+ {
+ int revision = DEFAULT_VERSION;
+ COSNumber cosRevision = (COSNumber)encryptionDictionary.getDictionaryObject( COSName.getPDFName( "R" ) );
+ if( cosRevision != null )
+ {
+ revision = cosRevision.intValue();
+ }
+ return revision;
+ }
+
+ /**
+ * This will set the R entry of the encryption dictionary.<br /><br />
+ * See PDF Reference 1.4 Table 3.14. <br /><br/>
+ *
+ * <b>Note: This value is used to decrypt the pdf document. If you change this when
+ * the document is encrypted then decryption will fail!.</b>
+ *
+ * @param revision The new encryption version.
+ */
+ public void setRevision( int revision )
+ {
+ encryptionDictionary.setInt( COSName.getPDFName( "R" ), revision );
+ }
+
+ /**
+ * This will get the O entry in the standard encryption dictionary.
+ *
+ * @return A 32 byte array or null if there is no owner key.
+ */
+ public byte[] getOwnerKey()
+ {
+ byte[] o = null;
+ COSString owner = (COSString)encryptionDictionary.getDictionaryObject( COSName.getPDFName( "O" ) );
+ if( owner != null )
+ {
+ o = owner.getBytes();
+ }
+ return o;
+ }
+
+ /**
+ * This will set the O entry in the standard encryption dictionary.
+ *
+ * @param o A 32 byte array or null if there is no owner key.
+ *
+ * @throws IOException If there is an error setting the data.
+ */
+ public void setOwnerKey( byte[] o ) throws IOException
+ {
+ COSString owner = new COSString();
+ owner.append( o );
+ encryptionDictionary.setItem( COSName.getPDFName( "O" ), owner );
+ }
+
+ /**
+ * This will get the U entry in the standard encryption dictionary.
+ *
+ * @return A 32 byte array or null if there is no user key.
+ */
+ public byte[] getUserKey()
+ {
+ byte[] u = null;
+ COSString user = (COSString)encryptionDictionary.getDictionaryObject( COSName.getPDFName( "U" ) );
+ if( user != null )
+ {
+ u = user.getBytes();
+ }
+ return u;
+ }
+
+ /**
+ * This will set the U entry in the standard encryption dictionary.
+ *
+ * @param u A 32 byte array.
+ *
+ * @throws IOException If there is an error setting the data.
+ */
+ public void setUserKey( byte[] u ) throws IOException
+ {
+ COSString user = new COSString();
+ user.append( u );
+ encryptionDictionary.setItem( COSName.getPDFName( "U" ), user );
+ }
+
+ /**
+ * This will get the permissions bit mask.
+ *
+ * @return The permissions bit mask.
+ */
+ public int getPermissions()
+ {
+ int permissions = 0;
+ COSInteger p = (COSInteger)encryptionDictionary.getDictionaryObject( COSName.getPDFName( "P" ) );
+ if( p != null )
+ {
+ permissions = p.intValue();
+ }
+ return permissions;
+ }
+
+ /**
+ * This will set the permissions bit mask.
+ *
+ * @param p The new permissions bit mask
+ */
+ public void setPermissions( int p )
+ {
+ encryptionDictionary.setInt( COSName.getPDFName( "P" ), p );
+ }
+
+ private boolean isPermissionBitOn( int bit )
+ {
+ return (getPermissions() & (1 << (bit-1))) != 0;
+ }
+
+ private boolean setPermissionBit( int bit, boolean value )
+ {
+ int permissions = getPermissions();
+ if( value )
+ {
+ permissions = permissions | (1 << (bit-1));
+ }
+ else
+ {
+ permissions = permissions & (0xFFFFFFFF ^ (1 << (bit-1)));
+ }
+ setPermissions( permissions );
+
+ return (getPermissions() & (1 << (bit-1))) != 0;
+ }
+
+ /**
+ * This will tell if the user can print.
+ *
+ * @return true If supplied with the user password they are allowed to print.
+ */
+ public boolean canPrint()
+ {
+ return isPermissionBitOn( PRINT_BIT );
+ }
+
+ /**
+ * Set if the user can print.
+ *
+ * @param allowPrinting A boolean determining if the user can print.
+ */
+ public void setCanPrint( boolean allowPrinting )
+ {
+ setPermissionBit( PRINT_BIT, allowPrinting );
+ }
+
+ /**
+ * This will tell if the user can modify contents of the document.
+ *
+ * @return true If supplied with the user password they are allowed to modify the document
+ */
+ public boolean canModify()
+ {
+ return isPermissionBitOn( MODIFICATION_BIT );
+ }
+
+ /**
+ * Set if the user can modify the document.
+ *
+ * @param allowModifications A boolean determining if the user can modify the document.
+ */
+ public void setCanModify( boolean allowModifications )
+ {
+ setPermissionBit( MODIFICATION_BIT, allowModifications );
+ }
+
+ /**
+ * This will tell if the user can extract text and images from the PDF document.
+ *
+ * @return true If supplied with the user password they are allowed to extract content
+ * from the PDF document
+ */
+ public boolean canExtractContent()
+ {
+ return isPermissionBitOn( EXTRACT_BIT );
+ }
+
+ /**
+ * Set if the user can extract content from the document.
+ *
+ * @param allowExtraction A boolean determining if the user can extract content
+ * from the document.
+ */
+ public void setCanExtractContent( boolean allowExtraction )
+ {
+ setPermissionBit( EXTRACT_BIT, allowExtraction );
+ }
+
+ /**
+ * This will tell if the user can add/modify text annotations, fill in interactive forms fields.
+ *
+ * @return true If supplied with the user password they are allowed to modify annotations.
+ */
+ public boolean canModifyAnnotations()
+ {
+ return isPermissionBitOn( MODIFY_ANNOTATIONS_BIT );
+ }
+
+ /**
+ * Set if the user can modify annotations.
+ *
+ * @param allowAnnotationModification A boolean determining if the user can modify annotations.
+ */
+ public void setCanModifyAnnotations( boolean allowAnnotationModification )
+ {
+ setPermissionBit( MODIFY_ANNOTATIONS_BIT, allowAnnotationModification );
+ }
+
+ /**
+ * This will tell if the user can fill in interactive forms.
+ *
+ * @return true If supplied with the user password they are allowed to fill in form fields.
+ */
+ public boolean canFillInForm()
+ {
+ return isPermissionBitOn( FILL_IN_FORM_BIT );
+ }
+
+ /**
+ * Set if the user can fill in interactive forms.
+ *
+ * @param allowFillingInForm A boolean determining if the user can fill in interactive forms.
+ */
+ public void setCanFillInForm( boolean allowFillingInForm )
+ {
+ setPermissionBit( FILL_IN_FORM_BIT, allowFillingInForm );
+ }
+
+ /**
+ * This will tell if the user can extract text and images from the PDF document
+ * for accessibility purposes.
+ *
+ * @return true If supplied with the user password they are allowed to extract content
+ * from the PDF document
+ */
+ public boolean canExtractForAccessibility()
+ {
+ return isPermissionBitOn( EXTRACT_FOR_ACCESSIBILITY_BIT );
+ }
+
+ /**
+ * Set if the user can extract content from the document for accessibility purposes.
+ *
+ * @param allowExtraction A boolean determining if the user can extract content
+ * from the document.
+ */
+ public void setCanExtractForAccessibility( boolean allowExtraction )
+ {
+ setPermissionBit( EXTRACT_FOR_ACCESSIBILITY_BIT, allowExtraction );
+ }
+
+ /**
+ * This will tell if the user can insert/rotate/delete pages.
+ *
+ * @return true If supplied with the user password they are allowed to extract content
+ * from the PDF document
+ */
+ public boolean canAssembleDocument()
+ {
+ return isPermissionBitOn( ASSEMBLE_DOCUMENT_BIT );
+ }
+
+ /**
+ * Set if the user can insert/rotate/delete pages.
+ *
+ * @param allowAssembly A boolean determining if the user can assemble the document.
+ */
+ public void setCanAssembleDocument( boolean allowAssembly )
+ {
+ setPermissionBit( ASSEMBLE_DOCUMENT_BIT, allowAssembly );
+ }
+
+ /**
+ * This will tell if the user can print the document in a degraded format.
+ *
+ * @return true If supplied with the user password they are allowed to print the
+ * document in a degraded format.
+ */
+ public boolean canPrintDegraded()
+ {
+ return isPermissionBitOn( DEGRADED_PRINT_BIT );
+ }
+
+ /**
+ * Set if the user can print the document in a degraded format.
+ *
+ * @param allowAssembly A boolean determining if the user can print the
+ * document in a degraded format.
+ */
+ public void setCanPrintDegraded( boolean allowAssembly )
+ {
+ setPermissionBit( DEGRADED_PRINT_BIT, allowAssembly );
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+/**
+ * This class represents the protection policy to apply to a document.
+ *
+ * Objects implementing this abstract class can be passed to the protect method of PDDocument
+ * to protect a document.
+ *
+ * @see org.apache.pdfbox.pdmodel.PDDocument#protect(ProtectionPolicy)
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.3 $
+ */
+public abstract class ProtectionPolicy
+{
+
+ private static final int DEFAULT_KEY_LENGTH = 40;
+
+ private int encryptionKeyLength = DEFAULT_KEY_LENGTH;
+
+ /**
+ * set the length in (bits) of the secret key that will be
+ * used to encrypt document data.
+ * The default value is 40 bits, which provides a low security level
+ * but is compatible with old versions of Acrobat Reader.
+ *
+ * @param l the length in bits (must be 40 or 128)
+ */
+ public void setEncryptionKeyLength(int l)
+ {
+ if(l!=40 && l!=128)
+ {
+ throw new RuntimeException("Invalid key length '" + l + "' value must be 40 or 128!");
+ }
+ encryptionKeyLength = l;
+ }
+
+ /**
+ * Get the length of the secrete key that will be used to encrypt
+ * document data.
+ *
+ * @return The length (in bits) of the encryption key.
+ */
+ public int getEncryptionKeyLength()
+ {
+ return encryptionKeyLength;
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+
+/**
+ * This class holds necessary information to decrypt a PDF document
+ * protected by the public key security handler.
+ *
+ * To decrypt such a document, we need:
+ * <ul>
+ * <li>a valid X509 certificate which correspond to one of the recipient of the document</li>
+ * <li>the private key corresponding to this certificate
+ * <li>the password to decrypt the private key if necessary</li>
+ * </ul>
+ *
+ * Objects of this class can be used with the <code>openProtection</code> method of <code>PDDocument</code>.
+ *
+ * The following example shows how to decrypt a document using a PKCS#12 certificate
+ * (typically files with a pfx extension).
+ *
+ * <pre>
+ * PDDocument doc = PDDocument.load(document_path);
+ * KeyStore ks = KeyStore.getInstance("PKCS12");
+ * ks.load(new FileInputStream(certificate_path), password.toCharArray());
+ * PublicKeyDecryptionMaterial dm = new PublicKeyDecryptionMaterial(ks, null, password);
+ * doc.openProtection(dm);
+ * </pre>
+ *
+ * In this code sample certificate_path contains the path to the PKCS#12 certificate.
+ *
+ * @see org.apache.pdfbox.pdmodel.PDDocument#openProtection(DecryptionMaterial)
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.2 $
+ */
+
+public class PublicKeyDecryptionMaterial extends DecryptionMaterial
+{
+ private String password = null;
+ private KeyStore keyStore = null;
+ private String alias = null;
+
+ /**
+ * Create a new public key decryption material.
+ *
+ * @param keystore The keystore were the private key and the certificate are
+ * @param a The alias of the private key and the certificate.
+ * If the keystore contains only 1 entry, this parameter can be left null.
+ * @param pwd The password to extract the private key from the keystore.
+ */
+
+ public PublicKeyDecryptionMaterial(KeyStore keystore, String a, String pwd)
+ {
+ keyStore = keystore;
+ alias = a;
+ password = pwd;
+ }
+
+
+ /**
+ * Returns the certificate contained in the keystore.
+ *
+ * @return The certificate that will be used to try to open the document.
+ *
+ * @throws KeyStoreException If there is an error accessing the certificate.
+ */
+
+ public X509Certificate getCertificate() throws KeyStoreException
+ {
+ if(keyStore.size() == 1)
+ {
+ Enumeration aliases = keyStore.aliases();
+ String keyStoreAlias = (String)aliases.nextElement();
+ return (X509Certificate)keyStore.getCertificate(keyStoreAlias);
+ }
+ else
+ {
+ if(keyStore.containsAlias(alias))
+ {
+ return (X509Certificate)keyStore.getCertificate(alias);
+ }
+ throw new KeyStoreException("the keystore does not contain the given alias");
+ }
+ }
+
+ /**
+ * Returns the password given by the user and that will be used
+ * to open the private key.
+ *
+ * @return The password.
+ */
+ public String getPassword()
+ {
+ return password;
+ }
+
+ /**
+ * returns The private key that will be used to open the document protection.
+ * @return The private key.
+ * @throws KeyStoreException If there is an error accessing the key.
+ */
+ public Key getPrivateKey() throws KeyStoreException
+ {
+ try
+ {
+ if(keyStore.size() == 1)
+ {
+ Enumeration aliases = keyStore.aliases();
+ String keyStoreAlias = (String)aliases.nextElement();
+ return keyStore.getKey(keyStoreAlias, password.toCharArray());
+ }
+ else
+ {
+ if(keyStore.containsAlias(alias))
+ {
+ return keyStore.getKey(alias, password.toCharArray());
+ }
+ throw new KeyStoreException("the keystore does not contain the given alias");
+ }
+ }
+ catch(UnrecoverableKeyException ex)
+ {
+ throw new KeyStoreException("the private key is not recoverable");
+ }
+ catch(NoSuchAlgorithmException ex)
+ {
+ throw new KeyStoreException("the algorithm necessary to recover the key is not available");
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * This class represents the protection policy to use to protect
+ * a document with the public key security handler as described
+ * in the PDF specification 1.6 p104.
+ *
+ * PDF documents are encrypted so that they can be decrypted by
+ * one or more recipients. Each recipient have its own access permission.
+ *
+ * The following code sample shows how to protect a document using
+ * the public key security handler. In this code sample, <code>doc</code> is
+ * a <code>PDDocument</code> object.
+ *
+ * <pre>
+ * PublicKeyProtectionPolicy policy = new PublicKeyProtectionPolicy();
+ * PublicKeyRecipient recip = new PublicKeyRecipient();
+ * AccessPermission ap = new AccessPermission();
+ * ap.setCanModify(false);
+ * recip.setPermission(ap);
+ *
+ * // load the recipient's certificate
+ * InputStream inStream = new FileInputStream(certificate_path);
+ * CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ * X509Certificate certificate = (X509Certificate)cf.generateCertificate(inStream);
+ * inStream.close();
+ *
+ * recip.setX509(certificate); // set the recipient's certificate
+ * policy.addRecipient(recip);
+ * policy.setEncryptionKeyLength(128); // the document will be encrypted with 128 bits secret key
+ * doc.protect(policy);
+ * doc.save(out);
+ * </pre>
+ *
+ *
+ * @see org.apache.pdfbox.pdmodel.PDDocument#protect(ProtectionPolicy)
+ * @see AccessPermission
+ * @see PublicKeyRecipient
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.2 $
+ */
+public class PublicKeyProtectionPolicy extends ProtectionPolicy
+{
+
+ /**
+ * The list of recipients.
+ */
+ private ArrayList recipients = null;
+
+ /**
+ * The X509 certificate used to decrypt the current document.
+ */
+ private X509Certificate decryptionCertificate;
+
+ /**
+ * Constructor for encryption. Just creates an empty recipients list.
+ */
+ public PublicKeyProtectionPolicy()
+ {
+ recipients = new ArrayList();
+ }
+
+ /**
+ * Adds a new recipient to the recipients list.
+ *
+ * @param r A new recipient.
+ */
+ public void addRecipient(PublicKeyRecipient r)
+ {
+ recipients.add(r);
+ }
+
+ /**
+ * Removes a recipient from the recipients list.
+ *
+ * @param r The recipient to remove.
+ *
+ * @return true If a recipient was found and removed.
+ */
+ public boolean removeRecipient(PublicKeyRecipient r)
+ {
+ return recipients.remove(r);
+ }
+
+ /**
+ * Returns an iterator to browse the list of recipients. Object
+ * found in this iterator are <code>PublicKeyRecipient</code>.
+ *
+ * @return The recipients list iterator.
+ */
+ public Iterator getRecipientsIterator()
+ {
+ return recipients.iterator();
+ }
+
+ /**
+ * Getter of the property <tt>decryptionCertificate</tt>.
+ *
+ * @return Returns the decryptionCertificate.
+ */
+ public X509Certificate getDecryptionCertificate()
+ {
+ return decryptionCertificate;
+ }
+
+ /**
+ * Setter of the property <tt>decryptionCertificate</tt>.
+ *
+ * @param aDecryptionCertificate The decryption certificate to set.
+ */
+ public void setDecryptionCertificate(X509Certificate aDecryptionCertificate)
+ {
+ this.decryptionCertificate = aDecryptionCertificate;
+ }
+
+ /**
+ * Returns the number of recipients.
+ *
+ * @return The number of recipients.
+ */
+ public int getRecipientsNumber()
+ {
+ return recipients.size();
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * Represents a recipient in the public key protection policy.
+ *
+ * @see PublicKeyProtectionPolicy
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.2 $
+ */
+public class PublicKeyRecipient
+{
+ private X509Certificate x509;
+
+ private AccessPermission permission;
+
+ /**
+ * Returns the X509 certificate of the recipient.
+ *
+ * @return The X509 certificate
+ */
+ public X509Certificate getX509()
+ {
+ return x509;
+ }
+
+ /**
+ * Set the X509 certificate of the recipient.
+ *
+ * @param aX509 The X509 certificate
+ */
+ public void setX509(X509Certificate aX509)
+ {
+ this.x509 = aX509;
+ }
+
+ /**
+ * Returns the access permission granted to the recipient.
+ *
+ * @return The access permission object.
+ */
+ public AccessPermission getPermission()
+ {
+ return permission;
+ }
+
+ /**
+ * Set the access permission granted to the recipient.
+ *
+ * @param permissions The permission to set.
+ */
+ public void setPermission(AccessPermission permissions)
+ {
+ this.permission = permissions;
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.Iterator;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DERObject;
+import org.bouncycastle.asn1.DERObjectIdentifier;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.ContentInfo;
+import org.bouncycastle.asn1.cms.EncryptedContentInfo;
+import org.bouncycastle.asn1.cms.EnvelopedData;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
+import org.bouncycastle.asn1.cms.RecipientIdentifier;
+import org.bouncycastle.asn1.cms.RecipientInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.TBSCertificateStructure;
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * This class implements the public key security handler
+ * described in the PDF specification.
+ *
+ * @see PDF Spec 1.6 p104
+ *
+ * @see PublicKeyProtectionPolicy to see how to protect document with this security handler.
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.3 $
+ */
+public class PublicKeySecurityHandler extends SecurityHandler
+{
+
+ /**
+ * The filter name.
+ */
+ public static final String FILTER = "Adobe.PubSec";
+
+ private static final String SUBFILTER = "adbe.pkcs7.s4";
+
+ private PublicKeyProtectionPolicy policy = null;
+
+ /**
+ * Constructor.
+ */
+ public PublicKeySecurityHandler()
+ {
+ }
+
+ /**
+ * Constructor used for encryption.
+ *
+ * @param p The protection policy.
+ */
+ public PublicKeySecurityHandler(PublicKeyProtectionPolicy p)
+ {
+ policy = p;
+ this.keyLength = policy.getEncryptionKeyLength();
+ }
+
+ /**
+ * Decrypt the document.
+ *
+ * @param doc The document to decrypt.
+ * @param decryptionMaterial The data used to decrypt the document.
+ *
+ * @throws CryptographyException If there is an error during decryption.
+ * @throws IOException If there is an error accessing data.
+ */
+ public void decryptDocument(PDDocument doc, DecryptionMaterial decryptionMaterial)
+ throws CryptographyException, IOException
+ {
+ this.document = doc;
+
+ PDEncryptionDictionary dictionary = doc.getEncryptionDictionary();
+
+ if(dictionary.getLength() != 0)
+ {
+ this.keyLength = dictionary.getLength();
+ }
+
+ if(!(decryptionMaterial instanceof PublicKeyDecryptionMaterial))
+ {
+ throw new CryptographyException(
+ "Provided decryption material is not compatible with the document");
+ }
+
+ PublicKeyDecryptionMaterial material = (PublicKeyDecryptionMaterial)decryptionMaterial;
+
+ try
+ {
+ boolean foundRecipient = false;
+
+ // the decrypted content of the enveloped data that match
+ // the certificate in the decryption material provided
+ byte[] envelopedData = null;
+
+ // the bytes of each recipient in the recipients array
+ byte[][] recipientFieldsBytes = new byte[dictionary.getRecipientsLength()][];
+
+ int recipientFieldsLength = 0;
+
+ for(int i=0; i<dictionary.getRecipientsLength(); i++)
+ {
+ COSString recipientFieldString = dictionary.getRecipientStringAt(i);
+ byte[] recipientBytes = recipientFieldString.getBytes();
+ CMSEnvelopedData data = new CMSEnvelopedData(recipientBytes);
+ Iterator recipCertificatesIt = data.getRecipientInfos().getRecipients().iterator();
+ while(recipCertificatesIt.hasNext())
+ {
+ RecipientInformation ri =
+ (RecipientInformation)recipCertificatesIt.next();
+ // Impl: if a matching certificate was previously found it is an error,
+ // here we just don't care about it
+ if(ri.getRID().match(material.getCertificate()) && !foundRecipient)
+ {
+ foundRecipient = true;
+ envelopedData = ri.getContent(material.getPrivateKey(), "BC");
+ }
+ }
+ recipientFieldsBytes[i] = recipientBytes;
+ recipientFieldsLength += recipientBytes.length;
+ }
+ if(!foundRecipient || envelopedData == null)
+ {
+ throw new CryptographyException("The certificate matches no recipient entry");
+ }
+ if(envelopedData.length != 24)
+ {
+ throw new CryptographyException("The enveloped data does not contain 24 bytes");
+ }
+ // now envelopedData contains:
+ // - the 20 bytes seed
+ // - the 4 bytes of permission for the current user
+
+ byte[] accessBytes = new byte[4];
+ System.arraycopy(envelopedData, 20, accessBytes, 0, 4);
+
+ currentAccessPermission = new AccessPermission(accessBytes);
+ currentAccessPermission.setReadOnly();
+
+ // what we will put in the SHA1 = the seed + each byte contained in the recipients array
+ byte[] sha1Input = new byte[recipientFieldsLength + 20];
+
+ // put the seed in the sha1 input
+ System.arraycopy(envelopedData, 0, sha1Input, 0, 20);
+
+ // put each bytes of the recipients array in the sha1 input
+ int sha1InputOffset = 20;
+ for(int i=0; i<recipientFieldsBytes.length; i++)
+ {
+ System.arraycopy(
+ recipientFieldsBytes[i], 0,
+ sha1Input, sha1InputOffset, recipientFieldsBytes[i].length);
+ sha1InputOffset += recipientFieldsBytes[i].length;
+ }
+
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] mdResult = md.digest(sha1Input);
+
+ // we have the encryption key ...
+ encryptionKey = new byte[this.keyLength/8];
+ System.arraycopy(mdResult, 0, encryptionKey, 0, this.keyLength/8);
+
+ proceedDecryption();
+
+
+ }
+ catch(CMSException e)
+ {
+ throw new CryptographyException(e);
+ }
+ catch(KeyStoreException e)
+ {
+ throw new CryptographyException(e);
+ }
+ catch(NoSuchProviderException e)
+ {
+ throw new CryptographyException(e);
+ }
+ catch(NoSuchAlgorithmException e)
+ {
+ throw new CryptographyException(e);
+ }
+
+ }
+
+ /**
+ * Prepare the document for encryption.
+ *
+ * @param doc The document that will be encrypted.
+ *
+ * @throws CryptographyException If there is an error while encrypting.
+ */
+ public void prepareDocumentForEncryption(PDDocument doc) throws CryptographyException
+ {
+
+ try
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ PDEncryptionDictionary dictionary = doc.getEncryptionDictionary();
+ if (dictionary == null)
+ {
+ dictionary = new PDEncryptionDictionary();
+ }
+
+ dictionary.setFilter(FILTER);
+ dictionary.setLength(this.keyLength);
+ dictionary.setVersion(2);
+ dictionary.setSubFilter(SUBFILTER);
+
+ byte[][] recipientsField = new byte[policy.getRecipientsNumber()][];
+
+ // create the 20 bytes seed
+
+ byte[] seed = new byte[20];
+
+ KeyGenerator key = KeyGenerator.getInstance("AES");
+ key.init(192, new SecureRandom());
+ SecretKey sk = key.generateKey();
+ System.arraycopy(sk.getEncoded(), 0, seed, 0, 20); // create the 20 bytes seed
+
+
+ Iterator it = policy.getRecipientsIterator();
+ int i = 0;
+
+
+ while(it.hasNext())
+ {
+ PublicKeyRecipient recipient = (PublicKeyRecipient)it.next();
+ X509Certificate certificate = recipient.getX509();
+ int permission = recipient.getPermission().getPermissionBytesForPublicKey();
+
+ byte[] pkcs7input = new byte[24];
+ byte one = (byte)(permission);
+ byte two = (byte)(permission >>> 8);
+ byte three = (byte)(permission >>> 16);
+ byte four = (byte)(permission >>> 24);
+
+ System.arraycopy(seed, 0, pkcs7input, 0, 20); // put this seed in the pkcs7 input
+
+ pkcs7input[20] = four;
+ pkcs7input[21] = three;
+ pkcs7input[22] = two;
+ pkcs7input[23] = one;
+
+ DERObject obj = createDERForRecipient(pkcs7input, certificate);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ DEROutputStream k = new DEROutputStream(baos);
+
+ k.writeObject(obj);
+
+ recipientsField[i] = baos.toByteArray();
+
+ i++;
+ }
+
+ dictionary.setRecipients(recipientsField);
+
+ int sha1InputLength = seed.length;
+
+ for(int j=0; j<dictionary.getRecipientsLength(); j++)
+ {
+ COSString string = dictionary.getRecipientStringAt(j);
+ sha1InputLength += string.getBytes().length;
+ }
+
+
+ byte[] sha1Input = new byte[sha1InputLength];
+
+ System.arraycopy(seed, 0, sha1Input, 0, 20);
+
+ int sha1InputOffset = 20;
+
+
+ for(int j=0; j<dictionary.getRecipientsLength(); j++)
+ {
+ COSString string = dictionary.getRecipientStringAt(j);
+ System.arraycopy(
+ string.getBytes(), 0,
+ sha1Input, sha1InputOffset, string.getBytes().length);
+ sha1InputOffset += string.getBytes().length;
+ }
+
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+
+ byte[] mdResult = md.digest(sha1Input);
+
+ this.encryptionKey = new byte[this.keyLength/8];
+ System.arraycopy(mdResult, 0, this.encryptionKey, 0, this.keyLength/8);
+
+ doc.setEncryptionDictionary(dictionary);
+ doc.getDocument().setEncryptionDictionary(dictionary.encryptionDictionary);
+
+ }
+ catch(NoSuchAlgorithmException ex)
+ {
+ throw new CryptographyException(ex);
+ }
+ catch(NoSuchProviderException ex)
+ {
+ throw new CryptographyException(ex);
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ throw new CryptographyException(e);
+ }
+
+ }
+
+ private DERObject createDERForRecipient(byte[] in, X509Certificate cert)
+ throws IOException,
+ GeneralSecurityException
+ {
+
+ String s = "1.2.840.113549.3.2";
+
+ AlgorithmParameterGenerator algorithmparametergenerator = AlgorithmParameterGenerator.getInstance(s);
+ AlgorithmParameters algorithmparameters = algorithmparametergenerator.generateParameters();
+ ByteArrayInputStream bytearrayinputstream = new ByteArrayInputStream(algorithmparameters.getEncoded("ASN.1"));
+ ASN1InputStream asn1inputstream = new ASN1InputStream(bytearrayinputstream);
+ DERObject derobject = asn1inputstream.readObject();
+ KeyGenerator keygenerator = KeyGenerator.getInstance(s);
+ keygenerator.init(128);
+ SecretKey secretkey = keygenerator.generateKey();
+ Cipher cipher = Cipher.getInstance(s);
+ cipher.init(1, secretkey, algorithmparameters);
+ byte[] abyte1 = cipher.doFinal(in);
+ DEROctetString deroctetstring = new DEROctetString(abyte1);
+ KeyTransRecipientInfo keytransrecipientinfo = computeRecipientInfo(cert, secretkey.getEncoded());
+ DERSet derset = new DERSet(new RecipientInfo(keytransrecipientinfo));
+ AlgorithmIdentifier algorithmidentifier = new AlgorithmIdentifier(new DERObjectIdentifier(s), derobject);
+ EncryptedContentInfo encryptedcontentinfo =
+ new EncryptedContentInfo(PKCSObjectIdentifiers.data, algorithmidentifier, deroctetstring);
+ EnvelopedData env = new EnvelopedData(null, derset, encryptedcontentinfo, null);
+ ContentInfo contentinfo =
+ new ContentInfo(PKCSObjectIdentifiers.envelopedData, env);
+ return contentinfo.getDERObject();
+ }
+
+ private KeyTransRecipientInfo computeRecipientInfo(X509Certificate x509certificate, byte[] abyte0)
+ throws GeneralSecurityException, IOException
+ {
+ ASN1InputStream asn1inputstream =
+ new ASN1InputStream(new ByteArrayInputStream(x509certificate.getTBSCertificate()));
+ TBSCertificateStructure tbscertificatestructure =
+ TBSCertificateStructure.getInstance(asn1inputstream.readObject());
+ AlgorithmIdentifier algorithmidentifier = tbscertificatestructure.getSubjectPublicKeyInfo().getAlgorithmId();
+ IssuerAndSerialNumber issuerandserialnumber =
+ new IssuerAndSerialNumber(
+ tbscertificatestructure.getIssuer(),
+ tbscertificatestructure.getSerialNumber().getValue());
+ Cipher cipher = Cipher.getInstance(algorithmidentifier.getObjectId().getId());
+ cipher.init(1, x509certificate.getPublicKey());
+ DEROctetString deroctetstring = new DEROctetString(cipher.doFinal(abyte0));
+ RecipientIdentifier recipId = new RecipientIdentifier(issuerandserialnumber);
+ return new KeyTransRecipientInfo( recipId, algorithmidentifier, deroctetstring);
+ }
+
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+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.cos.COSString;
+import org.apache.pdfbox.encryption.ARCFour;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * This class represents a security handler as described in the PDF specifications.
+ * A security handler is responsible of documents protection.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.4 $
+ */
+
+public abstract class SecurityHandler
+{
+
+ /* ------------------------------------------------
+ * CONSTANTS
+ -------------------------------------------------- */
+
+ private static final int DEFAULT_KEY_LENGTH = 40;
+
+ /*
+ * See 7.6.2, page 58, PDF 32000-1:2008
+ */
+ private final static byte[] AES_SALT = {(byte) 0x73, (byte) 0x41, (byte) 0x6c, (byte) 0x54};
+
+ /**
+ * The value of V field of the Encryption dictionary.
+ */
+ protected int version;
+
+ /**
+ * The length of the secret key used to encrypt the document.
+ */
+ protected int keyLength = DEFAULT_KEY_LENGTH;
+
+ /**
+ * The encryption key that will used to encrypt / decrypt.
+ */
+ protected byte[] encryptionKey;
+
+ /**
+ * The document whose security is handled by this security handler.
+ */
+
+ protected PDDocument document;
+
+ /**
+ * The RC4 implementation used for cryptographic functions.
+ */
+ protected ARCFour rc4 = new ARCFour();
+
+ private Set<COSBase> objects = new HashSet<COSBase>();
+
+ private Set<COSDictionary> potentialSignatures = new HashSet<COSDictionary>();
+
+ /*
+ * If true, AES will be used
+ */
+ private boolean aes;
+
+ /**
+ * The access permission granted to the current user for the document. These
+ * permissions are computed during decryption and are in read only mode.
+ */
+
+ protected AccessPermission currentAccessPermission = null;
+
+ /**
+ * Prepare the document for encryption.
+ *
+ * @param doc The document that will be encrypted.
+ *
+ * @throws CryptographyException If there is an error while preparing.
+ * @throws IOException If there is an error with the document.
+ */
+ public abstract void prepareDocumentForEncryption(PDDocument doc) throws CryptographyException, IOException;
+
+ /**
+ * Prepare the document for decryption.
+ *
+ * @param doc The document to decrypt.
+ * @param mat Information required to decrypt the document.
+ * @throws CryptographyException If there is an error while preparing.
+ * @throws IOException If there is an error with the document.
+ */
+ public abstract void decryptDocument(PDDocument doc, DecryptionMaterial mat)
+ throws CryptographyException, IOException;
+
+
+ /**
+ * This method must be called by an implementation of this class to really proceed
+ * to decryption.
+ *
+ * @throws IOException If there is an error in the decryption.
+ * @throws CryptographyException If there is an error in the decryption.
+ */
+ protected void proceedDecryption() throws IOException, CryptographyException
+ {
+
+ COSDictionary trailer = document.getDocument().getTrailer();
+ COSArray fields = (COSArray)trailer.getObjectFromPath( "Root/AcroForm/Fields" );
+
+ //We need to collect all the signature dictionaries, for some
+ //reason the 'Contents' entry of signatures is not really encrypted
+ if( fields != null )
+ {
+ for( int i=0; i<fields.size(); i++ )
+ {
+ COSDictionary field = (COSDictionary)fields.getObject( i );
+ if (field!= null)
+ {
+ addDictionaryAndSubDictionary( potentialSignatures, field );
+ }
+ else
+ {
+ throw new IOException("Could not decypt document, object not found.");
+ }
+ }
+ }
+
+ List<COSObject> allObjects = document.getDocument().getObjects();
+ Iterator<COSObject> objectIter = allObjects.iterator();
+ while( objectIter.hasNext() )
+ {
+ decryptObject( objectIter.next() );
+ }
+ document.setEncryptionDictionary( null );
+ }
+
+ private void addDictionaryAndSubDictionary( Set<COSDictionary> set, COSDictionary dic )
+ {
+ set.add( dic );
+ COSArray kids = (COSArray)dic.getDictionaryObject( COSName.KIDS );
+ for( int i=0; kids != null && i<kids.size(); i++ )
+ {
+ addDictionaryAndSubDictionary( set, (COSDictionary)kids.getObject( i ) );
+ }
+ COSBase value = dic.getDictionaryObject( COSName.V );
+ if( value instanceof COSDictionary )
+ {
+ addDictionaryAndSubDictionary( set, (COSDictionary)value );
+ }
+ }
+
+ /**
+ * Encrypt a set of data.
+ *
+ * @param objectNumber The data object number.
+ * @param genNumber The data generation number.
+ * @param data The data to encrypt.
+ * @param output The output to write the encrypted data to.
+ * @throws CryptographyException If there is an error during the encryption.
+ * @throws IOException If there is an error reading the data.
+ * @deprecated While this works fine for RC4 encryption, it will never decrypt AES data
+ * You should use encryptData(objectNumber, genNumber, data, output, decrypt)
+ * which can do everything. This function is just here for compatibility
+ * reasons and will be removed in the future.
+ */
+ public void encryptData(long objectNumber, long genNumber, InputStream data, OutputStream output)
+ throws CryptographyException, IOException
+ {
+ // default to encrypting since the function is named "encryptData"
+ encryptData(objectNumber, genNumber, data, output, false);
+ }
+
+ /**
+ * Encrypt a set of data.
+ *
+ * @param objectNumber The data object number.
+ * @param genNumber The data generation number.
+ * @param data The data to encrypt.
+ * @param output The output to write the encrypted data to.
+ * @param decrypt true to decrypt the data, false to encrypt it
+ *
+ * @throws CryptographyException If there is an error during the encryption.
+ * @throws IOException If there is an error reading the data.
+ */
+ public void encryptData(long objectNumber, long genNumber, InputStream data, OutputStream output, boolean decrypt)
+ throws CryptographyException, IOException
+ {
+ if (aes && !decrypt) {
+ throw new IllegalArgumentException("AES encryption is not yet implemented.");
+ }
+
+ byte[] newKey = new byte[ encryptionKey.length + 5 ];
+ System.arraycopy( encryptionKey, 0, newKey, 0, encryptionKey.length );
+ //PDF 1.4 reference pg 73
+ //step 1
+ //we have the reference
+
+ //step 2
+ newKey[newKey.length -5] = (byte)(objectNumber & 0xff);
+ newKey[newKey.length -4] = (byte)((objectNumber >> 8) & 0xff);
+ newKey[newKey.length -3] = (byte)((objectNumber >> 16) & 0xff);
+ newKey[newKey.length -2] = (byte)(genNumber & 0xff);
+ newKey[newKey.length -1] = (byte)((genNumber >> 8) & 0xff);
+
+
+ //step 3
+ byte[] digestedKey = null;
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance( "MD5" );
+ md.update(newKey);
+ if (aes) {
+ md.update(AES_SALT);
+ }
+ digestedKey = md.digest();
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e );
+ }
+
+ //step 4
+ int length = Math.min( newKey.length, 16 );
+ byte[] finalKey = new byte[ length ];
+ System.arraycopy( digestedKey, 0, finalKey, 0, length );
+
+ if (aes)
+ {
+ byte[] iv = new byte[16];
+
+ data.read(iv);
+
+ try {
+ Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+
+ SecretKey aesKey = new SecretKeySpec(finalKey, "AES");
+
+ IvParameterSpec ips = new IvParameterSpec(iv);
+
+ decryptCipher.init(decrypt ? Cipher.DECRYPT_MODE : Cipher.ENCRYPT_MODE, aesKey, ips);
+
+ CipherInputStream cipherStream = new CipherInputStream(data, decryptCipher);
+
+ try {
+ byte buffer[] = new byte[4096];
+ long count = 0L;
+ for(int n = 0; -1 != (n = cipherStream.read(buffer));)
+ {
+ output.write(buffer, 0, n);
+ count += n;
+ }
+ }
+ finally {
+ cipherStream.close();
+ }
+ }
+ catch (InvalidKeyException e) {
+ throw new WrappedIOException(e);
+ }
+ catch (InvalidAlgorithmParameterException e) {
+ throw new WrappedIOException(e);
+ }
+ catch (NoSuchAlgorithmException e) {
+ throw new WrappedIOException(e);
+ }
+ catch (NoSuchPaddingException e) {
+ throw new WrappedIOException(e);
+ }
+ }
+ else {
+ rc4.setKey( finalKey );
+ rc4.write( data, output );
+ }
+
+ output.flush();
+ }
+
+ /**
+ * This will decrypt an object in the document.
+ *
+ * @param object The object to decrypt.
+ *
+ * @throws CryptographyException If there is an error decrypting the stream.
+ * @throws IOException If there is an error getting the stream data.
+ */
+ private void decryptObject( COSObject object )
+ throws CryptographyException, IOException
+ {
+ long objNum = object.getObjectNumber().intValue();
+ long genNum = object.getGenerationNumber().intValue();
+ COSBase base = object.getObject();
+ decrypt( base, objNum, genNum );
+ }
+
+ /**
+ * This will dispatch to the correct method.
+ *
+ * @param obj The object to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation Number.
+ *
+ * @throws CryptographyException If there is an error decrypting the stream.
+ * @throws IOException If there is an error getting the stream data.
+ */
+ private void decrypt( COSBase obj, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ if( !objects.contains( obj ) )
+ {
+ objects.add( obj );
+
+ if( obj instanceof COSString )
+ {
+ decryptString( (COSString)obj, objNum, genNum );
+ }
+ else if( obj instanceof COSStream )
+ {
+ decryptStream( (COSStream)obj, objNum, genNum );
+ }
+ else if( obj instanceof COSDictionary )
+ {
+ decryptDictionary( (COSDictionary)obj, objNum, genNum );
+ }
+ else if( obj instanceof COSArray )
+ {
+ decryptArray( (COSArray)obj, objNum, genNum );
+ }
+ }
+ }
+
+ /**
+ * This will decrypt a stream.
+ *
+ * @param stream The stream to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation number.
+ *
+ * @throws CryptographyException If there is an error getting the stream.
+ * @throws IOException If there is an error getting the stream data.
+ */
+ public void decryptStream( COSStream stream, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ decryptDictionary( stream, objNum, genNum );
+ InputStream encryptedStream = stream.getFilteredStream();
+ encryptData( objNum,
+ genNum,
+ encryptedStream,
+ stream.createFilteredStream(),
+ true /* decrypt */);
+ }
+
+ /**
+ * This will encrypt a stream, but not the dictionary as the dictionary is
+ * encrypted by visitFromString() in COSWriter and we don't want to encrypt
+ * it twice.
+ *
+ * @param stream The stream to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation number.
+ *
+ * @throws CryptographyException If there is an error getting the stream.
+ * @throws IOException If there is an error getting the stream data.
+ */
+ public void encryptStream( COSStream stream, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ InputStream encryptedStream = stream.getFilteredStream();
+ encryptData( objNum,
+ genNum,
+ encryptedStream,
+ stream.createFilteredStream(),
+ false /* encrypt */);
+ }
+
+ /**
+ * This will decrypt a dictionary.
+ *
+ * @param dictionary The dictionary to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation number.
+ *
+ * @throws CryptographyException If there is an error decrypting the document.
+ * @throws IOException If there is an error creating a new string.
+ */
+ private void decryptDictionary( COSDictionary dictionary, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ for( Map.Entry<COSName, COSBase> entry : dictionary.entrySet() )
+ {
+ //if we are a signature dictionary and contain a Contents entry then
+ //we don't decrypt it.
+ if( !(entry.getKey().getName().equals( "Contents" ) &&
+ entry.getValue() instanceof COSString &&
+ potentialSignatures.contains( dictionary )))
+ {
+ decrypt( entry.getValue(), objNum, genNum );
+ }
+ }
+ }
+
+ /**
+ * This will decrypt a string.
+ *
+ * @param string the string to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation number.
+ *
+ * @throws CryptographyException If an error occurs during decryption.
+ * @throws IOException If an error occurs writing the new string.
+ */
+ public void decryptString( COSString string, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ ByteArrayInputStream data = new ByteArrayInputStream( string.getBytes() );
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ encryptData( objNum, genNum, data, buffer, true /* decrypt */ );
+ string.reset();
+ string.append( buffer.toByteArray() );
+ }
+
+ /**
+ * This will decrypt an array.
+ *
+ * @param array The array to decrypt.
+ * @param objNum The object number.
+ * @param genNum The object generation number.
+ *
+ * @throws CryptographyException If an error occurs during decryption.
+ * @throws IOException If there is an error accessing the data.
+ */
+ private void decryptArray( COSArray array, long objNum, long genNum )
+ throws CryptographyException, IOException
+ {
+ for( int i=0; i<array.size(); i++ )
+ {
+ decrypt( array.get( i ), objNum, genNum );
+ }
+ }
+
+ /**
+ * Getter of the property <tt>keyLength</tt>.
+ * @return Returns the keyLength.
+ * @uml.property name="keyLength"
+ */
+ public int getKeyLength()
+ {
+ return keyLength;
+ }
+
+ /**
+ * Setter of the property <tt>keyLength</tt>.
+ *
+ * @param keyLen The keyLength to set.
+ */
+ public void setKeyLength(int keyLen)
+ {
+ this.keyLength = keyLen;
+ }
+
+ /**
+ * Returns the access permissions that were computed during document decryption.
+ * The returned object is in read only mode.
+ *
+ * @return the access permissions or null if the document was not decrypted.
+ */
+ public AccessPermission getCurrentAccessPermission()
+ {
+ return currentAccessPermission;
+ }
+
+ /*
+ * True if AES is used for encryption and decryption.
+ */
+ public boolean isAES() {
+ return aes;
+ }
+
+ /*
+ * Set to true if AES for encryption and decryption should be used.
+ */
+ public void setAES(boolean aes) {
+ this.aes = aes;
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.lang.reflect.Constructor;
+import java.security.Security;
+import java.util.Hashtable;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * This class manages security handlers for the application. It follows the singleton pattern.
+ * To be usable, security managers must be registered in it. Security managers are retrieved by
+ * the application when necessary.
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.3 $
+ *
+ */
+public class SecurityHandlersManager
+{
+
+ /**
+ * The unique instance of this manager.
+ */
+ private static SecurityHandlersManager instance;
+
+ /**
+ * hashtable used to index handlers regarding their name.
+ * Basically this will be used when opening an encrypted
+ * document to find the appropriate security handler to handle
+ * security features of the document.
+ */
+ private Hashtable handlerNames = null;
+
+ /**
+ * Hashtable used to index handlers regarding the class of
+ * protection policy they use. Basically this will be used when
+ * encrypting a document.
+ */
+ private Hashtable handlerPolicyClasses = null;
+
+ /**
+ * private constructor.
+ */
+ private SecurityHandlersManager()
+ {
+ handlerNames = new Hashtable();
+ handlerPolicyClasses = new Hashtable();
+ try
+ {
+ this.registerHandler(
+ StandardSecurityHandler.FILTER,
+ StandardSecurityHandler.class,
+ StandardProtectionPolicy.class);
+ this.registerHandler(
+ PublicKeySecurityHandler.FILTER,
+ PublicKeySecurityHandler.class,
+ PublicKeyProtectionPolicy.class);
+ }
+ catch(Exception e)
+ {
+ System.err.println("SecurityHandlersManager strange error with builtin handlers: " + e.getMessage());
+ System.exit(1);
+ }
+ }
+
+ /**
+ * register a security handler.
+ *
+ * If the security handler was already registered an exception is thrown.
+ * If another handler was previously registered for the same filter name or
+ * for the same policy name, an exception is thrown
+ *
+ * @param filterName The name of the filter.
+ * @param securityHandlerClass Security Handler class to register.
+ * @param protectionPolicyClass Protection Policy class to register.
+ *
+ * @throws BadSecurityHandlerException If there is an error registering the security handler.
+ */
+ public void registerHandler(String filterName, Class securityHandlerClass, Class protectionPolicyClass)
+ throws BadSecurityHandlerException
+ {
+ if(handlerNames.contains(securityHandlerClass) || handlerPolicyClasses.contains(securityHandlerClass))
+ {
+ throw new BadSecurityHandlerException("the following security handler was already registered: " +
+ securityHandlerClass.getName());
+ }
+
+ if(SecurityHandler.class.isAssignableFrom(securityHandlerClass))
+ {
+ try
+ {
+ if(handlerNames.containsKey(filterName))
+ {
+ throw new BadSecurityHandlerException("a security handler was already registered " +
+ "for the filter name " + filterName);
+ }
+ if(handlerPolicyClasses.containsKey(protectionPolicyClass))
+ {
+ throw new BadSecurityHandlerException("a security handler was already registered " +
+ "for the policy class " + protectionPolicyClass.getName());
+ }
+
+ handlerNames.put(filterName, securityHandlerClass);
+ handlerPolicyClasses.put(protectionPolicyClass, securityHandlerClass);
+ }
+ catch(Exception e)
+ {
+ throw new BadSecurityHandlerException(e);
+ }
+ }
+ else
+ {
+ throw new BadSecurityHandlerException("The class is not a super class of SecurityHandler");
+ }
+ }
+
+
+ /**
+ * Get the singleton instance.
+ *
+ * @return The SecurityHandlersManager.
+ */
+ public static SecurityHandlersManager getInstance()
+ {
+ if(instance == null)
+ {
+ instance = new SecurityHandlersManager();
+ }
+ Security.addProvider(new BouncyCastleProvider());
+
+ return instance;
+ }
+
+ /**
+ * Get the security handler for the protection policy.
+ *
+ * @param policy The policy to get the security handler for.
+ *
+ * @return The appropriate security handler.
+ *
+ * @throws BadSecurityHandlerException If it is unable to create a SecurityHandler.
+ */
+ public SecurityHandler getSecurityHandler(ProtectionPolicy policy) throws BadSecurityHandlerException
+ {
+
+ Object found = handlerPolicyClasses.get(policy.getClass());
+ if(found == null)
+ {
+ throw new BadSecurityHandlerException(
+ "Cannot find an appropriate security handler for " + policy.getClass().getName());
+ }
+ Class handlerclass = (Class) found;
+ Class[] argsClasses = {policy.getClass()};
+ Object[] args = {policy};
+ try
+ {
+ Constructor c = handlerclass.getDeclaredConstructor(argsClasses);
+ SecurityHandler handler = (SecurityHandler)c.newInstance(args);
+ return handler;
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ throw new BadSecurityHandlerException(
+ "problem while trying to instanciate the security handler "+
+ handlerclass.getName() + ": " + e.getMessage());
+ }
+ }
+
+
+
+ /**
+ * Retrieve the appropriate SecurityHandler for a the given filter name.
+ * The filter name is an entry of the encryption dictionary of an encrypted document.
+ *
+ * @param filterName The filter name.
+ *
+ * @return The appropriate SecurityHandler if it exists.
+ *
+ * @throws BadSecurityHandlerException If the security handler does not exist.
+ */
+ public SecurityHandler getSecurityHandler(String filterName) throws BadSecurityHandlerException
+ {
+ Object found = handlerNames.get(filterName);
+ if(found == null)
+ {
+ throw new BadSecurityHandlerException("Cannot find an appropriate security handler for " + filterName);
+ }
+ Class handlerclass = (Class) found;
+ Class[] argsClasses = {};
+ Object[] args = {};
+ try
+ {
+ Constructor c = handlerclass.getDeclaredConstructor(argsClasses);
+ SecurityHandler handler = (SecurityHandler)c.newInstance(args);
+ return handler;
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ throw new BadSecurityHandlerException(
+ "problem while trying to instanciate the security handler "+
+ handlerclass.getName() + ": " + e.getMessage());
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+
+/**
+ *
+ * Represents the necessary information to decrypt a document protected by
+ * the standard security handler (password protection).
+ *
+ * This is only composed of a password.
+ *
+ * The following example shows how to decrypt a document protected with
+ * the standard security handler:
+ *
+ * <pre>
+ * PDDocument doc = PDDocument.load(in);
+ * StandardDecryptionMaterial dm = new StandardDecryptionMaterial("password");
+ * doc.openProtection(dm);
+ * </pre>
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.2 $
+ */
+
+public class StandardDecryptionMaterial extends DecryptionMaterial
+{
+
+ private String password = null;
+
+ /**
+ * Create a new standard decryption material with the given password.
+ *
+ * @param pwd The password.
+ */
+ public StandardDecryptionMaterial(String pwd)
+ {
+ password = pwd;
+ }
+
+ /**
+ * Returns the password.
+ *
+ * @return The password used to decrypt the document.
+ */
+ public String getPassword()
+ {
+ return password;
+ }
+
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+/**
+ * This class represents the protection policy to add to a document
+ * for password-based protection.
+ *
+ * The following example shows how to protect a PDF document with password.
+ * In this example, the document will be protected so that someone opening
+ * the document with the user password <code>user_pwd</code> will not be
+ * able to modify the document.
+ *
+ * <pre>
+ * AccessPermission ap = new AccessPermission();
+ * ap.setCanModify(false);
+ * StandardProtectionPolicy policy = new StandardProtectionPolicy(owner_pwd, user_pwd, ap);
+ * doc.protect(policy);
+ * </pre>
+ *
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ * @version $Revision: 1.3 $
+ */
+public class StandardProtectionPolicy extends ProtectionPolicy
+{
+
+ private AccessPermission permissions;
+
+ private String ownerPassword = "";
+
+ private String userPassword = "";
+
+
+ /**
+ * Creates an new instance of the standard protection policy
+ * in order to protect a PDF document with passwords.
+ *
+ * @param ownerPass The owner's password.
+ * @param userPass The users's password.
+ * @param perms The access permissions given to the user.
+ */
+ public StandardProtectionPolicy(String ownerPass, String userPass, AccessPermission perms)
+ {
+ this.permissions = perms;
+ this.userPassword = userPass;
+ this.ownerPassword = ownerPass;
+ }
+
+ /**
+ * Getter of the property <tt>permissions</tt>.
+ *
+ * @return Returns the permissions.
+ */
+ public AccessPermission getPermissions()
+ {
+ return permissions;
+ }
+
+ /**
+ * Setter of the property <tt>permissions</tt>.
+ *
+ * @param perms The permissions to set.
+ */
+ public void setPermissions(AccessPermission perms)
+ {
+ this.permissions = perms;
+ }
+
+ /**
+ * Getter of the property <tt>ownerPassword</tt>.
+ *
+ * @return Returns the ownerPassword.
+ */
+ public String getOwnerPassword()
+ {
+ return ownerPassword;
+ }
+
+ /**
+ * Setter of the property <tt>ownerPassword</tt>.
+ *
+ * @param ownerPass The ownerPassword to set.
+ */
+ public void setOwnerPassword(String ownerPass)
+ {
+ this.ownerPassword = ownerPass;
+ }
+
+ /**
+ * Getter of the property <tt>userPassword</tt>.
+ *
+ * @return Returns the userPassword.
+ */
+ public String getUserPassword()
+ {
+ return userPassword;
+ }
+
+ /**
+ * Setter of the property <tt>userPassword</tt>.
+ *
+ * @param userPass The userPassword to set.
+ */
+ public void setUserPassword(String userPass)
+ {
+ this.userPassword = userPass;
+ }
+}
--- /dev/null
+/*
+ * 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.encryption;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.encryption.ARCFour;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ *
+ * The class implements the standard security handler as decribed
+ * in the PDF specifications. This security handler protects document
+ * with password.
+ *
+ * @see StandardProtectionPolicy to see how to protect document with this security handler.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
+ *
+ * @version $Revision: 1.5 $
+ */
+
+public class StandardSecurityHandler extends SecurityHandler
+{
+ /**
+ * Type of security handler.
+ */
+ public static final String FILTER = "Standard";
+
+ private static final int DEFAULT_VERSION = 1;
+
+ private static final int DEFAULT_REVISION = 3;
+
+ private int revision = DEFAULT_REVISION;
+
+ private StandardProtectionPolicy policy;
+
+ private ARCFour rc4 = new ARCFour();
+
+ /**
+ * Protection policy class for this handler.
+ */
+ public static final Class PROTECTION_POLICY_CLASS = StandardProtectionPolicy.class;
+
+ /**
+ * Standard padding for encryption.
+ */
+ public static final byte[] ENCRYPT_PADDING =
+ {
+ (byte)0x28, (byte)0xBF, (byte)0x4E, (byte)0x5E, (byte)0x4E,
+ (byte)0x75, (byte)0x8A, (byte)0x41, (byte)0x64, (byte)0x00,
+ (byte)0x4E, (byte)0x56, (byte)0xFF, (byte)0xFA, (byte)0x01,
+ (byte)0x08, (byte)0x2E, (byte)0x2E, (byte)0x00, (byte)0xB6,
+ (byte)0xD0, (byte)0x68, (byte)0x3E, (byte)0x80, (byte)0x2F,
+ (byte)0x0C, (byte)0xA9, (byte)0xFE, (byte)0x64, (byte)0x53,
+ (byte)0x69, (byte)0x7A
+ };
+
+ /**
+ * Constructor.
+ */
+ public StandardSecurityHandler()
+ {
+ }
+
+ /**
+ * Constructor used for encryption.
+ *
+ * @param p The protection policy.
+ */
+ public StandardSecurityHandler(StandardProtectionPolicy p)
+ {
+ policy = p;
+ keyLength = policy.getEncryptionKeyLength();
+ }
+
+
+ /**
+ * Computes the version number of the StandardSecurityHandler
+ * regarding the encryption key length.
+ * See PDF Spec 1.6 p 93
+ *
+ * @return The computed cersion number.
+ */
+ private int computeVersionNumber()
+ {
+ if(keyLength == 40)
+ {
+ return DEFAULT_VERSION;
+ }
+ return 2;
+ }
+
+ /**
+ * Computes the revision version of the StandardSecurityHandler to
+ * use regarding the version number and the permissions bits set.
+ * See PDF Spec 1.6 p98
+ *
+ * @return The computed revision number.
+ */
+ private int computeRevisionNumber()
+ {
+ if(version == 2
+ && !policy.getPermissions().canFillInForm()
+ && !policy.getPermissions().canExtractForAccessibility()
+ && !policy.getPermissions().canPrintDegraded() )
+ {
+ return 2;
+ }
+ return 3;
+ }
+
+ /**
+ * Decrypt the document.
+ *
+ * @param doc The document to be decrypted.
+ * @param decryptionMaterial Information used to decrypt the document.
+ *
+ * @throws IOException If there is an error accessing data.
+ * @throws CryptographyException If there is an error with decryption.
+ */
+ public void decryptDocument(PDDocument doc, DecryptionMaterial decryptionMaterial)
+ throws CryptographyException, IOException
+ {
+ document = doc;
+
+ PDEncryptionDictionary dictionary = document.getEncryptionDictionary();
+ if(!(decryptionMaterial instanceof StandardDecryptionMaterial))
+ {
+ throw new CryptographyException("Provided decryption material is not compatible with the document");
+ }
+
+ StandardDecryptionMaterial material = (StandardDecryptionMaterial)decryptionMaterial;
+
+ String password = material.getPassword();
+ if(password == null)
+ {
+ password = "";
+ }
+
+ int dicPermissions = dictionary.getPermissions();
+ int dicRevision = dictionary.getRevision();
+ int dicLength = dictionary.getLength()/8;
+
+ //some documents may have not document id, see
+ //test\encryption\encrypted_doc_no_id.pdf
+ COSArray documentIDArray = document.getDocument().getDocumentID();
+ byte[] documentIDBytes = null;
+ if( documentIDArray != null && documentIDArray.size() >= 1 )
+ {
+ COSString id = (COSString)documentIDArray.getObject( 0 );
+ documentIDBytes = id.getBytes();
+ }
+ else
+ {
+ documentIDBytes = new byte[0];
+ }
+
+ // we need to know whether the meta data was encrypted for password calculation
+ boolean encryptMetadata = dictionary.isEncryptMetaData();
+
+ byte[] u = dictionary.getUserKey();
+ byte[] o = dictionary.getOwnerKey();
+
+ boolean isUserPassword =
+ isUserPassword(
+ password.getBytes("ISO-8859-1"),
+ u,
+ o,
+ dicPermissions,
+ documentIDBytes,
+ dicRevision,
+ dicLength,
+ encryptMetadata);
+ boolean isOwnerPassword =
+ isOwnerPassword(
+ password.getBytes("ISO-8859-1"),
+ u,
+ o,
+ dicPermissions,
+ documentIDBytes,
+ dicRevision,
+ dicLength,
+ encryptMetadata);
+
+ if( isUserPassword )
+ {
+ currentAccessPermission = new AccessPermission( dicPermissions );
+ encryptionKey =
+ computeEncryptedKey(
+ password.getBytes("ISO-8859-1"),
+ o,
+ dicPermissions,
+ documentIDBytes,
+ dicRevision,
+ dicLength,
+ encryptMetadata );
+ }
+ else if( isOwnerPassword )
+ {
+ currentAccessPermission = AccessPermission.getOwnerAccessPermission();
+ byte[] computedUserPassword = getUserPassword(password.getBytes("ISO-8859-1"),o,dicRevision,dicLength );
+ encryptionKey =
+ computeEncryptedKey(
+ computedUserPassword,
+ o,
+ dicPermissions,
+ documentIDBytes,
+ dicRevision,
+ dicLength,
+ encryptMetadata);
+ }
+ else
+ {
+ throw new CryptographyException(
+ "Error: The supplied password does not match either the owner or user password in the document." );
+ }
+
+ // detect whether AES encryption is used. This assumes that the encryption algo is
+ // stored in the PDCryptFilterDictionary
+ PDCryptFilterDictionary stdCryptFilterDictionary = doc.getEncryptionDictionary().
+ getStdCryptFilterDictionary();
+
+ if (stdCryptFilterDictionary != null)
+ {
+ COSName cryptFilterMethod = stdCryptFilterDictionary.getCryptFilterMethod();
+
+ if (cryptFilterMethod != null) {
+ setAES("AESV2".equalsIgnoreCase(cryptFilterMethod.getName()));
+ }
+ }
+
+ this.proceedDecryption();
+ }
+
+ /**
+ * Prepare document for encryption.
+ *
+ * @param doc The documeent to encrypt.
+ *
+ * @throws IOException If there is an error accessing data.
+ * @throws CryptographyException If there is an error with decryption.
+ */
+ public void prepareDocumentForEncryption(PDDocument doc) throws CryptographyException, IOException
+ {
+ document = doc;
+ PDEncryptionDictionary encryptionDictionary = document.getEncryptionDictionary();
+ if(encryptionDictionary == null)
+ {
+ encryptionDictionary = new PDEncryptionDictionary();
+ }
+ version = computeVersionNumber();
+ revision = computeRevisionNumber();
+ encryptionDictionary.setFilter(FILTER);
+ encryptionDictionary.setVersion(version);
+ encryptionDictionary.setRevision(revision);
+ encryptionDictionary.setLength(keyLength);
+
+ String ownerPassword = policy.getOwnerPassword();
+ String userPassword = policy.getUserPassword();
+ if( ownerPassword == null )
+ {
+ ownerPassword = "";
+ }
+ if( userPassword == null )
+ {
+ userPassword = "";
+ }
+
+ int permissionInt = policy.getPermissions().getPermissionBytes();
+
+ encryptionDictionary.setPermissions(permissionInt);
+
+ int length = keyLength/8;
+
+ COSArray idArray = document.getDocument().getDocumentID();
+
+ //check if the document has an id yet. If it does not then
+ //generate one
+ if( idArray == null || idArray.size() < 2 )
+ {
+ idArray = new COSArray();
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance( "MD5" );
+ BigInteger time = BigInteger.valueOf( System.currentTimeMillis() );
+ md.update( time.toByteArray() );
+ md.update( ownerPassword.getBytes("ISO-8859-1") );
+ md.update( userPassword.getBytes("ISO-8859-1") );
+ md.update( document.getDocument().toString().getBytes() );
+ byte[] id = md.digest( this.toString().getBytes("ISO-8859-1") );
+ COSString idString = new COSString();
+ idString.append( id );
+ idArray.add( idString );
+ idArray.add( idString );
+ document.getDocument().setDocumentID( idArray );
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e );
+ }
+ catch(IOException e )
+ {
+ throw new CryptographyException( e );
+ }
+ }
+
+ COSString id = (COSString)idArray.getObject( 0 );
+
+ byte[] o = computeOwnerPassword(
+ ownerPassword.getBytes("ISO-8859-1"),
+ userPassword.getBytes("ISO-8859-1"), revision, length);
+
+ byte[] u = computeUserPassword(
+ userPassword.getBytes("ISO-8859-1"),
+ o, permissionInt, id.getBytes(), revision, length, true);
+
+ encryptionKey = computeEncryptedKey(
+ userPassword.getBytes("ISO-8859-1"), o, permissionInt, id.getBytes(), revision, length, true);
+
+ encryptionDictionary.setOwnerKey(o);
+ encryptionDictionary.setUserKey(u);
+
+ document.setEncryptionDictionary( encryptionDictionary );
+ document.getDocument().setEncryptionDictionary(encryptionDictionary.getCOSDictionary());
+
+ }
+
+ /**
+ * Check for owner password.
+ *
+ * @param ownerPassword The owner password.
+ * @param u The u entry of the encryption dictionary.
+ * @param o The o entry of the encryption dictionary.
+ * @param permissions The set of permissions on the document.
+ * @param id The document id.
+ * @param encRevision The encryption algorithm revision.
+ * @param length The encryption key length.
+ *
+ * @return True If the ownerPassword param is the owner password.
+ *
+ * @throws CryptographyException If there is an error during encryption.
+ * @throws IOException If there is an error accessing data.
+ */
+ public final boolean isOwnerPassword(
+ byte[] ownerPassword,
+ byte[] u,
+ byte[] o,
+ int permissions,
+ byte[] id,
+ int encRevision,
+ int length,
+ boolean encryptMetadata)
+ throws CryptographyException, IOException
+ {
+ byte[] userPassword = getUserPassword( ownerPassword, o, encRevision, length );
+ return isUserPassword( userPassword, u, o, permissions, id, encRevision, length, encryptMetadata );
+ }
+
+ /**
+ * Get the user password based on the owner password.
+ *
+ * @param ownerPassword The plaintext owner password.
+ * @param o The o entry of the encryption dictionary.
+ * @param encRevision The encryption revision number.
+ * @param length The key length.
+ *
+ * @return The u entry of the encryption dictionary.
+ *
+ * @throws CryptographyException If there is an error generating the user password.
+ * @throws IOException If there is an error accessing data while generating the user password.
+ */
+ public final byte[] getUserPassword(
+ byte[] ownerPassword,
+ byte[] o,
+ int encRevision,
+ long length )
+ throws CryptographyException, IOException
+ {
+ try
+ {
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+ //3.3 STEP 1
+ byte[] ownerPadded = truncateOrPad( ownerPassword );
+
+ //3.3 STEP 2
+ MessageDigest md = MessageDigest.getInstance( "MD5" );
+ md.update( ownerPadded );
+ byte[] digest = md.digest();
+
+ //3.3 STEP 3
+ if( encRevision == 3 || encRevision == 4 )
+ {
+ for( int i=0; i<50; i++ )
+ {
+ md.reset();
+ md.update( digest );
+ digest = md.digest();
+ }
+ }
+ if( encRevision == 2 && length != 5 )
+ {
+ throw new CryptographyException(
+ "Error: Expected length=5 actual=" + length );
+ }
+
+ //3.3 STEP 4
+ byte[] rc4Key = new byte[ (int)length ];
+ System.arraycopy( digest, 0, rc4Key, 0, (int)length );
+
+ //3.7 step 2
+ if( encRevision == 2 )
+ {
+ rc4.setKey( rc4Key );
+ rc4.write( o, result );
+ }
+ else if( encRevision == 3 || encRevision == 4)
+ {
+ /**
+ byte[] iterationKey = new byte[ rc4Key.length ];
+ byte[] dataToEncrypt = o;
+ for( int i=19; i>=0; i-- )
+ {
+ System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+ for( int j=0; j< iterationKey.length; j++ )
+ {
+ iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+ }
+ rc4.setKey( iterationKey );
+ rc4.write( dataToEncrypt, result );
+ dataToEncrypt = result.toByteArray();
+ result.reset();
+ }
+ result.write( dataToEncrypt, 0, dataToEncrypt.length );
+ */
+ byte[] iterationKey = new byte[ rc4Key.length ];
+
+
+ byte[] otemp = new byte[ o.length ]; //sm
+ System.arraycopy( o, 0, otemp, 0, o.length ); //sm
+ rc4.write( o, result);//sm
+
+ for( int i=19; i>=0; i-- )
+ {
+ System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+ for( int j=0; j< iterationKey.length; j++ )
+ {
+ iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+ }
+ rc4.setKey( iterationKey );
+ result.reset(); //sm
+ rc4.write( otemp, result ); //sm
+ otemp = result.toByteArray(); //sm
+ }
+ }
+
+
+ return result.toByteArray();
+
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e );
+ }
+ }
+
+ /**
+ * Compute the encryption key.
+ *
+ * @param password The password to compute the encrypted key.
+ * @param o The o entry of the encryption dictionary.
+ * @param permissions The permissions for the document.
+ * @param id The document id.
+ * @param encRevision The revision of the encryption algorithm.
+ * @param length The length of the encryption key.
+ *
+ * @return The encrypted key bytes.
+ *
+ * @throws CryptographyException If there is an error with encryption.
+ */
+ public final byte[] computeEncryptedKey(
+ byte[] password,
+ byte[] o,
+ int permissions,
+ byte[] id,
+ int encRevision,
+ int length,
+ boolean encryptMetadata)
+ throws CryptographyException
+ {
+ byte[] result = new byte[ length ];
+ try
+ {
+ //PDFReference 1.4 pg 78
+ //step1
+ byte[] padded = truncateOrPad( password );
+
+ //step 2
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ md.update( padded );
+
+ //step 3
+ md.update( o );
+
+ //step 4
+ byte zero = (byte)(permissions >>> 0);
+ byte one = (byte)(permissions >>> 8);
+ byte two = (byte)(permissions >>> 16);
+ byte three = (byte)(permissions >>> 24);
+
+ md.update( zero );
+ md.update( one );
+ md.update( two );
+ md.update( three );
+
+ //step 5
+ md.update( id );
+
+ //(Security handlers of revision 4 or greater) If document metadata is not being encrypted,
+ //pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash function.
+ //see 7.6.3.3 Algorithm 2 Step f of PDF 32000-1:2008
+ if( encRevision == 4 && !encryptMetadata)
+ {
+ md.update(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff});
+ }
+
+ byte[] digest = md.digest();
+
+ //step 6
+ if( encRevision == 3 || encRevision == 4)
+ {
+ for( int i=0; i<50; i++ )
+ {
+ md.reset();
+ md.update( digest, 0, length );
+ digest = md.digest();
+ }
+ }
+
+ //step 7
+ if( encRevision == 2 && length != 5 )
+ {
+ throw new CryptographyException(
+ "Error: length should be 5 when revision is two actual=" + length );
+ }
+ System.arraycopy( digest, 0, result, 0, length );
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e );
+ }
+ return result;
+ }
+
+ /**
+ * This will compute the user password hash.
+ *
+ * @param password The plain text password.
+ * @param o The owner password hash.
+ * @param permissions The document permissions.
+ * @param id The document id.
+ * @param encRevision The revision of the encryption.
+ * @param length The length of the encryption key.
+ *
+ * @return The user password.
+ *
+ * @throws CryptographyException If there is an error computing the user password.
+ * @throws IOException If there is an IO error.
+ */
+
+ public final byte[] computeUserPassword(
+ byte[] password,
+ byte[] o,
+ int permissions,
+ byte[] id,
+ int encRevision,
+ int length,
+ boolean encryptMetadata)
+ throws CryptographyException, IOException
+ {
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+ //STEP 1
+ byte[] encryptionKey = computeEncryptedKey( password, o, permissions, id, encRevision, length, encryptMetadata );
+
+ if( encRevision == 2 )
+ {
+ //STEP 2
+ rc4.setKey( encryptionKey );
+ rc4.write( ENCRYPT_PADDING, result );
+ }
+ else if( encRevision == 3 || encRevision == 4 )
+ {
+ try
+ {
+ //STEP 2
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ //md.update( truncateOrPad( password ) );
+ md.update( ENCRYPT_PADDING );
+
+ //STEP 3
+ md.update( id );
+ result.write( md.digest() );
+
+ //STEP 4 and 5
+ byte[] iterationKey = new byte[ encryptionKey.length ];
+ for( int i=0; i<20; i++ )
+ {
+ System.arraycopy( encryptionKey, 0, iterationKey, 0, iterationKey.length );
+ for( int j=0; j< iterationKey.length; j++ )
+ {
+ iterationKey[j] = (byte)(iterationKey[j] ^ i);
+ }
+ rc4.setKey( iterationKey );
+ ByteArrayInputStream input = new ByteArrayInputStream( result.toByteArray() );
+ result.reset();
+ rc4.write( input, result );
+ }
+
+ //step 6
+ byte[] finalResult = new byte[32];
+ System.arraycopy( result.toByteArray(), 0, finalResult, 0, 16 );
+ System.arraycopy( ENCRYPT_PADDING, 0, finalResult, 16, 16 );
+ result.reset();
+ result.write( finalResult );
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e );
+ }
+ }
+ return result.toByteArray();
+ }
+
+ /**
+ * Compute the owner entry in the encryption dictionary.
+ *
+ * @param ownerPassword The plaintext owner password.
+ * @param userPassword The plaintext user password.
+ * @param encRevision The revision number of the encryption algorithm.
+ * @param length The length of the encryption key.
+ *
+ * @return The o entry of the encryption dictionary.
+ *
+ * @throws CryptographyException If there is an error with encryption.
+ * @throws IOException If there is an error accessing data.
+ */
+ public final byte[] computeOwnerPassword(
+ byte[] ownerPassword,
+ byte[] userPassword,
+ int encRevision,
+ int length )
+ throws CryptographyException, IOException
+ {
+ try
+ {
+ //STEP 1
+ byte[] ownerPadded = truncateOrPad( ownerPassword );
+
+ //STEP 2
+ MessageDigest md = MessageDigest.getInstance( "MD5" );
+ md.update( ownerPadded );
+ byte[] digest = md.digest();
+
+ //STEP 3
+ if( encRevision == 3 || encRevision == 4)
+ {
+ for( int i=0; i<50; i++ )
+ {
+ md.reset();
+ md.update( digest, 0, length );
+ digest = md.digest();
+ }
+ }
+ if( encRevision == 2 && length != 5 )
+ {
+ throw new CryptographyException(
+ "Error: Expected length=5 actual=" + length );
+ }
+
+ //STEP 4
+ byte[] rc4Key = new byte[ length ];
+ System.arraycopy( digest, 0, rc4Key, 0, length );
+
+ //STEP 5
+ byte[] paddedUser = truncateOrPad( userPassword );
+
+
+ //STEP 6
+ rc4.setKey( rc4Key );
+ ByteArrayOutputStream crypted = new ByteArrayOutputStream();
+ rc4.write( new ByteArrayInputStream( paddedUser ), crypted );
+
+
+ //STEP 7
+ if( encRevision == 3 || encRevision == 4 )
+ {
+ byte[] iterationKey = new byte[ rc4Key.length ];
+ for( int i=1; i<20; i++ )
+ {
+ System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
+ for( int j=0; j< iterationKey.length; j++ )
+ {
+ iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
+ }
+ rc4.setKey( iterationKey );
+ ByteArrayInputStream input = new ByteArrayInputStream( crypted.toByteArray() );
+ crypted.reset();
+ rc4.write( input, crypted );
+ }
+ }
+
+ //STEP 8
+ return crypted.toByteArray();
+ }
+ catch( NoSuchAlgorithmException e )
+ {
+ throw new CryptographyException( e.getMessage() );
+ }
+ }
+
+
+ /**
+ * This will take the password and truncate or pad it as necessary.
+ *
+ * @param password The password to pad or truncate.
+ *
+ * @return The padded or truncated password.
+ */
+ private final byte[] truncateOrPad( byte[] password )
+ {
+ byte[] padded = new byte[ ENCRYPT_PADDING.length ];
+ int bytesBeforePad = Math.min( password.length, padded.length );
+ System.arraycopy( password, 0, padded, 0, bytesBeforePad );
+ System.arraycopy( ENCRYPT_PADDING, 0, padded, bytesBeforePad, ENCRYPT_PADDING.length-bytesBeforePad );
+ return padded;
+ }
+
+ /**
+ * Check if a plaintext password is the user password.
+ *
+ * @param password The plaintext password.
+ * @param u The u entry of the encryption dictionary.
+ * @param o The o entry of the encryption dictionary.
+ * @param permissions The permissions set in the the PDF.
+ * @param id The document id used for encryption.
+ * @param encRevision The revision of the encryption algorithm.
+ * @param length The length of the encryption key.
+ *
+ * @return true If the plaintext password is the user password.
+ *
+ * @throws CryptographyException If there is an error during encryption.
+ * @throws IOException If there is an error accessing data.
+ */
+ public final boolean isUserPassword(
+ byte[] password,
+ byte[] u,
+ byte[] o,
+ int permissions,
+ byte[] id,
+ int encRevision,
+ int length,
+ boolean encryptMetadata)
+ throws CryptographyException, IOException
+ {
+ boolean matches = false;
+ //STEP 1
+ byte[] computedValue = computeUserPassword( password, o, permissions, id, encRevision, length,
+ encryptMetadata );
+ if( encRevision == 2 )
+ {
+ //STEP 2
+ matches = arraysEqual( u, computedValue );
+ }
+ else if( encRevision == 3 || encRevision == 4 )
+ {
+ //STEP 2
+ matches = arraysEqual( u, computedValue, 16 );
+ }
+ else
+ {
+ throw new IOException( "Unknown Encryption Revision " + encRevision );
+ }
+ return matches;
+ }
+
+ /**
+ * Check if a plaintext password is the user password.
+ *
+ * @param password The plaintext password.
+ * @param u The u entry of the encryption dictionary.
+ * @param o The o entry of the encryption dictionary.
+ * @param permissions The permissions set in the the PDF.
+ * @param id The document id used for encryption.
+ * @param encRevision The revision of the encryption algorithm.
+ * @param length The length of the encryption key.
+ *
+ * @return true If the plaintext password is the user password.
+ *
+ * @throws CryptographyException If there is an error during encryption.
+ * @throws IOException If there is an error accessing data.
+ */
+ public final boolean isUserPassword(
+ String password,
+ byte[] u,
+ byte[] o,
+ int permissions,
+ byte[] id,
+ int encRevision,
+ int length,
+ boolean encryptMetadata )
+ throws CryptographyException, IOException
+ {
+ return isUserPassword(password.getBytes("ISO-8859-1"),
+ u,o,permissions, id, encRevision, length, encryptMetadata);
+ }
+
+ /**
+ * Check for owner password.
+ *
+ * @param password The owner password.
+ * @param u The u entry of the encryption dictionary.
+ * @param o The o entry of the encryption dictionary.
+ * @param permissions The set of permissions on the document.
+ * @param id The document id.
+ * @param encRevision The encryption algorithm revision.
+ * @param length The encryption key length.
+ *
+ * @return True If the ownerPassword param is the owner password.
+ *
+ * @throws CryptographyException If there is an error during encryption.
+ * @throws IOException If there is an error accessing data.
+ */
+ public final boolean isOwnerPassword(
+ String password,
+ byte[] u,
+ byte[] o,
+ int permissions,
+ byte[] id,
+ int encRevision,
+ int length,
+ boolean encryptMetadata)
+ throws CryptographyException, IOException
+ {
+ return isOwnerPassword(password.getBytes("ISO-8859-1"),
+ u,o,permissions, id, encRevision, length, encryptMetadata);
+ }
+
+ private static final boolean arraysEqual( byte[] first, byte[] second, int count )
+ {
+ boolean equal = first.length >= count && second.length >= count;
+ for( int i=0; i<count && equal; i++ )
+ {
+ equal = first[i] == second[i];
+ }
+ return equal;
+ }
+
+ /**
+ * This will compare two byte[] for equality.
+ *
+ * @param first The first byte array.
+ * @param second The second byte array.
+ *
+ * @return true If the arrays contain the exact same data.
+ */
+ private static final boolean arraysEqual( byte[] first, byte[] second )
+ {
+ boolean equal = first.length == second.length;
+ for( int i=0; i<first.length && equal; i++ )
+ {
+ equal = first[i] == second[i];
+ }
+ return equal;
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The encryption package will handle the PDF document security handlers and the functionality of pluggable security handlers.
+</body>
+</html>
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.util.Calendar;
+
+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.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.util.BitFlagHelper;
+import org.apache.pdfbox.util.DateConverter;
+import org.w3c.dom.Element;
+
+/**
+ * This represents an FDF annotation that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public abstract class FDFAnnotation implements COSObjectable
+{
+ /**
+ * Annotation dictionary.
+ */
+ protected COSDictionary annot;
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotation()
+ {
+ annot = new COSDictionary();
+ annot.setName( "Type", "Annot" );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The FDF annotation.
+ */
+ public FDFAnnotation( COSDictionary a )
+ {
+ annot = a;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting data from the element.
+ */
+ public FDFAnnotation( Element element ) throws IOException
+ {
+ this();
+
+ String page = element.getAttribute( "page" );
+ if( page != null )
+ {
+ setPage( Integer.parseInt( page ) );
+ }
+
+ String color = element.getAttribute( "color" );
+ if( color != null )
+ {
+ if( color.length() == 7 && color.charAt( 0 ) == '#' )
+ {
+ int colorValue = Integer.parseInt(color.substring(1,7), 16);
+ setColor( new Color(colorValue) );
+ }
+ }
+
+ setDate( element.getAttribute( "date" ) );
+
+ String flags = element.getAttribute( "flags" );
+ if( flags != null )
+ {
+ String[] flagTokens = flags.split( "," );
+ for( int i=0; i< flagTokens.length; i++ )
+ {
+ if( flagTokens[i].equals( "invisible" ) )
+ {
+ setInvisible( true );
+ }
+ else if( flagTokens[i].equals( "hidden" ) )
+ {
+ setHidden( true );
+ }
+ else if( flagTokens[i].equals( "print" ) )
+ {
+ setPrinted( true );
+ }
+ else if( flagTokens[i].equals( "nozoom" ) )
+ {
+ setNoZoom( true );
+ }
+ else if( flagTokens[i].equals( "norotate" ) )
+ {
+ setNoRotate( true );
+ }
+ else if( flagTokens[i].equals( "noview" ) )
+ {
+ setNoView( true );
+ }
+ else if( flagTokens[i].equals( "readonly" ) )
+ {
+ setReadOnly( true );
+ }
+ else if( flagTokens[i].equals( "locked" ) )
+ {
+ setLocked( true );
+ }
+ else if( flagTokens[i].equals( "togglenoview" ) )
+ {
+ setToggleNoView( true );
+ }
+ }
+
+ }
+
+ setName( element.getAttribute( "name" ) );
+
+ String rect = element.getAttribute( "rect" );
+ if( rect != null )
+ {
+ String[] rectValues = rect.split( "," );
+ float[] values = new float[ rectValues.length ];
+ for( int i=0; i<rectValues.length; i++ )
+ {
+ values[i] = Float.parseFloat( rectValues[i] );
+ }
+ COSArray array = new COSArray();
+ array.setFloatArray( values );
+ setRectangle( new PDRectangle( array ) );
+ }
+
+ setName( element.getAttribute( "title" ) );
+ setCreationDate( DateConverter.toCalendar( element.getAttribute( "creationdate" ) ) );
+ String opac = element.getAttribute( "opacity" );
+ if( opac != null )
+ {
+ setOpacity( Float.parseFloat( opac ) );
+ }
+ setSubject( element.getAttribute( "subject" ) );
+ }
+
+ /**
+ * Create the correct FDFAnnotation.
+ *
+ * @param fdfDic The FDF dictionary.
+ *
+ * @return A newly created FDFAnnotation
+ *
+ * @throws IOException If there is an error accessing the FDF information.
+ */
+ public static FDFAnnotation create( COSDictionary fdfDic ) throws IOException
+ {
+ FDFAnnotation retval = null;
+ if( fdfDic == null )
+ {
+ //do nothing and return null
+ }
+ else if( FDFAnnotationText.SUBTYPE.equals( fdfDic.getNameAsString( COSName.SUBTYPE ) ) )
+ {
+ retval = new FDFAnnotationText( fdfDic );
+ }
+ else
+ {
+ throw new IOException( "Unknown annotation type '" + fdfDic.getNameAsString( COSName.SUBTYPE ) + "'" );
+ }
+ return retval;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return annot;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return annot;
+ }
+
+ /**
+ * This will get the page number or null if it does not exist.
+ *
+ * @return The page number.
+ */
+ public Integer getPage()
+ {
+ Integer retval = null;
+ COSNumber page = (COSNumber)annot.getDictionaryObject( "Page" );
+ if( page != null )
+ {
+ retval = new Integer( page.intValue() );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the page.
+ *
+ * @param page The page number.
+ */
+ public void setPage( int page )
+ {
+ annot.setInt( "Page", page );
+ }
+
+ /**
+ * Get the annotation color.
+ *
+ * @return The annotation color, or null if there is none.
+ */
+ public Color getColor()
+ {
+ Color retval = null;
+ COSArray array = (COSArray)annot.getDictionaryObject( "color" );
+ if( array != null )
+ {
+ float[] rgb = array.toFloatArray();
+ if( rgb.length >=3 )
+ {
+ retval = new Color( rgb[0], rgb[1], rgb[2] );
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * Set the annotation color.
+ *
+ * @param c The annotation color.
+ */
+ public void setColor( Color c )
+ {
+ COSArray color = null;
+ if( c != null )
+ {
+ float[] colors = c.getRGBColorComponents( null );
+ color = new COSArray();
+ color.setFloatArray( colors );
+ }
+ annot.setItem( "color", color );
+ }
+
+ /**
+ * Modification date.
+ *
+ * @return The date as a string.
+ */
+ public String getDate()
+ {
+ return annot.getString( "date" );
+ }
+
+ /**
+ * The annotation modification date.
+ *
+ * @param date The date to store in the FDF annotation.
+ */
+ public void setDate( String date )
+ {
+ annot.setString( "date", date );
+ }
+
+ /**
+ * Get the invisible flag.
+ *
+ * @return The invisible flag.
+ */
+ public boolean isInvisible()
+ {
+ return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_INVISIBLE );
+ }
+
+ /**
+ * Set the invisible flag.
+ *
+ * @param invisible The new invisible flag.
+ */
+ public void setInvisible( boolean invisible )
+ {
+ BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_INVISIBLE, invisible );
+ }
+
+ /**
+ * Get the hidden flag.
+ *
+ * @return The hidden flag.
+ */
+ public boolean isHidden()
+ {
+ return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_HIDDEN );
+ }
+
+ /**
+ * Set the hidden flag.
+ *
+ * @param hidden The new hidden flag.
+ */
+ public void setHidden( boolean hidden )
+ {
+ BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_HIDDEN, hidden );
+ }
+
+ /**
+ * Get the printed flag.
+ *
+ * @return The printed flag.
+ */
+ public boolean isPrinted()
+ {
+ return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_PRINTED );
+ }
+
+ /**
+ * Set the printed flag.
+ *
+ * @param printed The new printed flag.
+ */
+ public void setPrinted( boolean printed )
+ {
+ BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_PRINTED, printed );
+ }
+
+ /**
+ * Get the noZoom flag.
+ *
+ * @return The noZoom flag.
+ */
+ public boolean isNoZoom()
+ {
+ return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_NO_ZOOM );
+ }
+
+ /**
+ * Set the noZoom flag.
+ *
+ * @param noZoom The new noZoom flag.
+ */
+ public void setNoZoom( boolean noZoom )
+ {
+ BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_NO_ZOOM, noZoom );
+ }
+
+ /**
+ * Get the noRotate flag.
+ *
+ * @return The noRotate flag.
+ */
+ public boolean isNoRotate()
+ {
+ return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_NO_ROTATE );
+ }
+
+ /**
+ * Set the noRotate flag.
+ *
+ * @param noRotate The new noRotate flag.
+ */
+ public void setNoRotate( boolean noRotate )
+ {
+ BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_NO_ROTATE, noRotate );
+ }
+
+ /**
+ * Get the noView flag.
+ *
+ * @return The noView flag.
+ */
+ public boolean isNoView()
+ {
+ return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_NO_VIEW );
+ }
+
+ /**
+ * Set the noView flag.
+ *
+ * @param noView The new noView flag.
+ */
+ public void setNoView( boolean noView )
+ {
+ BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_NO_VIEW, noView );
+ }
+
+ /**
+ * Get the readOnly flag.
+ *
+ * @return The readOnly flag.
+ */
+ public boolean isReadOnly()
+ {
+ return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_READ_ONLY );
+ }
+
+ /**
+ * Set the readOnly flag.
+ *
+ * @param readOnly The new readOnly flag.
+ */
+ public void setReadOnly( boolean readOnly )
+ {
+ BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_READ_ONLY, readOnly );
+ }
+
+ /**
+ * Get the locked flag.
+ *
+ * @return The locked flag.
+ */
+ public boolean isLocked()
+ {
+ return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_LOCKED );
+ }
+
+ /**
+ * Set the locked flag.
+ *
+ * @param locked The new locked flag.
+ */
+ public void setLocked( boolean locked )
+ {
+ BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_LOCKED, locked );
+ }
+
+ /**
+ * Get the toggleNoView flag.
+ *
+ * @return The toggleNoView flag.
+ */
+ public boolean isToggleNoView()
+ {
+ return BitFlagHelper.getFlag( annot, COSName.F, PDAnnotation.FLAG_TOGGLE_NO_VIEW );
+ }
+
+ /**
+ * Set the toggleNoView flag.
+ *
+ * @param toggleNoView The new toggleNoView flag.
+ */
+ public void setToggleNoView( boolean toggleNoView )
+ {
+ BitFlagHelper.setFlag( annot, COSName.F, PDAnnotation.FLAG_TOGGLE_NO_VIEW, toggleNoView );
+ }
+
+ /**
+ * Set a unique name for an annotation.
+ *
+ * @param name The unique annotation name.
+ */
+ public void setName( String name )
+ {
+ annot.setString( COSName.NM, name );
+ }
+
+ /**
+ * Get the annotation name.
+ *
+ * @return The unique name of the annotation.
+ */
+ public String getName()
+ {
+ return annot.getString( COSName.NM );
+ }
+
+ /**
+ * Set the rectangle associated with this annotation.
+ *
+ * @param rectangle The annotation rectangle.
+ */
+ public void setRectangle( PDRectangle rectangle )
+ {
+ annot.setItem( COSName.RECT, rectangle );
+ }
+
+ /**
+ * The rectangle associated with this annotation.
+ *
+ * @return The annotation rectangle.
+ */
+ public PDRectangle getRectangle()
+ {
+ PDRectangle retval = null;
+ COSArray rectArray = (COSArray)annot.getDictionaryObject( COSName.RECT );
+ if( rectArray != null )
+ {
+ retval = new PDRectangle( rectArray );
+ }
+
+ return retval;
+ }
+
+ /**
+ * Set a unique title for an annotation.
+ *
+ * @param title The annotation title.
+ */
+ public void setTitle( String title )
+ {
+ annot.setString( COSName.T, title );
+ }
+
+ /**
+ * Get the annotation title.
+ *
+ * @return The title of the annotation.
+ */
+ public String getTitle()
+ {
+ return annot.getString( COSName.T );
+ }
+
+ /**
+ * The annotation create date.
+ *
+ * @return The date of the creation of the annotation date
+ *
+ * @throws IOException If there is an error converting the string to a Calendar object.
+ */
+ public Calendar getCreationDate() throws IOException
+ {
+ return annot.getDate( COSName.CREATION_DATE );
+ }
+
+ /**
+ * Set the creation date.
+ *
+ * @param date The date the annotation was created.
+ */
+ public void setCreationDate( Calendar date )
+ {
+ annot.setDate( COSName.CREATION_DATE, date );
+ }
+
+ /**
+ * Set the annotation opacity.
+ *
+ * @param opacity The new opacity value.
+ */
+ public void setOpacity( float opacity )
+ {
+ annot.setFloat( COSName.CA, opacity );
+ }
+
+ /**
+ * Get the opacity value.
+ *
+ * @return The opacity of the annotation.
+ */
+ public float getOpacity()
+ {
+ return annot.getFloat( COSName.CA, 1f );
+
+ }
+
+ /**
+ * A short description of the annotation.
+ *
+ * @param subject The annotation subject.
+ */
+ public void setSubject( String subject )
+ {
+ annot.setString( COSName.SUBJ, subject );
+ }
+
+ /**
+ * Get the description of the annotation.
+ *
+ * @return The subject of the annotation.
+ */
+ public String getSubject()
+ {
+ return annot.getString( COSName.SUBJ );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Caret FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationCaret extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Caret";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationCaret()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationCaret( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationCaret( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Circle FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationCircle extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Circle";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationCircle()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationCircle( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationCircle( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a FileAttachment FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationFileAttachment extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="FileAttachment";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationFileAttachment()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationFileAttachment( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationFileAttachment( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a FreeText FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationFreeText extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="FreeText";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationFreeText()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationFreeText( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationFreeText( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Highlight FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationHighlight extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Highlight";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationHighlight()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationHighlight( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationHighlight( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Ink FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationInk extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Ink";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationInk()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationInk( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationInk( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Line FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationLine extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Line";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationLine()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationLine( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationLine( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Polygon FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationPolygon extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Polygon";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationPolygon()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationPolygon( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationPolygon( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Polyline FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationPolyline extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Polyline";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationPolyline()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationPolyline( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationPolyline( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Sound FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationSound extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Sound";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationSound()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationSound( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationSound( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Square FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationSquare extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Square";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationSquare()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationSquare( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationSquare( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Squiggly FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationSquiggly extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Squiggly";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationSquiggly()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationSquiggly( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationSquiggly( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Stamp FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationStamp extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Stamp";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationStamp()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationStamp( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationStamp( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a StrikeOut FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationStrikeOut extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="StrikeOut";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationStrikeOut()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationStrikeOut( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationStrikeOut( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Text FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationText extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Text";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationText()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationText( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationText( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.w3c.dom.Element;
+
+/**
+ * This represents a Underline FDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FDFAnnotationUnderline extends FDFAnnotation
+{
+ /**
+ * COS Model value for SubType entry.
+ */
+ public static final String SUBTYPE ="Underline";
+
+ /**
+ * Default constructor.
+ */
+ public FDFAnnotationUnderline()
+ {
+ super();
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a An existing FDF Annotation.
+ */
+ public FDFAnnotationUnderline( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param element An XFDF element.
+ *
+ * @throws IOException If there is an error extracting information from the element.
+ */
+ public FDFAnnotationUnderline( Element element ) throws IOException
+ {
+ super( element );
+ annot.setName( COSName.SUBTYPE, SUBTYPE );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+
+import org.w3c.dom.Element;
+
+/**
+ * This represents an FDF catalog that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class FDFCatalog implements COSObjectable
+{
+ private COSDictionary catalog;
+
+ /**
+ * Default constructor.
+ */
+ public FDFCatalog()
+ {
+ catalog = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param cat The FDF documents catalog.
+ */
+ public FDFCatalog( COSDictionary cat )
+ {
+ catalog = cat;
+ }
+
+ /**
+ * This will create an FDF catalog from an XFDF XML document.
+ *
+ * @param element The XML document that contains the XFDF data.
+ * @throws IOException If there is an error reading from the dom.
+ */
+ public FDFCatalog( Element element ) throws IOException
+ {
+ this();
+ FDFDictionary fdfDict = new FDFDictionary( element );
+ setFDF( fdfDict );
+ }
+
+ /**
+ * This will write this element as an XML document.
+ *
+ * @param output The stream to write the xml to.
+ *
+ * @throws IOException If there is an error writing the XML.
+ */
+ public void writeXML( Writer output ) throws IOException
+ {
+ FDFDictionary fdf = getFDF();
+ fdf.writeXML( output );
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return catalog;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return catalog;
+ }
+
+ /**
+ * This will get the version that was specified in the catalog dictionary.
+ *
+ * @return The FDF version.
+ */
+ public String getVersion()
+ {
+ return catalog.getNameAsString( "Version" );
+ }
+
+ /**
+ * This will set the version of the FDF document.
+ *
+ * @param version The new version for the FDF document.
+ */
+ public void setVersion( String version )
+ {
+ catalog.setName( "Version", version );
+ }
+
+ /**
+ * This will get the FDF dictionary.
+ *
+ * @return The FDF dictionary.
+ */
+ public FDFDictionary getFDF()
+ {
+ COSDictionary fdf = (COSDictionary)catalog.getDictionaryObject( "FDF" );
+ FDFDictionary retval = null;
+ if( fdf != null )
+ {
+ retval = new FDFDictionary( fdf );
+ }
+ else
+ {
+ retval = new FDFDictionary();
+ setFDF( retval );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the FDF document.
+ *
+ * @param fdf The new FDF dictionary.
+ */
+ public void setFDF( FDFDictionary fdf )
+ {
+ catalog.setItem( "FDF", fdf );
+ }
+
+ /**
+ * This will get the signature or null if there is none.
+ *
+ * @return The signature.
+ */
+ public PDSignature getSignature()
+ {
+ PDSignature signature = null;
+ COSDictionary sig = (COSDictionary)catalog.getDictionaryObject( "Sig" );
+ if( sig != null )
+ {
+ signature = new PDSignature( sig );
+ }
+ return signature;
+ }
+
+ /**
+ * This will set the signature that is associated with this catalog.
+ *
+ * @param sig The new signature.
+ */
+ public void setSignature( PDSignature sig )
+ {
+ catalog.setItem( "Sig", sig );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDSimpleFileSpecification;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This represents an FDF dictionary that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class FDFDictionary implements COSObjectable
+{
+ private COSDictionary fdf;
+
+ /**
+ * Default constructor.
+ */
+ public FDFDictionary()
+ {
+ fdf = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fdfDictionary The FDF documents catalog.
+ */
+ public FDFDictionary( COSDictionary fdfDictionary )
+ {
+ fdf = fdfDictionary;
+ }
+
+ /**
+ * This will create an FDF dictionary from an XFDF XML document.
+ *
+ * @param fdfXML The XML document that contains the XFDF data.
+ * @throws IOException If there is an error reading from the dom.
+ */
+ public FDFDictionary( Element fdfXML ) throws IOException
+ {
+ this();
+ NodeList nodeList = fdfXML.getChildNodes();
+ for( int i=0; i<nodeList.getLength(); i++ )
+ {
+ Node node = nodeList.item( i );
+ if( node instanceof Element )
+ {
+ Element child = (Element)node;
+ if( child.getTagName().equals( "f" ) )
+ {
+ PDSimpleFileSpecification fs = new PDSimpleFileSpecification();
+ fs.setFile( child.getAttribute( "href" ) );
+ setFile(fs);
+
+ }
+ else if( child.getTagName().equals( "ids" ) )
+ {
+ COSArray ids = new COSArray();
+ String original = child.getAttribute( "original" );
+ String modified = child.getAttribute( "modified" );
+ ids.add( COSString.createFromHexString( original ) );
+ ids.add( COSString.createFromHexString( modified ) );
+ setID( ids );
+ }
+ else if( child.getTagName().equals( "fields" ) )
+ {
+ NodeList fields = child.getElementsByTagName( "field" );
+ List fieldList = new ArrayList();
+ for( int f=0; f<fields.getLength(); f++ )
+ {
+ fieldList.add( new FDFField( (Element)fields.item( f ) ) );
+ }
+ setFields( fieldList );
+ }
+ else if( child.getTagName().equals( "annots" ) )
+ {
+ NodeList annots = child.getChildNodes();
+ List annotList = new ArrayList();
+ for( int j=0; j<annots.getLength(); j++ )
+ {
+ Node annotNode = annots.item( i );
+ if( annotNode instanceof Element )
+ {
+ Element annot = (Element)annotNode;
+ if( annot.getNodeName().equals( "text" ) )
+ {
+ annotList.add( new FDFAnnotationText( annot ) );
+ }
+ else
+ {
+ throw new IOException( "Error: Unknown annotation type '" + annot.getNodeName() );
+ }
+ }
+ }
+ setAnnotations(annotList);
+ }
+ }
+ }
+ }
+
+ /**
+ * This will write this element as an XML document.
+ *
+ * @param output The stream to write the xml to.
+ *
+ * @throws IOException If there is an error writing the XML.
+ */
+ public void writeXML( Writer output ) throws IOException
+ {
+ PDFileSpecification fs = this.getFile();
+ if( fs != null )
+ {
+ output.write( "<f href=\"" + fs.getFile() + "\" />\n" );
+ }
+ COSArray ids = this.getID();
+ if( ids != null )
+ {
+ COSString original = (COSString)ids.getObject( 0 );
+ COSString modified = (COSString)ids.getObject( 1 );
+ output.write( "<ids original=\"" + original.getHexString() + "\" " );
+ output.write( "modified=\"" + modified.getHexString() + "\" />\n");
+ }
+ List fields = getFields();
+ if( fields != null && fields.size() > 0 )
+ {
+ output.write( "<fields>\n" );
+ for( int i=0; i<fields.size(); i++ )
+ {
+ ((FDFField)fields.get( i )).writeXML( output );
+ }
+ output.write( "</fields>\n" );
+ }
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return fdf;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return fdf;
+ }
+
+ /**
+ * The source file or target file: the PDF document file that
+ * this FDF file was exported from or is intended to be imported into.
+ *
+ * @return The F entry of the FDF dictionary.
+ *
+ * @throws IOException If there is an error creating the file spec.
+ */
+ public PDFileSpecification getFile() throws IOException
+ {
+ return PDFileSpecification.createFS( fdf.getDictionaryObject( "F" ) );
+ }
+
+ /**
+ * This will set the file specification.
+ *
+ * @param fs The file specification.
+ */
+ public void setFile( PDFileSpecification fs )
+ {
+ fdf.setItem( "F", fs );
+ }
+
+ /**
+ * This is the FDF id.
+ *
+ * @return The FDF ID.
+ */
+ public COSArray getID()
+ {
+ return (COSArray)fdf.getDictionaryObject( "ID" );
+ }
+
+ /**
+ * This will set the FDF id.
+ *
+ * @param id The new id for the FDF.
+ */
+ public void setID( COSArray id )
+ {
+ fdf.setItem( "ID", id );
+ }
+
+ /**
+ * This will get the list of FDF Fields. This will return a list of FDFField
+ * objects.
+ *
+ * @return A list of FDF fields.
+ */
+ public List getFields()
+ {
+ List retval = null;
+ COSArray fieldArray = (COSArray)fdf.getDictionaryObject( "Fields" );
+ if( fieldArray != null )
+ {
+ List fields = new ArrayList();
+ for( int i=0; i<fieldArray.size(); i++ )
+ {
+ fields.add( new FDFField( (COSDictionary)fieldArray.getObject( i ) ) );
+ }
+ retval = new COSArrayList( fields, fieldArray );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the list of fields. This should be a list of FDFField objects.
+ *
+ * @param fields The list of fields.
+ */
+ public void setFields( List fields )
+ {
+ fdf.setItem( "Fields", COSArrayList.converterToCOSArray( fields ) );
+ }
+
+ /**
+ * This will get the status string to be displayed as the result of an
+ * action.
+ *
+ * @return The status.
+ */
+ public String getStatus()
+ {
+ return fdf.getString( "Status" );
+ }
+
+ /**
+ * This will set the status string.
+ *
+ * @param status The new status string.
+ */
+ public void setStatus( String status )
+ {
+ fdf.setString( "Status", status );
+ }
+
+ /**
+ * This will get the list of FDF Pages. This will return a list of FDFPage objects.
+ *
+ * @return A list of FDF pages.
+ */
+ public List getPages()
+ {
+ List retval = null;
+ COSArray pageArray = (COSArray)fdf.getDictionaryObject( "Pages" );
+ if( pageArray != null )
+ {
+ List pages = new ArrayList();
+ for( int i=0; i<pageArray.size(); i++ )
+ {
+ pages.add( new FDFPage( (COSDictionary)pageArray.get( i ) ) );
+ }
+ retval = new COSArrayList( pages, pageArray );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the list of pages. This should be a list of FDFPage objects.
+ *
+ *
+ * @param pages The list of pages.
+ */
+ public void setPages( List pages )
+ {
+ fdf.setItem( "Pages", COSArrayList.converterToCOSArray( pages ) );
+ }
+
+ /**
+ * The encoding to be used for a FDF field. The default is PDFDocEncoding
+ * and this method will never return null.
+ *
+ * @return The encoding value.
+ */
+ public String getEncoding()
+ {
+ String encoding = fdf.getNameAsString( "Encoding" );
+ if( encoding == null )
+ {
+ encoding = "PDFDocEncoding";
+ }
+ return encoding;
+
+ }
+
+ /**
+ * This will set the encoding.
+ *
+ * @param encoding The new encoding.
+ */
+ public void setEncoding( String encoding )
+ {
+ fdf.setName( "Encoding", encoding );
+ }
+
+ /**
+ * This will get the list of FDF Annotations. This will return a list of FDFAnnotation objects
+ * or null if the entry is not set.
+ *
+ * @return A list of FDF annotations.
+ *
+ * @throws IOException If there is an error creating the annotation list.
+ */
+ public List getAnnotations() throws IOException
+ {
+ List retval = null;
+ COSArray annotArray = (COSArray)fdf.getDictionaryObject( "Annots" );
+ if( annotArray != null )
+ {
+ List annots = new ArrayList();
+ for( int i=0; i<annotArray.size(); i++ )
+ {
+ annots.add( FDFAnnotation.create( (COSDictionary)annotArray.getObject( i ) ) );
+ }
+ retval = new COSArrayList( annots, annotArray );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the list of annotations. This should be a list of FDFAnnotation objects.
+ *
+ *
+ * @param annots The list of annotations.
+ */
+ public void setAnnotations( List annots )
+ {
+ fdf.setItem( "Annots", COSArrayList.converterToCOSArray( annots ) );
+ }
+
+ /**
+ * This will get the incremental updates since the PDF was last opened.
+ *
+ * @return The differences entry of the FDF dictionary.
+ */
+ public COSStream getDifferences()
+ {
+ return (COSStream)fdf.getDictionaryObject( "Differences" );
+ }
+
+ /**
+ * This will set the differences stream.
+ *
+ * @param diff The new differences stream.
+ */
+ public void setDifferences( COSStream diff )
+ {
+ fdf.setItem( "Differences", diff );
+ }
+
+ /**
+ * This will get the target frame in the browser to open this document.
+ *
+ * @return The target frame.
+ */
+ public String getTarget()
+ {
+ return fdf.getString( "Target" );
+ }
+
+ /**
+ * This will set the target frame in the browser to open this document.
+ *
+ * @param target The new target frame.
+ */
+ public void setTarget( String target )
+ {
+ fdf.setString( "Target", target );
+ }
+
+ /**
+ * This will get the list of embedded FDF entries, or null if the entry is null.
+ * This will return a list of PDFileSpecification objects.
+ *
+ * @return A list of embedded FDF files.
+ *
+ * @throws IOException If there is an error creating the file spec.
+ */
+ public List getEmbeddedFDFs() throws IOException
+ {
+ List retval = null;
+ COSArray embeddedArray = (COSArray)fdf.getDictionaryObject( "EmbeddedFDFs" );
+ if( embeddedArray != null )
+ {
+ List embedded = new ArrayList();
+ for( int i=0; i<embeddedArray.size(); i++ )
+ {
+ embedded.add( PDFileSpecification.createFS( embeddedArray.get( i ) ) );
+ }
+ retval = new COSArrayList( embedded, embeddedArray );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the list of embedded FDFs. This should be a list of
+ * PDFileSpecification objects.
+ *
+ *
+ * @param embedded The list of embedded FDFs.
+ */
+ public void setEmbeddedFDFs( List embedded )
+ {
+ fdf.setItem( "EmbeddedFDFs", COSArrayList.converterToCOSArray( embedded ) );
+ }
+
+ /**
+ * This will get the java script entry.
+ *
+ * @return The java script entry describing javascript commands.
+ */
+ public FDFJavaScript getJavaScript()
+ {
+ FDFJavaScript fs = null;
+ COSDictionary dic = (COSDictionary)fdf.getDictionaryObject( "JavaScript" );
+ if( dic != null )
+ {
+ fs = new FDFJavaScript( dic );
+ }
+ return fs;
+ }
+
+ /**
+ * This will set the JavaScript entry.
+ *
+ * @param js The javascript entries.
+ */
+ public void setJavaScript( FDFJavaScript js )
+ {
+ fdf.setItem( "JavaScript", js );
+ }
+
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.exceptions.COSVisitorException;
+
+import org.apache.pdfbox.pdfparser.PDFParser;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+
+import org.apache.pdfbox.util.XMLUtil;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * This is the in-memory representation of the FDF document. You need to call
+ * close() on this object when you are done using it!!
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class FDFDocument
+{
+ private COSDocument document;
+
+ /**
+ * Constructor, creates a new FDF document.
+ *
+ * @throws IOException If there is an error creating this document.
+ */
+ public FDFDocument() throws IOException
+ {
+ document = new COSDocument();
+ document.setHeaderString( "%FDF-1.2" );
+
+ //First we need a trailer
+ document.setTrailer( new COSDictionary() );
+
+ //Next we need the root dictionary.
+ FDFCatalog catalog = new FDFCatalog();
+ setCatalog( catalog );
+ }
+
+ /**
+ * Constructor that uses an existing document. The COSDocument that
+ * is passed in must be valid.
+ *
+ * @param doc The COSDocument that this document wraps.
+ */
+ public FDFDocument( COSDocument doc )
+ {
+ document = doc;
+ }
+
+ /**
+ * This will create an FDF document from an XFDF XML document.
+ *
+ * @param doc The XML document that contains the XFDF data.
+ * @throws IOException If there is an error reading from the dom.
+ */
+ public FDFDocument( Document doc ) throws IOException
+ {
+ this();
+ Element xfdf = doc.getDocumentElement();
+ if( !xfdf.getNodeName().equals( "xfdf" ) )
+ {
+ throw new IOException( "Error while importing xfdf document, " +
+ "root should be 'xfdf' and not '" + xfdf.getNodeName() + "'" );
+ }
+ FDFCatalog cat = new FDFCatalog( xfdf );
+ setCatalog( cat );
+ }
+
+ /**
+ * This will write this element as an XML document.
+ *
+ * @param output The stream to write the xml to.
+ *
+ * @throws IOException If there is an error writing the XML.
+ */
+ public void writeXML( Writer output ) throws IOException
+ {
+ output.write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
+ output.write( "<xfdf xmlns=\"http://ns.adobe.com/xfdf/\" xml:space=\"preserve\">\n" );
+
+ getCatalog().writeXML( output );
+
+ output.write( "</xfdf>\n" );
+ }
+
+
+
+ /**
+ * This will get the low level document.
+ *
+ * @return The document that this layer sits on top of.
+ */
+ public COSDocument getDocument()
+ {
+ return document;
+ }
+
+ /**
+ * This will get the FDF Catalog. This is guaranteed to not return null.
+ *
+ * @return The documents /Root dictionary
+ */
+ public FDFCatalog getCatalog()
+ {
+ FDFCatalog retval = null;
+ COSDictionary trailer = document.getTrailer();
+ COSDictionary root = (COSDictionary)trailer.getDictionaryObject( COSName.ROOT );
+ if( root == null )
+ {
+ retval = new FDFCatalog();
+ setCatalog( retval );
+ }
+ else
+ {
+ retval = new FDFCatalog( root );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the FDF catalog for this FDF document.
+ *
+ * @param cat The FDF catalog.
+ */
+ public void setCatalog( FDFCatalog cat )
+ {
+ COSDictionary trailer = document.getTrailer();
+ trailer.setItem( COSName.ROOT, cat );
+ }
+
+ /**
+ * This will load a document from a file.
+ *
+ * @param filename The name of the file to load.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static FDFDocument load( String filename ) throws IOException
+ {
+ return load( new BufferedInputStream( new FileInputStream( filename ) ) );
+ }
+
+ /**
+ * This will load a document from a file.
+ *
+ * @param file The name of the file to load.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static FDFDocument load( File file ) throws IOException
+ {
+ return load( new BufferedInputStream( new FileInputStream( file ) ) );
+ }
+
+ /**
+ * This will load a document from an input stream.
+ *
+ * @param input The stream that contains the document.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static FDFDocument load( InputStream input ) throws IOException
+ {
+ PDFParser parser = new PDFParser( input );
+ parser.parse();
+ return parser.getFDFDocument();
+ }
+
+ /**
+ * This will load a document from a file.
+ *
+ * @param filename The name of the file to load.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static FDFDocument loadXFDF( String filename ) throws IOException
+ {
+ return loadXFDF( new BufferedInputStream( new FileInputStream( filename ) ) );
+ }
+
+ /**
+ * This will load a document from a file.
+ *
+ * @param file The name of the file to load.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static FDFDocument loadXFDF( File file ) throws IOException
+ {
+ return loadXFDF( new BufferedInputStream( new FileInputStream( file ) ) );
+ }
+
+ /**
+ * This will load a document from an input stream.
+ *
+ * @param input The stream that contains the document.
+ *
+ * @return The document that was loaded.
+ *
+ * @throws IOException If there is an error reading from the stream.
+ */
+ public static FDFDocument loadXFDF( InputStream input ) throws IOException
+ {
+ Document doc = XMLUtil.parse( input );
+ return new FDFDocument( doc );
+ }
+
+ /**
+ * This will save this document to the filesystem.
+ *
+ * @param fileName The file to save as.
+ *
+ * @throws IOException If there is an error saving the document.
+ * @throws COSVisitorException If an error occurs while generating the data.
+ */
+ public void save( File fileName ) throws IOException, COSVisitorException
+ {
+ save( new FileOutputStream( fileName ) );
+ }
+
+ /**
+ * This will save this document to the filesystem.
+ *
+ * @param fileName The file to save as.
+ *
+ * @throws IOException If there is an error saving the document.
+ * @throws COSVisitorException If an error occurs while generating the data.
+ */
+ public void save( String fileName ) throws IOException, COSVisitorException
+ {
+ save( new FileOutputStream( fileName ) );
+ }
+
+ /**
+ * This will save the document to an output stream.
+ *
+ * @param output The stream to write to.
+ *
+ * @throws IOException If there is an error writing the document.
+ * @throws COSVisitorException If an error occurs while generating the data.
+ */
+ public void save( OutputStream output ) throws IOException, COSVisitorException
+ {
+ COSWriter writer = null;
+ try
+ {
+ writer = new COSWriter( output );
+ writer.write( document );
+ writer.close();
+ }
+ finally
+ {
+ if( writer != null )
+ {
+ writer.close();
+ }
+ }
+ }
+
+ /**
+ * This will save this document to the filesystem.
+ *
+ * @param fileName The file to save as.
+ *
+ * @throws IOException If there is an error saving the document.
+ * @throws COSVisitorException If an error occurs while generating the data.
+ */
+ public void saveXFDF( File fileName ) throws IOException, COSVisitorException
+ {
+ saveXFDF( new BufferedWriter( new FileWriter( fileName ) ) );
+ }
+
+ /**
+ * This will save this document to the filesystem.
+ *
+ * @param fileName The file to save as.
+ *
+ * @throws IOException If there is an error saving the document.
+ * @throws COSVisitorException If an error occurs while generating the data.
+ */
+ public void saveXFDF( String fileName ) throws IOException, COSVisitorException
+ {
+ saveXFDF( new BufferedWriter( new FileWriter( fileName ) ) );
+ }
+
+ /**
+ * This will save the document to an output stream and close the stream.
+ *
+ * @param output The stream to write to.
+ *
+ * @throws IOException If there is an error writing the document.
+ * @throws COSVisitorException If an error occurs while generating the data.
+ */
+ public void saveXFDF( Writer output ) throws IOException, COSVisitorException
+ {
+ try
+ {
+ writeXML( output );
+ }
+ finally
+ {
+ if( output != null )
+ {
+ output.close();
+ }
+ }
+ }
+
+ /**
+ * This will close the underlying COSDocument object.
+ *
+ * @throws IOException If there is an error releasing resources.
+ */
+ public void close() throws IOException
+ {
+ document.close();
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+import org.apache.pdfbox.pdmodel.interactive.action.PDAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+
+import org.apache.pdfbox.util.XMLUtil;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This represents an FDF field that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class FDFField implements COSObjectable
+{
+ private COSDictionary field;
+
+ /**
+ * Default constructor.
+ */
+ public FDFField()
+ {
+ field = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param f The FDF field.
+ */
+ public FDFField( COSDictionary f )
+ {
+ field = f;
+ }
+
+ /**
+ * This will create an FDF field from an XFDF XML document.
+ *
+ * @param fieldXML The XML document that contains the XFDF data.
+ * @throws IOException If there is an error reading from the dom.
+ */
+ public FDFField( Element fieldXML ) throws IOException
+ {
+ this();
+ this.setPartialFieldName( fieldXML.getAttribute( "name" ) );
+ NodeList nodeList = fieldXML.getChildNodes();
+ List<FDFField> kids = new ArrayList<FDFField>();
+ for( int i=0; i<nodeList.getLength(); i++ )
+ {
+ Node node = nodeList.item( i );
+ if( node instanceof Element )
+ {
+ Element child = (Element)node;
+ if( child.getTagName().equals( "value" ) )
+ {
+ setValue( XMLUtil.getNodeValue( child ) );
+ }
+ else if( child.getTagName().equals( "value-richtext" ) )
+ {
+ setRichText( new PDTextStream( XMLUtil.getNodeValue( child ) ) );
+ }
+ else if( child.getTagName().equals( "field" ) )
+ {
+ kids.add( new FDFField( child ) );
+ }
+ }
+ }
+ if( kids.size() > 0 )
+ {
+ setKids( kids );
+ }
+
+ }
+
+ /**
+ * This will write this element as an XML document.
+ *
+ * @param output The stream to write the xml to.
+ *
+ * @throws IOException If there is an error writing the XML.
+ */
+ public void writeXML( Writer output ) throws IOException
+ {
+ output.write( "<field name=\"" + getPartialFieldName() + "\">\n");
+ Object value = getValue();
+ if( value != null )
+ {
+ output.write( "<value>" + value + "</value>\n" );
+ }
+ PDTextStream rt = getRichText();
+ if( rt != null )
+ {
+ output.write( "<value-richtext>" + rt.getAsString() + "</value-richtext>\n" );
+ }
+ List<FDFField> kids = getKids();
+ if( kids != null )
+ {
+ for( int i=0; i<kids.size(); i++ )
+ {
+ ((FDFField)kids.get( i ) ).writeXML( output );
+ }
+ }
+ output.write( "</field>\n");
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return field;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return field;
+ }
+
+ /**
+ * This will get the list of kids. This will return a list of FDFField objects.
+ * This will return null if the underlying list is null.
+ *
+ * @return The list of kids.
+ */
+ public List<FDFField> getKids()
+ {
+ COSArray kids = (COSArray)field.getDictionaryObject( COSName.KIDS );
+ List<FDFField> retval = null;
+ if( kids != null )
+ {
+ List<FDFField> actuals = new ArrayList<FDFField>();
+ for( int i=0; i<kids.size(); i++ )
+ {
+ actuals.add( new FDFField( (COSDictionary)kids.getObject( i ) ) );
+ }
+ retval = new COSArrayList( actuals, kids );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the list of kids.
+ *
+ * @param kids A list of FDFField objects.
+ */
+ public void setKids( List<FDFField> kids )
+ {
+ field.setItem( COSName.KIDS, COSArrayList.converterToCOSArray( kids ) );
+ }
+
+ /**
+ * This will get the "T" entry in the field dictionary. A partial field
+ * name. Where the fully qualified field name is a concatenation of
+ * the parent's fully qualified field name and "." as a separator. For example<br/>
+ * Address.State<br />
+ * Address.City<br />
+ *
+ * @return The partial field name.
+ */
+ public String getPartialFieldName()
+ {
+ return field.getString( COSName.T );
+ }
+
+ /**
+ * This will set the partial field name.
+ *
+ * @param partial The partial field name.
+ */
+ public void setPartialFieldName( String partial )
+ {
+ field.setString( COSName.T, partial );
+ }
+
+ /**
+ * This will set the value for the field. This will return type will either be <br />
+ * String : Checkboxes, Radio Button <br />
+ * java.util.List of strings: Choice Field
+ * PDTextStream: Textfields
+ *
+ * @return The value of the field.
+ *
+ * @throws IOException If there is an error getting the value.
+ */
+ public Object getValue() throws IOException
+ {
+ Object retval = null;
+ COSBase value = field.getDictionaryObject( COSName.V );
+ if( value instanceof COSName )
+ {
+ retval = ((COSName)value).getName();
+ }
+ else if( value instanceof COSArray )
+ {
+ retval = COSArrayList.convertCOSStringCOSArrayToList( (COSArray)value );
+ }
+ else if( value instanceof COSString || value instanceof COSStream )
+ {
+ retval = PDTextStream.createTextStream( value );
+ }
+ else if( value == null )
+ {
+ //Ok, value is null so do nothing
+ }
+ else
+ {
+ throw new IOException( "Error:Unknown type for field import" + value );
+ }
+ return retval;
+ }
+
+ /**
+ * You should pass in a string, or a java.util.List of strings to set the
+ * value.
+ *
+ * @param value The value that should populate when imported.
+ *
+ * @throws IOException If there is an error setting the value.
+ */
+ public void setValue( Object value ) throws IOException
+ {
+ COSBase cos = null;
+ if( value instanceof List )
+ {
+ cos = COSArrayList.convertStringListToCOSStringCOSArray( (List)value );
+ }
+ else if( value instanceof String )
+ {
+ cos = COSName.getPDFName( (String)value );
+ }
+ else if( value instanceof COSObjectable )
+ {
+ cos = ((COSObjectable)value).getCOSObject();
+ }
+ else if( value == null )
+ {
+ //do nothing and let cos remain null as well.
+ }
+ else
+ {
+ throw new IOException( "Error:Unknown type for field import" + value );
+ }
+ field.setItem( COSName.V, cos );
+ }
+
+ /**
+ * This will get the Ff entry of the cos dictionary. If it it not present then
+ * this method will return null.
+ *
+ * @return The field flags.
+ */
+ public Integer getFieldFlags()
+ {
+ Integer retval = null;
+ COSNumber ff = (COSNumber)field.getDictionaryObject( COSName.FF );
+ if( ff != null )
+ {
+ retval = new Integer( ff.intValue() );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the field flags that are associated with this field. The Ff entry
+ * in the FDF field dictionary.
+ *
+ * @param ff The new value for the field flags.
+ */
+ public void setFieldFlags( Integer ff )
+ {
+ COSInteger value = null;
+ if( ff != null )
+ {
+ value = COSInteger.get( ff );
+ }
+ field.setItem( COSName.FF, value );
+ }
+
+ /**
+ * This will get the field flags that are associated with this field. The Ff entry
+ * in the FDF field dictionary.
+ *
+ * @param ff The new value for the field flags.
+ */
+ public void setFieldFlags( int ff )
+ {
+ field.setInt( COSName.FF, ff );
+ }
+
+ /**
+ * This will get the SetFf entry of the cos dictionary. If it it not present then
+ * this method will return null.
+ *
+ * @return The field flags.
+ */
+ public Integer getSetFieldFlags()
+ {
+ Integer retval = null;
+ COSNumber ff = (COSNumber)field.getDictionaryObject( COSName.SET_FF );
+ if( ff != null )
+ {
+ retval = new Integer( ff.intValue() );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the field flags that are associated with this field. The SetFf entry
+ * in the FDF field dictionary.
+ *
+ * @param ff The new value for the "set field flags".
+ */
+ public void setSetFieldFlags( Integer ff )
+ {
+ COSInteger value = null;
+ if( ff != null )
+ {
+ value = COSInteger.get( ff );
+ }
+ field.setItem( COSName.SET_FF, value );
+ }
+
+ /**
+ * This will get the field flags that are associated with this field. The SetFf entry
+ * in the FDF field dictionary.
+ *
+ * @param ff The new value for the "set field flags".
+ */
+ public void setSetFieldFlags( int ff )
+ {
+ field.setInt( COSName.SET_FF, ff );
+ }
+
+ /**
+ * This will get the ClrFf entry of the cos dictionary. If it it not present then
+ * this method will return null.
+ *
+ * @return The field flags.
+ */
+ public Integer getClearFieldFlags()
+ {
+ Integer retval = null;
+ COSNumber ff = (COSNumber)field.getDictionaryObject( COSName.CLR_FF );
+ if( ff != null )
+ {
+ retval = new Integer( ff.intValue() );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the field flags that are associated with this field. The ClrFf entry
+ * in the FDF field dictionary.
+ *
+ * @param ff The new value for the "clear field flags".
+ */
+ public void setClearFieldFlags( Integer ff )
+ {
+ COSInteger value = null;
+ if( ff != null )
+ {
+ value = COSInteger.get( ff );
+ }
+ field.setItem( COSName.CLR_FF, value );
+ }
+
+ /**
+ * This will get the field flags that are associated with this field. The ClrFf entry
+ * in the FDF field dictionary.
+ *
+ * @param ff The new value for the "clear field flags".
+ */
+ public void setClearFieldFlags( int ff )
+ {
+ field.setInt( COSName.CLR_FF, ff );
+ }
+
+ /**
+ * This will get the F entry of the cos dictionary. If it it not present then
+ * this method will return null.
+ *
+ * @return The widget field flags.
+ */
+ public Integer getWidgetFieldFlags()
+ {
+ Integer retval = null;
+ COSNumber f = (COSNumber)field.getDictionaryObject( "F" );
+ if( f != null )
+ {
+ retval = new Integer( f.intValue() );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the widget field flags that are associated with this field. The F entry
+ * in the FDF field dictionary.
+ *
+ * @param f The new value for the field flags.
+ */
+ public void setWidgetFieldFlags( Integer f )
+ {
+ COSInteger value = null;
+ if( f != null )
+ {
+ value = COSInteger.get( f );
+ }
+ field.setItem( COSName.F, value );
+ }
+
+ /**
+ * This will get the field flags that are associated with this field. The F entry
+ * in the FDF field dictionary.
+ *
+ * @param f The new value for the field flags.
+ */
+ public void setWidgetFieldFlags( int f )
+ {
+ field.setInt( COSName.F, f );
+ }
+
+ /**
+ * This will get the SetF entry of the cos dictionary. If it it not present then
+ * this method will return null.
+ *
+ * @return The field flags.
+ */
+ public Integer getSetWidgetFieldFlags()
+ {
+ Integer retval = null;
+ COSNumber ff = (COSNumber)field.getDictionaryObject( COSName.SET_F );
+ if( ff != null )
+ {
+ retval = new Integer( ff.intValue() );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the widget field flags that are associated with this field. The SetF entry
+ * in the FDF field dictionary.
+ *
+ * @param ff The new value for the "set widget field flags".
+ */
+ public void setSetWidgetFieldFlags( Integer ff )
+ {
+ COSInteger value = null;
+ if( ff != null )
+ {
+ value = COSInteger.get( ff );
+ }
+ field.setItem( COSName.SET_F, value );
+ }
+
+ /**
+ * This will get the widget field flags that are associated with this field. The SetF entry
+ * in the FDF field dictionary.
+ *
+ * @param ff The new value for the "set widget field flags".
+ */
+ public void setSetWidgetFieldFlags( int ff )
+ {
+ field.setInt( COSName.SET_F, ff );
+ }
+
+ /**
+ * This will get the ClrF entry of the cos dictionary. If it it not present then
+ * this method will return null.
+ *
+ * @return The widget field flags.
+ */
+ public Integer getClearWidgetFieldFlags()
+ {
+ Integer retval = null;
+ COSNumber ff = (COSNumber)field.getDictionaryObject( COSName.CLR_F );
+ if( ff != null )
+ {
+ retval = new Integer( ff.intValue() );
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the field flags that are associated with this field. The ClrF entry
+ * in the FDF field dictionary.
+ *
+ * @param ff The new value for the "clear widget field flags".
+ */
+ public void setClearWidgetFieldFlags( Integer ff )
+ {
+ COSInteger value = null;
+ if( ff != null )
+ {
+ value = COSInteger.get( ff );
+ }
+ field.setItem( COSName.CLR_F, value );
+ }
+
+ /**
+ * This will get the field flags that are associated with this field. The ClrF entry
+ * in the FDF field dictionary.
+ *
+ * @param ff The new value for the "clear field flags".
+ */
+ public void setClearWidgetFieldFlags( int ff )
+ {
+ field.setInt( COSName.CLR_F, ff );
+ }
+
+ /**
+ * This will get the appearance dictionary that specifies the appearance of
+ * a pushbutton field.
+ *
+ * @return The AP entry of this dictionary.
+ */
+ public PDAppearanceDictionary getAppearanceDictionary()
+ {
+ PDAppearanceDictionary retval = null;
+ COSDictionary dict = (COSDictionary)field.getDictionaryObject( COSName.AP );
+ if( dict != null )
+ {
+ retval = new PDAppearanceDictionary( dict );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the appearance dictionary.
+ *
+ * @param ap The apperance dictionary.
+ */
+ public void setAppearanceDictionary( PDAppearanceDictionary ap )
+ {
+ field.setItem( COSName.AP, ap );
+ }
+
+ /**
+ * This will get named page references..
+ *
+ * @return The named page references.
+ */
+ public FDFNamedPageReference getAppearanceStreamReference()
+ {
+ FDFNamedPageReference retval = null;
+ COSDictionary ref = (COSDictionary)field.getDictionaryObject( COSName.AP_REF );
+ if( ref != null )
+ {
+ retval = new FDFNamedPageReference( ref );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the named page references.
+ *
+ * @param ref The named page references.
+ */
+ public void setAppearanceStreamReference( FDFNamedPageReference ref )
+ {
+ field.setItem( COSName.AP_REF, ref );
+ }
+
+ /**
+ * This will get the icon fit that is associated with this field.
+ *
+ * @return The IF entry.
+ */
+ public FDFIconFit getIconFit()
+ {
+ FDFIconFit retval = null;
+ COSDictionary dic = (COSDictionary)field.getDictionaryObject( "IF" );
+ if( dic != null )
+ {
+ retval = new FDFIconFit( dic );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the icon fit entry.
+ *
+ * @param fit The icon fit object.
+ */
+ public void setIconFit( FDFIconFit fit )
+ {
+ field.setItem( "IF", fit );
+ }
+
+ /**
+ * This will return a list of options for a choice field. The value in the
+ * list will be 1 of 2 types. java.lang.String or FDFOptionElement.
+ *
+ * @return A list of all options.
+ */
+ public List getOptions()
+ {
+ List retval = null;
+ COSArray array = (COSArray)field.getDictionaryObject( COSName.OPT );
+ if( array != null )
+ {
+ List objects = new ArrayList();
+ for( int i=0; i<array.size(); i++ )
+ {
+ COSBase next = array.getObject( i );
+ if( next instanceof COSString )
+ {
+ objects.add( ((COSString)next).getString() );
+ }
+ else
+ {
+ COSArray value = (COSArray)next;
+ objects.add( new FDFOptionElement( value ) );
+ }
+ }
+ retval = new COSArrayList( objects, array );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the options for the choice field. The objects in the list
+ * should either be java.lang.String or FDFOptionElement.
+ *
+ * @param options The options to set.
+ */
+ public void setOptions( List options )
+ {
+ COSArray value = COSArrayList.converterToCOSArray( options );
+ field.setItem( COSName.OPT, value );
+ }
+
+ /**
+ * This will get the action that is associated with this field.
+ *
+ * @return The A entry in the field dictionary.
+ */
+ public PDAction getAction()
+ {
+ return PDActionFactory.createAction( (COSDictionary)field.getDictionaryObject( COSName.A ) );
+ }
+
+ /**
+ * This will set the action that is associated with this field.
+ *
+ * @param a The new action.
+ */
+ public void setAction( PDAction a )
+ {
+ field.setItem( COSName.A, a );
+ }
+
+ /**
+ * This will get a list of additional actions that will get executed based
+ * on events.
+ *
+ * @return The AA entry in this field dictionary.
+ */
+ public PDAdditionalActions getAdditionalActions()
+ {
+ PDAdditionalActions retval = null;
+ COSDictionary dict = (COSDictionary)field.getDictionaryObject( COSName.AA );
+ if( dict != null )
+ {
+ retval = new PDAdditionalActions( dict );
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set the additional actions that are associated with this field.
+ *
+ * @param aa The additional actions.
+ */
+ public void setAdditionalActions( PDAdditionalActions aa )
+ {
+ field.setItem( COSName.AA, aa );
+ }
+
+ /**
+ * This will set the rich text that is associated with this field.
+ *
+ * @return The rich text XHTML stream.
+ */
+ public PDTextStream getRichText()
+ {
+ COSBase rv = field.getDictionaryObject( COSName.RV );
+ return PDTextStream.createTextStream( rv );
+ }
+
+ /**
+ * This will set the rich text value.
+ *
+ * @param rv The rich text value for the stream.
+ */
+ public void setRichText( PDTextStream rv )
+ {
+ field.setItem( COSName.RV, rv );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRange;
+
+/**
+ * This represents an Icon fit dictionary for an FDF field.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class FDFIconFit implements COSObjectable
+{
+ private COSDictionary fit;
+
+ /**
+ * A scale option.
+ */
+ public static final String SCALE_OPTION_ALWAYS = "A";
+ /**
+ * A scale option.
+ */
+ public static final String SCALE_OPTION_ONLY_WHEN_ICON_IS_BIGGER = "B";
+ /**
+ * A scale option.
+ */
+ public static final String SCALE_OPTION_ONLY_WHEN_ICON_IS_SMALLER = "S";
+ /**
+ * A scale option.
+ */
+ public static final String SCALE_OPTION_NEVER = "N";
+
+ /**
+ * Scale to fill with of annotation, disregarding aspect ratio.
+ */
+ public static final String SCALE_TYPE_ANAMORPHIC = "A";
+ /**
+ * Scale to fit width or height, smaller of two, while retaining aspect ration.
+ */
+ public static final String SCALE_TYPE_PROPORTIONAL = "P";
+
+
+
+ /**
+ * Default constructor.
+ */
+ public FDFIconFit()
+ {
+ fit = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param f The icon fit dictionary.
+ */
+ public FDFIconFit( COSDictionary f )
+ {
+ fit = f;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return fit;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return fit;
+ }
+
+ /**
+ * This will get the scale option. See the SCALE_OPTION_XXX constants. This
+ * is guaranteed to never return null. Default: Always
+ *
+ * @return The scale option.
+ */
+ public String getScaleOption()
+ {
+ String retval = fit.getNameAsString( "SW" );
+ if( retval == null )
+ {
+ retval = SCALE_OPTION_ALWAYS;
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the scale option for the icon. Set the SCALE_OPTION_XXX constants.
+ *
+ * @param option The scale option.
+ */
+ public void setScaleOption( String option )
+ {
+ fit.setName( "SW", option );
+ }
+
+ /**
+ * This will get the scale type. See the SCALE_TYPE_XXX constants. This is
+ * guaranteed to never return null. Default: Proportional
+ *
+ * @return The scale type.
+ */
+ public String getScaleType()
+ {
+ String retval = fit.getNameAsString( "S" );
+ if( retval == null )
+ {
+ retval = SCALE_TYPE_PROPORTIONAL;
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the scale type. See the SCALE_TYPE_XXX constants.
+ *
+ * @param scale The scale type.
+ */
+ public void setScaleType( String scale )
+ {
+ fit.setName( "S", scale );
+ }
+
+ /**
+ * This is guaranteed to never return null.<br />
+ *
+ * To quote the PDF Spec
+ * "An array of two numbers between 0.0 and 1.0 indicating the fraction of leftover
+ * space to allocate at the left and bottom of the icon. A value of [0.0 0.0] positions the
+ * icon at the bottom-left corner of the annotation rectangle; a value of [0.5 0.5] centers it
+ * within the rectangle. This entry is used only if the icon is scaled proportionally. Default
+ * value: [0.5 0.5]."
+ *
+ * @return The fractional space to allocate.
+ */
+ public PDRange getFractionalSpaceToAllocate()
+ {
+ PDRange retval = null;
+ COSArray array = (COSArray)fit.getDictionaryObject( "A" );
+ if( array == null )
+ {
+ retval = new PDRange();
+ retval.setMin( .5f );
+ retval.setMax( .5f );
+ setFractionalSpaceToAllocate( retval );
+ }
+ else
+ {
+ retval = new PDRange( array );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set frational space to allocate.
+ *
+ * @param space The space to allocate.
+ */
+ public void setFractionalSpaceToAllocate( PDRange space )
+ {
+ fit.setItem( "A", space );
+ }
+
+ /**
+ * This will tell if the icon should scale to fit the annotation bounds. Default: false
+ *
+ * @return A flag telling if the icon should scale.
+ */
+ public boolean shouldScaleToFitAnnotation()
+ {
+ return fit.getBoolean( "FB", false );
+ }
+
+ /**
+ * This will tell the icon to scale.
+ *
+ * @param value The flag value.
+ */
+ public void setScaleToFitAnnotation( boolean value )
+ {
+ fit.setBoolean( "FB", value );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+import org.apache.pdfbox.pdmodel.common.PDNamedTextStream;
+
+/**
+ * This represents an FDF JavaScript dictionary that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class FDFJavaScript implements COSObjectable
+{
+ private COSDictionary js;
+
+ /**
+ * Default constructor.
+ */
+ public FDFJavaScript()
+ {
+ js = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param javaScript The FDF java script.
+ */
+ public FDFJavaScript( COSDictionary javaScript )
+ {
+ js = javaScript;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return js;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return js;
+ }
+
+ /**
+ * This will get the javascript that is executed before the import.
+ *
+ * @return Some javascript code.
+ */
+ public PDTextStream getBefore()
+ {
+ return PDTextStream.createTextStream( js.getDictionaryObject( "Before" ) );
+ }
+
+ /**
+ * This will set the javascript code the will get execute before the import.
+ *
+ * @param before A reference to some javascript code.
+ */
+ public void setBefore( PDTextStream before )
+ {
+ js.setItem( "Before", before );
+ }
+
+ /**
+ * This will get the javascript that is executed after the import.
+ *
+ * @return Some javascript code.
+ */
+ public PDTextStream getAfter()
+ {
+ return PDTextStream.createTextStream( js.getDictionaryObject( "After" ) );
+ }
+
+ /**
+ * This will set the javascript code the will get execute after the import.
+ *
+ * @param after A reference to some javascript code.
+ */
+ public void setAfter( PDTextStream after )
+ {
+ js.setItem( "After", after );
+ }
+
+ /**
+ * This will return a list of PDNamedTextStream objects. This is the "Doc"
+ * entry of the pdf document. These will be added to the PDF documents
+ * javascript name tree. This will not return null.
+ *
+ * @return A list of all named javascript entries.
+ */
+ public List getNamedJavaScripts()
+ {
+ COSArray array = (COSArray)js.getDictionaryObject( "Doc" );
+ List namedStreams = new ArrayList();
+ if( array == null )
+ {
+ array = new COSArray();
+ js.setItem( "Doc", array );
+ }
+ for( int i=0; i<array.size(); i++ )
+ {
+ COSName name = (COSName)array.get( i );
+ i++;
+ COSBase stream = array.get( i );
+ PDNamedTextStream namedStream = new PDNamedTextStream( name, stream );
+ namedStreams.add( namedStream );
+ }
+ return new COSArrayList( namedStreams, array );
+ }
+
+ /**
+ * This should be a list of PDNamedTextStream objects.
+ *
+ * @param namedStreams The named streams.
+ */
+ public void setNamedJavaScripts( List namedStreams )
+ {
+ COSArray array = COSArrayList.converterToCOSArray( namedStreams );
+ js.setItem( "Doc", array );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * This represents an FDF named page reference that is part of the FDF field.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class FDFNamedPageReference implements COSObjectable
+{
+ private COSDictionary ref;
+
+ /**
+ * Default constructor.
+ */
+ public FDFNamedPageReference()
+ {
+ ref = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param r The FDF named page reference dictionary.
+ */
+ public FDFNamedPageReference( COSDictionary r )
+ {
+ ref = r;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return ref;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return ref;
+ }
+
+ /**
+ * This will get the name of the referenced page. A required parameter.
+ *
+ * @return The name of the referenced page.
+ */
+ public String getName()
+ {
+ return ref.getString( "Name" );
+ }
+
+ /**
+ * This will set the name of the referenced page.
+ *
+ * @param name The referenced page name.
+ */
+ public void setName( String name )
+ {
+ ref.setString( "Name", name );
+ }
+
+ /**
+ * This will get the file specification of this reference. An optional parameter.
+ *
+ * @return The F entry for this dictionary.
+ *
+ * @throws IOException If there is an error creating the file spec.
+ */
+ public PDFileSpecification getFileSpecification() throws IOException
+ {
+ return PDFileSpecification.createFS( ref.getDictionaryObject( "F" ) );
+ }
+
+ /**
+ * This will set the file specification for this named page reference.
+ *
+ * @param fs The file specification to set.
+ */
+ public void setFileSpecification( PDFileSpecification fs )
+ {
+ ref.setItem( "F", fs );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents an object that can be used in a Field's Opt entry to represent
+ * an available option and a default appearance string.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class FDFOptionElement implements COSObjectable
+{
+ private COSArray option;
+
+ /**
+ * Default constructor.
+ */
+ public FDFOptionElement()
+ {
+ option = new COSArray();
+ option.add( new COSString( "" ) );
+ option.add( new COSString( "" ) );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param o The option element.
+ */
+ public FDFOptionElement( COSArray o )
+ {
+ option = o;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return option;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSArray getCOSArray()
+ {
+ return option;
+ }
+
+ /**
+ * This will get the string of one of the available options. A required element.
+ *
+ * @return An available option.
+ */
+ public String getOption()
+ {
+ return ((COSString)option.getObject( 0 ) ).getString();
+ }
+
+ /**
+ * This will set the string for an available option.
+ *
+ * @param opt One of the available options.
+ */
+ public void setOption( String opt )
+ {
+ option.set( 0, new COSString( opt ) );
+ }
+
+ /**
+ * This will get the string of default appearance string. A required element.
+ *
+ * @return A default appearance string.
+ */
+ public String getDefaultAppearanceString()
+ {
+ return ((COSString)option.getObject( 1 ) ).getString();
+ }
+
+ /**
+ * This will set the default appearance string.
+ *
+ * @param da The default appearance string.
+ */
+ public void setDefaultAppearanceString( String da )
+ {
+ option.set( 1, new COSString( da ) );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents an FDF page that is part of the FDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class FDFPage implements COSObjectable
+{
+ private COSDictionary page;
+
+ /**
+ * Default constructor.
+ */
+ public FDFPage()
+ {
+ page = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param p The FDF page.
+ */
+ public FDFPage( COSDictionary p )
+ {
+ page = p;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return page;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return page;
+ }
+
+ /**
+ * This will get a list of FDFTemplage objects that describe the named pages
+ * that serve as templates.
+ *
+ * @return A list of templates.
+ */
+ public List getTemplates()
+ {
+ List retval = null;
+ COSArray array = (COSArray)page.getDictionaryObject( "Templates" );
+ if( array != null )
+ {
+ List objects = new ArrayList();
+ for( int i=0; i<array.size(); i++ )
+ {
+ objects.add( new FDFTemplate( (COSDictionary)array.getObject( i ) ) );
+ }
+ retval = new COSArrayList( objects, array );
+ }
+ return retval;
+ }
+
+ /**
+ * A list of FDFTemplate objects.
+ *
+ * @param templates A list of templates for this Page.
+ */
+ public void setTemplates( List templates )
+ {
+ page.setItem( "Templates", COSArrayList.converterToCOSArray( templates ) );
+ }
+
+ /**
+ * This will get the FDF page info object.
+ *
+ * @return The Page info.
+ */
+ public FDFPageInfo getPageInfo()
+ {
+ FDFPageInfo retval = null;
+ COSDictionary dict = (COSDictionary)page.getDictionaryObject( "Info" );
+ if( dict != null )
+ {
+ retval = new FDFPageInfo( dict );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the page info.
+ *
+ * @param info The new page info dictionary.
+ */
+ public void setPageInfo( FDFPageInfo info )
+ {
+ page.setItem( "Info", info );
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents an FDF page info that is part of the FDF page.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class FDFPageInfo implements COSObjectable
+{
+ private COSDictionary pageInfo;
+
+ /**
+ * Default constructor.
+ */
+ public FDFPageInfo()
+ {
+ pageInfo = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param p The FDF page.
+ */
+ public FDFPageInfo( COSDictionary p )
+ {
+ pageInfo = p;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return pageInfo;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return pageInfo;
+ }
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.fdf;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents an FDF template that is part of the FDF page.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class FDFTemplate implements COSObjectable
+{
+ private COSDictionary template;
+
+ /**
+ * Default constructor.
+ */
+ public FDFTemplate()
+ {
+ template = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param t The FDF page template.
+ */
+ public FDFTemplate( COSDictionary t )
+ {
+ template = t;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return template;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return template;
+ }
+
+ /**
+ * This is the template reference.
+ *
+ * @return The template reference.
+ */
+ public FDFNamedPageReference getTemplateReference()
+ {
+ FDFNamedPageReference retval = null;
+ COSDictionary dict = (COSDictionary)template.getDictionaryObject( "TRef" );
+ if( dict != null )
+ {
+ retval = new FDFNamedPageReference( dict );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the template reference.
+ *
+ * @param tRef The template reference.
+ */
+ public void setTemplateReference( FDFNamedPageReference tRef )
+ {
+ template.setItem( "TRef", tRef );
+ }
+
+ /**
+ * This will get a list of fields that are part of this template.
+ *
+ * @return A list of fields.
+ */
+ public List getFields()
+ {
+ List retval = null;
+ COSArray array = (COSArray)template.getDictionaryObject( "Fields" );
+ if( array != null )
+ {
+ List fields = new ArrayList();
+ for( int i=0; i<array.size(); i++ )
+ {
+ fields.add( new FDFField( (COSDictionary)array.getObject( i ) ) );
+ }
+ retval = new COSArrayList( fields, array );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a list of fields for this template.
+ *
+ * @param fields The list of fields to set for this template.
+ */
+ public void setFields( List fields )
+ {
+ template.setItem( "Fields", COSArrayList.converterToCOSArray( fields ) );
+ }
+
+ /**
+ * A flag telling if the fields imported from the template may be renamed if there are conflicts.
+ *
+ * @return A flag telling if the fields can be renamed.
+ */
+ public boolean shouldRename()
+ {
+ return template.getBoolean( "Rename", false );
+ }
+
+ /**
+ * This will set if the fields can be renamed.
+ *
+ * @param value The flag value.
+ */
+ public void setRename( boolean value )
+ {
+ template.setBoolean( "Rename", value );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The fdf package will handle all of the logic used for FDF objects inside of the PDF/FDF document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.font;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Properties;
+
+import org.apache.pdfbox.util.ResourceLoader;
+
+/**
+ * This class is used as font manager.
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+
+public class FontManager
+{
+
+ // HashMap with all known fonts
+ private static HashMap envFonts = new HashMap();
+ // the standard font
+ private static String standardFont = null;
+ private static Properties fontMapping = new Properties();
+
+ static {
+ try
+ {
+ ResourceLoader.loadProperties(
+ "org/apache/pdfbox/resources/FontMapping.properties",
+ fontMapping );
+ }
+ catch( IOException io )
+ {
+ io.printStackTrace();
+ throw new RuntimeException( "Error loading font mapping" );
+ }
+ loadFonts();
+ loadFontMapping();
+ loadBasefontMapping();
+ setStandardFont();
+ }
+
+ private FontManager()
+ {
+ }
+ /**
+ * Get the standard font from the environment, usually Arial or Times New Roman.
+ *
+ * @return The standard font
+ *
+ */
+ public static java.awt.Font getStandardFont()
+ {
+ if (standardFont != null)
+ {
+ return getAwtFont(standardFont);
+ }
+ return null;
+ }
+
+ /**
+ * Get the font for the given fontname.
+ *
+ * @param font The name of the font.
+ *
+ * @return The font we are looking for or a similar font or null if nothing is found.
+ *
+ */
+ public static java.awt.Font getAwtFont(String font)
+ {
+ String fontname = normalizeFontname(font);
+ if (envFonts.containsKey(fontname))
+ {
+ return (java.awt.Font)envFonts.get(fontname);
+ }
+ return null;
+ }
+
+ /**
+ * Load all available fonts from the environment.
+ */
+ private static void loadFonts()
+ {
+ java.awt.Font[] allFonts = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
+ int numberOfFonts = allFonts.length;
+ for (int i=0;i<numberOfFonts;i++)
+ {
+ java.awt.Font font = allFonts[i];
+ String family = normalizeFontname(font.getFamily());
+ String psname = normalizeFontname(font.getPSName());
+ if (isBoldItalic(font))
+ {
+ envFonts.put(family+"bolditalic", font);
+ }
+ else if (isBold(font))
+ {
+ envFonts.put(family+"bold", font);
+ }
+ else if (isItalic(font))
+ {
+ envFonts.put(family+"italic", font);
+ }
+ else
+ {
+ envFonts.put(family, font);
+ }
+ if (!family.equals(psname))
+ {
+ envFonts.put(normalizeFontname(font.getPSName()),font);
+ }
+ }
+ }
+
+ private static void setStandardFont()
+ {
+ // One of the following fonts will be the standard-font
+ if (envFonts.containsKey("arial"))
+ {
+ standardFont = "arial";
+ }
+ else if (envFonts.containsKey("timesnewroman"))
+ {
+ standardFont = "timesnewroman";
+ }
+ }
+
+ /**
+ * Normalize the fontname.
+ *
+ * @param fontname The name of the font.
+ *
+ * @return The normalized name of the font.
+ *
+ */
+ private static String normalizeFontname(String fontname)
+ {
+ // Terminate all whitespaces, commas and hyphens
+ String normalizedFontname = fontname.toLowerCase().replaceAll(" ","").replaceAll(",","").replaceAll("-","");
+ // Terminate trailing characters up to the "+".
+ // As far as I know, these characters are used in names of embedded fonts
+ // If the embedded font can't be read, we'll try to find it here
+ if (normalizedFontname.indexOf("+") > -1)
+ {
+ normalizedFontname = normalizedFontname.substring(normalizedFontname.indexOf("+")+1);
+ }
+ // normalize all kinds of fonttypes. There are several possible version which have to be normalized
+ // e.g. Arial,Bold Arial-BoldMT Helevtica-oblique ...
+ boolean isBold = normalizedFontname.indexOf("bold") > -1;
+ boolean isItalic = normalizedFontname.indexOf("italic") > -1 || normalizedFontname.indexOf("oblique") > -1;
+ normalizedFontname = normalizedFontname.toLowerCase().replaceAll("bold" , "")
+ .replaceAll("italic" , "").replaceAll("oblique" , "");
+ if (isBold)
+ {
+ normalizedFontname += "bold";
+ }
+ if (isItalic)
+ {
+ normalizedFontname += "italic";
+ }
+ return normalizedFontname;
+ }
+
+
+ /**
+ * Add a font-mapping.
+ *
+ * @param font The name of the font.
+ *
+ * @param mappedName The name of the mapped font.
+ *
+ */
+ private static boolean addFontMapping(String font, String mappedName)
+ {
+ String fontname = normalizeFontname(font);
+ // is there already a font mapping ?
+ if (envFonts.containsKey(fontname))
+ {
+ return false;
+ }
+ String mappedFontname = normalizeFontname(mappedName);
+ // is the mapped font available ?
+ if (!envFonts.containsKey(mappedFontname))
+ {
+ return false;
+ }
+ envFonts.put(fontname, envFonts.get(mappedFontname));
+ return true;
+ }
+
+ /**
+ * Load the mapping for the well knwon font-substitutions.
+ *
+ */
+ private static void loadFontMapping()
+ {
+ boolean addedMapping = true;
+ // There could be some recursive mappings in the fontmapping, so that we have to
+ // read the list until no more additional mapping is added to it
+ while (addedMapping)
+ {
+ int counter = 0;
+ Enumeration keys = fontMapping.keys();
+ while (keys.hasMoreElements())
+ {
+ String key = (String)keys.nextElement();
+ if (addFontMapping(key,(String)fontMapping.get(key)))
+ {
+ counter++;
+ }
+ }
+ if (counter == 0)
+ {
+ addedMapping = false;
+ }
+ }
+ }
+
+ /**
+ * Mapping for the basefonts.
+ */
+ private static void loadBasefontMapping()
+ {
+ addFontMapping("Times-Roman","TimesNewRoman");
+ addFontMapping("Times-Bold","TimesNewRoman,Bold");
+ addFontMapping("Times-Italic","TimesNewRoman,Italic");
+ addFontMapping("Times-BoldItalic","TimesNewRoman,Bold,Italic");
+ addFontMapping("Helvetica-Oblique","Helvetica,Italic");
+ addFontMapping("Helvetica-BoldOblique","Helvetica,Bold,Italic");
+ addFontMapping("Courier-Oblique","Courier,Italic");
+ addFontMapping("Courier-BoldOblique","Courier,Bold,Italic");
+ }
+
+ /**
+ * Try to determine if the font has both a BOLD and an ITALIC-type.
+ *
+ * @param name The font.
+ *
+ * @return font has BOLD and ITALIC-type or not
+ */
+ private static boolean isBoldItalic(java.awt.Font font)
+ {
+ return isBold(font) && isItalic(font);
+ }
+
+ /**
+ * Try to determine if the font has a BOLD-type.
+ *
+ * @param name The font.
+ *
+ * @return font has BOLD-type or not
+ */
+ private static boolean isBold(java.awt.Font font)
+ {
+ String name = font.getName().toLowerCase();
+ if (name.indexOf("bold") > -1)
+ {
+ return true;
+ }
+ String psname = font.getPSName().toLowerCase();
+ if (psname.indexOf("bold") > -1)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Try to determine if the font has an ITALIC-type.
+ *
+ * @param name The font.
+ *
+ * @return font has ITALIC-type or not
+ */
+ private static boolean isItalic(java.awt.Font font)
+ {
+ String name = font.getName().toLowerCase();
+ // oblique is the same as italic
+ if (name.indexOf("italic") > -1 || name.indexOf("oblique") > -1)
+ {
+ return true;
+ }
+ String psname = font.getPSName().toLowerCase();
+ if (psname.indexOf("italic") > -1 || psname.indexOf("oblique") > -1)
+ {
+ return true;
+ }
+ return false;
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.encoding.conversion.CMapSubstitution;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.util.ResourceLoader;
+
+/**
+ * This is implementation for the CIDFontType0/CIDFontType2 Fonts.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.11 $
+ */
+public abstract class PDCIDFont extends PDSimpleFont
+{
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDCIDFont.class);
+
+ private Map<Integer,Float> widthCache = null;
+ private long defaultWidth = 0;
+
+ /**
+ * Constructor.
+ */
+ public PDCIDFont()
+ {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fontDictionary The font dictionary according to the PDF specification.
+ */
+ public PDCIDFont( COSDictionary fontDictionary )
+ {
+ super( fontDictionary );
+ extractWidths();
+ }
+
+ /**
+ * This will get the fonts bouding box.
+ *
+ * @return The fonts bouding box.
+ *
+ * @throws IOException If there is an error getting the font bounding box.
+ */
+ public PDRectangle getFontBoundingBox() throws IOException
+ {
+ throw new RuntimeException( "getFontBoundingBox(): Not yet implemented" );
+ }
+
+ /**
+ * This will get the default width. The default value for the default width is 1000.
+ *
+ * @return The default width for the glyphs in this font.
+ */
+ public long getDefaultWidth()
+ {
+ if (defaultWidth == 0)
+ {
+ COSNumber number = (COSNumber)font.getDictionaryObject( COSName.DW);
+ if( number != null )
+ {
+ defaultWidth = number.intValue();
+ }
+ else
+ {
+ defaultWidth = 1000;
+ }
+ }
+ return defaultWidth;
+ }
+
+ /**
+ * This will set the default width for the glyphs of this font.
+ *
+ * @param dw The default width.
+ */
+ public void setDefaultWidth( long dw )
+ {
+ defaultWidth = dw;
+ font.setLong( COSName.DW, dw );
+ }
+
+ /**
+ * This will get the font width for a character.
+ *
+ * @param c The character code to get the width for.
+ * @param offset The offset into the array.
+ * @param length The length of the data.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public float getFontWidth( byte[] c, int offset, int length ) throws IOException
+ {
+
+ float retval = getDefaultWidth();
+ int code = getCodeFromArray( c, offset, length );
+
+ Float widthFloat = widthCache.get( code );
+ if( widthFloat != null )
+ {
+ retval = widthFloat.floatValue();
+ }
+ return retval;
+ }
+
+ private void extractWidths()
+ {
+ if (widthCache == null)
+ {
+ widthCache = new HashMap<Integer,Float>();
+ COSArray widths = (COSArray)font.getDictionaryObject( COSName.W );
+ if( widths != null )
+ {
+ int size = widths.size();
+ int counter = 0;
+ while (counter < size)
+ {
+ COSNumber firstCode = (COSNumber)widths.getObject( counter++ );
+ COSBase next = widths.getObject( counter++ );
+ if( next instanceof COSArray )
+ {
+ COSArray array = (COSArray)next;
+ int startRange = firstCode.intValue();
+ int arraySize = array.size();
+ for (int i=0; i<arraySize; i++)
+ {
+ COSNumber width = (COSNumber)array.get(i);
+ widthCache.put(startRange+i, width.floatValue());
+ }
+ }
+ else
+ {
+ COSNumber secondCode = (COSNumber)next;
+ COSNumber rangeWidth = (COSNumber)widths.getObject( counter++ );
+ int startRange = firstCode.intValue();
+ int endRange = secondCode.intValue();
+ float width = rangeWidth.floatValue();
+ for (int i=startRange; i<=endRange; i++) {
+ widthCache.put(i,width);
+ }
+ }
+ }
+ }
+ }
+ }
+ /**
+ * This will get the font height for a character.
+ *
+ * @param c The character code to get the height for.
+ * @param offset The offset into the array.
+ * @param length The length of the data.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public float getFontHeight( byte[] c, int offset, int length ) throws IOException
+ {
+ float retval = 0;
+ PDFontDescriptor desc = getFontDescriptor();
+ float xHeight = desc.getXHeight();
+ float capHeight = desc.getCapHeight();
+ if( xHeight != 0f && capHeight != 0 )
+ {
+ //do an average of these two. Can we do better???
+ retval = (xHeight + capHeight)/2f;
+ }
+ else if( xHeight != 0 )
+ {
+ retval = xHeight;
+ }
+ else if( capHeight != 0 )
+ {
+ retval = capHeight;
+ }
+ else
+ {
+ retval = 0;
+ }
+ if( retval == 0 )
+ {
+ retval = desc.getAscent();
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the average font width for all characters.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public float getAverageFontWidth() throws IOException
+ {
+ float totalWidths = 0.0f;
+ float characterCount = 0.0f;
+ float defaultWidth = getDefaultWidth();
+ COSArray widths = (COSArray)font.getDictionaryObject( COSName.W );
+
+ if( widths != null )
+ {
+ for( int i=0; i<widths.size(); i++ )
+ {
+ COSNumber firstCode = (COSNumber)widths.getObject( i++ );
+ COSBase next = widths.getObject( i );
+ if( next instanceof COSArray )
+ {
+ COSArray array = (COSArray)next;
+ for( int j=0; j<array.size(); j++ )
+ {
+ COSNumber width = (COSNumber)array.get( j );
+ totalWidths+=width.floatValue();
+ characterCount += 1;
+ }
+ }
+ else
+ {
+ i++;
+ COSNumber rangeWidth = (COSNumber)widths.getObject( i );
+ if( rangeWidth.floatValue() > 0 )
+ {
+ totalWidths += rangeWidth.floatValue();
+ characterCount += 1;
+ }
+ }
+ }
+ }
+ float average = totalWidths / characterCount;
+ if( average <= 0 )
+ {
+ average = defaultWidth;
+ }
+ return average;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float getFontWidth( int charCode )
+ {
+ float width = -1;
+ if (widthCache.containsKey(charCode))
+ {
+ width = widthCache.get(charCode);
+ }
+ return width;
+ }
+
+ /**
+ * Extract the CIDSystemInfo.
+ * @return the CIDSystemInfo as String
+ */
+ private String getCIDSystemInfo()
+ {
+ String cidSystemInfo = null;
+ COSDictionary cidsysteminfo = (COSDictionary)font.getDictionaryObject(COSName.CIDSYSTEMINFO);
+ if (cidsysteminfo != null)
+ {
+ String ordering = cidsysteminfo.getString(COSName.ORDERING);
+ String registry = cidsysteminfo.getString(COSName.REGISTRY);
+ int supplement = cidsysteminfo.getInt(COSName.SUPPLEMENT);
+ cidSystemInfo = registry + "-" + ordering+ "-" + supplement;
+ }
+ return cidSystemInfo;
+ }
+
+ @Override
+ protected void determineEncoding()
+ {
+ String cidSystemInfo = getCIDSystemInfo();
+ if (cidSystemInfo != null)
+ {
+ if (cidSystemInfo.contains("Identity"))
+ {
+ cidSystemInfo = "Identity-H";
+ }
+ else if (cidSystemInfo.startsWith("Adobe-UCS-"))
+ {
+ cidSystemInfo = "Adobe-Identity-UCS";
+ }
+ else
+ {
+ cidSystemInfo = cidSystemInfo.substring(0,cidSystemInfo.lastIndexOf("-"))+"-UCS2";
+ }
+ cmap = cmapObjects.get( cidSystemInfo );
+ if (cmap == null)
+ {
+ String resourceName = resourceRootCMAP + cidSystemInfo;
+ try {
+ parseCmap( resourceRootCMAP, ResourceLoader.loadResource( resourceName ) );
+ if( cmap == null)
+ {
+ log.error("Error: Could not parse predefined CMAP file for '" + cidSystemInfo + "'" );
+ }
+ }
+ catch(IOException exception)
+ {
+ log.error("Error: Could not find predefined CMAP file for '" + cidSystemInfo + "'" );
+ }
+ }
+ }
+ else
+ {
+ super.determineEncoding();
+ }
+ }
+
+ @Override
+ public String encode(byte[] c, int offset, int length) throws IOException
+ {
+ String result = null;
+ if (cmap != null)
+ {
+ result = cmapEncoding(getCodeFromArray( c, offset, length ), length, true);
+ }
+ else
+ {
+ result = super.encode(c, offset, length);
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.awt.Font;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is implementation of the CIDFontType0 Font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDCIDFontType0Font extends PDCIDFont
+{
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDCIDFontType0Font.class);
+
+ /**
+ * Constructor.
+ */
+ public PDCIDFontType0Font()
+ {
+ super();
+ font.setItem( COSName.SUBTYPE, COSName.CID_FONT_TYPE0 );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fontDictionary The font dictionary according to the PDF specification.
+ */
+ public PDCIDFontType0Font( COSDictionary fontDictionary )
+ {
+ super( fontDictionary );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Font getawtFont() throws IOException
+ {
+ Font awtFont = null;
+ PDFontDescriptorDictionary fd = (PDFontDescriptorDictionary)getFontDescriptor();
+ if( fd.getFontFile3() != null )
+ {
+ // create a font with the embedded data
+ PDType1CFont type1CFont = new PDType1CFont( super.font );
+ awtFont = type1CFont.getawtFont();
+ if (awtFont == null)
+ {
+ awtFont = FontManager.getAwtFont(fd.getFontName());
+ if (awtFont != null)
+ {
+ log.info("Using font "+awtFont.getName()+ " instead");
+ }
+ }
+ }
+ return awtFont;
+ }
+
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This is implementation of the CIDFontType2 Font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDCIDFontType2Font extends PDCIDFont
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDCIDFontType2Font.class);
+
+ /**
+ * Constructor.
+ */
+ public PDCIDFontType2Font()
+ {
+ super();
+ font.setItem( COSName.SUBTYPE, COSName.CID_FONT_TYPE2 );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fontDictionary The font dictionary according to the PDF specification.
+ */
+ public PDCIDFontType2Font( COSDictionary fontDictionary )
+ {
+ super( fontDictionary );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Font getawtFont() throws IOException
+ {
+ Font awtFont = null;
+ PDFontDescriptorDictionary fd = (PDFontDescriptorDictionary)getFontDescriptor();
+ PDStream ff2Stream = fd.getFontFile2();
+ if( ff2Stream != null )
+ {
+ try
+ {
+ // create a font with the embedded data
+ awtFont = Font.createFont( Font.TRUETYPE_FONT, ff2Stream.createInputStream() );
+ }
+ catch( FontFormatException f )
+ {
+ log.info("Can't read the embedded font " + fd.getFontName() );
+ }
+ if (awtFont == null)
+ {
+ awtFont = FontManager.getAwtFont(fd.getFontName());
+ if (awtFont != null)
+ {
+ log.info("Using font "+awtFont.getName()+ " instead");
+ }
+ }
+ }
+ // TODO FontFile3
+ return awtFont;
+ }
+
+}
--- /dev/null
+/*
+ * 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.font;
+
+import org.apache.fontbox.afm.AFMParser;
+import org.apache.fontbox.afm.FontMetric;
+import org.apache.fontbox.cmap.CMapParser;
+import org.apache.fontbox.cmap.CMap;
+
+import org.apache.pdfbox.encoding.Encoding;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+import org.apache.pdfbox.util.ResourceLoader;
+
+import java.awt.Graphics;
+import java.awt.geom.AffineTransform;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This is the base class for all PDF fonts.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.46 $
+ */
+public abstract class PDFont implements COSObjectable
+{
+
+ /**
+ * The cos dictionary for this font.
+ */
+ protected COSDictionary font;
+
+ /**
+ * This is only used if this is a font object and it has an encoding.
+ */
+ private Encoding fontEncoding = null;
+
+ /**
+ * The descriptor of the font
+ */
+ private PDFontDescriptor fontDescriptor = null;
+
+ /**
+ * The font matrix
+ */
+ protected PDMatrix fontMatrix = null;
+
+ /**
+ * This is only used if this is a font object and it has an encoding and it is
+ * a type0 font with a cmap.
+ */
+ protected CMap cmap = null;
+
+ private boolean hasToUnicode = false;
+
+ protected static Map<String, CMap> cmapObjects =
+ Collections.synchronizedMap( new HashMap<String, CMap>() );
+
+ /**
+ * A list a floats representing the widths
+ */
+ private List<Float> widths = null;
+
+ /**
+ * The static map of the default Adobe font metrics.
+ */
+ private static final Map<String, FontMetric> afmObjects =
+ Collections.unmodifiableMap( getAdobeFontMetrics() );
+
+ // TODO move the Map to PDType1Font as these are the 14 Standard fonts
+ // which are definitely Type 1 fonts
+ private static Map<String, FontMetric> getAdobeFontMetrics() {
+ Map<String, FontMetric> metrics = new HashMap<String, FontMetric>();
+ addAdobeFontMetric( metrics, "Courier-Bold" );
+ addAdobeFontMetric( metrics, "Courier-BoldOblique" );
+ addAdobeFontMetric( metrics, "Courier" );
+ addAdobeFontMetric( metrics, "Courier-Oblique" );
+ addAdobeFontMetric( metrics, "Helvetica" );
+ addAdobeFontMetric( metrics, "Helvetica-Bold" );
+ addAdobeFontMetric( metrics, "Helvetica-BoldOblique" );
+ addAdobeFontMetric( metrics, "Helvetica-Oblique" );
+ addAdobeFontMetric( metrics, "Symbol" );
+ addAdobeFontMetric( metrics, "Times-Bold" );
+ addAdobeFontMetric( metrics, "Times-BoldItalic" );
+ addAdobeFontMetric( metrics, "Times-Italic" );
+ addAdobeFontMetric( metrics, "Times-Roman" );
+ addAdobeFontMetric( metrics, "ZapfDingbats" );
+ return metrics;
+ }
+
+ protected final static String resourceRootCMAP = "org/apache/pdfbox/resources/cmap/";
+ private final static String resourceRootAFM = "org/apache/pdfbox/resources/afm/";
+
+ private static void addAdobeFontMetric(
+ Map<String, FontMetric> metrics, String name ) {
+ try {
+ String resource = resourceRootAFM + name + ".afm";
+ InputStream afmStream = ResourceLoader.loadResource( resource );
+ if( afmStream != null )
+ {
+ try {
+ AFMParser parser = new AFMParser( afmStream );
+ parser.parse();
+ metrics.put( name, parser.getResult() );
+ } finally {
+ afmStream.close();
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+
+ /**
+ * This will clear AFM resources that are stored statically.
+ * This is usually not a problem unless you want to reclaim
+ * resources for a long running process.
+ *
+ * SPECIAL NOTE: The font calculations are currently in COSObject, which
+ * is where they will reside until PDFont is mature enough to take them over.
+ * PDFont is the appropriate place for them and not in COSObject but we need font
+ * calculations for text extraction. THIS METHOD WILL BE MOVED OR REMOVED
+ * TO ANOTHER LOCATION IN A FUTURE VERSION OF PDFBOX.
+ */
+ public static void clearResources()
+ {
+ cmapObjects.clear();
+ }
+
+ /**
+ * Constructor.
+ */
+ public PDFont()
+ {
+ font = new COSDictionary();
+ font.setItem( COSName.TYPE, COSName.FONT );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fontDictionary The font dictionary according to the PDF specification.
+ */
+ public PDFont( COSDictionary fontDictionary )
+ {
+ font = fontDictionary;
+ determineEncoding();
+ }
+
+ /**
+ * This will get the font descriptor for this font.
+ *
+ * @return The font descriptor for this font.
+ *
+ */
+ public PDFontDescriptor getFontDescriptor()
+ {
+ if(fontDescriptor == null)
+ {
+ COSDictionary fd = (COSDictionary)font.getDictionaryObject( COSName.FONT_DESC );
+ if (fd != null)
+ {
+ fontDescriptor = new PDFontDescriptorDictionary( fd );
+ }
+ else
+ {
+ FontMetric afm = getAFM();
+ if( afm != null )
+ {
+ fontDescriptor = new PDFontDescriptorAFM( afm );
+ }
+ }
+ }
+ return fontDescriptor;
+ }
+
+ /**
+ * This will set the font descriptor.
+ *
+ * @param fontDescriptor The font descriptor.
+ */
+ public void setFontDescriptor( PDFontDescriptorDictionary fontDescriptor )
+ {
+ COSDictionary dic = null;
+ if( fontDescriptor != null )
+ {
+ dic = fontDescriptor.getCOSDictionary();
+ }
+ font.setItem( COSName.FONT_DESC, dic );
+ this.fontDescriptor = fontDescriptor;
+ }
+
+ /**
+ * Determines the encoding for the font.
+ * This method as to be overwritten, as there are different
+ * possibilities to define a mapping.
+ */
+ protected abstract void determineEncoding();
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return font;
+ }
+
+ /**
+ * This will get the font width for a character.
+ *
+ * @param c The character code to get the width for.
+ * @param offset The offset into the array.
+ * @param length The length of the data.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public abstract float getFontWidth( byte[] c, int offset, int length ) throws IOException;
+
+ /**
+ * This will get the font width for a character.
+ *
+ * @param c The character code to get the width for.
+ * @param offset The offset into the array.
+ * @param length The length of the data.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public abstract float getFontHeight( byte[] c, int offset, int length ) throws IOException;
+
+ /**
+ * This will get the width of this string for this font.
+ *
+ * @param string The string to get the width of.
+ *
+ * @return The width of the string in 1000 units of text space, ie 333 567...
+ *
+ * @throws IOException If there is an error getting the width information.
+ */
+ public float getStringWidth( String string ) throws IOException
+ {
+ byte[] data = string.getBytes("ISO-8859-1");
+ float totalWidth = 0;
+ for( int i=0; i<data.length; i++ )
+ {
+ totalWidth+=getFontWidth( data, i, 1 );
+ }
+ return totalWidth;
+ }
+
+ /**
+ * This will get the average font width for all characters.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public abstract float getAverageFontWidth() throws IOException;
+
+ /**
+ * This will draw a string on a canvas using the font.
+ *
+ * @param string The string to draw.
+ * @param g The graphics to draw onto.
+ * @param fontSize The size of the font to draw.
+ * @param at The transformation matrix with all infos for scaling and shearing of the font.
+ * @param x The x coordinate to draw at.
+ * @param y The y coordinate to draw at.
+ *
+ * @throws IOException If there is an error drawing the specific string.
+ */
+ public abstract void drawString( String string, Graphics g, float fontSize,
+ AffineTransform at, float x, float y ) throws IOException;
+
+ /**
+ * Used for multibyte encodings.
+ *
+ * @param data The array of data.
+ * @param offset The offset into the array.
+ * @param length The number of bytes to use.
+ *
+ * @return The int value of data from the array.
+ */
+ protected int getCodeFromArray( byte[] data, int offset, int length )
+ {
+ int code = 0;
+ for( int i=0; i<length; i++ )
+ {
+ code <<= 8;
+ code |= (data[offset+i]+256)%256;
+ }
+ return code;
+ }
+
+ /**
+ * This will attempt to get the font width from an AFM file.
+ *
+ * @param code The character code we are trying to get.
+ *
+ * @return The font width from the AFM file.
+ *
+ * @throws IOException if we cannot find the width.
+ */
+ protected float getFontWidthFromAFMFile( int code ) throws IOException
+ {
+ float retval = 0;
+ FontMetric metric = getAFM();
+ if( metric != null )
+ {
+ Encoding encoding = getFontEncoding();
+ String characterName = encoding.getName( code );
+ retval = metric.getCharacterWidth( characterName );
+ }
+ return retval;
+ }
+
+ /**
+ * This will attempt to get the average font width from an AFM file.
+ *
+ * @return The average font width from the AFM file.
+ *
+ * @throws IOException if we cannot find the width.
+ */
+ protected float getAverageFontWidthFromAFMFile() throws IOException
+ {
+ float retval = 0;
+ FontMetric metric = getAFM();
+ if( metric != null )
+ {
+ retval = metric.getAverageCharacterWidth();
+ }
+ return retval;
+ }
+
+ /**
+ * This will get an AFM object if one exists.
+ *
+ * @return The afm object from the name.
+ *
+ */
+ protected FontMetric getAFM()
+ {
+ if(isType1Font() && afm==null){
+ COSBase baseFont = font.getDictionaryObject( COSName.BASE_FONT );
+ String name = null;
+ if( baseFont instanceof COSName )
+ {
+ name = ((COSName)baseFont).getName();
+ if (name.indexOf("+") > -1)
+ {
+ name = name.substring(name.indexOf("+")+1);
+ }
+
+ }
+ else if( baseFont instanceof COSString )
+ {
+ COSString string = (COSString)baseFont;
+ name = string.getString();
+ }
+ if( name != null )
+ {
+ afm = afmObjects.get( name );
+ }
+ }
+ return afm;
+ }
+
+ private FontMetric afm = null;
+
+ private COSBase encoding = null;
+ /**
+ * cache the {@link COSName#ENCODING} object from
+ * the font's dictionary since it is called so often.
+ * <p>
+ * Use this method instead of
+ * <pre>
+ * font.getDictionaryObject(COSName.ENCODING);
+ * </pre>
+ * @return
+ */
+ protected COSBase getEncoding(){
+ if(encoding==null)
+ {
+ encoding = font.getDictionaryObject( COSName.ENCODING );
+ }
+ return encoding;
+ }
+
+ /**
+ * Set the encoding object from the fonts dictionary.
+ * @param encoding the given encoding.
+ */
+ protected void setEncoding(COSBase encoding){
+ font.setItem( COSName.ENCODING, encoding );
+ this.encoding = encoding;
+ }
+
+ /**
+ * Encode the given value using the CMap of the font.
+ *
+ * @param code the code to encode.
+ * @param length the byte length of the given code.
+ * @param isCIDFont indicates that the used font is a CID font.
+ *
+ * @return The value of the encoded character.
+ */
+ protected String cmapEncoding( int code, int length, boolean isCIDFont ) throws IOException
+ {
+ String retval = null;
+ if (cmap != null)
+ {
+ retval = cmap.lookup(code, length);
+ if (retval == null && isCIDFont)
+ {
+ retval = cmap.lookupCID(code);
+ }
+ }
+ return retval;
+ }
+ /**
+ * This will perform the encoding of a character if needed.
+ *
+ * @param c The character to encode.
+ * @param offset The offset into the array to get the data
+ * @param length The number of bytes to read.
+ *
+ * @return The value of the encoded character.
+ *
+ * @throws IOException If there is an error during the encoding.
+ */
+ public String encode( byte[] c, int offset, int length ) throws IOException
+ {
+ String retval = null;
+ int code = getCodeFromArray( c, offset, length );
+ if( cmap != null )
+ {
+ retval = cmapEncoding(code, length, false);
+ }
+
+ // there is no cmap but probably an encoding with a suitable mapping
+ if( retval == null )
+ {
+ Encoding encoding = getFontEncoding();
+ if( encoding != null )
+ {
+ retval = encoding.getCharacter( code );
+ }
+ if( retval == null && (cmap == null || length == 2))
+ {
+ retval = getStringFromArray( c, offset, length );
+ }
+ }
+ return retval;
+ }
+
+ private static final String[] SINGLE_CHAR_STRING = new String[256];
+ private static final String[][] DOUBLE_CHAR_STRING = new String[256][256];
+ static
+ {
+ for( int i=0; i<256; i++ )
+ {
+ try
+ {
+ SINGLE_CHAR_STRING[i] = new String( new byte[] {(byte)i}, "ISO-8859-1" );
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ // Nothing should happen here
+ e.printStackTrace();
+ }
+ for( int j=0; j<256; j++ )
+ {
+ try
+ {
+ DOUBLE_CHAR_STRING[i][j] = new String( new byte[] {(byte)i, (byte)j}, "UTF-16BE" );
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ // Nothing should happen here
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private static String getStringFromArray( byte[] c, int offset, int length ) throws IOException
+ {
+ String retval = null;
+ if( length == 1 )
+ {
+ retval = SINGLE_CHAR_STRING[(c[offset]+256)%256];
+ }
+ else if( length == 2 )
+ {
+ retval = DOUBLE_CHAR_STRING[(c[offset]+256)%256][(c[offset+1]+256)%256];
+ }
+ else
+ {
+ throw new IOException( "Error:Unknown character length:" + length );
+ }
+ return retval;
+ }
+
+ protected void parseCmap( String cmapRoot, InputStream cmapStream)
+ {
+ if( cmapStream != null )
+ {
+ CMapParser parser = new CMapParser();
+ try
+ {
+ cmap = parser.parse( cmapRoot, cmapStream );
+ // limit the cache to external CMaps
+ if (cmapRoot != null)
+ {
+ cmapObjects.put( cmap.getName(), cmap );
+ }
+ }
+ catch (IOException exception) {}
+ }
+ }
+
+ /**
+ * The will set the encoding for this font.
+ *
+ * @param enc The font encoding.
+ */
+ public void setFontEncoding( Encoding enc )
+ {
+ fontEncoding = enc;
+ }
+
+ /**
+ * This will get or create the encoder.
+ *
+ * @return The encoding to use.
+ */
+ public Encoding getFontEncoding()
+ {
+ return fontEncoding;
+ }
+
+ /**
+ * This will always return "Font" for fonts.
+ *
+ * @return The type of object that this is.
+ */
+ public String getType()
+ {
+ return font.getNameAsString( COSName.TYPE );
+ }
+
+ // Memorized values to avoid repeated dictionary lookups
+ private String subtype = null;
+ private boolean type1Font;
+ private boolean trueTypeFont;
+ private boolean typeFont;
+
+ /**
+ * This will get the subtype of font, Type1, Type3, ...
+ *
+ * @return The type of font that this is.
+ */
+ public String getSubType()
+ {
+ if (subtype == null) {
+ subtype = font.getNameAsString( COSName.SUBTYPE );
+ type1Font = "Type1".equals(subtype);
+ trueTypeFont = "TrueType".equals(subtype);
+ typeFont = type1Font || "Type0".equals(subtype) || trueTypeFont;
+ }
+ return subtype;
+ }
+
+ /**
+ * Determines if the font is a type 1 font.
+ * @return returns true if the font is a type 1 font
+ */
+ protected boolean isType1Font() {
+ getSubType();
+ return type1Font;
+ }
+
+ private boolean isTrueTypeFont() {
+ getSubType();
+ return trueTypeFont;
+ }
+
+ private boolean isTypeFont() {
+ getSubType();
+ return typeFont;
+ }
+
+ /**
+ * The PostScript name of the font.
+ *
+ * @return The postscript name of the font.
+ */
+ public String getBaseFont()
+ {
+ return font.getNameAsString( COSName.BASE_FONT );
+ }
+
+ /**
+ * Set the PostScript name of the font.
+ *
+ * @param baseFont The postscript name for the font.
+ */
+ public void setBaseFont( String baseFont )
+ {
+ font.setName( COSName.BASE_FONT, baseFont );
+ }
+
+ /**
+ * The code for the first char or -1 if there is none.
+ *
+ * @return The code for the first character.
+ */
+ public int getFirstChar()
+ {
+ return font.getInt( COSName.FIRST_CHAR, -1 );
+ }
+
+ /**
+ * Set the first character this font supports.
+ *
+ * @param firstChar The first character.
+ */
+ public void setFirstChar( int firstChar )
+ {
+ font.setInt( COSName.FIRST_CHAR, firstChar );
+ }
+
+ /**
+ * The code for the last char or -1 if there is none.
+ *
+ * @return The code for the last character.
+ */
+ public int getLastChar()
+ {
+ return font.getInt( COSName.LAST_CHAR, -1 );
+ }
+
+ /**
+ * Set the last character this font supports.
+ *
+ * @param lastChar The last character.
+ */
+ public void setLastChar( int lastChar )
+ {
+ font.setInt( COSName.LAST_CHAR, lastChar );
+ }
+
+ /**
+ * The widths of the characters. This will be null for the standard 14 fonts.
+ *
+ * @return The widths of the characters.
+ */
+ public List<Float> getWidths()
+ {
+ if (widths == null)
+ {
+ COSArray array = (COSArray)font.getDictionaryObject( COSName.WIDTHS );
+ if (array != null)
+ {
+ widths = COSArrayList.convertFloatCOSArrayToList( array );
+ }
+ }
+ return widths;
+ }
+
+ /**
+ * Set the widths of the characters code.
+ *
+ * @param widths The widths of the character codes.
+ */
+ public void setWidths( List<Float> widths )
+ {
+ this.widths = widths;
+ font.setItem( COSName.WIDTHS, COSArrayList.converterToCOSArray( this.widths ) );
+ }
+
+ /**
+ * This will get the matrix that is used to transform glyph space to
+ * text space. By default there are 1000 glyph units to 1 text space
+ * unit, but type3 fonts can use any value.
+ *
+ * Note:If this is a type3 font then it can be modified via the PDType3Font.setFontMatrix, otherwise this
+ * is a read-only property.
+ *
+ * @return The matrix to transform from glyph space to text space.
+ */
+ public PDMatrix getFontMatrix()
+ {
+ if (fontMatrix == null)
+ {
+ COSArray array = (COSArray)font.getDictionaryObject( COSName.FONT_MATRIX );
+ if( array == null )
+ {
+ array = new COSArray();
+ array.add( new COSFloat( 0.001f ) );
+ array.add( COSInteger.ZERO );
+ array.add( COSInteger.ZERO );
+ array.add( new COSFloat( 0.001f ) );
+ array.add( COSInteger.ZERO );
+ array.add( COSInteger.ZERO );
+ }
+ fontMatrix = new PDMatrix(array);
+ }
+ return fontMatrix;
+ }
+
+ /**
+ * This will get the fonts bounding box.
+ *
+ * @return The fonts bounding box.
+ *
+ * @throws IOException If there is an error getting the bounding box.
+ */
+ public abstract PDRectangle getFontBoundingBox() throws IOException;
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals( Object other )
+ {
+ return other instanceof PDFont && ((PDFont)other).getCOSObject() == this.getCOSObject();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return this.getCOSObject().hashCode();
+ }
+
+ /**
+ * Determines the width of the given character.
+ * @param charCode the code of the given character
+ * @return the width of the character
+ */
+ public float getFontWidth( int charCode )
+ {
+ float width = -1;
+ int firstChar = getFirstChar();
+ int lastChar = getLastChar();
+ if (charCode >= firstChar && charCode <= lastChar)
+ {
+ List<Float> widths = getWidths();
+ // maybe the font doesn't provide any widths
+ if (widths != null)
+ {
+ width = widths.get(charCode-firstChar).floatValue();
+ }
+ }
+ else
+ {
+ PDFontDescriptor fd = getFontDescriptor();
+ if (fd instanceof PDFontDescriptorDictionary)
+ {
+ width = fd.getMissingWidth();
+ }
+ }
+ return width;
+ }
+
+ /**
+ * Determines if a font as a ToUnicode entry.
+ * @return true if the font has a ToUnicode entry
+ */
+ protected boolean hasToUnicode()
+ {
+ return hasToUnicode;
+ }
+
+ /**
+ * Sets hasToUnicode to the given value.
+ * @param hasToUnicode the given value for hasToUnicode
+ */
+ protected void setHasToUnicode(boolean hasToUnicode)
+ {
+ this.hasToUnicode = hasToUnicode;
+ }
+
+ public COSString createString(String text) throws IOException
+ {
+ return new COSString(text);
+
+ }
+
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This class represents an interface to the font description. This will depend
+ * on the font type for the actual implementation. If it is a AFM/cmap/or embedded font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public abstract class PDFontDescriptor
+{
+ /**
+ * A font descriptor flag. See PDF Reference for description.
+ */
+ private static final int FLAG_FIXED_PITCH = 1;
+ /**
+ * A font descriptor flag. See PDF Reference for description.
+ */
+ private static final int FLAG_SERIF = 2;
+ /**
+ * A font descriptor flag. See PDF Reference for description.
+ */
+ private static final int FLAG_SYMBOLIC = 4;
+ /**
+ * A font descriptor flag. See PDF Reference for description.
+ */
+ private static final int FLAG_SCRIPT = 8;
+ /**
+ * A font descriptor flag. See PDF Reference for description.
+ */
+ private static final int FLAG_NON_SYMBOLIC = 32;
+ /**
+ * A font descriptor flag. See PDF Reference for description.
+ */
+ private static final int FLAG_ITALIC = 64;
+ /**
+ * A font descriptor flag. See PDF Reference for description.
+ */
+ private static final int FLAG_ALL_CAP = 65536;
+ /**
+ * A font descriptor flag. See PDF Reference for description.
+ */
+ private static final int FLAG_SMALL_CAP = 131072;
+ /**
+ * A font descriptor flag. See PDF Reference for description.
+ */
+ private static final int FLAG_FORCE_BOLD = 262144;
+
+
+ /**
+ * Get the font name.
+ *
+ * @return The name of the font.
+ */
+ public abstract String getFontName();
+
+ /**
+ * This will set the font name.
+ *
+ * @param fontName The new name for the font.
+ */
+ public abstract void setFontName( String fontName );
+
+ /**
+ * A string representing the preferred font family.
+ *
+ * @return The font family.
+ */
+ public abstract String getFontFamily();
+
+ /**
+ * This will set the font family.
+ *
+ * @param fontFamily The font family.
+ */
+ public abstract void setFontFamily( String fontFamily );
+
+ /**
+ * A string representing the preferred font stretch.
+ * According to the PDF Spec:
+ * The font stretch value; it must be one of the following (ordered from
+ * narrowest to widest): UltraCondensed, ExtraCondensed, Condensed, SemiCondensed,
+ * Normal, SemiExpanded, Expanded, ExtraExpanded or UltraExpanded.
+ *
+ * @return The font stretch.
+ */
+ public abstract String getFontStretch();
+
+ /**
+ * This will set the font stretch.
+ *
+ * @param fontStretch The font stretch
+ */
+ public abstract void setFontStretch( String fontStretch );
+
+ /**
+ * The weight of the font. According to the PDF spec "possible values are
+ * 100, 200, 300, 400, 500, 600, 700, 800 or 900" Where a higher number is
+ * more weight and appears to be more bold.
+ *
+ * @return The font weight.
+ */
+ public abstract float getFontWeight();
+
+ /**
+ * Set the weight of the font.
+ *
+ * @param fontWeight The new weight of the font.
+ */
+ public abstract void setFontWeight( float fontWeight );
+
+ /**
+ * This will get the font flags.
+ *
+ * @return The font flags.
+ */
+ public abstract int getFlags();
+
+ /**
+ * This will set the font flags.
+ *
+ * @param flags The new font flags.
+ */
+ public abstract void setFlags( int flags );
+
+ /**
+ * A convenience method that checks the flag bit.
+ *
+ * @return The flag value.
+ */
+ public boolean isFixedPitch()
+ {
+ return isFlagBitOn( FLAG_FIXED_PITCH );
+ }
+
+ /**
+ * A convenience method that sets the flag bit.
+ *
+ * @param flag The flag value.
+ */
+ public void setFixedPitch( boolean flag )
+ {
+ setFlagBit( FLAG_FIXED_PITCH, flag );
+ }
+
+ /**
+ * A convenience method that checks the flag bit.
+ *
+ * @return The flag value.
+ */
+ public boolean isSerif()
+ {
+ return isFlagBitOn( FLAG_SERIF );
+ }
+
+ /**
+ * A convenience method that sets the flag bit.
+ *
+ * @param flag The flag value.
+ */
+ public void setSerif( boolean flag )
+ {
+ setFlagBit( FLAG_SERIF, flag );
+ }
+
+ /**
+ * A convenience method that checks the flag bit.
+ *
+ * @return The flag value.
+ */
+ public boolean isSymbolic()
+ {
+ return isFlagBitOn( FLAG_SYMBOLIC );
+ }
+
+ /**
+ * A convenience method that sets the flag bit.
+ *
+ * @param flag The flag value.
+ */
+ public void setSymbolic( boolean flag )
+ {
+ setFlagBit( FLAG_SYMBOLIC, flag );
+ }
+
+ /**
+ * A convenience method that checks the flag bit.
+ *
+ * @return The flag value.
+ */
+ public boolean isScript()
+ {
+ return isFlagBitOn( FLAG_SCRIPT );
+ }
+
+ /**
+ * A convenience method that sets the flag bit.
+ *
+ * @param flag The flag value.
+ */
+ public void setScript( boolean flag )
+ {
+ setFlagBit( FLAG_SCRIPT, flag );
+ }
+
+ /**
+ * A convenience method that checks the flag bit.
+ *
+ * @return The flag value.
+ */
+ public boolean isNonSymbolic()
+ {
+ return isFlagBitOn( FLAG_NON_SYMBOLIC );
+ }
+
+ /**
+ * A convenience method that sets the flag bit.
+ *
+ * @param flag The flag value.
+ */
+ public void setNonSymbolic( boolean flag )
+ {
+ setFlagBit( FLAG_NON_SYMBOLIC, flag );
+ }
+
+ /**
+ * A convenience method that checks the flag bit.
+ *
+ * @return The flag value.
+ */
+ public boolean isItalic()
+ {
+ return isFlagBitOn( FLAG_ITALIC );
+ }
+
+ /**
+ * A convenience method that sets the flag bit.
+ *
+ * @param flag The flag value.
+ */
+ public void setItalic( boolean flag )
+ {
+ setFlagBit( FLAG_ITALIC, flag );
+ }
+
+ /**
+ * A convenience method that checks the flag bit.
+ *
+ * @return The flag value.
+ */
+ public boolean isAllCap()
+ {
+ return isFlagBitOn( FLAG_ALL_CAP);
+ }
+
+ /**
+ * A convenience method that sets the flag bit.
+ *
+ * @param flag The flag value.
+ */
+ public void setAllCap( boolean flag )
+ {
+ setFlagBit( FLAG_ALL_CAP, flag );
+ }
+
+ /**
+ * A convenience method that checks the flag bit.
+ *
+ * @return The flag value.
+ */
+ public boolean isSmallCap()
+ {
+ return isFlagBitOn( FLAG_SMALL_CAP );
+ }
+
+ /**
+ * A convenience method that sets the flag bit.
+ *
+ * @param flag The flag value.
+ */
+ public void setSmallCap( boolean flag )
+ {
+ setFlagBit( FLAG_SMALL_CAP, flag );
+ }
+
+ /**
+ * A convenience method that checks the flag bit.
+ *
+ * @return The flag value.
+ */
+ public boolean isForceBold()
+ {
+ return isFlagBitOn( FLAG_FORCE_BOLD );
+ }
+
+ /**
+ * A convenience method that sets the flag bit.
+ *
+ * @param flag The flag value.
+ */
+ public void setForceBold( boolean flag )
+ {
+ setFlagBit( FLAG_FORCE_BOLD, flag );
+ }
+
+ private boolean isFlagBitOn( int bit )
+ {
+ return (getFlags() & bit) != 0;
+ }
+
+ private void setFlagBit( int bit, boolean value )
+ {
+ int flags = getFlags();
+ if( value )
+ {
+ flags = flags | bit;
+ }
+ else
+ {
+ flags = flags & (0xFFFFFFFF ^ bit);
+ }
+ setFlags( flags );
+ }
+
+ /**
+ * This will get the fonts bouding box.
+ *
+ * @return The fonts bouding box.
+ */
+ public abstract PDRectangle getFontBoundingBox();
+
+ /**
+ * Set the fonts bounding box.
+ *
+ * @param rect The new bouding box.
+ */
+ public abstract void setFontBoundingBox( PDRectangle rect );
+
+ /**
+ * This will get the italic angle for the font.
+ *
+ * @return The italic angle.
+ */
+ public abstract float getItalicAngle();
+
+ /**
+ * This will set the italic angle for the font.
+ *
+ * @param angle The new italic angle for the font.
+ */
+ public abstract void setItalicAngle( float angle );
+
+ /**
+ * This will get the ascent for the font.
+ *
+ * @return The ascent.
+ */
+ public abstract float getAscent();
+
+ /**
+ * This will set the ascent for the font.
+ *
+ * @param ascent The new ascent for the font.
+ */
+ public abstract void setAscent( float ascent );
+
+ /**
+ * This will get the descent for the font.
+ *
+ * @return The descent.
+ */
+ public abstract float getDescent();
+
+ /**
+ * This will set the descent for the font.
+ *
+ * @param descent The new descent for the font.
+ */
+ public abstract void setDescent( float descent );
+
+ /**
+ * This will get the leading for the font.
+ *
+ * @return The leading.
+ */
+ public abstract float getLeading();
+
+ /**
+ * This will set the leading for the font.
+ *
+ * @param leading The new leading for the font.
+ */
+ public abstract void setLeading( float leading );
+
+ /**
+ * This will get the CapHeight for the font.
+ *
+ * @return The cap height.
+ */
+ public abstract float getCapHeight();
+
+ /**
+ * This will set the cap height for the font.
+ *
+ * @param capHeight The new cap height for the font.
+ */
+ public abstract void setCapHeight( float capHeight );
+
+ /**
+ * This will get the x height for the font.
+ *
+ * @return The x height.
+ */
+ public abstract float getXHeight();
+
+ /**
+ * This will set the x height for the font.
+ *
+ * @param xHeight The new x height for the font.
+ */
+ public abstract void setXHeight( float xHeight );
+
+ /**
+ * This will get the stemV for the font.
+ *
+ * @return The stem v value.
+ */
+ public abstract float getStemV();
+
+ /**
+ * This will set the stem V for the font.
+ *
+ * @param stemV The new stem v for the font.
+ */
+ public abstract void setStemV( float stemV );
+
+ /**
+ * This will get the stemH for the font.
+ *
+ * @return The stem h value.
+ */
+ public abstract float getStemH();
+
+ /**
+ * This will set the stem H for the font.
+ *
+ * @param stemH The new stem h for the font.
+ */
+ public abstract void setStemH( float stemH );
+
+ /**
+ * This will get the average width for the font. This is part of the
+ * definition in the font description. If it is not present then PDFBox
+ * will make an attempt to calculate it.
+ *
+ * @return The average width value.
+ *
+ * @throws IOException If there is an error calculating the average width.
+ */
+ public abstract float getAverageWidth() throws IOException;
+
+ /**
+ * This will set the average width for the font.
+ *
+ * @param averageWidth The new average width for the font.
+ */
+ public abstract void setAverageWidth( float averageWidth );
+
+ /**
+ * This will get the max width for the font.
+ *
+ * @return The max width value.
+ */
+ public abstract float getMaxWidth();
+
+ /**
+ * This will set the max width for the font.
+ *
+ * @param maxWidth The new max width for the font.
+ */
+ public abstract void setMaxWidth( float maxWidth );
+
+ /**
+ * This will get the character set for the font.
+ *
+ * @return The character set value.
+ */
+ public abstract String getCharSet();
+
+ /**
+ * This will set the character set for the font.
+ *
+ * @param charSet The new character set for the font.
+ */
+ public abstract void setCharacterSet( String charSet );
+
+ /**
+ * This will get the missing width for the font.
+ *
+ * @return The missing width value.
+ */
+ public abstract float getMissingWidth();
+
+ /**
+ * This will set the missing width for the font.
+ *
+ * @param charSet The new missing width for the font.
+ */
+ public abstract void setMissingWidth( float missingWidth );
+
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.io.IOException;
+
+import org.apache.fontbox.afm.FontMetric;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+import org.apache.fontbox.util.BoundingBox;
+
+/**
+ * This class represents the font descriptor when the font information
+ * is coming from an AFM file.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDFontDescriptorAFM extends PDFontDescriptor
+{
+ private FontMetric afm;
+
+ /**
+ * Constructor.
+ *
+ * @param afmFile The AFM file.
+ */
+ public PDFontDescriptorAFM( FontMetric afmFile )
+ {
+ afm = afmFile;
+ }
+
+ /**
+ * Get the font name.
+ *
+ * @return The name of the font.
+ */
+ public String getFontName()
+ {
+ return afm.getFontName();
+ }
+
+ /**
+ * This will set the font name.
+ *
+ * @param fontName The new name for the font.
+ */
+ public void setFontName( String fontName )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * A string representing the preferred font family.
+ *
+ * @return The font family.
+ */
+ public String getFontFamily()
+ {
+ return afm.getFamilyName();
+ }
+
+ /**
+ * This will set the font family.
+ *
+ * @param fontFamily The font family.
+ */
+ public void setFontFamily( String fontFamily )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * The weight of the font. According to the PDF spec "possible values are
+ * 100, 200, 300, 400, 500, 600, 700, 800 or 900" Where a higher number is
+ * more weight and appears to be more bold.
+ *
+ * @return The font weight.
+ */
+ public float getFontWeight()
+ {
+ String weight = afm.getWeight();
+ float retval = 500;
+ if( weight != null && weight.equalsIgnoreCase( "bold" ) )
+ {
+ retval = 900;
+ }
+ else if( weight != null && weight.equalsIgnoreCase( "light" ) )
+ {
+ retval = 100;
+ }
+ return retval;
+ }
+
+ /**
+ * Set the weight of the font.
+ *
+ * @param fontWeight The new weight of the font.
+ */
+ public void setFontWeight( float fontWeight )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * A string representing the preferred font stretch.
+ *
+ * @return The font stretch.
+ */
+ public String getFontStretch()
+ {
+ return null;
+ }
+
+ /**
+ * This will set the font stretch.
+ *
+ * @param fontStretch The font stretch
+ */
+ public void setFontStretch( String fontStretch )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the font flags.
+ *
+ * @return The font flags.
+ */
+ public int getFlags()
+ {
+ //I believe that the only flag that AFM supports is the is fixed pitch
+ return afm.isFixedPitch() ? 1 : 0;
+ }
+
+ /**
+ * This will set the font flags.
+ *
+ * @param flags The new font flags.
+ */
+ public void setFlags( int flags )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the fonts bouding box.
+ *
+ * @return The fonts bouding box.
+ */
+ public PDRectangle getFontBoundingBox()
+ {
+ BoundingBox box = afm.getFontBBox();
+ PDRectangle retval = null;
+ if( box != null )
+ {
+ retval = new PDRectangle( box );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the fonts bounding box.
+ *
+ * @param rect The new bouding box.
+ */
+ public void setFontBoundingBox( PDRectangle rect )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the italic angle for the font.
+ *
+ * @return The italic angle.
+ */
+ public float getItalicAngle()
+ {
+ return afm.getItalicAngle();
+ }
+
+ /**
+ * This will set the italic angle for the font.
+ *
+ * @param angle The new italic angle for the font.
+ */
+ public void setItalicAngle( float angle )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the ascent for the font.
+ *
+ * @return The ascent.
+ */
+ public float getAscent()
+ {
+ return afm.getAscender();
+ }
+
+ /**
+ * This will set the ascent for the font.
+ *
+ * @param ascent The new ascent for the font.
+ */
+ public void setAscent( float ascent )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the descent for the font.
+ *
+ * @return The descent.
+ */
+ public float getDescent()
+ {
+ return afm.getDescender();
+ }
+
+ /**
+ * This will set the descent for the font.
+ *
+ * @param descent The new descent for the font.
+ */
+ public void setDescent( float descent )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the leading for the font.
+ *
+ * @return The leading.
+ */
+ public float getLeading()
+ {
+ //AFM does not support setting the leading so we will just ignore it.
+ return 0f;
+ }
+
+ /**
+ * This will set the leading for the font.
+ *
+ * @param leading The new leading for the font.
+ */
+ public void setLeading( float leading )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the CapHeight for the font.
+ *
+ * @return The cap height.
+ */
+ public float getCapHeight()
+ {
+ return afm.getCapHeight();
+ }
+
+ /**
+ * This will set the cap height for the font.
+ *
+ * @param capHeight The new cap height for the font.
+ */
+ public void setCapHeight( float capHeight )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the x height for the font.
+ *
+ * @return The x height.
+ */
+ public float getXHeight()
+ {
+ return afm.getXHeight();
+ }
+
+ /**
+ * This will set the x height for the font.
+ *
+ * @param xHeight The new x height for the font.
+ */
+ public void setXHeight( float xHeight )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the stemV for the font.
+ *
+ * @return The stem v value.
+ */
+ public float getStemV()
+ {
+ //afm does not have a stem v
+ return 0;
+ }
+
+ /**
+ * This will set the stem V for the font.
+ *
+ * @param stemV The new stem v for the font.
+ */
+ public void setStemV( float stemV )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the stemH for the font.
+ *
+ * @return The stem h value.
+ */
+ public float getStemH()
+ {
+ //afm does not have a stem h
+ return 0;
+ }
+
+ /**
+ * This will set the stem H for the font.
+ *
+ * @param stemH The new stem h for the font.
+ */
+ public void setStemH( float stemH )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the average width for the font.
+ *
+ * @return The average width value.
+ *
+ * @throws IOException If there is an error calculating the average width.
+ */
+ public float getAverageWidth() throws IOException
+ {
+ return afm.getAverageCharacterWidth();
+ }
+
+ /**
+ * This will set the average width for the font.
+ *
+ * @param averageWidth The new average width for the font.
+ */
+ public void setAverageWidth( float averageWidth )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the max width for the font.
+ *
+ * @return The max width value.
+ */
+ public float getMaxWidth()
+ {
+ //afm does not support max width;
+ return 0;
+ }
+
+ /**
+ * This will set the max width for the font.
+ *
+ * @param maxWidth The new max width for the font.
+ */
+ public void setMaxWidth( float maxWidth )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the missing width for the font.
+ *
+ * @return The missing width value.
+ */
+ public float getMissingWidth()
+ {
+ return 0;
+ }
+
+ /**
+ * This will set the missing width for the font.
+ *
+ * @param missingWidth The new missing width for the font.
+ */
+ public void setMissingWidth( float missingWidth )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+
+ /**
+ * This will get the character set for the font.
+ *
+ * @return The character set value.
+ */
+ public String getCharSet()
+ {
+ return afm.getCharacterSet();
+ }
+
+ /**
+ * This will set the character set for the font.
+ *
+ * @param charSet The new character set for the font.
+ */
+ public void setCharacterSet( String charSet )
+ {
+ throw new UnsupportedOperationException( "The AFM Font descriptor is immutable" );
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+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.COSString;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This class represents an implementation to the font descriptor that gets its
+ * information from a COS Dictionary.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDFontDescriptorDictionary extends PDFontDescriptor implements COSObjectable
+{
+ private COSDictionary dic;
+ private float xHeight = Float.NEGATIVE_INFINITY;
+ private float capHeight = Float.NEGATIVE_INFINITY;
+ private int flags = -1;
+
+ /**
+ * Constructor.
+ */
+ public PDFontDescriptorDictionary()
+ {
+ dic = new COSDictionary();
+ dic.setItem( COSName.TYPE, COSName.FONT_DESC );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param desc The wrapped COS Dictionary.
+ */
+ public PDFontDescriptorDictionary( COSDictionary desc )
+ {
+ dic = desc;
+ }
+
+ /**
+ * This will get the dictionary for this object.
+ *
+ * @return The COS dictionary.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return dic;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return dic;
+ }
+
+ /**
+ * Get the font name.
+ *
+ * @return The name of the font.
+ */
+ public String getFontName()
+ {
+ String retval = null;
+ COSName name = (COSName)dic.getDictionaryObject( COSName.FONT_NAME );
+ if( name != null )
+ {
+ retval = name.getName();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the font name.
+ *
+ * @param fontName The new name for the font.
+ */
+ public void setFontName( String fontName )
+ {
+ COSName name = null;
+ if( fontName != null )
+ {
+ name = COSName.getPDFName( fontName );
+ }
+ dic.setItem( COSName.FONT_NAME, name );
+ }
+
+ /**
+ * A string representing the preferred font family.
+ *
+ * @return The font family.
+ */
+ public String getFontFamily()
+ {
+ String retval = null;
+ COSString name = (COSString)dic.getDictionaryObject( COSName.FONT_FAMILY );
+ if( name != null )
+ {
+ retval = name.getString();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the font family.
+ *
+ * @param fontFamily The font family.
+ */
+ public void setFontFamily( String fontFamily )
+ {
+ COSString name = null;
+ if( fontFamily != null )
+ {
+ name = new COSString( fontFamily );
+ }
+ dic.setItem( COSName.FONT_FAMILY, name );
+ }
+
+ /**
+ * The weight of the font. According to the PDF spec "possible values are
+ * 100, 200, 300, 400, 500, 600, 700, 800 or 900" Where a higher number is
+ * more weight and appears to be more bold.
+ *
+ * @return The font weight.
+ */
+ public float getFontWeight()
+ {
+ return dic.getFloat( COSName.FONT_WEIGHT,0 );
+ }
+
+ /**
+ * Set the weight of the font.
+ *
+ * @param fontWeight The new weight of the font.
+ */
+ public void setFontWeight( float fontWeight )
+ {
+ dic.setFloat( COSName.FONT_WEIGHT, fontWeight );
+ }
+
+ /**
+ * A string representing the preferred font stretch.
+ * According to the PDF Spec:
+ * The font stretch value; it must be one of the following (ordered from
+ * narrowest to widest): UltraCondensed, ExtraCondensed, Condensed, SemiCondensed,
+ * Normal, SemiExpanded, Expanded, ExtraExpanded or UltraExpanded.
+ *
+ * @return The stretch of the font.
+ */
+ public String getFontStretch()
+ {
+ String retval = null;
+ COSName name = (COSName)dic.getDictionaryObject( COSName.FONT_STRETCH );
+ if( name != null )
+ {
+ retval = name.getName();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the font stretch.
+ *
+ * @param fontStretch The new stretch for the font.
+ */
+ public void setFontStretch( String fontStretch )
+ {
+ COSName name = null;
+ if( fontStretch != null )
+ {
+ name = COSName.getPDFName( fontStretch );
+ }
+ dic.setItem( COSName.FONT_STRETCH, name );
+ }
+
+ /**
+ * This will get the font flags.
+ *
+ * @return The font flags.
+ */
+ public int getFlags()
+ {
+ if (flags == -1)
+ {
+ flags = dic.getInt( COSName.FLAGS, 0 );
+ }
+ return flags;
+ }
+
+ /**
+ * This will set the font flags.
+ *
+ * @param flags The new font flags.
+ */
+ public void setFlags( int flags )
+ {
+ dic.setInt( COSName.FLAGS, flags );
+ this.flags = flags;
+ }
+
+ /**
+ * This will get the fonts bounding box.
+ *
+ * @return The fonts bounding box.
+ */
+ public PDRectangle getFontBoundingBox()
+ {
+ COSArray rect = (COSArray)dic.getDictionaryObject( COSName.FONT_BBOX );
+ PDRectangle retval = null;
+ if( rect != null )
+ {
+ retval = new PDRectangle( rect );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the fonts bounding box.
+ *
+ * @param rect The new bouding box.
+ */
+ public void setFontBoundingBox( PDRectangle rect )
+ {
+ COSArray array = null;
+ if( rect != null )
+ {
+ array = rect.getCOSArray();
+ }
+ dic.setItem( COSName.FONT_BBOX, array );
+ }
+
+ /**
+ * This will get the italic angle for the font.
+ *
+ * @return The italic angle.
+ */
+ public float getItalicAngle()
+ {
+ return dic.getFloat( COSName.ITALIC_ANGLE, 0 );
+ }
+
+ /**
+ * This will set the italic angle for the font.
+ *
+ * @param angle The new italic angle for the font.
+ */
+ public void setItalicAngle( float angle )
+ {
+ dic.setFloat( COSName.ITALIC_ANGLE, angle );
+ }
+
+ /**
+ * This will get the ascent for the font.
+ *
+ * @return The ascent.
+ */
+ public float getAscent()
+ {
+ return dic.getFloat( COSName.ASCENT, 0 );
+ }
+
+ /**
+ * This will set the ascent for the font.
+ *
+ * @param ascent The new ascent for the font.
+ */
+ public void setAscent( float ascent )
+ {
+ dic.setFloat( COSName.ASCENT, ascent );
+ }
+
+ /**
+ * This will get the descent for the font.
+ *
+ * @return The descent.
+ */
+ public float getDescent()
+ {
+ return dic.getFloat( COSName.DESCENT, 0 );
+ }
+
+ /**
+ * This will set the descent for the font.
+ *
+ * @param descent The new descent for the font.
+ */
+ public void setDescent( float descent )
+ {
+ dic.setFloat( COSName.DESCENT, descent );
+ }
+
+ /**
+ * This will get the leading for the font.
+ *
+ * @return The leading.
+ */
+ public float getLeading()
+ {
+ return dic.getFloat( COSName.LEADING, 0 );
+ }
+
+ /**
+ * This will set the leading for the font.
+ *
+ * @param leading The new leading for the font.
+ */
+ public void setLeading( float leading )
+ {
+ dic.setFloat( COSName.LEADING, leading );
+ }
+
+ /**
+ * This will get the CapHeight for the font.
+ *
+ * @return The cap height.
+ */
+ public float getCapHeight()
+ {
+ if(capHeight==Float.NEGATIVE_INFINITY){
+ /* We observed a negative value being returned with
+ * the Scheherazade font. PDFBOX-429 was logged for this.
+ * We are not sure if returning the absolute value
+ * is the correct fix, but it seems to work. */
+ capHeight = java.lang.Math.abs(dic.getFloat( COSName.CAP_HEIGHT, 0 ));
+ }
+ return capHeight;
+ }
+
+
+ /**
+ * This will set the cap height for the font.
+ *
+ * @param capHeight The new cap height for the font.
+ */
+ public void setCapHeight( float capHeight )
+ {
+ dic.setFloat( COSName.CAP_HEIGHT, capHeight );
+ this.capHeight = capHeight;
+ }
+
+ /**
+ * This will get the x height for the font.
+ *
+ * @return The x height.
+ */
+ public float getXHeight()
+ {
+ if(xHeight==Float.NEGATIVE_INFINITY){
+ /* We observed a negative value being returned with
+ * the Scheherazade font. PDFBOX-429 was logged for this.
+ * We are not sure if returning the absolute value
+ * is the correct fix, but it seems to work. */
+ xHeight = java.lang.Math.abs(dic.getFloat( COSName.XHEIGHT, 0 ));
+ }
+ return xHeight;
+ }
+
+ /**
+ * This will set the x height for the font.
+ *
+ * @param xHeight The new x height for the font.
+ */
+ public void setXHeight( float xHeight )
+ {
+ dic.setFloat( COSName.XHEIGHT, xHeight );
+ this.xHeight = xHeight;
+ }
+
+ /**
+ * This will get the stemV for the font.
+ *
+ * @return The stem v value.
+ */
+ public float getStemV()
+ {
+ return dic.getFloat( COSName.STEM_V, 0 );
+ }
+
+ /**
+ * This will set the stem V for the font.
+ *
+ * @param stemV The new stem v for the font.
+ */
+ public void setStemV( float stemV )
+ {
+ dic.setFloat( COSName.STEM_V, stemV );
+ }
+
+ /**
+ * This will get the stemH for the font.
+ *
+ * @return The stem h value.
+ */
+ public float getStemH()
+ {
+ return dic.getFloat( COSName.STEM_H, 0 );
+ }
+
+ /**
+ * This will set the stem H for the font.
+ *
+ * @param stemH The new stem h for the font.
+ */
+ public void setStemH( float stemH )
+ {
+ dic.setFloat( COSName.STEM_H, stemH );
+ }
+
+ /**
+ * This will get the average width for the font.
+ *
+ * @return The average width value.
+ */
+ public float getAverageWidth()
+ {
+ return dic.getFloat( COSName.AVG_WIDTH, 0 );
+ }
+
+ /**
+ * This will set the average width for the font.
+ *
+ * @param averageWidth The new average width for the font.
+ */
+ public void setAverageWidth( float averageWidth )
+ {
+ dic.setFloat( COSName.AVG_WIDTH, averageWidth );
+ }
+
+ /**
+ * This will get the max width for the font.
+ *
+ * @return The max width value.
+ */
+ public float getMaxWidth()
+ {
+ return dic.getFloat( COSName.MAX_WIDTH, 0 );
+ }
+
+ /**
+ * This will set the max width for the font.
+ *
+ * @param maxWidth The new max width for the font.
+ */
+ public void setMaxWidth( float maxWidth )
+ {
+ dic.setFloat( COSName.MAX_WIDTH, maxWidth );
+ }
+
+ /**
+ * This will get the missing width for the font.
+ *
+ * @return The missing width value.
+ */
+ public float getMissingWidth()
+ {
+ return dic.getFloat( COSName.MISSING_WIDTH, 0 );
+ }
+
+ /**
+ * This will set the missing width for the font.
+ *
+ * @param missingWidth The new missing width for the font.
+ */
+ public void setMissingWidth( float missingWidth )
+ {
+ dic.setFloat( COSName.MISSING_WIDTH, missingWidth );
+ }
+
+ /**
+ * This will get the character set for the font.
+ *
+ * @return The character set value.
+ */
+ public String getCharSet()
+ {
+ String retval = null;
+ COSString name = (COSString)dic.getDictionaryObject( COSName.CHAR_SET );
+ if( name != null )
+ {
+ retval = name.getString();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the character set for the font.
+ *
+ * @param charSet The new character set for the font.
+ */
+ public void setCharacterSet( String charSet )
+ {
+ COSString name = null;
+ if( charSet != null )
+ {
+ name = new COSString( charSet );
+ }
+ dic.setItem( COSName.CHAR_SET, name );
+ }
+
+ /**
+ * A stream containing a Type 1 font program.
+ *
+ * @return A stream containing a Type 1 font program.
+ */
+ public PDStream getFontFile()
+ {
+ PDStream retval = null;
+ COSStream stream = (COSStream)dic.getDictionaryObject( COSName.FONT_FILE );
+ if( stream != null )
+ {
+ retval = new PDStream( stream );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the type 1 font program.
+ *
+ * @param type1Stream The type 1 stream.
+ */
+ public void setFontFile( PDStream type1Stream )
+ {
+ dic.setItem( COSName.FONT_FILE, type1Stream );
+ }
+
+ /**
+ * A stream containing a true type font program.
+ *
+ * @return A stream containing a true type font program.
+ */
+ public PDStream getFontFile2()
+ {
+ PDStream retval = null;
+ COSStream stream = (COSStream)dic.getDictionaryObject( COSName.FONT_FILE2 );
+ if( stream != null )
+ {
+ retval = new PDStream( stream );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the true type font program.
+ *
+ * @param ttfStream The true type stream.
+ */
+ public void setFontFile2( PDStream ttfStream )
+ {
+ dic.setItem( COSName.FONT_FILE2, ttfStream );
+ }
+
+ /**
+ * A stream containing a font program that is not true type or type 1.
+ *
+ * @return A stream containing a font program.
+ */
+ public PDStream getFontFile3()
+ {
+ PDStream retval = null;
+ COSStream stream = (COSStream)dic.getDictionaryObject( COSName.FONT_FILE3 );
+ if( stream != null )
+ {
+ retval = new PDStream( stream );
+ }
+ return retval;
+ }
+
+ /**
+ * Set a stream containing a font program that is not true type or type 1.
+ *
+ * @param stream The font program stream.
+ */
+ public void setFontFile3( PDStream stream )
+ {
+ dic.setItem( COSName.FONT_FILE3, stream );
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This will create the correct type of font based on information in the dictionary.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDFontFactory
+{
+ /**
+ * private constructor, should only use static methods in this class.
+ */
+ private PDFontFactory()
+ {
+ }
+
+ /**
+ * Logger instance.
+ */
+ private static final Log log = LogFactory.getLog(PDFontFactory.class);
+
+ /**
+ * This will create the correct font based on information in the dictionary.
+ *
+ * @param dic The populated dictionary.
+ *
+ * @param fontCache A Map to cache already created fonts
+ *
+ * @return The corrent implementation for the font.
+ *
+ * @throws IOException If the dictionary is not valid.
+ */
+ public static PDFont createFont(COSDictionary dic, Map fontCache) throws IOException
+ {
+ PDFont retval = null;
+ if (fontCache != null)
+ {
+ String fontKey = dic.getNameAsString(COSName.BASE_FONT) + dic.getNameAsString(COSName.NAME)
+ + dic.getNameAsString(COSName.SUBTYPE);
+ if (dic.getItem(COSName.ENCODING) != null)
+ {
+ fontKey += dic.getItem(COSName.ENCODING).toString();
+ }
+ if (fontCache.containsKey(fontKey))
+ {
+ retval = (PDFont)fontCache.get(fontKey);
+ }
+ else
+ {
+ retval = PDFontFactory.createFont( dic );
+ fontCache.put(fontKey, retval);
+ }
+ }
+ else
+ {
+ retval = PDFontFactory.createFont( dic );
+ }
+ return retval;
+ }
+
+ /**
+ * This will create the correct font based on information in the dictionary.
+ *
+ * @param dic The populated dictionary.
+ *
+ * @return The corrent implementation for the font.
+ *
+ * @throws IOException If the dictionary is not valid.
+ */
+ public static PDFont createFont( COSDictionary dic ) throws IOException
+ {
+ PDFont retval = null;
+
+ COSName type = (COSName)dic.getDictionaryObject( COSName.TYPE );
+ if( !type.equals( COSName.FONT ) )
+ {
+ throw new IOException( "Cannot create font if /Type is not /Font. Actual=" +type );
+ }
+
+ COSName subType = (COSName)dic.getDictionaryObject( COSName.SUBTYPE );
+ if( subType.equals( COSName.TYPE1) )
+ {
+ retval = new PDType1Font( dic );
+ }
+ else if( subType.equals( COSName.MM_TYPE1 ) )
+ {
+ retval = new PDMMType1Font( dic );
+ }
+ else if( subType.equals( COSName.TRUE_TYPE ) )
+ {
+ retval = new PDTrueTypeFont( dic );
+ }
+ else if( subType.equals( COSName.TYPE3 ) )
+ {
+ retval = new PDType3Font( dic );
+ }
+ else if( subType.equals( COSName.TYPE0 ) )
+ {
+ retval = new PDType0Font( dic );
+ }
+ else if( subType.equals( COSName.CID_FONT_TYPE0 ) )
+ {
+ retval = new PDCIDFontType0Font( dic );
+ }
+ else if( subType.equals( COSName.CID_FONT_TYPE2 ) )
+ {
+ retval = new PDCIDFontType2Font( dic );
+ }
+ else
+ {
+ log.warn("Substituting TrueType for unknown font subtype=" + dic.getDictionaryObject( COSName.SUBTYPE ).toString());
+ //throw new IOException( "Unknown font subtype=" + subType );
+ retval = new PDTrueTypeFont( dic );
+ }
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is implementation of the Multiple Master Type1 Font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDMMType1Font extends PDSimpleFont
+{
+ /**
+ * Constructor.
+ */
+ public PDMMType1Font()
+ {
+ super();
+ font.setItem( COSName.SUBTYPE, COSName.MM_TYPE1 );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fontDictionary The font dictionary according to the PDF specification.
+ */
+ public PDMMType1Font( COSDictionary fontDictionary )
+ {
+ super( fontDictionary );
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.NoninvertibleTransformException;
+import java.io.IOException;
+
+import java.util.HashMap;
+
+import org.apache.fontbox.afm.FontMetric;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.encoding.DictionaryEncoding;
+import org.apache.pdfbox.encoding.Encoding;
+import org.apache.pdfbox.encoding.EncodingManager;
+import org.apache.pdfbox.encoding.conversion.CMapSubstitution;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.util.ResourceLoader;
+
+/**
+ * This class contains implementation details of the simple pdf fonts.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.18 $
+ */
+public abstract class PDSimpleFont extends PDFont
+{
+ private final HashMap<Integer, Float> mFontSizes =
+ new HashMap<Integer, Float>(128);
+
+ private float avgFontWidth = 0.0f;
+
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDSimpleFont.class);
+
+ /**
+ * Constructor.
+ */
+ public PDSimpleFont()
+ {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fontDictionary The font dictionary according to the PDF specification.
+ */
+ public PDSimpleFont( COSDictionary fontDictionary )
+ {
+ super( fontDictionary );
+ }
+
+ /**
+ * Looks up, creates, returns the AWT Font.
+ */
+ public Font getawtFont() throws IOException
+ {
+ log.error("Not yet implemented:" + getClass().getName() );
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void drawString( String string, Graphics g, float fontSize,
+ AffineTransform at, float x, float y ) throws IOException
+ {
+ Font _awtFont = getawtFont();
+
+ // mdavis - fix fontmanager.so/dll on sun.font.FileFont.getGlyphImage
+ // for font with bad cmaps?
+ // Type1 fonts are not affected as they don't have cmaps
+ if (!isType1Font() && _awtFont.canDisplayUpTo(string) != -1) {
+ log.warn("Changing font on <" + string + "> from <"
+ + _awtFont.getName() + "> to the default font");
+ _awtFont = Font.decode(null);
+ }
+
+ Graphics2D g2d = (Graphics2D)g;
+ g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
+ writeFont(g2d, at, _awtFont, x, y, string);
+ }
+
+ /**
+ * This will get the font height for a character.
+ *
+ * @param c The character code to get the width for.
+ * @param offset The offset into the array.
+ * @param length The length of the data.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public float getFontHeight( byte[] c, int offset, int length ) throws IOException
+ {
+ float retval = 0;
+ int code = getCodeFromArray( c, offset, length );
+ FontMetric metric = getAFM();
+ if( metric != null )
+ {
+ Encoding encoding = getFontEncoding();
+ String characterName = encoding.getName( code );
+ retval = metric.getCharacterHeight( characterName );
+ }
+ else
+ {
+ PDFontDescriptor desc = getFontDescriptor();
+ if( desc != null )
+ {
+ float xHeight = desc.getXHeight();
+ float capHeight = desc.getCapHeight();
+ if( xHeight != 0f && capHeight != 0 )
+ {
+ //do an average of these two. Can we do better???
+ retval = (xHeight + capHeight)/2f;
+ }
+ else if( xHeight != 0 )
+ {
+ retval = xHeight;
+ }
+ else if( capHeight != 0 )
+ {
+ retval = capHeight;
+ }
+ else
+ {
+ retval = 0;
+ }
+ //hmm, not sure if this is 100% correct
+ //but gives a height, Should we add Descent as well??
+ if( retval == 0 )
+ {
+ retval = desc.getAscent();
+ }
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the font width for a character.
+ *
+ * @param c The character code to get the width for.
+ * @param offset The offset into the array.
+ * @param length The length of the data.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public float getFontWidth( byte[] c, int offset, int length ) throws IOException
+ {
+ int code = getCodeFromArray( c, offset, length );
+ Float fontWidth = mFontSizes.get(code);
+ if (fontWidth == null)
+ {
+ fontWidth = getFontWidth(code);
+ if (fontWidth == -1)
+ {
+ //hmm should this be in PDType1Font??
+ fontWidth = getFontWidthFromAFMFile( code );
+ }
+ mFontSizes.put(code, fontWidth);
+ }
+ return fontWidth;
+ }
+
+ /**
+ * This will get the average font width for all characters.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public float getAverageFontWidth() throws IOException
+ {
+ float average = 0.0f;
+
+ //AJW
+ if (avgFontWidth != 0.0f)
+ {
+ average = avgFontWidth;
+ }
+ else
+ {
+ float totalWidth = 0.0f;
+ float characterCount = 0.0f;
+ COSArray widths = (COSArray)font.getDictionaryObject( COSName.WIDTHS );
+ if( widths != null )
+ {
+ for( int i=0; i<widths.size(); i++ )
+ {
+ COSNumber fontWidth = (COSNumber)widths.getObject( i );
+ if( fontWidth.floatValue() > 0 )
+ {
+ totalWidth += fontWidth.floatValue();
+ characterCount += 1;
+ }
+ }
+ }
+
+ if( totalWidth > 0 )
+ {
+ average = totalWidth / characterCount;
+ }
+ else
+ {
+ average = getAverageFontWidthFromAFMFile();
+ }
+ avgFontWidth = average;
+ }
+ return average;
+ }
+
+
+ /**
+ * This will get the ToUnicode object.
+ *
+ * @return The ToUnicode object.
+ */
+ public COSBase getToUnicode()
+ {
+ return font.getDictionaryObject( COSName.TO_UNICODE );
+ }
+
+ /**
+ * This will set the ToUnicode object.
+ *
+ * @param unicode The unicode object.
+ */
+ public void setToUnicode( COSBase unicode )
+ {
+ font.setItem( COSName.TO_UNICODE, unicode );
+ }
+
+ /**
+ * This will get the fonts bounding box.
+ *
+ * @return The fonts bouding box.
+ *
+ * @throws IOException If there is an error getting the bounding box.
+ */
+ public PDRectangle getFontBoundingBox() throws IOException
+ {
+ return getFontDescriptor().getFontBoundingBox();
+ }
+
+ /**
+ * This will draw a string on a canvas using the font.
+ *
+ * @param g2d The graphics to draw onto.
+ * @param at The transformation matrix with all infos for scaling and shearing of the font.
+ * @param awtFont The font to draw.
+ * @param x The x coordinate to draw at.
+ * @param y The y coordinate to draw at.
+ * @param string The string to draw.
+ *
+ */
+ protected void writeFont(final Graphics2D g2d, final AffineTransform at, final Font awtFont,
+ final float x, final float y, final String string)
+ {
+ // check if we have a rotation
+ if (!at.isIdentity())
+ {
+ try
+ {
+ AffineTransform atInv = at.createInverse();
+ // do only apply the size of the transform, rotation will be realized by rotating the graphics,
+ // otherwise the hp printers will not render the font
+ g2d.setFont(awtFont.deriveFont(1f));
+ // apply the inverse transformation to the graphics, which should be the same as applying the
+ // transformation itself to the text
+ g2d.transform(at);
+ // translate the coordinates
+ Point2D.Float newXy = new Point2D.Float(x,y);
+ atInv.transform(new Point2D.Float( x, y), newXy);
+ g2d.drawString( string, (float)newXy.getX(), (float)newXy.getY() );
+ // restore the original transformation
+ g2d.transform(atInv);
+ }
+ catch (NoninvertibleTransformException e)
+ {
+ log.error("Error in "+getClass().getName()+".writeFont",e);
+ }
+ }
+ else
+ {
+ g2d.setFont( awtFont.deriveFont( at ) );
+ g2d.drawString( string, x, y );
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void determineEncoding()
+ {
+ String cmapName = null;
+ COSName encodingName = null;
+ COSBase encoding = getEncoding();
+ Encoding fontEncoding = null;
+ if (encoding != null)
+ {
+ if (encoding instanceof COSName)
+ {
+ if (cmap == null)
+ {
+ encodingName = (COSName)encoding;
+ cmap = cmapObjects.get( encodingName.getName() );
+ if (cmap == null)
+ {
+ cmapName = encodingName.getName();
+ }
+ }
+ if (cmap == null && cmapName != null)
+ {
+ try
+ {
+ fontEncoding =
+ EncodingManager.INSTANCE.getEncoding(encodingName);
+ }
+ catch(IOException exception)
+ {
+ log.debug("Debug: Could not find encoding for " + encodingName );
+ }
+ }
+ }
+ else if (encoding instanceof COSDictionary)
+ {
+ try
+ {
+ fontEncoding = new DictionaryEncoding((COSDictionary)encoding);
+ }
+ catch(IOException exception)
+ {
+ log.error("Error: Could not create the DictionaryEncoding" );
+ }
+ }
+ else if(encoding instanceof COSStream )
+ {
+ if (cmap == null)
+ {
+ COSStream encodingStream = (COSStream)encoding;
+ try
+ {
+ parseCmap( null, encodingStream.getUnfilteredStream() );
+ }
+ catch(IOException exception)
+ {
+ log.error("Error: Could not parse the embedded CMAP" );
+ }
+ }
+ }
+ }
+ setFontEncoding(fontEncoding);
+ extractToUnicodeEncoding();
+
+ if (cmap == null && cmapName != null)
+ {
+ String resourceName = resourceRootCMAP + cmapName;
+ try {
+ parseCmap( resourceRootCMAP, ResourceLoader.loadResource( resourceName ) );
+ if( cmap == null && encodingName == null)
+ {
+ log.error("Error: Could not parse predefined CMAP file for '" + cmapName + "'" );
+ }
+ }
+ catch(IOException exception)
+ {
+ log.error("Error: Could not find predefined CMAP file for '" + cmapName + "'" );
+ }
+ }
+ }
+
+ private void extractToUnicodeEncoding()
+ {
+ COSName encodingName = null;
+ String cmapName = null;
+ COSBase toUnicode = getToUnicode();
+ if( toUnicode != null )
+ {
+ setHasToUnicode(true);
+ if ( toUnicode instanceof COSStream )
+ {
+ try {
+ parseCmap(null, ((COSStream)toUnicode).getUnfilteredStream());
+ }
+ catch(IOException exception)
+ {
+ log.error("Error: Could not load embedded CMAP" );
+ }
+ }
+ else if ( toUnicode instanceof COSName)
+ {
+ encodingName = (COSName)toUnicode;
+ cmap = cmapObjects.get( encodingName.getName() );
+ if (cmap == null)
+ {
+ cmapName = encodingName.getName();
+ String resourceName = resourceRootCMAP + cmapName;
+ try {
+ parseCmap( resourceRootCMAP, ResourceLoader.loadResource( resourceName ));
+ }
+ catch(IOException exception)
+ {
+ log.error("Error: Could not find predefined CMAP file for '" + cmapName + "'" );
+ }
+ if( cmap == null)
+ {
+ log.error("Error: Could not parse predefined CMAP file for '" + cmapName + "'" );
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.ttf.CMAPEncodingEntry;
+import org.apache.fontbox.ttf.CMAPTable;
+import org.apache.fontbox.ttf.GlyphData;
+import org.apache.fontbox.ttf.GlyphTable;
+import org.apache.fontbox.ttf.HeaderTable;
+import org.apache.fontbox.ttf.HorizontalHeaderTable;
+import org.apache.fontbox.ttf.HorizontalMetricsTable;
+import org.apache.fontbox.ttf.NamingTable;
+import org.apache.fontbox.ttf.NameRecord;
+import org.apache.fontbox.ttf.OS2WindowsMetricsTable;
+import org.apache.fontbox.ttf.PostScriptTable;
+import org.apache.fontbox.ttf.TTFParser;
+import org.apache.fontbox.ttf.TrueTypeFont;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.encoding.WinAnsiEncoding;
+import org.apache.pdfbox.util.ResourceLoader;
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This is the TrueType implementation of fonts.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.17 $
+ */
+public class PDTrueTypeFont extends PDSimpleFont
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDTrueTypeFont.class);
+
+ /**
+ * This is the key to a property in the PDFBox_External_Fonts.properties
+ * file to load a Font when a mapping does not exist for the current font.
+ */
+ public static final String UNKNOWN_FONT = "UNKNOWN_FONT";
+
+ private Font awtFont = null;
+
+ private static Properties externalFonts = new Properties();
+ private static Map<String,TrueTypeFont> loadedExternalFonts = new HashMap<String,TrueTypeFont>();
+
+ static
+ {
+ try
+ {
+ ResourceLoader.loadProperties(
+ "org/apache/pdfbox/resources/PDFBox_External_Fonts.properties",
+ externalFonts );
+ }
+ catch( IOException io )
+ {
+ throw new RuntimeException( "Error loading font resources", io );
+ }
+ }
+
+
+ /**
+ * Constructor.
+ */
+ public PDTrueTypeFont()
+ {
+ super();
+ font.setItem( COSName.SUBTYPE, COSName.TRUE_TYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fontDictionary The font dictionary according to the PDF specification.
+ */
+ public PDTrueTypeFont( COSDictionary fontDictionary ) throws IOException
+ {
+ super( fontDictionary );
+ ensureFontDescriptor();
+ }
+
+ /**
+ * This will load a TTF font from a font file.
+ *
+ * @param doc The PDF document that will hold the embedded font.
+ * @param file The file on the filesystem that holds the font file.
+ * @return A true type font.
+ * @throws IOException If there is an error loading the file data.
+ */
+ public static PDTrueTypeFont loadTTF( PDDocument doc, String file ) throws IOException
+ {
+ return loadTTF( doc, new File( file ) );
+ }
+
+ /**
+ * This will load a TTF to be embedded into a document.
+ *
+ * @param doc The PDF document that will hold the embedded font.
+ * @param file a ttf file.
+ * @return a PDTrueTypeFont instance.
+ * @throws IOException If there is an error loading the data.
+ */
+ public static PDTrueTypeFont loadTTF( PDDocument doc, File file ) throws IOException
+ {
+ return loadTTF( doc, new FileInputStream( file ) );
+ }
+
+ /**
+ * This will load a TTF to be embedded into a document.
+ *
+ * @param doc The PDF document that will hold the embedded font.
+ * @param stream a ttf input stream.
+ * @return a PDTrueTypeFont instance.
+ * @throws IOException If there is an error loading the data.
+ */
+ public static PDTrueTypeFont loadTTF( PDDocument doc, InputStream stream ) throws IOException
+ {
+ PDTrueTypeFont retval = new PDTrueTypeFont();
+ PDFontDescriptorDictionary fd = new PDFontDescriptorDictionary();
+ retval.setFontDescriptor( fd );
+ PDStream fontStream = new PDStream(doc, stream, false );
+ fontStream.getStream().setInt( COSName.LENGTH1, fontStream.getByteArray().length );
+ fontStream.addCompression();
+ fd.setFontFile2( fontStream );
+ // As the stream was close within the PDStream constructor, we have to recreate it
+ stream = fontStream.createInputStream();
+ try
+ {
+ retval.loadDescriptorDictionary(fd, stream);
+ }
+ finally
+ {
+ stream.close();
+ }
+ //only support winansi encoding right now, should really
+ //just use Identity-H with unicode mapping
+ retval.setFontEncoding( new WinAnsiEncoding() );
+ retval.setEncoding(COSName.WIN_ANSI_ENCODING);
+ return retval;
+ }
+
+ private void ensureFontDescriptor() throws IOException
+ {
+ if( getFontDescriptor() == null )
+ {
+ PDFontDescriptorDictionary fdd = new PDFontDescriptorDictionary();
+ setFontDescriptor(fdd);
+ InputStream ttfData = getExternalTTFData();
+ if( ttfData != null )
+ {
+ try
+ {
+ loadDescriptorDictionary(fdd, ttfData);
+ }
+ finally
+ {
+ ttfData.close();
+ }
+ }
+ }
+ }
+
+ private void loadDescriptorDictionary(PDFontDescriptorDictionary fd, InputStream ttfData) throws IOException
+ {
+ TrueTypeFont ttf = null;
+ try
+ {
+ TTFParser parser = new TTFParser();
+ ttf = parser.parseTTF( ttfData );
+ NamingTable naming = ttf.getNaming();
+ List<NameRecord> records = naming.getNameRecords();
+ for( int i=0; i<records.size(); i++ )
+ {
+ NameRecord nr = records.get( i );
+ if( nr.getNameId() == NameRecord.NAME_POSTSCRIPT_NAME )
+ {
+ setBaseFont( nr.getString() );
+ fd.setFontName( nr.getString() );
+ }
+ else if( nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME )
+ {
+ fd.setFontFamily( nr.getString() );
+ }
+ }
+
+ OS2WindowsMetricsTable os2 = ttf.getOS2Windows();
+ fd.setNonSymbolic( true );
+ switch( os2.getFamilyClass() )
+ {
+ case OS2WindowsMetricsTable.FAMILY_CLASS_SYMBOLIC:
+ fd.setSymbolic( true );
+ fd.setNonSymbolic( false );
+ break;
+ case OS2WindowsMetricsTable.FAMILY_CLASS_SCRIPTS:
+ fd.setScript( true );
+ break;
+ case OS2WindowsMetricsTable.FAMILY_CLASS_CLAREDON_SERIFS:
+ case OS2WindowsMetricsTable.FAMILY_CLASS_FREEFORM_SERIFS:
+ case OS2WindowsMetricsTable.FAMILY_CLASS_MODERN_SERIFS:
+ case OS2WindowsMetricsTable.FAMILY_CLASS_OLDSTYLE_SERIFS:
+ case OS2WindowsMetricsTable.FAMILY_CLASS_SLAB_SERIFS:
+ fd.setSerif( true );
+ break;
+ default:
+ //do nothing
+ }
+ switch( os2.getWidthClass() )
+ {
+ case OS2WindowsMetricsTable.WIDTH_CLASS_ULTRA_CONDENSED:
+ fd.setFontStretch( "UltraCondensed" );
+ break;
+ case OS2WindowsMetricsTable.WIDTH_CLASS_EXTRA_CONDENSED:
+ fd.setFontStretch( "ExtraCondensed" );
+ break;
+ case OS2WindowsMetricsTable.WIDTH_CLASS_CONDENSED:
+ fd.setFontStretch( "Condensed" );
+ break;
+ case OS2WindowsMetricsTable.WIDTH_CLASS_SEMI_CONDENSED:
+ fd.setFontStretch( "SemiCondensed" );
+ break;
+ case OS2WindowsMetricsTable.WIDTH_CLASS_MEDIUM:
+ fd.setFontStretch( "Normal" );
+ break;
+ case OS2WindowsMetricsTable.WIDTH_CLASS_SEMI_EXPANDED:
+ fd.setFontStretch( "SemiExpanded" );
+ break;
+ case OS2WindowsMetricsTable.WIDTH_CLASS_EXPANDED:
+ fd.setFontStretch( "Expanded" );
+ break;
+ case OS2WindowsMetricsTable.WIDTH_CLASS_EXTRA_EXPANDED:
+ fd.setFontStretch( "ExtraExpanded" );
+ break;
+ case OS2WindowsMetricsTable.WIDTH_CLASS_ULTRA_EXPANDED:
+ fd.setFontStretch( "UltraExpanded" );
+ break;
+ default:
+ //do nothing
+ }
+ fd.setFontWeight( os2.getWeightClass() );
+
+ //todo retval.setFixedPitch
+ //todo retval.setNonSymbolic
+ //todo retval.setItalic
+ //todo retval.setAllCap
+ //todo retval.setSmallCap
+ //todo retval.setForceBold
+
+ HeaderTable header = ttf.getHeader();
+ PDRectangle rect = new PDRectangle();
+ float scaling = 1000f/header.getUnitsPerEm();
+ rect.setLowerLeftX( header.getXMin() * 1000f/header.getUnitsPerEm() );
+ rect.setLowerLeftY( header.getYMin() * 1000f/header.getUnitsPerEm() );
+ rect.setUpperRightX( header.getXMax() * 1000f/header.getUnitsPerEm() );
+ rect.setUpperRightY( header.getYMax() * 1000f/header.getUnitsPerEm() );
+ fd.setFontBoundingBox( rect );
+
+ HorizontalHeaderTable hHeader = ttf.getHorizontalHeader();
+ fd.setAscent( hHeader.getAscender() * 1000f/header.getUnitsPerEm() );
+ fd.setDescent( hHeader.getDescender() * 1000f/header.getUnitsPerEm() );
+
+ GlyphTable glyphTable = ttf.getGlyph();
+ GlyphData[] glyphs = glyphTable.getGlyphs();
+
+ PostScriptTable ps = ttf.getPostScript();
+ fd.setFixedPitch( ps.getIsFixedPitch() > 0 );
+ fd.setItalicAngle( ps.getItalicAngle() );
+
+ String[] names = ps.getGlyphNames();
+ if( names != null )
+ {
+ for( int i=0; i<names.length; i++ )
+ {
+ //if we have a capital H then use that, otherwise use the
+ //tallest letter
+ if( names[i].equals( "H" ) )
+ {
+ fd.setCapHeight( (glyphs[i].getBoundingBox().getUpperRightY()* 1000f)/
+ header.getUnitsPerEm() );
+ }
+ if( names[i].equals( "x" ) )
+ {
+ fd.setXHeight( (glyphs[i].getBoundingBox().getUpperRightY()* 1000f)/header.getUnitsPerEm() );
+ }
+ }
+ }
+
+ //hmm there does not seem to be a clear definition for StemV,
+ //this is close enough and I am told it doesn't usually get used.
+ fd.setStemV( (fd.getFontBoundingBox().getWidth() * .13f) );
+
+
+ CMAPTable cmapTable = ttf.getCMAP();
+ CMAPEncodingEntry[] cmaps = cmapTable.getCmaps();
+ int[] glyphToCCode = null;
+ for( int i=0; i<cmaps.length; i++ )
+ {
+ if( cmaps[i].getPlatformId() == CMAPTable.PLATFORM_WINDOWS &&
+ cmaps[i].getPlatformEncodingId() == CMAPTable.ENCODING_UNICODE )
+ {
+ glyphToCCode = cmaps[i].getGlyphIdToCharacterCode();
+ }
+ }
+ int firstChar = 0;
+ int maxWidths = glyphToCCode.length;
+ HorizontalMetricsTable hMet = ttf.getHorizontalMetrics();
+ int[] widthValues = hMet.getAdvanceWidth();
+ List<Float> widths = new ArrayList<Float>(maxWidths);
+ float zero = 250;
+ for( int i=0; i<maxWidths; i++ )
+ {
+ widths.add( zero );
+ }
+ for( int i=0; i<widthValues.length; i++ )
+ {
+ if(glyphToCCode[i]-firstChar < widths.size() && glyphToCCode[i]-firstChar >= 0
+ && widths.get( glyphToCCode[i]-firstChar) == zero )
+ {
+ widths.set( glyphToCCode[i]-firstChar, widthValues[i]*scaling );
+ }
+ }
+ setWidths( widths );
+ setFirstChar( firstChar );
+ setLastChar( firstChar + widths.size()-1 );
+
+ }
+ finally
+ {
+ if( ttf != null )
+ {
+ ttf.close();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Font getawtFont() throws IOException
+ {
+ PDFontDescriptorDictionary fd = (PDFontDescriptorDictionary)getFontDescriptor();
+ if( awtFont == null )
+ {
+ PDStream ff2Stream = fd.getFontFile2();
+ if( ff2Stream != null )
+ {
+ try
+ {
+ // create a font with the embedded data
+ awtFont = Font.createFont( Font.TRUETYPE_FONT, ff2Stream.createInputStream() );
+ }
+ catch( FontFormatException f )
+ {
+ log.info("Can't read the embedded font " + fd.getFontName() );
+ }
+ if (awtFont == null)
+ {
+ awtFont = FontManager.getAwtFont(fd.getFontName());
+ if (awtFont != null)
+ {
+ log.info("Using font "+awtFont.getName()+ " instead");
+ }
+ }
+ }
+ else
+ {
+ // check if the font is part of our environment
+ awtFont = FontManager.getAwtFont(fd.getFontName());
+ if (awtFont == null)
+ {
+ log.info("Can't find the specified font " + fd.getFontName() );
+ // check if there is a font mapping for an external font file
+ TrueTypeFont ttf = getExternalFontFile2( fd );
+ if( ttf != null )
+ {
+ try
+ {
+ awtFont = Font.createFont( Font.TRUETYPE_FONT, ttf.getOriginalData() );
+ }
+ catch( FontFormatException f )
+ {
+ log.info("Can't read the external fontfile " + fd.getFontName() );
+ }
+ }
+ }
+ }
+ if (awtFont == null)
+ {
+ // we can't find anything, so we have to use the standard font
+ awtFont = FontManager.getStandardFont();
+ log.info("Using font "+awtFont.getName()+ " instead");
+ }
+ }
+ return awtFont;
+ }
+
+ private InputStream getExternalTTFData() throws IOException
+ {
+ String ttfResource = externalFonts.getProperty( UNKNOWN_FONT );
+ String baseFont = getBaseFont();
+ if( baseFont != null && externalFonts.containsKey(baseFont) )
+ {
+ ttfResource = externalFonts.getProperty( baseFont );
+ }
+ return (ttfResource != null ? ResourceLoader.loadResource(ttfResource) : null);
+ }
+
+ /**
+ * Permit to load an external TTF Font program file
+ *
+ * Created by Pascal Allain
+ * Vertical7 Inc.
+ *
+ * @param fd The font descriptor currently used
+ * @return A PDStream with the Font File program, null if fd is null
+ * @throws IOException If the font is not found
+ */
+ private TrueTypeFont getExternalFontFile2(PDFontDescriptorDictionary fd)
+ throws IOException
+ {
+ TrueTypeFont retval = null;
+
+ if ( fd != null )
+ {
+ String baseFont = getBaseFont();
+ String fontResource = externalFonts.getProperty( UNKNOWN_FONT );
+ if( (baseFont != null) &&
+ (externalFonts.containsKey(baseFont)) )
+ {
+ fontResource = externalFonts.getProperty(baseFont);
+ }
+ if( fontResource != null )
+ {
+ retval = (TrueTypeFont)loadedExternalFonts.get( baseFont );
+ if( retval == null )
+ {
+ TTFParser ttfParser = new TTFParser();
+ InputStream fontStream = ResourceLoader.loadResource( fontResource );
+ if( fontStream == null )
+ {
+ throw new IOException( "Error missing font resource '" + externalFonts.get(baseFont) + "'" );
+ }
+ retval = ttfParser.parseTTF( fontStream );
+ loadedExternalFonts.put( baseFont, retval );
+ }
+ }
+ }
+
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.awt.Font;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This is implementation of the Type0 Font.
+ * See <a href="https://issues.apache.org/jira/browse/PDFBOX-605">PDFBOX-605</a>
+ * for the related improvement issue.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class PDType0Font extends PDSimpleFont
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDType0Font.class);
+
+ private COSArray descendantFontArray;
+ private PDFont descendentFont;
+ private COSDictionary descendantFontDictionary;
+ private Font awtFont;
+ /**
+ * Constructor.
+ */
+ public PDType0Font()
+ {
+ super();
+ font.setItem( COSName.SUBTYPE, COSName.TYPE0 );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fontDictionary The font dictionary according to the PDF specification.
+ */
+ public PDType0Font( COSDictionary fontDictionary )
+ {
+ super( fontDictionary );
+ descendantFontDictionary = (COSDictionary)getDescendantFonts().getObject( 0 );
+ if (descendantFontDictionary != null)
+ {
+ try
+ {
+ descendentFont = PDFontFactory.createFont( descendantFontDictionary );
+ }
+ catch (IOException exception)
+ {
+ log.error("Error while creating the descendant font!");
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Font getawtFont() throws IOException
+ {
+ if (awtFont == null)
+ {
+ if (descendentFont != null)
+ {
+ awtFont = ((PDSimpleFont)descendentFont).getawtFont();
+ }
+ if (awtFont == null)
+ {
+ awtFont = FontManager.getStandardFont();
+ log.info("Using font "+awtFont.getName()+ " instead of "+descendentFont.getFontDescriptor().getFontName());
+ }
+ }
+ return awtFont;
+ }
+
+ /**
+ * This will get the fonts bounding box.
+ *
+ * @return The fonts bounding box.
+ *
+ * @throws IOException If there is an error getting the bounding box.
+ */
+ public PDRectangle getFontBoundingBox() throws IOException
+ {
+ throw new RuntimeException( "Not yet implemented" );
+ }
+
+ /**
+ * This will get the font width for a character.
+ *
+ * @param c The character code to get the width for.
+ * @param offset The offset into the array.
+ * @param length The length of the data.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public float getFontWidth( byte[] c, int offset, int length ) throws IOException
+ {
+ return descendentFont.getFontWidth( c, offset, length );
+ }
+
+ /**
+ * This will get the font height for a character.
+ *
+ * @param c The character code to get the height for.
+ * @param offset The offset into the array.
+ * @param length The length of the data.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public float getFontHeight( byte[] c, int offset, int length ) throws IOException
+ {
+ return descendentFont.getFontHeight( c, offset, length );
+ }
+
+ /**
+ * This will get the average font width for all characters.
+ *
+ * @return The width is in 1000 unit of text space, ie 333 or 777
+ *
+ * @throws IOException If an error occurs while parsing.
+ */
+ public float getAverageFontWidth() throws IOException
+ {
+ return descendentFont.getAverageFontWidth();
+ }
+
+ private COSArray getDescendantFonts()
+ {
+ if (descendantFontArray == null)
+ {
+ descendantFontArray = (COSArray)font.getDictionaryObject( COSName.DESCENDANT_FONTS );
+ }
+ return descendantFontArray;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float getFontWidth( int charCode )
+ {
+ return descendentFont.getFontWidth(charCode);
+ }
+
+ @Override
+ public String encode(byte[] c, int offset, int length) throws IOException
+ {
+ if (hasToUnicode())
+ {
+ return super.encode(c, offset, length);
+ }
+ else
+ {
+ int result = cmap.lookupCID(c, offset, length);
+ if (result != -1)
+ {
+ return descendentFont.cmapEncoding(result, 2, true);
+ }
+ return null;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.fontbox.afm.AFMParser;
+import org.apache.fontbox.afm.CharMetric;
+import org.apache.fontbox.afm.FontMetric;
+
+import org.apache.fontbox.pfb.PfbParser;
+
+import org.apache.pdfbox.encoding.AFMEncoding;
+import org.apache.pdfbox.encoding.DictionaryEncoding;
+import org.apache.pdfbox.encoding.Encoding;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is implementation of the Type1 Font
+ * with a afm and a pfb file.
+ *
+ * @author <a href="mailto:m.g.n@gmx.de">Michael Niedermair</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDType1AfmPfbFont extends PDType1Font
+{
+ /**
+ * the buffersize.
+ */
+ private static final int BUFFERSIZE = 0xffff;
+
+ /**
+ * The font metric.
+ */
+ private FontMetric metric;
+
+ /**
+ * Create a new object.
+ * @param doc The PDF document that will hold the embedded font.
+ * @param afmname The font filename.
+ * @throws IOException If there is an error loading the data.
+ */
+ public PDType1AfmPfbFont(final PDDocument doc, final String afmname)
+ throws IOException
+ {
+
+ super();
+ InputStream afmin = new BufferedInputStream(new FileInputStream(afmname), BUFFERSIZE);
+ String pfbname = afmname.replaceAll(".AFM", "").replaceAll(".afm", "") + ".pfb";
+ InputStream pfbin = new BufferedInputStream(new FileInputStream(pfbname), BUFFERSIZE);
+ load(doc, afmin, pfbin);
+ }
+
+ /**
+ * Create a new object.
+ * @param doc The PDF document that will hold the embedded font.
+ * @param afm The afm input.
+ * @param pfb The pfb input.
+ * @throws IOException If there is an error loading the data.
+ */
+ public PDType1AfmPfbFont(final PDDocument doc, final InputStream afm, final InputStream pfb)
+ throws IOException
+ {
+ super();
+ load(doc, afm, pfb);
+ }
+
+ /**
+ * This will load a afm and pfb to be embedding into a document.
+ *
+ * @param doc The PDF document that will hold the embedded font.
+ * @param afm The afm input.
+ * @param pfb The pfb input.
+ * @throws IOException If there is an error loading the data.
+ */
+ private void load(final PDDocument doc, final InputStream afm,
+ final InputStream pfb) throws IOException
+ {
+
+ PDFontDescriptorDictionary fd = new PDFontDescriptorDictionary();
+ setFontDescriptor(fd);
+
+ // read the pfb
+ PfbParser pfbparser = new PfbParser(pfb);
+ pfb.close();
+
+ PDStream fontStream = new PDStream(doc, pfbparser.getInputStream(),
+ false);
+ fontStream.getStream().setInt("Length", pfbparser.size());
+ for (int i = 0; i < pfbparser.getLengths().length; i++)
+ {
+ fontStream.getStream().setInt("Length" + (i + 1),
+ pfbparser.getLengths()[i]);
+ }
+ fontStream.addCompression();
+ fd.setFontFile(fontStream);
+
+ // read the afm
+ AFMParser parser = new AFMParser(afm);
+ parser.parse();
+ metric = parser.getResult();
+ setFontEncoding(afmToDictionary(new AFMEncoding(metric)));
+
+ // set the values
+ setBaseFont(metric.getFontName());
+ fd.setFontName(metric.getFontName());
+ fd.setFontFamily(metric.getFamilyName());
+ fd.setNonSymbolic(true);
+ fd.setFontBoundingBox(new PDRectangle(metric.getFontBBox()));
+ fd.setItalicAngle(metric.getItalicAngle());
+ fd.setAscent(metric.getAscender());
+ fd.setDescent(metric.getDescender());
+ fd.setCapHeight(metric.getCapHeight());
+ fd.setXHeight(metric.getXHeight());
+ fd.setAverageWidth(metric.getAverageCharacterWidth());
+ fd.setCharacterSet(metric.getCharacterSet());
+
+ // get firstchar, lastchar
+ int firstchar = 255;
+ int lastchar = 0;
+
+ // widths
+ List<CharMetric> listmetric = metric.getCharMetrics();
+ Encoding encoding = getFontEncoding();
+ int maxWidths = 256;
+ List<Float> widths = new ArrayList<Float>(maxWidths);
+ float zero = 250;
+ Iterator<CharMetric> iter = listmetric.iterator();
+ for( int i=0; i<maxWidths; i++ )
+ {
+ widths.add(zero);
+ }
+ while (iter.hasNext())
+ {
+ CharMetric m = iter.next();
+ int n = m.getCharacterCode();
+ if (n > 0)
+ {
+ firstchar = Math.min(firstchar, n);
+ lastchar = Math.max(lastchar, n);
+ if (m.getWx() > 0)
+ {
+ float width = m.getWx();
+ widths.set(n,new Float(width));
+ // germandbls has 2 character codes !! Don't ask me why .....
+ // StandardEncoding = 0373 = 251
+ // WinANSIEncoding = 0337 = 223
+ if (m.getName().equals("germandbls") && n != 223)
+ {
+ widths.set(0337,new Float(width));
+ }
+ }
+ }
+ else
+ {
+ // my AFMPFB-Fonts has no character-codes for german umlauts
+ // so that I've to add them here by hand
+ if (m.getName().equals("adieresis"))
+ {
+ widths.set(0344,(Float)widths.get(encoding.getCode("a")));
+ }
+ else if (m.getName().equals("odieresis"))
+ {
+ widths.set(0366,(Float)widths.get(encoding.getCode("o")));
+ }
+ else if (m.getName().equals("udieresis"))
+ {
+ widths.set(0374,(Float)widths.get(encoding.getCode("u")));
+ }
+ else if (m.getName().equals("Adieresis"))
+ {
+ widths.set(0304,(Float)widths.get(encoding.getCode("A")));
+ }
+ else if (m.getName().equals("Odieresis"))
+ {
+ widths.set(0326,(Float)widths.get(encoding.getCode("O")));
+ }
+ else if (m.getName().equals("Udieresis"))
+ {
+ widths.set(0334,(Float)widths.get(encoding.getCode("U")));
+ }
+ }
+ }
+ setFirstChar(0);
+ setLastChar(255);
+ setWidths(widths);
+ }
+
+ /*
+ * This will generate a Encoding from the AFM-Encoding, because the AFM-Enconding isn't exported to the pdf
+ * and consequently the StandardEncoding is used so that any special character is missing
+ * I've copied the code from the pdfbox-forum posted by V0JT4 and made some additions concerning german umlauts
+ * see also https://sourceforge.net/forum/message.php?msg_id=4705274
+ */
+ private DictionaryEncoding afmToDictionary(AFMEncoding encoding) throws java.io.IOException
+ {
+ COSArray array = new COSArray();
+ array.add(COSInteger.ZERO);
+ for (int i = 0; i < 256; i++)
+ {
+ array.add(COSName.getPDFName(encoding.getName(i)));
+ }
+ // my AFMPFB-Fonts has no character-codes for german umlauts
+ // so that I've to add them here by hand
+ array.set( 0337+1, COSName.getPDFName("germandbls"));
+ array.set( 0344+1, COSName.getPDFName("adieresis"));
+ array.set( 0366+1, COSName.getPDFName("odieresis"));
+ array.set( 0374+1, COSName.getPDFName("udieresis"));
+ array.set( 0304+1, COSName.getPDFName("Adieresis"));
+ array.set( 0326+1, COSName.getPDFName("Odieresis"));
+ array.set( 0334+1, COSName.getPDFName("Udieresis"));
+
+ COSDictionary dictionary = new COSDictionary();
+ dictionary.setItem(COSName.NAME, COSName.ENCODING);
+ dictionary.setItem(COSName.DIFFERENCES, array);
+ dictionary.setItem(COSName.BASE_ENCODING, COSName.STANDARD_ENCODING);
+ return new DictionaryEncoding(dictionary);
+ }
+
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fontbox.afm.AFMParser;
+import org.apache.fontbox.afm.FontMetric;
+import org.apache.fontbox.cff.AFMFormatter;
+import org.apache.fontbox.cff.charset.CFFCharset;
+import org.apache.fontbox.cff.encoding.CFFEncoding;
+import org.apache.fontbox.cff.CFFFont;
+import org.apache.fontbox.cff.CFFParser;
+import org.apache.fontbox.cff.Type1FontFormatter;
+import org.apache.fontbox.util.BoundingBox;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+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.encoding.Encoding;
+import org.apache.pdfbox.encoding.EncodingManager;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This class represents a CFF/Type2 Font (aka Type1C Font).
+ * @author Villu Ruusmann
+ * @version $Revision: 10.0$
+ */
+public class PDType1CFont extends PDSimpleFont
+{
+ private CFFFont cffFont = null;
+
+ private Map<Integer, String> codeToName = new HashMap<Integer, String>();
+
+ private Map<Integer, String> codeToCharacter = new HashMap<Integer, String>();
+
+ private Map<String, Integer> characterToCode = new HashMap<String, Integer>();
+
+ private FontMetric fontMetric = null;
+
+ private Font awtFont = null;
+
+ private Map<String, Float> glyphWidths = new HashMap<String, Float>();
+
+ private Map<String, Float> glyphHeights = new HashMap<String, Float>();
+
+ private Float avgWidth = null;
+
+ private PDRectangle fontBBox = null;
+
+ private static final Log log = LogFactory.getLog(PDType1CFont.class);
+
+ private static final byte[] SPACE_BYTES = {(byte)32};
+
+ private COSDictionary fontDict = null;
+
+ /**
+ * Constructor.
+ * @param fontDictionary the corresponding dictionary
+ */
+ public PDType1CFont( COSDictionary fontDictionary ) throws IOException
+ {
+ super( fontDictionary );
+ fontDict = fontDictionary;
+ load();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String encode( byte[] bytes, int offset, int length ) throws IOException
+ {
+ String character = getCharacter(bytes, offset, length);
+ if( character == null )
+ {
+ log.debug("No character for code " + (bytes[offset] & 0xff) + " in " + this.cffFont.getName());
+ return null;
+ }
+
+ return character;
+ }
+
+ private String getCharacter( byte[] bytes, int offset, int length )
+ {
+ if (length > 2)
+ {
+ return null;
+ }
+ int code = bytes[offset] & 0xff;
+ if (length == 2)
+ {
+ code = code * 256 + bytes[offset+1] & 0xff;
+ }
+ return (String)this.codeToCharacter.get(code);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float getFontWidth( byte[] bytes, int offset, int length ) throws IOException
+ {
+ String name = getName(bytes, offset, length);
+ if( name == null && !Arrays.equals(SPACE_BYTES, bytes) )
+ {
+ log.debug("No name for code " + (bytes[offset] & 0xff) + " in " + this.cffFont.getName());
+
+ return 0;
+ }
+
+ Float width = (Float)this.glyphWidths.get(name);
+ if( width == null )
+ {
+ width = Float.valueOf(getFontMetric().getCharacterWidth(name));
+ this.glyphWidths.put(name, width);
+ }
+
+ return width.floatValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float getFontHeight( byte[] bytes, int offset, int length ) throws IOException
+ {
+ String name = getName(bytes, offset, length);
+ if( name == null )
+ {
+ log.debug("No name for code " + (bytes[offset] & 0xff) + " in " + this.cffFont.getName());
+
+ return 0;
+ }
+
+ Float height = (Float)this.glyphHeights.get(name);
+ if( height == null )
+ {
+ height = Float.valueOf(getFontMetric().getCharacterHeight(name));
+ this.glyphHeights.put(name, height);
+ }
+
+ return height.floatValue();
+ }
+
+ private String getName( byte[] bytes, int offset, int length )
+ {
+ if (length > 2)
+ {
+ return null;
+ }
+
+ int code = bytes[offset] & 0xff;
+ if (length == 2)
+ {
+ code = code * 256 + bytes[offset+1] & 0xff;
+ }
+
+ return (String)this.codeToName.get(code);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float getStringWidth( String string ) throws IOException
+ {
+ float width = 0;
+
+ for( int i = 0; i < string.length(); i++ )
+ {
+ String character = string.substring(i, i + 1);
+
+ Integer code = getCode(character);
+ if( code == null )
+ {
+ log.debug("No code for character " + character);
+
+ return 0;
+ }
+
+ width += getFontWidth(new byte[]{(byte)code.intValue()}, 0, 1);
+ }
+
+ return width;
+ }
+
+ private Integer getCode( String character )
+ {
+ return (Integer)this.characterToCode.get(character);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public float getAverageFontWidth() throws IOException
+ {
+ if( this.avgWidth == null )
+ {
+ this.avgWidth = Float.valueOf(getFontMetric().getAverageCharacterWidth());
+ }
+
+ return this.avgWidth.floatValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PDRectangle getFontBoundingBox() throws IOException
+ {
+ if( this.fontBBox == null )
+ {
+ this.fontBBox = new PDRectangle(getFontMetric().getFontBBox());
+ }
+
+ return this.fontBBox;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PDMatrix getFontMatrix()
+ {
+ if( fontMatrix == null )
+ {
+ List<Number> numbers = (List<Number>)this.cffFont.getProperty("FontMatrix");
+ if( numbers != null && numbers.size() == 6 )
+ {
+ COSArray array = new COSArray();
+ for(Number number : numbers)
+ {
+ array.add(new COSFloat(number.floatValue()));
+ }
+ fontMatrix = new PDMatrix(array);
+ }
+ else
+ {
+ super.getFontMatrix();
+ }
+ }
+ return fontMatrix;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Font getawtFont() throws IOException
+ {
+ if (awtFont == null)
+ {
+ this.awtFont = prepareAwtFont(this.cffFont);
+ }
+ return awtFont;
+ }
+
+ private FontMetric getFontMetric()
+ {
+ if (fontMetric == null)
+ {
+ try
+ {
+ fontMetric = prepareFontMetric(cffFont);
+ }
+ catch (IOException exception)
+ {
+ log.error("An error occured while extracting the font metrics!", exception);
+ }
+ }
+ return fontMetric;
+ }
+
+ private void load() throws IOException
+ {
+ byte[] cffBytes = loadBytes();
+
+ CFFParser cffParser = new CFFParser();
+ List<CFFFont> fonts = cffParser.parse(cffBytes);
+
+ this.cffFont = (CFFFont)fonts.get(0);
+
+ CFFEncoding encoding = this.cffFont.getEncoding();
+ PDFEncoding pdfEncoding = new PDFEncoding(encoding);
+
+ CFFCharset charset = this.cffFont.getCharset();
+ PDFCharset pdfCharset = new PDFCharset(charset);
+
+ Map<String,byte[]> charStringsDict = this.cffFont.getCharStringsDict();
+ Map<String,byte[]> pdfCharStringsDict = new LinkedHashMap<String,byte[]>();
+ pdfCharStringsDict.put(".notdef", charStringsDict.get(".notdef"));
+
+ Map<Integer,String> codeToNameMap = new LinkedHashMap<Integer,String>();
+
+ Collection<CFFFont.Mapping> mappings = this.cffFont.getMappings();
+ for( Iterator<CFFFont.Mapping> it = mappings.iterator(); it.hasNext();)
+ {
+ CFFFont.Mapping mapping = it.next();
+ Integer code = Integer.valueOf(mapping.getCode());
+ String name = mapping.getName();
+ codeToNameMap.put(code, name);
+ }
+
+ Set<String> knownNames = new HashSet<String>(codeToNameMap.values());
+
+ Map<Integer,String> codeToNameOverride = loadOverride();
+ for( Iterator<Map.Entry<Integer, String>> it = (codeToNameOverride.entrySet()).iterator(); it.hasNext();)
+ {
+ Map.Entry<Integer, String> entry = it.next();
+ Integer code = (Integer)entry.getKey();
+ String name = (String)entry.getValue();
+ if(knownNames.contains(name))
+ {
+ codeToNameMap.put(code, name);
+ }
+ }
+
+ Map nameToCharacter;
+ try
+ {
+ // TODO remove access by reflection
+ Field nameToCharacterField = Encoding.class.getDeclaredField("NAME_TO_CHARACTER");
+ nameToCharacterField.setAccessible(true);
+ nameToCharacter = (Map)nameToCharacterField.get(null);
+ }
+ catch( Exception e )
+ {
+ throw new RuntimeException(e);
+ }
+
+ for( Iterator<Map.Entry<Integer,String>> it = (codeToNameMap.entrySet()).iterator(); it.hasNext();)
+ {
+ Map.Entry<Integer,String> entry = it.next();
+ Integer code = (Integer)entry.getKey();
+ String name = (String)entry.getValue();
+ String uniName = "uni";
+ String character = (String)nameToCharacter.get(name);
+ if( character != null )
+ {
+ for( int j = 0; j < character.length(); j++ )
+ {
+ uniName += hexString(character.charAt(j), 4);
+ }
+ }
+ else
+ {
+ uniName += hexString(code.intValue(), 4);
+ character = String.valueOf((char)code.intValue());
+ }
+ pdfEncoding.register(code.intValue(), code.intValue());
+ pdfCharset.register(code.intValue(), uniName);
+ this.codeToName.put(code, uniName);
+ this.codeToCharacter.put(code, character);
+ this.characterToCode.put(character, code);
+ pdfCharStringsDict.put(uniName, charStringsDict.get(name));
+ }
+
+ this.cffFont.setEncoding(pdfEncoding);
+ this.cffFont.setCharset(pdfCharset);
+ charStringsDict.clear();
+ charStringsDict.putAll(pdfCharStringsDict);
+ Number defaultWidthX = (Number)this.cffFont.getProperty("defaultWidthX");
+ this.glyphWidths.put(null, Float.valueOf(defaultWidthX.floatValue()));
+ }
+
+ private byte[] loadBytes() throws IOException
+ {
+ PDFontDescriptor fd = getFontDescriptor();
+ if( fd != null && fd instanceof PDFontDescriptorDictionary)
+ {
+ PDStream ff3Stream = ((PDFontDescriptorDictionary)fd).getFontFile3();
+ if( ff3Stream != null )
+ {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ InputStream is = ff3Stream.createInputStream();
+ try
+ {
+ byte[] buf = new byte[512];
+ while(true)
+ {
+ int count = is.read(buf);
+ if( count < 0 )
+ {
+ break;
+ }
+ os.write(buf, 0, count);
+ }
+ }
+ finally
+ {
+ is.close();
+ }
+
+ return os.toByteArray();
+ }
+ }
+
+ throw new IOException();
+ }
+
+ private Map<Integer,String> loadOverride() throws IOException
+ {
+ Map<Integer,String> result = new LinkedHashMap<Integer,String>();
+ COSBase encoding = fontDict.getDictionaryObject(COSName.ENCODING);
+ if( encoding instanceof COSName )
+ {
+ COSName name = (COSName)encoding;
+ result.putAll(loadEncoding(name));
+ }
+ else if( encoding instanceof COSDictionary )
+ {
+ COSDictionary encodingDic = (COSDictionary)encoding;
+ COSName baseName = (COSName)encodingDic.getDictionaryObject(COSName.BASE_ENCODING);
+ if( baseName != null )
+ {
+ result.putAll(loadEncoding(baseName));
+ }
+ COSArray differences = (COSArray)encodingDic.getDictionaryObject(COSName.DIFFERENCES);
+ if( differences != null )
+ {
+ result.putAll(loadDifferences(differences));
+ }
+ }
+
+ return result;
+ }
+
+ private Map<Integer,String> loadEncoding(COSName name) throws IOException
+ {
+ Map<Integer,String> result = new LinkedHashMap<Integer,String>();
+ Encoding encoding = EncodingManager.INSTANCE.getEncoding(name);
+ for( Iterator<Map.Entry<Integer,String>> it = (encoding.getCodeToNameMap().entrySet()).iterator();
+ it.hasNext();)
+ {
+ Map.Entry<Integer,String> entry = it.next();
+ result.put(entry.getKey(), (entry.getValue()));
+ }
+
+ return result;
+ }
+
+ private Map<Integer,String> loadDifferences(COSArray differences)
+ {
+ Map<Integer,String> result = new LinkedHashMap<Integer,String>();
+ Integer code = null;
+ for( int i = 0; i < differences.size(); i++)
+ {
+ COSBase element = differences.get(i);
+ if( element instanceof COSNumber )
+ {
+ COSNumber number = (COSNumber)element;
+ code = Integer.valueOf(number.intValue());
+ }
+ else
+ {
+ if( element instanceof COSName )
+ {
+ COSName name = (COSName)element;
+ result.put(code, name.getName());
+ code = Integer.valueOf(code.intValue() + 1);
+ }
+ }
+ }
+ return result;
+ }
+
+
+ private static String hexString( int code, int length )
+ {
+ String string = Integer.toHexString(code);
+ while(string.length() < length)
+ {
+ string = ("0" + string);
+ }
+
+ return string;
+ }
+
+ private FontMetric prepareFontMetric( CFFFont font ) throws IOException
+ {
+ byte[] afmBytes = AFMFormatter.format(font);
+
+ InputStream is = new ByteArrayInputStream(afmBytes);
+ try
+ {
+ AFMParser afmParser = new AFMParser(is);
+ afmParser.parse();
+
+ FontMetric result = afmParser.getResult();
+
+ // Replace default FontBBox value with a newly computed one
+ BoundingBox bounds = result.getFontBBox();
+ List<Integer> numbers = Arrays.asList(
+ Integer.valueOf((int)bounds.getLowerLeftX()),
+ Integer.valueOf((int)bounds.getLowerLeftY()),
+ Integer.valueOf((int)bounds.getUpperRightX()),
+ Integer.valueOf((int)bounds.getUpperRightY())
+ );
+ font.addValueToTopDict("FontBBox", numbers);
+
+ return result;
+ }
+ finally
+ {
+ is.close();
+ }
+ }
+
+ private static Font prepareAwtFont( CFFFont font ) throws IOException
+ {
+ byte[] type1Bytes = Type1FontFormatter.format(font);
+
+ InputStream is = new ByteArrayInputStream(type1Bytes);
+ try
+ {
+ return Font.createFont(Font.TYPE1_FONT, is);
+ }
+ catch( FontFormatException ffe )
+ {
+ throw new WrappedIOException(ffe);
+ }
+ finally
+ {
+ is.close();
+ }
+ }
+
+ /**
+ * This class represents a PDFEncoding.
+ *
+ */
+ private static class PDFEncoding extends CFFEncoding
+ {
+
+ private PDFEncoding( CFFEncoding parent )
+ {
+ Iterator<Entry> parentEntries = parent.getEntries().iterator();
+ while(parentEntries.hasNext())
+ {
+ addEntry(parentEntries.next());
+ }
+ }
+
+ public boolean isFontSpecific()
+ {
+ return true;
+ }
+
+ }
+
+ /**
+ * This class represents a PDFCharset.
+ *
+ */
+ private static class PDFCharset extends CFFCharset
+ {
+ private PDFCharset( CFFCharset parent )
+ {
+ Iterator<Entry> parentEntries = parent.getEntries().iterator();
+ while(parentEntries.hasNext())
+ {
+ addEntry(parentEntries.next());
+ }
+ }
+
+ public boolean isFontSpecific()
+ {
+ return true;
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.afm.FontMetric;
+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.encoding.AFMEncoding;
+import org.apache.pdfbox.encoding.Encoding;
+import org.apache.pdfbox.encoding.EncodingManager;
+import org.apache.pdfbox.encoding.Type1Encoding;
+import org.apache.pdfbox.encoding.WinAnsiEncoding;
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This is implementation of the Type1 Font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.11 $
+ */
+public class PDType1Font extends PDSimpleFont
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDType1Font.class);
+
+ private PDType1CFont type1CFont = null;
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font TIMES_ROMAN = new PDType1Font( "Times-Roman" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font TIMES_BOLD = new PDType1Font( "Times-Bold" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font TIMES_ITALIC = new PDType1Font( "Times-Italic" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font TIMES_BOLD_ITALIC = new PDType1Font( "Times-BoldItalic" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font HELVETICA = new PDType1Font( "Helvetica" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font HELVETICA_BOLD = new PDType1Font( "Helvetica-Bold" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font HELVETICA_OBLIQUE = new PDType1Font( "Helvetica-Oblique" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font HELVETICA_BOLD_OBLIQUE = new PDType1Font( "Helvetica-BoldOblique" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font COURIER = new PDType1Font( "Courier" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font COURIER_BOLD = new PDType1Font( "Courier-Bold" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font COURIER_OBLIQUE = new PDType1Font( "Courier-Oblique" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font COURIER_BOLD_OBLIQUE = new PDType1Font( "Courier-BoldOblique" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font SYMBOL = new PDType1Font( "Symbol" );
+ /**
+ * Standard Base 14 Font.
+ */
+ public static final PDType1Font ZAPF_DINGBATS = new PDType1Font( "ZapfDingbats" );
+
+ private static final Map<String, PDType1Font> STANDARD_14 = new HashMap<String, PDType1Font>();
+ static
+ {
+ STANDARD_14.put( TIMES_ROMAN.getBaseFont(), TIMES_ROMAN );
+ STANDARD_14.put( TIMES_BOLD.getBaseFont(), TIMES_BOLD );
+ STANDARD_14.put( TIMES_ITALIC.getBaseFont(), TIMES_ITALIC );
+ STANDARD_14.put( TIMES_BOLD_ITALIC.getBaseFont(), TIMES_BOLD_ITALIC );
+ STANDARD_14.put( HELVETICA.getBaseFont(), HELVETICA );
+ STANDARD_14.put( HELVETICA_BOLD.getBaseFont(), HELVETICA_BOLD );
+ STANDARD_14.put( HELVETICA_OBLIQUE.getBaseFont(), HELVETICA_OBLIQUE );
+ STANDARD_14.put( HELVETICA_BOLD_OBLIQUE.getBaseFont(), HELVETICA_BOLD_OBLIQUE );
+ STANDARD_14.put( COURIER.getBaseFont(), COURIER );
+ STANDARD_14.put( COURIER_BOLD.getBaseFont(), COURIER_BOLD );
+ STANDARD_14.put( COURIER_OBLIQUE.getBaseFont(), COURIER_OBLIQUE );
+ STANDARD_14.put( COURIER_BOLD_OBLIQUE.getBaseFont(), COURIER_BOLD_OBLIQUE );
+ STANDARD_14.put( SYMBOL.getBaseFont(), SYMBOL );
+ STANDARD_14.put( ZAPF_DINGBATS.getBaseFont(), ZAPF_DINGBATS );
+ }
+
+ private Font awtFont = null;
+
+ /**
+ * Constructor.
+ */
+ public PDType1Font()
+ {
+ super();
+ font.setItem( COSName.SUBTYPE, COSName.TYPE1 );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fontDictionary The font dictionary according to the PDF specification.
+ */
+ public PDType1Font( COSDictionary fontDictionary )
+ {
+ super( fontDictionary );
+ PDFontDescriptor fd = getFontDescriptor();
+ if (fd != null && fd instanceof PDFontDescriptorDictionary)
+ {
+ // a Type1 font may contain a Type1C font
+ PDStream fontFile3 = ((PDFontDescriptorDictionary)fd).getFontFile3();
+ if (fontFile3 != null)
+ {
+ try
+ {
+ type1CFont = new PDType1CFont( super.font );
+ }
+ catch (IOException exception)
+ {
+ log.info("Can't read the embedded type1C font " + fd.getFontName() );
+ }
+ }
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param baseFont The base font for this font.
+ */
+ public PDType1Font( String baseFont )
+ {
+ this();
+ setBaseFont( baseFont );
+ setFontEncoding(new WinAnsiEncoding());
+ setEncoding(COSName.WIN_ANSI_ENCODING);
+ }
+
+ /**
+ * A convenience method to get one of the standard 14 font from name.
+ *
+ * @param name The name of the font to get.
+ *
+ * @return The font that matches the name or null if it does not exist.
+ */
+ public static PDType1Font getStandardFont( String name )
+ {
+ return (PDType1Font)STANDARD_14.get( name );
+ }
+
+ /**
+ * This will get the names of the standard 14 fonts.
+ *
+ * @return An array of the names of the standard 14 fonts.
+ */
+ public static String[] getStandard14Names()
+ {
+ return (String[])STANDARD_14.keySet().toArray( new String[14] );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Font getawtFont() throws IOException
+ {
+ if( awtFont == null )
+ {
+ if (type1CFont != null)
+ {
+ awtFont = type1CFont.getawtFont();
+ }
+ else
+ {
+ String baseFont = getBaseFont();
+ PDFontDescriptor fd = getFontDescriptor();
+ if (fd != null && fd instanceof PDFontDescriptorDictionary)
+ {
+ PDFontDescriptorDictionary fdDictionary = (PDFontDescriptorDictionary)fd;
+ if( fdDictionary.getFontFile() != null )
+ {
+ try
+ {
+ // create a type1 font with the embedded data
+ awtFont = Font.createFont( Font.TYPE1_FONT, fdDictionary.getFontFile().createInputStream() );
+ }
+ catch (FontFormatException e)
+ {
+ log.info("Can't read the embedded type1 font " + fd.getFontName() );
+ }
+ }
+ if (awtFont == null)
+ {
+ // check if the font is part of our environment
+ awtFont = FontManager.getAwtFont(fd.getFontName());
+ if (awtFont == null)
+ {
+ log.info("Can't find the specified font " + fd.getFontName() );
+ }
+ }
+ }
+ else
+ {
+ // check if the font is part of our environment
+ awtFont = FontManager.getAwtFont(baseFont);
+ if (awtFont == null)
+ {
+ log.info("Can't find the specified basefont " + baseFont );
+ }
+ }
+ }
+ if (awtFont == null)
+ {
+ // we can't find anything, so we have to use the standard font
+ awtFont = FontManager.getStandardFont();
+ log.info("Using font "+awtFont.getName()+ " instead");
+ }
+ }
+ return awtFont;
+ }
+
+ protected void determineEncoding()
+ {
+ super.determineEncoding();
+ Encoding fontEncoding = getFontEncoding();
+ if(fontEncoding == null)
+ {
+ FontMetric metric = getAFM();
+ if (metric != null)
+ {
+ fontEncoding = new AFMEncoding( metric );
+ }
+ setFontEncoding(fontEncoding);
+ }
+ getEncodingFromFont(getFontEncoding() == null);
+ }
+
+ /**
+ * Tries to get the encoding for the type1 font.
+ *
+ */
+ private void getEncodingFromFont(boolean extractEncoding)
+ {
+ // This whole section of code needs to be replaced with an actual type1 font parser!!
+ // Get the font program from the embedded type font.
+ PDFontDescriptor fontDescriptor = getFontDescriptor();
+ if( fontDescriptor != null && fontDescriptor instanceof PDFontDescriptorDictionary)
+ {
+ PDStream fontFile = ((PDFontDescriptorDictionary)fontDescriptor).getFontFile();
+ if( fontFile != null )
+ {
+ BufferedReader in = null;
+ try
+ {
+ in = new BufferedReader(new InputStreamReader(fontFile.createInputStream()));
+
+ // this section parses the font program stream searching for a /Encoding entry
+ // if it contains an array of values a Type1Encoding will be returned
+ // if it encoding contains an encoding name the corresponding Encoding will be returned
+ String line = "";
+ Type1Encoding encoding = null;
+ while( (line = in.readLine()) != null)
+ {
+ if (extractEncoding)
+ {
+ if (line.startsWith("currentdict end")) {
+ if (encoding != null)
+ setFontEncoding(encoding);
+ break;
+ }
+ if (line.startsWith("/Encoding"))
+ {
+ if(line.contains("array"))
+ {
+ StringTokenizer st = new StringTokenizer(line);
+ // ignore the first token
+ st.nextElement();
+ int arraySize = Integer.parseInt(st.nextToken());
+ encoding = new Type1Encoding(arraySize);
+ }
+ // if there is already an encoding, we don't need to
+ // assign another one
+ else if (getFontEncoding() == null)
+ {
+ StringTokenizer st = new StringTokenizer(line);
+ // ignore the first token
+ st.nextElement();
+ String type1Encoding = st.nextToken();
+ setFontEncoding(
+ EncodingManager.INSTANCE.getEncoding(
+ COSName.getPDFName(type1Encoding)));
+ break;
+ }
+ }
+ else if (line.startsWith("dup")) {
+ StringTokenizer st = new StringTokenizer(line.replaceAll("/"," /"));
+ // ignore the first token
+ st.nextElement();
+ int index = Integer.parseInt(st.nextToken());
+ String name = st.nextToken();
+ if(encoding == null)
+ log.warn("Unable to get character encoding. Encoding defintion found without /Encoding line.");
+ else
+ encoding.addCharacterEncoding(index, name.replace("/", ""));
+ }
+ }
+ // according to the pdf reference, all font matrices should be same, except for type 3 fonts.
+ // but obviously there are some type1 fonts with different matrix values, see pdf sample
+ // attached to PDFBOX-935
+ if (line.startsWith("/FontMatrix"))
+ {
+ String matrixValues = line.substring(line.indexOf("[")+1,line.lastIndexOf("]"));
+ StringTokenizer st = new StringTokenizer(matrixValues);
+ COSArray array = new COSArray();
+ if (st.countTokens() >= 6)
+ {
+ try
+ {
+ for (int i=0;i<6;i++)
+ {
+ COSFloat floatValue = new COSFloat(Float.parseFloat(st.nextToken()));
+ array.add(floatValue);
+ }
+ fontMatrix = new PDMatrix(array);
+ }
+ catch (NumberFormatException exception)
+ {
+ log.error("Can't read the fontmatrix from embedded font file!");
+ }
+ }
+ }
+ }
+ }
+ catch(IOException exception)
+ {
+ log.error("Error: Could not extract the encoding from the embedded type1 font.");
+ }
+ finally
+ {
+ if (in != null)
+ {
+ try
+ {
+ in.close();
+ }
+ catch(IOException exception)
+ {
+ log.error("An error occurs while closing the stream used to read the embedded type1 font.");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String encode(byte[] c, int offset, int length) throws IOException
+ {
+ if (type1CFont != null && getFontEncoding() == null)
+ {
+ return type1CFont.encode(c, offset, length);
+ }
+ else
+ {
+ return super.encode(c, offset, length);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public PDMatrix getFontMatrix()
+ {
+ if (type1CFont != null)
+ {
+ return type1CFont.getFontMatrix();
+ }
+ else
+ {
+ return super.getFontMatrix();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.geom.AffineTransform;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This is implementation of the Type3 Font.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class PDType3Font extends PDSimpleFont
+{
+ //A map of character code to java.awt.Image for the glyph
+ private Map images = new HashMap();
+
+ /**
+ * Constructor.
+ */
+ public PDType3Font()
+ {
+ super();
+ font.setItem( COSName.SUBTYPE, COSName.TYPE3 );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param fontDictionary The font dictionary according to the PDF specification.
+ */
+ public PDType3Font( COSDictionary fontDictionary )
+ {
+ super( fontDictionary );
+ }
+
+ /**
+ * Type3 fonts have their glyphs defined as a content stream. This
+ * will create the image that represents that character
+ *
+ * @throws IOException If there is an error creating the image.
+ */
+ private Image createImageIfNecessary( char character ) throws IOException
+ {
+ Character c = new Character( character );
+ Image retval = (Image)images.get( c );
+ if( retval == null )
+ {
+ COSDictionary charProcs = (COSDictionary)font.getDictionaryObject( COSName.CHAR_PROCS );
+ COSStream stream = (COSStream)charProcs.getDictionaryObject( COSName.getPDFName( "" + character ) );
+ if( stream != null )
+ {
+ Type3StreamParser parser = new Type3StreamParser();
+ retval = parser.createImage( stream );
+ images.put( c, retval );
+ }
+ else
+ {
+ //stream should not be null!!
+ }
+ }
+ return retval;
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void drawString( String string, Graphics g, float fontSize, AffineTransform at, float x, float y )
+ throws IOException
+ {
+ //if( string.equals( "V" )|| string.equals( "o" ) )
+ {
+ for(int i=0; i<string.length(); i++)
+ {
+ //todo need to use image observers and such
+ char c = string.charAt( i );
+ Image image = createImageIfNecessary( c );
+ if( image != null )
+ {
+ int newWidth = (int)(.12*image.getWidth(null));
+ int newHeight = (int)(.12*image.getHeight(null));
+ if( newWidth > 0 && newHeight > 0 )
+ {
+ image = image.getScaledInstance( newWidth, newHeight, Image.SCALE_SMOOTH );
+ g.drawImage( image, (int)x, (int)y, null );
+ x+=newWidth;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the font matrix for this type3 font.
+ *
+ * @param matrix The font matrix for this type3 font.
+ */
+ public void setFontMatrix( PDMatrix matrix )
+ {
+ font.setItem( "FontMatrix", matrix );
+ }
+}
--- /dev/null
+/*
+ * 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.font;
+
+import java.awt.Image;
+
+import java.io.IOException;
+
+import java.util.List;
+
+import org.apache.fontbox.util.BoundingBox;
+
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDInlinedImage;
+
+import org.apache.pdfbox.util.ImageParameters;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.PDFStreamEngine;
+
+/**
+ * This class will handle creating an image for a type 3 glyph.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.10 $
+ */
+public class Type3StreamParser extends PDFStreamEngine
+{
+ private PDInlinedImage image = null;
+ private BoundingBox box = null;
+
+
+ /**
+ * This will parse a type3 stream and create an image from it.
+ *
+ * @param type3Stream The stream containing the operators to draw the image.
+ *
+ * @return The image that was created.
+ *
+ * @throws IOException If there is an error processing the stream.
+ */
+ public Image createImage( COSStream type3Stream ) throws IOException
+ {
+ processStream( null, null, type3Stream );
+ return image.createImage();
+ }
+
+ /**
+ * This is used to handle an operation.
+ *
+ * @param operator The operation to perform.
+ * @param arguments The list of arguments.
+ *
+ * @throws IOException If there is an error processing the operation.
+ */
+ protected void processOperator( PDFOperator operator, List arguments ) throws IOException
+ {
+ super.processOperator( operator, arguments );
+ String operation = operator.getOperation();
+ /**
+ if( operation.equals( "b" ) )
+ {
+ //Close, fill, and stroke path using nonzero winding number rule
+ }
+ else if( operation.equals( "B" ) )
+ {
+ //Fill and stroke path using nonzero winding number rule
+ }
+ else if( operation.equals( "b*" ) )
+ {
+ //Close, fill, and stroke path using even-odd rule
+ }
+ else if( operation.equals( "B*" ) )
+ {
+ //Fill and stroke path using even-odd rule
+ }
+ else if( operation.equals( "BDC" ) )
+ {
+ //(PDF 1.2) Begin marked-content sequence with property list
+ }
+ else **/if( operation.equals( "BI" ) )
+ {
+ ImageParameters params = operator.getImageParameters();
+ image = new PDInlinedImage();
+ image.setImageParameters( params );
+ image.setImageData( operator.getImageData() );
+ //begin inline image object
+ }/**
+ else if( operation.equals( "BMC" ) )
+ {
+ //(PDF 1.2) Begin marked-content sequence
+ }
+ else if( operation.equals( "BT" ) )
+ {
+ log.debug( "<BT>" );
+ textMatrix = new Matrix();
+ textLineMatrix = new Matrix();
+ }
+ else if( operation.equals( "BX" ) )
+ {
+ //(PDF 1.1) Begin compatibility section
+ }
+ else if( operation.equals( "c" ) )
+ {
+ //Append curved segment to path (three control points)
+ }
+ else if( operation.equals( "cm" ) )
+ {
+ }
+ else if( operation.equals( "cs" ) )
+ {
+ }
+ else if( operation.equals( "CS" ) )
+ {
+ }
+ else if( operation.equals( "d" ) )
+ {
+ //Set the line dash pattern in the graphics state
+ }
+ else */if( operation.equals( "d0" ) )
+ {
+ //set glyph with for a type3 font
+ //COSNumber horizontalWidth = (COSNumber)arguments.get( 0 );
+ //COSNumber verticalWidth = (COSNumber)arguments.get( 1 );
+ //width = horizontalWidth.intValue();
+ //height = verticalWidth.intValue();
+ }
+ else if( operation.equals( "d1" ) )
+ {
+ //set glyph with and bounding box for type 3 font
+ //COSNumber horizontalWidth = (COSNumber)arguments.get( 0 );
+ //COSNumber verticalWidth = (COSNumber)arguments.get( 1 );
+ COSNumber llx = (COSNumber)arguments.get( 2 );
+ COSNumber lly = (COSNumber)arguments.get( 3 );
+ COSNumber urx = (COSNumber)arguments.get( 4 );
+ COSNumber ury = (COSNumber)arguments.get( 5 );
+
+ //width = horizontalWidth.intValue();
+ //height = verticalWidth.intValue();
+ box = new BoundingBox();
+ box.setLowerLeftX( llx.floatValue() );
+ box.setLowerLeftY( lly.floatValue() );
+ box.setUpperRightX( urx.floatValue() );
+ box.setUpperRightY( ury.floatValue() );
+ }/*
+ else if( operation.equals( "Do" ) )
+ {
+ //invoke named object.
+ }
+ else if( operation.equals( "DP" ) )
+ {
+ //(PDF 1.2) De.ne marked-content point with property list
+ }
+ else if( operation.equals( "EI" ) )
+ {
+ //end inline image object
+ }
+ else if( operation.equals( "EMC" ) )
+ {
+ //End inline image object
+ }
+ else if( operation.equals( "ET" ) )
+ {
+ log.debug( "<ET>" );
+ textMatrix = null;
+ textLineMatrix = null;
+ }
+ else if( operation.equals( "EX" ) )
+ {
+ //(PDF 1.1) End compatibility section
+ }
+ else if( operation.equals( "f" ) )
+ {
+ //Fill the path, using the nonzero winding number rule to determine the region to .ll
+ }
+ else if( operation.equals( "F" ) )
+ {
+ }
+ else if( operation.equals( "f*" ) )
+ {
+ //Fill path using even-odd rule
+ }
+ else if( operation.equals( "g" ) )
+ {
+ }
+ else if( operation.equals( "G" ) )
+ {
+ }
+ else if( operation.equals( "gs" ) )
+ {
+ }
+ else if( operation.equals( "h" ) )
+ {
+ //close subpath
+ }
+ else if( operation.equals( "i" ) )
+ {
+ //set flatness tolerance, not sure what this does
+ }
+ else if( operation.equals( "ID" ) )
+ {
+ //begin inline image data
+ }
+ else if( operation.equals( "j" ) )
+ {
+ //Set the line join style in the graphics state
+ //System.out.println( "<j>" );
+ }
+ else if( operation.equals( "J" ) )
+ {
+ //Set the line cap style in the graphics state
+ //System.out.println( "<J>" );
+ }
+ else if( operation.equals( "k" ) )
+ {
+ //Set CMYK color for nonstroking operations
+ }
+ else if( operation.equals( "K" ) )
+ {
+ //Set CMYK color for stroking operations
+ }
+ else if( operation.equals( "l" ) )
+ {
+ //append straight line segment from the current point to the point.
+ COSNumber x = (COSNumber)arguments.get( 0 );
+ COSNumber y = (COSNumber)arguments.get( 1 );
+ linePath.lineTo( x.floatValue(), pageSize.getHeight()-y.floatValue() );
+ }
+ else if( operation.equals( "m" ) )
+ {
+ COSNumber x = (COSNumber)arguments.get( 0 );
+ COSNumber y = (COSNumber)arguments.get( 1 );
+ linePath.reset();
+ linePath.moveTo( x.floatValue(), pageSize.getHeight()-y.floatValue() );
+ //System.out.println( "<m x=\"" + x.getValue() + "\" y=\"" + y.getValue() + "\" >" );
+ }
+ else if( operation.equals( "M" ) )
+ {
+ //System.out.println( "<M>" );
+ }
+ else if( operation.equals( "MP" ) )
+ {
+ //(PDF 1.2) Define marked-content point
+ }
+ else if( operation.equals( "n" ) )
+ {
+ //End path without .lling or stroking
+ //System.out.println( "<n>" );
+ }
+ else if( operation.equals( "q" ) )
+ {
+ //save graphics state
+ if( log.isDebugEnabled() )
+ {
+ log.debug( "<" + operation + "> - save state" );
+ }
+ graphicsStack.push(graphicsState.clone());
+ }
+ else if( operation.equals( "Q" ) )
+ {
+ //restore graphics state
+ if( log.isDebugEnabled() )
+ {
+ log.debug( "<" + operation + "> - restore state" );
+ }
+ graphicsState = (PDGraphicsState)graphicsStack.pop();
+ }
+ else if( operation.equals( "re" ) )
+ {
+ }
+ else if( operation.equals( "rg" ) )
+ {
+ //Set RGB color for nonstroking operations
+ }
+ else if( operation.equals( "RG" ) )
+ {
+ //Set RGB color for stroking operations
+ }
+ else if( operation.equals( "ri" ) )
+ {
+ //Set color rendering intent
+ }
+ else if( operation.equals( "s" ) )
+ {
+ //Close and stroke path
+ }
+ else if( operation.equals( "S" ) )
+ {
+ graphics.draw( linePath );
+ }
+ else if( operation.equals( "sc" ) )
+ {
+ //set color for nonstroking operations
+ //System.out.println( "<sc>" );
+ }
+ else if( operation.equals( "SC" ) )
+ {
+ //set color for stroking operations
+ //System.out.println( "<SC>" );
+ }
+ else if( operation.equals( "scn" ) )
+ {
+ //set color for nonstroking operations special
+ }
+ else if( operation.equals( "SCN" ) )
+ {
+ //set color for stroking operations special
+ }
+ else if( operation.equals( "sh" ) )
+ {
+ //(PDF 1.3) Paint area de.ned by shading pattern
+ }
+ else if( operation.equals( "T*" ) )
+ {
+ if (log.isDebugEnabled())
+ {
+ log.debug("<T* graphicsState.getTextState().getLeading()=\"" +
+ graphicsState.getTextState().getLeading() + "\">");
+ }
+ //move to start of next text line
+ if( graphicsState.getTextState().getLeading() == 0 )
+ {
+ graphicsState.getTextState().setLeading( -.01f );
+ }
+ Matrix td = new Matrix();
+ td.setValue( 2, 1, -1 * graphicsState.getTextState().getLeading() * textMatrix.getValue(1,1));
+ textLineMatrix = textLineMatrix.multiply( td );
+ textMatrix = textLineMatrix.copy();
+ }
+ else if( operation.equals( "Tc" ) )
+ {
+ //set character spacing
+ COSNumber characterSpacing = (COSNumber)arguments.get( 0 );
+ if (log.isDebugEnabled())
+ {
+ log.debug("<Tc characterSpacing=\"" + characterSpacing.floatValue() + "\" />");
+ }
+ graphicsState.getTextState().setCharacterSpacing( characterSpacing.floatValue() );
+ }
+ else if( operation.equals( "Td" ) )
+ {
+ COSNumber x = (COSNumber)arguments.get( 0 );
+ COSNumber y = (COSNumber)arguments.get( 1 );
+ if (log.isDebugEnabled())
+ {
+ log.debug("<Td x=\"" + x.floatValue() + "\" y=\"" + y.floatValue() + "\">");
+ }
+ Matrix td = new Matrix();
+ td.setValue( 2, 0, x.floatValue() * textMatrix.getValue(0,0) );
+ td.setValue( 2, 1, y.floatValue() * textMatrix.getValue(1,1) );
+ //log.debug( "textLineMatrix before " + textLineMatrix );
+ textLineMatrix = textLineMatrix.multiply( td );
+ //log.debug( "textLineMatrix after " + textLineMatrix );
+ textMatrix = textLineMatrix.copy();
+ }
+ else if( operation.equals( "TD" ) )
+ {
+ //move text position and set leading
+ COSNumber x = (COSNumber)arguments.get( 0 );
+ COSNumber y = (COSNumber)arguments.get( 1 );
+ if (log.isDebugEnabled())
+ {
+ log.debug("<TD x=\"" + x.floatValue() + "\" y=\"" + y.floatValue() + "\">");
+ }
+ graphicsState.getTextState().setLeading( -1 * y.floatValue() );
+ Matrix td = new Matrix();
+ td.setValue( 2, 0, x.floatValue() * textMatrix.getValue(0,0) );
+ td.setValue( 2, 1, y.floatValue() * textMatrix.getValue(1,1) );
+ //log.debug( "textLineMatrix before " + textLineMatrix );
+ textLineMatrix = textLineMatrix.multiply( td );
+ //log.debug( "textLineMatrix after " + textLineMatrix );
+ textMatrix = textLineMatrix.copy();
+ }
+ else if( operation.equals( "Tf" ) )
+ {
+ //set font and size
+ COSName fontName = (COSName)arguments.get( 0 );
+ graphicsState.getTextState().setFontSize( ((COSNumber)arguments.get( 1 ) ).floatValue() );
+
+ if (log.isDebugEnabled())
+ {
+ log.debug("<Tf font=\"" + fontName.getName() + "\" size=\"" +
+ graphicsState.getTextState().getFontSize() + "\">");
+ }
+
+ //old way
+ //graphicsState.getTextState().getFont() = (COSObject)stream.getDictionaryObject( fontName );
+ //if( graphicsState.getTextState().getFont() == null )
+ //{
+ // graphicsState.getTextState().getFont() = (COSObject)graphicsState.getTextState().getFont()
+ // Dictionary.getItem( fontName );
+ //}
+ graphicsState.getTextState().setFont( (PDFont)fonts.get( fontName.getName() ) );
+ if( graphicsState.getTextState().getFont() == null )
+ {
+ throw new IOException( "Error: Could not find font(" + fontName + ") in map=" + fonts );
+ }
+ //log.debug( "Font Resource=" + fontResource );
+ //log.debug( "Current Font=" + graphicsState.getTextState().getFont() );
+ //log.debug( "graphicsState.getTextState().getFontSize()=" + graphicsState.getTextState().getFontSize() );
+ }
+ else if( operation.equals( "Tj" ) )
+ {
+ COSString string = (COSString)arguments.get( 0 );
+ TextPosition pos = showString( string.getBytes() );
+ if (log.isDebugEnabled())
+ {
+ log.debug("<Tj string=\"" + string.getString() + "\">");
+ }
+ }
+ else if( operation.equals( "TJ" ) )
+ {
+ Matrix td = new Matrix();
+
+ COSArray array = (COSArray)arguments.get( 0 );
+ for( int i=0; i<array.size(); i++ )
+ {
+ COSBase next = array.get( i );
+ if( next instanceof COSNumber )
+ {
+ float value = -1*
+ (((COSNumber)next).floatValue()/1000) *
+ graphicsState.getTextState().getFontSize() *
+ textMatrix.getValue(1,1);
+
+ if (log.isDebugEnabled())
+ {
+ log.debug( "<TJ(" + i + ") value=\"" + value +
+ "\", param=\"" + ((COSNumber)next).floatValue() +
+ "\", fontsize=\"" + graphicsState.getTextState().getFontSize() + "\">" );
+ }
+ td.setValue( 2, 0, value );
+ textMatrix = textMatrix.multiply( td );
+ }
+ else if( next instanceof COSString )
+ {
+ TextPosition pos = showString( ((COSString)next).getBytes() );
+ if (log.isDebugEnabled())
+ {
+ log.debug("<TJ(" + i + ") string=\"" + pos.getString() + "\">");
+ }
+ }
+ else
+ {
+ throw new IOException( "Unknown type in array for TJ operation:" + next );
+ }
+ }
+ }
+ else if( operation.equals( "TL" ) )
+ {
+ COSNumber leading = (COSNumber)arguments.get( 0 );
+ graphicsState.getTextState().setLeading( leading.floatValue() );
+ if (log.isDebugEnabled())
+ {
+ log.debug("<TL leading=\"" + leading.floatValue() + "\" >");
+ }
+ }
+ else if( operation.equals( "Tm" ) )
+ {
+ //Set text matrix and text line matrix
+ COSNumber a = (COSNumber)arguments.get( 0 );
+ COSNumber b = (COSNumber)arguments.get( 1 );
+ COSNumber c = (COSNumber)arguments.get( 2 );
+ COSNumber d = (COSNumber)arguments.get( 3 );
+ COSNumber e = (COSNumber)arguments.get( 4 );
+ COSNumber f = (COSNumber)arguments.get( 5 );
+
+ if (log.isDebugEnabled())
+ {
+ log.debug("<Tm " +
+ "a=\"" + a.floatValue() + "\" " +
+ "b=\"" + b.floatValue() + "\" " +
+ "c=\"" + c.floatValue() + "\" " +
+ "d=\"" + d.floatValue() + "\" " +
+ "e=\"" + e.floatValue() + "\" " +
+ "f=\"" + f.floatValue() + "\" >");
+ }
+
+ textMatrix = new Matrix();
+ textMatrix.setValue( 0, 0, a.floatValue() );
+ textMatrix.setValue( 0, 1, b.floatValue() );
+ textMatrix.setValue( 1, 0, c.floatValue() );
+ textMatrix.setValue( 1, 1, d.floatValue() );
+ textMatrix.setValue( 2, 0, e.floatValue() );
+ textMatrix.setValue( 2, 1, f.floatValue() );
+ textLineMatrix = textMatrix.copy();
+ }
+ else if( operation.equals( "Tr" ) )
+ {
+ //Set text rendering mode
+ //System.out.println( "<Tr>" );
+ }
+ else if( operation.equals( "Ts" ) )
+ {
+ //Set text rise
+ //System.out.println( "<Ts>" );
+ }
+ else if( operation.equals( "Tw" ) )
+ {
+ //set word spacing
+ COSNumber wordSpacing = (COSNumber)arguments.get( 0 );
+ if (log.isDebugEnabled())
+ {
+ log.debug("<Tw wordSpacing=\"" + wordSpacing.floatValue() + "\" />");
+ }
+ graphicsState.getTextState().setWordSpacing( wordSpacing.floatValue() );
+ }
+ else if( operation.equals( "Tz" ) )
+ {
+ //Set horizontal text scaling
+ }
+ else if( operation.equals( "v" ) )
+ {
+ //Append curved segment to path (initial point replicated)
+ }
+ else if( operation.equals( "w" ) )
+ {
+ //Set the line width in the graphics state
+ //System.out.println( "<w>" );
+ }
+ else if( operation.equals( "W" ) )
+ {
+ //Set clipping path using nonzero winding number rule
+ //System.out.println( "<W>" );
+ }
+ else if( operation.equals( "W*" ) )
+ {
+ //Set clipping path using even-odd rule
+ }
+ else if( operation.equals( "y" ) )
+ {
+ //Append curved segment to path (final point replicated)
+ }
+ else if( operation.equals( "'" ) )
+ {
+ // Move to start of next text line, and show text
+ //
+ COSString string = (COSString)arguments.get( 0 );
+ if (log.isDebugEnabled())
+ {
+ log.debug("<' string=\"" + string.getString() + "\">");
+ }
+
+ Matrix td = new Matrix();
+ td.setValue( 2, 1, -1 * graphicsState.getTextState().getLeading() * textMatrix.getValue(1,1));
+ textLineMatrix = textLineMatrix.multiply( td );
+ textMatrix = textLineMatrix.copy();
+
+ showString( string.getBytes() );
+ }
+ else if( operation.equals( "\"" ) )
+ {
+ //Set word and character spacing, move to next line, and show text
+ //
+ COSNumber wordSpacing = (COSNumber)arguments.get( 0 );
+ COSNumber characterSpacing = (COSNumber)arguments.get( 1 );
+ COSString string = (COSString)arguments.get( 2 );
+
+ if (log.isDebugEnabled())
+ {
+ log.debug("<\" wordSpacing=\"" + wordSpacing +
+ "\", characterSpacing=\"" + characterSpacing +
+ "\", string=\"" + string.getString() + "\">");
+ }
+
+ graphicsState.getTextState().setCharacterSpacing( characterSpacing.floatValue() );
+ graphicsState.getTextState().setWordSpacing( wordSpacing.floatValue() );
+
+ Matrix td = new Matrix();
+ td.setValue( 2, 1, -1 * graphicsState.getTextState().getLeading() * textMatrix.getValue(1,1));
+ textLineMatrix = textLineMatrix.multiply( td );
+ textMatrix = textLineMatrix.copy();
+
+ showString( string.getBytes() );
+ }*/
+ }
+
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+Classes to deal with font functionality in a PDF Document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+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.pdmodel.common.COSObjectable;
+
+import java.io.IOException;
+
+
+/**
+ * This class represents the graphics state dictionary that is stored in the PDF document.
+ * The PDGraphicsStateValue holds the current runtime values as a stream is being executed.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDExtendedGraphicsState implements COSObjectable
+{
+ /**
+ * Rendering intent constants, see PDF Reference 1.5 Section 4.5.4 Rendering Intents.
+ */
+ public static final String RENDERING_INTENT_ABSOLUTE_COLORIMETRIC = "AbsoluteColorimetric";
+ /**
+ * Rendering intent constants, see PDF Reference 1.5 Section 4.5.4 Rendering Intents.
+ */
+ public static final String RENDERING_INTENT_RELATIVE_COLORIMETRIC = "RelativeColorimetric";
+ /**
+ * Rendering intent constants, see PDF Reference 1.5 Section 4.5.4 Rendering Intents.
+ */
+ public static final String RENDERING_INTENT_SATURATION = "Saturation";
+ /**
+ * Rendering intent constants, see PDF Reference 1.5 Section 4.5.4 Rendering Intents.
+ */
+ public static final String RENDERING_INTENT_PERCEPTUAL = "Perceptual";
+
+
+ private COSDictionary graphicsState;
+
+ /**
+ * Default constructor, creates blank graphics state.
+ */
+ public PDExtendedGraphicsState()
+ {
+ graphicsState = new COSDictionary();
+ graphicsState.setItem( COSName.TYPE, COSName.EXT_G_STATE );
+ }
+
+ /**
+ * Create a graphics state from an existing dictionary.
+ *
+ * @param dictionary The existing graphics state.
+ */
+ public PDExtendedGraphicsState( COSDictionary dictionary )
+ {
+ graphicsState = dictionary;
+ }
+
+ /**
+ * This will implement the gs operator.
+ *
+ * @param gs The state to copy this dictionaries values into.
+ *
+ * @throws IOException If there is an error copying font information.
+ */
+ public void copyIntoGraphicsState( PDGraphicsState gs ) throws IOException
+ {
+ for( COSName key : graphicsState.keySet() )
+ {
+ if( key.equals( COSName.LW ) )
+ {
+ gs.setLineWidth( getLineWidth().doubleValue() );
+ }
+ else if( key.equals( COSName.LC ) )
+ {
+ gs.setLineCap( getLineCapStyle() );
+ }
+ else if( key.equals( COSName.LJ ) )
+ {
+ gs.setLineJoin( getLineJoinStyle() );
+ }
+ else if( key.equals( COSName.ML ) )
+ {
+ gs.setMiterLimit( getMiterLimit().doubleValue() );
+ }
+ else if( key.equals( COSName.D ) )
+ {
+ gs.setLineDashPattern( getLineDashPattern() );
+ }
+ else if( key.equals( COSName.RI ) )
+ {
+ gs.setRenderingIntent( getRenderingIntent() );
+ }
+ else if( key.equals( COSName.OPM ) )
+ {
+ gs.setOverprintMode( getOverprintMode().doubleValue() );
+ }
+ else if( key.equals( COSName.FONT ) )
+ {
+ PDFontSetting setting = getFontSetting();
+ gs.getTextState().setFont( setting.getFont() );
+ gs.getTextState().setFontSize( setting.getFontSize() );
+ }
+ else if( key.equals( COSName.FL ) )
+ {
+ gs.setFlatness( getFlatnessTolerance().floatValue() );
+ }
+ else if( key.equals( COSName.SM ) )
+ {
+ gs.setSmoothness( getSmoothnessTolerance().floatValue() );
+ }
+ else if( key.equals( COSName.SA ) )
+ {
+ gs.setStrokeAdjustment( getAutomaticStrokeAdjustment() );
+ }
+ else if( key.equals( COSName.CA ) )
+ {
+ gs.setAlphaConstants( getStrokingAlpaConstant().floatValue() );
+ }
+ else if( key.equals( COSName.CA_NS ) )
+ {
+ gs.setNonStrokeAlphaConstants(getNonStrokingAlpaConstant().floatValue() );
+ }
+ else if( key.equals( COSName.AIS ) )
+ {
+ gs.setAlphaSource( getAlphaSourceFlag() );
+ }
+ else if( key.equals( COSName.TK ) )
+ {
+ gs.getTextState().setKnockoutFlag( getTextKnockoutFlag() );
+ }
+ }
+ }
+
+ /**
+ * This will get the underlying dictionary that this class acts on.
+ *
+ * @return The underlying dictionary for this class.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return graphicsState;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return graphicsState;
+ }
+
+ /**
+ * This will get the line width. This will return null if there is no line width
+ *
+ * @return null or the LW value of the dictionary.
+ */
+ public Float getLineWidth()
+ {
+ return getFloatItem( COSName.LW );
+ }
+
+ /**
+ * This will set the line width.
+ *
+ * @param width The line width for the object.
+ */
+ public void setLineWidth( Float width )
+ {
+ setFloatItem( COSName.LW, width );
+ }
+
+ /**
+ * This will get the line cap style.
+ *
+ * @return null or the LC value of the dictionary.
+ */
+ public int getLineCapStyle()
+ {
+ return graphicsState.getInt( COSName.LC );
+ }
+
+ /**
+ * This will set the line cap style for the graphics state.
+ *
+ * @param style The new line cap style to set.
+ */
+ public void setLineCapStyle( int style )
+ {
+ graphicsState.setInt( COSName.LC, style );
+ }
+
+ /**
+ * This will get the line join style.
+ *
+ * @return null or the LJ value in the dictionary.
+ */
+ public int getLineJoinStyle()
+ {
+ return graphicsState.getInt( COSName.LJ );
+ }
+
+ /**
+ * This will set the line join style.
+ *
+ * @param style The new line join style.
+ */
+ public void setLineJoinStyle( int style )
+ {
+ graphicsState.setInt( COSName.LJ, style );
+ }
+
+
+ /**
+ * This will get the miter limit.
+ *
+ * @return null or the ML value in the dictionary.
+ */
+ public Float getMiterLimit()
+ {
+ return getFloatItem( COSName.ML );
+ }
+
+ /**
+ * This will set the miter limit for the graphics state.
+ *
+ * @param miterLimit The new miter limit value
+ */
+ public void setMiterLimit( Float miterLimit )
+ {
+ setFloatItem( COSName.ML, miterLimit );
+ }
+
+ /**
+ * This will get the dash pattern.
+ *
+ * @return null or the D value in the dictionary.
+ */
+ public PDLineDashPattern getLineDashPattern()
+ {
+ PDLineDashPattern retval = null;
+ COSArray dp = (COSArray)graphicsState.getDictionaryObject( COSName.D );
+ if( dp != null )
+ {
+ retval = new PDLineDashPattern( dp );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the dash pattern for the graphics state.
+ *
+ * @param dashPattern The dash pattern
+ */
+ public void setLineDashPattern( PDLineDashPattern dashPattern )
+ {
+ graphicsState.setItem( COSName.D, dashPattern.getCOSObject() );
+ }
+
+ /**
+ * This will get the rendering intent.
+ *
+ * @return null or the RI value in the dictionary.
+ */
+ public String getRenderingIntent()
+ {
+ return graphicsState.getNameAsString( "RI" );
+ }
+
+ /**
+ * This will set the rendering intent for the graphics state.
+ *
+ * @param ri The new rendering intent
+ */
+ public void setRenderingIntent( String ri )
+ {
+ graphicsState.setName( "RI", ri );
+ }
+
+ /**
+ * This will get the overprint control.
+ *
+ * @return The overprint control or null if one has not been set.
+ */
+ public boolean getStrokingOverprintControl()
+ {
+ return graphicsState.getBoolean( COSName.OP, false );
+ }
+
+ /**
+ * This will get the overprint control(OP).
+ *
+ * @param op The overprint control.
+ */
+ public void setStrokingOverprintControl( boolean op )
+ {
+ graphicsState.setBoolean( COSName.OP, op );
+ }
+
+ /**
+ * This will get the overprint control for non stroking operations. If this
+ * value is null then the regular overprint control value will be returned.
+ *
+ * @return The overprint control or null if one has not been set.
+ */
+ public boolean getNonStrokingOverprintControl()
+ {
+ return graphicsState.getBoolean( COSName.OP_NS, getStrokingOverprintControl() );
+ }
+
+ /**
+ * This will get the overprint control(OP).
+ *
+ * @param op The overprint control.
+ */
+ public void setNonStrokingOverprintControl( boolean op )
+ {
+ graphicsState.setBoolean( COSName.OP_NS, op );
+ }
+
+ /**
+ * This will get the overprint control mode.
+ *
+ * @return The overprint control mode or null if one has not been set.
+ */
+ public Float getOverprintMode()
+ {
+ return getFloatItem( COSName.OPM );
+ }
+
+ /**
+ * This will get the overprint mode(OPM).
+ *
+ * @param overprintMode The overprint mode
+ */
+ public void setOverprintMode( Float overprintMode )
+ {
+ setFloatItem( COSName.OPM, overprintMode );
+ }
+
+ /**
+ * This will get the font setting of the graphics state.
+ *
+ * @return The font setting.
+ */
+ public PDFontSetting getFontSetting()
+ {
+ PDFontSetting setting = null;
+ COSArray font = (COSArray)graphicsState.getDictionaryObject( COSName.FONT );
+ if( font != null )
+ {
+ setting = new PDFontSetting( font );
+ }
+ return setting;
+ }
+
+ /**
+ * This will set the font setting for this graphics state.
+ *
+ * @param fs The new font setting.
+ */
+ public void setFontSetting( PDFontSetting fs )
+ {
+ graphicsState.setItem( COSName.FONT, fs );
+ }
+
+ /**
+ * This will get the flatness tolerance.
+ *
+ * @return The flatness tolerance or null if one has not been set.
+ */
+ public Float getFlatnessTolerance()
+ {
+ return getFloatItem( COSName.FL );
+ }
+
+ /**
+ * This will get the flatness tolerance.
+ *
+ * @param flatness The new flatness tolerance
+ */
+ public void setFlatnessTolerance( Float flatness )
+ {
+ setFloatItem( COSName.FL, flatness );
+ }
+
+ /**
+ * This will get the smothness tolerance.
+ *
+ * @return The smothness tolerance or null if one has not been set.
+ */
+ public Float getSmoothnessTolerance()
+ {
+ return getFloatItem( COSName.SM );
+ }
+
+ /**
+ * This will get the smoothness tolerance.
+ *
+ * @param smoothness The new smoothness tolerance
+ */
+ public void setSmoothnessTolerance( Float smoothness )
+ {
+ setFloatItem( COSName.SM, smoothness );
+ }
+
+ /**
+ * This will get the automatic stroke adjustment flag.
+ *
+ * @return The automatic stroke adjustment flag or null if one has not been set.
+ */
+ public boolean getAutomaticStrokeAdjustment()
+ {
+ return graphicsState.getBoolean( COSName.SA,false );
+ }
+
+ /**
+ * This will get the automatic stroke adjustment flag.
+ *
+ * @param sa The new automatic stroke adjustment flag.
+ */
+ public void setAutomaticStrokeAdjustment( boolean sa )
+ {
+ graphicsState.setBoolean( COSName.SA, sa );
+ }
+
+ /**
+ * This will get the stroking alpha constant.
+ *
+ * @return The stroking alpha constant or null if one has not been set.
+ */
+ public Float getStrokingAlpaConstant()
+ {
+ return getFloatItem( COSName.CA );
+ }
+
+ /**
+ * This will get the stroking alpha constant.
+ *
+ * @param alpha The new stroking alpha constant.
+ */
+ public void setStrokingAlphaConstant( Float alpha )
+ {
+ setFloatItem( COSName.CA, alpha );
+ }
+
+ /**
+ * This will get the non stroking alpha constant.
+ *
+ * @return The non stroking alpha constant or null if one has not been set.
+ */
+ public Float getNonStrokingAlpaConstant()
+ {
+ return getFloatItem( COSName.CA_NS );
+ }
+
+ /**
+ * This will get the non stroking alpha constant.
+ *
+ * @param alpha The new non stroking alpha constant.
+ */
+ public void setNonStrokingAlphaConstant( Float alpha )
+ {
+ setFloatItem( COSName.CA_NS, alpha );
+ }
+
+ /**
+ * This will get the alpha source flag.
+ *
+ * @return The alpha source flag.
+ */
+ public boolean getAlphaSourceFlag()
+ {
+ return graphicsState.getBoolean( COSName.AIS, false );
+ }
+
+ /**
+ * This will get the alpha source flag.
+ *
+ * @param alpha The alpha source flag.
+ */
+ public void setAlphaSourceFlag( boolean alpha )
+ {
+ graphicsState.setBoolean( COSName.AIS, alpha );
+ }
+
+ /**
+ * This will get the text knockout flag.
+ *
+ * @return The text knockout flag.
+ */
+ public boolean getTextKnockoutFlag()
+ {
+ return graphicsState.getBoolean( COSName.TK,true );
+ }
+
+ /**
+ * This will get the text knockout flag.
+ *
+ * @param tk The text knockout flag.
+ */
+ public void setTextKnockoutFlag( boolean tk )
+ {
+ graphicsState.setBoolean( COSName.TK, tk );
+ }
+
+ /**
+ * This will get a float item from the dictionary.
+ *
+ * @param key The key to the item.
+ *
+ * @return The value for that item.
+ */
+ private Float getFloatItem( COSName key )
+ {
+ Float retval = null;
+ COSNumber value = (COSNumber)graphicsState.getDictionaryObject( key );
+ if( value != null )
+ {
+ retval = new Float( value.floatValue() );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a float object.
+ *
+ * @param key The key to the data that we are setting.
+ * @param value The value that we are setting.
+ */
+ private void setFloatItem( COSName key, Float value )
+ {
+ if( value == null )
+ {
+ graphicsState.removeItem( key );
+ }
+ else
+ {
+ graphicsState.setItem( key, new COSFloat( value.floatValue() ) );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDFontFactory;
+
+import java.io.IOException;
+
+/**
+ * This class represents a font setting used for the graphics state. A font setting is a font and a
+ * font size. Maybe there is a better name for this?
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDFontSetting implements COSObjectable
+{
+ private COSArray fontSetting = null;
+
+ /**
+ * Creates a blank font setting, font will be null, size will be 1.
+ */
+ public PDFontSetting()
+ {
+ fontSetting = new COSArray();
+ fontSetting.add( null );
+ fontSetting.add( new COSFloat( 1 ) );
+ }
+
+ /**
+ * Constructs a font setting from an existing array.
+ *
+ * @param fs The new font setting value.
+ */
+ public PDFontSetting( COSArray fs )
+ {
+ fontSetting = fs;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return fontSetting;
+ }
+
+ /**
+ * This will get the font for this font setting.
+ *
+ * @return The font for this setting of null if one was not found.
+ *
+ * @throws IOException If there is an error getting the font.
+ */
+ public PDFont getFont() throws IOException
+ {
+ PDFont retval = null;
+ COSBase font = fontSetting.get( 0 );
+ if( font instanceof COSDictionary )
+ {
+ retval = PDFontFactory.createFont( (COSDictionary)font );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the font for this font setting.
+ *
+ * @param font The new font.
+ */
+ public void setFont( PDFont font )
+ {
+ fontSetting.set( 0, font );
+ }
+
+ /**
+ * This will get the size of the font.
+ *
+ * @return The size of the font.
+ */
+ public float getFontSize()
+ {
+ COSNumber size = (COSNumber)fontSetting.get( 1 );
+ return size.floatValue();
+ }
+
+ /**
+ * This will set the size of the font.
+ *
+ * @param size The new size of the font.
+ */
+ public void setFontSize( float size )
+ {
+ fontSetting.set( 1, new COSFloat( size ) );
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.text.PDTextState;
+import org.apache.pdfbox.util.Matrix;
+
+/**
+ * This class will hold the current state of the graphics parameters when executing a
+ * content stream.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDGraphicsState implements Cloneable
+{
+ private Matrix currentTransformationMatrix = new Matrix();
+
+ //Here are some attributes of the Graphics state, but have not been created yet.
+ //
+ //clippingPath
+ private PDColorState strokingColor = new PDColorState();
+ private PDColorState nonStrokingColor = new PDColorState();
+ private PDTextState textState = new PDTextState();
+ private double lineWidth = 0;
+ private int lineCap = 0;
+ private int lineJoin = 0;
+ private double miterLimit = 0;
+ private PDLineDashPattern lineDashPattern;
+ private String renderingIntent;
+ private boolean strokeAdjustment = false;
+ //blend mode
+ //soft mask
+ private double alphaConstants = 1.0;
+ private double nonStrokingAlphaConstants = 1.0;
+ private boolean alphaSource = false;
+
+ //DEVICE DEPENDENT parameters
+ private boolean overprint = false;
+ private double overprintMode = 0;
+ //black generation
+ //undercolor removal
+ //transfer
+ //halftone
+ private double flatness = 1.0;
+ private double smoothness = 0;
+
+ private GeneralPath currentClippingPath;
+
+ /**
+ * Default constructor.
+ */
+ public PDGraphicsState()
+ {
+ }
+
+ /**
+ * Constructor with a given pagesize to initialize the clipping path.
+ * @param page the size of the page
+ */
+ public PDGraphicsState(PDRectangle page)
+ {
+ currentClippingPath = new GeneralPath(new Rectangle(page.createDimension()));
+ if (page.getLowerLeftX() != 0 || page.getLowerLeftY() != 0)
+ {
+ //Compensate for offset
+ this.currentTransformationMatrix = this.currentTransformationMatrix.multiply(
+ Matrix.getTranslatingInstance(-page.getLowerLeftX(), -page.getLowerLeftY()));
+ }
+ }
+
+ /**
+ * Get the value of the CTM.
+ *
+ * @return The current transformation matrix.
+ */
+ public Matrix getCurrentTransformationMatrix()
+ {
+ return currentTransformationMatrix;
+ }
+
+ /**
+ * Set the value of the CTM.
+ *
+ * @param value The current transformation matrix.
+ */
+ public void setCurrentTransformationMatrix(Matrix value)
+ {
+ currentTransformationMatrix = value;
+ }
+
+ /**
+ * Get the value of the line width.
+ *
+ * @return The current line width.
+ */
+ public double getLineWidth()
+ {
+ return lineWidth;
+ }
+
+ /**
+ * set the value of the line width.
+ *
+ * @param value The current line width.
+ */
+ public void setLineWidth(double value)
+ {
+ lineWidth = value;
+ }
+
+ /**
+ * Get the value of the line cap.
+ *
+ * @return The current line cap.
+ */
+ public int getLineCap()
+ {
+ return lineCap;
+ }
+
+ /**
+ * set the value of the line cap.
+ *
+ * @param value The current line cap.
+ */
+ public void setLineCap(int value)
+ {
+ lineCap = value;
+ }
+
+ /**
+ * Get the value of the line join.
+ *
+ * @return The current line join value.
+ */
+ public int getLineJoin()
+ {
+ return lineJoin;
+ }
+
+ /**
+ * Get the value of the line join.
+ *
+ * @param value The current line join
+ */
+ public void setLineJoin(int value)
+ {
+ lineJoin = value;
+ }
+
+ /**
+ * Get the value of the miter limit.
+ *
+ * @return The current miter limit.
+ */
+ public double getMiterLimit()
+ {
+ return miterLimit;
+ }
+
+ /**
+ * set the value of the miter limit.
+ *
+ * @param value The current miter limit.
+ */
+ public void setMiterLimit(double value)
+ {
+ miterLimit = value;
+ }
+
+ /**
+ * Get the value of the stroke adjustment parameter.
+ *
+ * @return The current stroke adjustment.
+ */
+ public boolean isStrokeAdjustment()
+ {
+ return strokeAdjustment;
+ }
+
+ /**
+ * set the value of the stroke adjustment.
+ *
+ * @param value The value of the stroke adjustment parameter.
+ */
+ public void setStrokeAdjustment(boolean value)
+ {
+ strokeAdjustment = value;
+ }
+
+ /**
+ * Get the value of the stroke alpha constants property.
+ *
+ * @return The value of the stroke alpha constants parameter.
+ */
+ public double getAlphaConstants()
+ {
+ return alphaConstants;
+ }
+
+ /**
+ * set the value of the stroke alpha constants property.
+ *
+ * @param value The value of the stroke alpha constants parameter.
+ */
+ public void setAlphaConstants(double value)
+ {
+ alphaConstants = value;
+ }
+
+ /**
+ * Get the value of the non-stroke alpha constants property.
+ *
+ * @return The value of the non-stroke alpha constants parameter.
+ */
+ public double getNonStrokeAlphaConstants()
+ {
+ return nonStrokingAlphaConstants;
+ }
+
+ /**
+ * set the value of the non-stroke alpha constants property.
+ *
+ * @param value The value of the non-stroke alpha constants parameter.
+ */
+ public void setNonStrokeAlphaConstants(double value)
+ {
+ nonStrokingAlphaConstants = value;
+ }
+
+ /**
+ * get the value of the stroke alpha source property.
+ *
+ * @return The value of the stroke alpha source parameter.
+ */
+ public boolean isAlphaSource()
+ {
+ return alphaSource;
+ }
+
+ /**
+ * set the value of the alpha source property.
+ *
+ * @param value The value of the alpha source parameter.
+ */
+ public void setAlphaSource(boolean value)
+ {
+ alphaSource = value;
+ }
+
+ /**
+ * get the value of the overprint property.
+ *
+ * @return The value of the overprint parameter.
+ */
+ public boolean isOverprint()
+ {
+ return overprint;
+ }
+
+ /**
+ * set the value of the overprint property.
+ *
+ * @param value The value of the overprint parameter.
+ */
+ public void setOverprint(boolean value)
+ {
+ overprint = value;
+ }
+
+ /**
+ * get the value of the overprint mode property.
+ *
+ * @return The value of the overprint mode parameter.
+ */
+ public double getOverprintMode()
+ {
+ return overprintMode;
+ }
+
+ /**
+ * set the value of the overprint mode property.
+ *
+ * @param value The value of the overprint mode parameter.
+ */
+ public void setOverprintMode(double value)
+ {
+ overprintMode = value;
+ }
+
+ /**
+ * get the value of the flatness property.
+ *
+ * @return The value of the flatness parameter.
+ */
+ public double getFlatness()
+ {
+ return flatness;
+ }
+
+ /**
+ * set the value of the flatness property.
+ *
+ * @param value The value of the flatness parameter.
+ */
+ public void setFlatness(double value)
+ {
+ flatness = value;
+ }
+
+ /**
+ * get the value of the smoothness property.
+ *
+ * @return The value of the smoothness parameter.
+ */
+ public double getSmoothness()
+ {
+ return smoothness;
+ }
+
+ /**
+ * set the value of the smoothness property.
+ *
+ * @param value The value of the smoothness parameter.
+ */
+ public void setSmoothness(double value)
+ {
+ smoothness = value;
+ }
+
+ /**
+ * This will get the graphics text state.
+ *
+ * @return The graphics text state.
+ */
+ public PDTextState getTextState()
+ {
+ return textState;
+ }
+
+ /**
+ * This will set the graphics text state.
+ *
+ * @param value The graphics text state.
+ */
+ public void setTextState(PDTextState value)
+ {
+ textState = value;
+ }
+
+ /**
+ * This will get the current line dash pattern.
+ *
+ * @return The line dash pattern.
+ */
+ public PDLineDashPattern getLineDashPattern()
+ {
+ return lineDashPattern;
+ }
+
+ /**
+ * This will set the current line dash pattern.
+ *
+ * @param value The new line dash pattern.
+ */
+ public void setLineDashPattern(PDLineDashPattern value)
+ {
+ lineDashPattern = value;
+ }
+
+ /**
+ * This will get the rendering intent.
+ *
+ * @see PDExtendedGraphicsState
+ *
+ * @return The rendering intent
+ */
+ public String getRenderingIntent()
+ {
+ return renderingIntent;
+ }
+
+ /**
+ * This will set the rendering intent.
+ *
+ * @param value The new rendering intent.
+ */
+ public void setRenderingIntent(String value)
+ {
+ renderingIntent = value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object clone()
+ {
+ PDGraphicsState clone = null;
+ try
+ {
+ clone = (PDGraphicsState)super.clone();
+ clone.setTextState( (PDTextState)textState.clone() );
+ clone.setCurrentTransformationMatrix( currentTransformationMatrix.copy() );
+ clone.strokingColor = (PDColorState)strokingColor.clone();
+ clone.nonStrokingColor = ((PDColorState)nonStrokingColor.clone());
+ if( lineDashPattern != null )
+ {
+ clone.setLineDashPattern( (PDLineDashPattern)lineDashPattern.clone() );
+ }
+ if (currentClippingPath != null)
+ {
+ clone.setCurrentClippingPath((GeneralPath)currentClippingPath.clone());
+ }
+ }
+ catch( CloneNotSupportedException e )
+ {
+ e.printStackTrace();
+ }
+ return clone;
+ }
+
+ /**
+ * Returns the stroking color state.
+ *
+ * @return stroking color state
+ */
+ public PDColorState getStrokingColor()
+ {
+ return strokingColor;
+ }
+
+ /**
+ * Returns the non-stroking color state.
+ *
+ * @return non-stroking color state
+ */
+ public PDColorState getNonStrokingColor()
+ {
+ return nonStrokingColor;
+ }
+
+ /**
+ * This will set the current clipping path.
+ *
+ * @param pCurrentClippingPath The current clipping path.
+ *
+ */
+ public void setCurrentClippingPath(Shape pCurrentClippingPath)
+ {
+ if (pCurrentClippingPath != null)
+ {
+ if (pCurrentClippingPath instanceof GeneralPath)
+ {
+ currentClippingPath = (GeneralPath)pCurrentClippingPath;
+ }
+ else
+ {
+ currentClippingPath = new GeneralPath();
+ currentClippingPath.append(pCurrentClippingPath,false);
+ }
+ }
+ else
+ {
+ currentClippingPath = null;
+ }
+ }
+
+ /**
+ * This will get the current clipping path.
+ *
+ * @return The current clipping path.
+ */
+ public Shape getCurrentClippingPath()
+ {
+ return currentClippingPath;
+ }
+
+ public Composite getStrokeJavaComposite() {
+
+ return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) alphaConstants);
+ }
+
+ public Composite getNonStrokeJavaComposite() {
+
+ return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) nonStrokingAlphaConstants);
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import java.util.List;
+
+/**
+ * This class represents the line dash pattern for a graphics state. See PDF
+ * Reference 1.5 section 4.3.2
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class PDLineDashPattern implements COSObjectable, Cloneable
+{
+ private COSArray lineDashPattern = null;
+
+ /**
+ * Creates a blank line dash pattern. With no dashes and a phase of 0.
+ */
+ public PDLineDashPattern()
+ {
+ lineDashPattern = new COSArray();
+ lineDashPattern.add( new COSArray() );
+ lineDashPattern.add( COSInteger.ZERO );
+ }
+
+ /**
+ * Constructs a line dash pattern from an existing array.
+ *
+ * @param ldp The existing line dash pattern.
+ */
+ public PDLineDashPattern( COSArray ldp )
+ {
+ lineDashPattern = ldp;
+ }
+
+ /**
+ * Constructs a line dash pattern from an existing array.
+ *
+ * @param ldp The existing line dash pattern.
+ * @param phase The phase for the line dash pattern.
+ */
+ public PDLineDashPattern( COSArray ldp, int phase )
+ {
+ lineDashPattern = new COSArray();
+ lineDashPattern.add( ldp );
+ lineDashPattern.add( COSInteger.get( phase ) );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object clone()
+ {
+ PDLineDashPattern pattern = null;
+ try
+ {
+ pattern = (PDLineDashPattern)super.clone();
+ pattern.setDashPattern(getDashPattern());
+ pattern.setPhaseStart(getPhaseStart());
+ }
+ catch(CloneNotSupportedException exception)
+ {
+ exception.printStackTrace();
+ }
+ return pattern;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return lineDashPattern;
+ }
+
+ /**
+ * This will get the line dash pattern phase. The dash phase specifies the
+ * distance into the dash pattern at which to start the dash.
+ *
+ * @return The line dash pattern phase.
+ */
+ public int getPhaseStart()
+ {
+ COSNumber phase = (COSNumber)lineDashPattern.get( 1 );
+ return phase.intValue();
+ }
+
+ /**
+ * This will set the line dash pattern phase.
+ *
+ * @param phase The new line dash patter phase.
+ */
+ public void setPhaseStart( int phase )
+ {
+ lineDashPattern.set( 1, phase );
+ }
+
+ /**
+ * This will return a list of java.lang.Integer objects that represent the line
+ * dash pattern appearance.
+ *
+ * @return The line dash pattern.
+ */
+ public List getDashPattern()
+ {
+ COSArray dashPatterns = (COSArray)lineDashPattern.get( 0 );
+ return COSArrayList.convertIntegerCOSArrayToList( dashPatterns );
+ }
+
+ /**
+ * Get the line dash pattern as a COS object.
+ *
+ * @return The cos array line dash pattern.
+ */
+ public COSArray getCOSDashPattern()
+ {
+ return (COSArray)lineDashPattern.get( 0 );
+ }
+
+ /**
+ * This will replace the existing line dash pattern.
+ *
+ * @param dashPattern A list of java.lang.Integer objects.
+ */
+ public void setDashPattern( List dashPattern )
+ {
+ lineDashPattern.set( 0, COSArrayList.converterToCOSArray( dashPattern ) );
+ }
+
+ /**
+ * Checks if the dashPattern is empty or all values equals 0.
+ *
+ * @return true if the dashPattern is empty or all values equals 0
+ */
+ public boolean isDashPatternEmpty()
+ {
+ float[] dashPattern = getCOSDashPattern().toFloatArray();
+ boolean dashPatternEmpty = true;
+ if (dashPattern != null)
+ {
+ int arraySize = dashPattern.length;
+ for(int i=0;i<arraySize;i++)
+ {
+ if (dashPattern[i] > 0)
+ {
+ dashPatternEmpty = false;
+ break;
+ }
+ }
+ }
+ return dashPatternEmpty;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.common.function.PDFunction;
+
+
+
+import java.io.IOException;
+
+/**
+ * This class represents a Shading Pattern color space.
+ * See section 4.6.3 of the PDF 1.7 specification.
+ *
+ * @author <a href="mailto:Daniel.Wilson@BlackLocustSoftware.com">Daniel wilson</a>
+ * @version $Revision: 1.0 $
+ */
+public class PDShading implements COSObjectable
+{
+ private COSDictionary DictShading;
+ private COSName shadingname;
+ private COSArray domain = null;
+ private COSArray extend = null;
+ private PDFunction function = null;
+ private PDColorSpace colorspace = null;
+
+ /**
+ * The name of this object.
+ */
+ public static final String NAME = "Shading";
+
+ /**
+ * Default constructor.
+ */
+ public PDShading()
+ {
+ DictShading = new COSDictionary();
+ //DictShading.add( COSName.getPDFName( NAME ) );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param shading The shading dictionary.
+ */
+ public PDShading(COSName name, COSDictionary shading)
+ {
+ DictShading = shading;
+ shadingname = name;
+ }
+
+ /**
+ * This will return the name of the object.
+ *
+ * @return The name of the object.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return COSName.SHADING;
+ }
+
+ /**
+ * This will return the name of this particular shading dictionary
+ *
+ * @return The name of the shading dictionary
+ */
+ public COSName getShadingName()
+ {
+ return shadingname;
+ }
+
+ /**
+ * This will return the ShadingType -- an integer between 1 and 7 that specifies the gradient type.
+ * Required in all Shading Dictionaries.
+ *
+ * @return The Shading Type
+ */
+ public int getShadingType()
+ {
+ return DictShading.getInt(COSName.SHADING_TYPE);
+ }
+
+ /**
+ * This will return the Color Space.
+ * Required in all Shading Dictionaries.
+ *
+ * @return The Color Space of the shading dictionary
+ */
+ public PDColorSpace getColorSpace() throws IOException
+ {
+ if (colorspace == null)
+ {
+ colorspace = PDColorSpaceFactory.createColorSpace(DictShading.getDictionaryObject(COSName.COLORSPACE));
+ }
+ return colorspace;
+ }
+
+ /**
+ * This will return a boolean flag indicating whether to antialias the shading pattern.
+ *
+ * @return The antialias flag, defaulting to False
+ */
+ public boolean getAntiAlias()
+ {
+ return DictShading.getBoolean(COSName.ANTI_ALIAS,false);
+ }
+
+ /**
+ * Returns the coordinate array used by several of the gradient types. Interpretation depends on the ShadingType.
+ *
+ * @return The coordinate array.
+ */
+ public COSArray getCoords()
+ {
+ return (COSArray)(DictShading.getDictionaryObject(COSName.COORDS));
+ }
+
+ /**
+ * Returns the function used by several of the gradient types. Interpretation depends on the ShadingType.
+ *
+ * @return The gradient function.
+ */
+ public PDFunction getFunction() throws IOException
+ {
+ if (function == null)
+ {
+ function = PDFunction.create(DictShading.getDictionaryObject(COSName.FUNCTION));
+ }
+ return function;
+ }
+
+ /**
+ * Returns the Domain array used by several of the gradient types. Interpretation depends on the ShadingType.
+ *
+ * @return The Domain array.
+ */
+ public COSArray getDomain()
+ {
+ if (domain == null)
+ {
+ domain = (COSArray)(DictShading.getDictionaryObject(COSName.DOMAIN));
+ // use default values
+ if (domain == null)
+ {
+ domain = new COSArray();
+ domain.add(new COSFloat(0.0f));
+ domain.add(new COSFloat(1.0f));
+ }
+ }
+ return domain;
+ }
+
+ /**
+ * Returns the Extend array used by several of the gradient types. Interpretation depends on the ShadingType.
+ * Default is {false, false}.
+ *
+ * @return The Extend array.
+ */
+ public COSArray getExtend()
+ {
+ if (extend == null)
+ {
+ extend = (COSArray)(DictShading.getDictionaryObject(COSName.EXTEND));
+ // use default values
+ if (extend == null)
+ {
+ extend = new COSArray();
+ extend.add(COSBoolean.FALSE);
+ extend.add(COSBoolean.FALSE);
+ }
+ }
+ return extend;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ String sColorSpace;
+ String sFunction;
+ try
+ {
+ sColorSpace = getColorSpace().toString();
+ }catch (IOException e)
+ {
+ sColorSpace = "Failure retrieving ColorSpace: " + e.toString();
+ }
+ try
+ {
+ sFunction = getFunction().toString();
+ }catch(IOException e)
+ {
+ sFunction = "n/a";
+ }
+
+
+ String s = "Shading " + shadingname + "\n"
+ + "\tShadingType: " + getShadingType() + "\n"
+ + "\tColorSpace: " + sColorSpace + "\n"
+ + "\tAntiAlias: " + getAntiAlias() + "\n"
+ + "\tCoords: " + (getCoords() != null ? getCoords().toString() : "") + "\n"
+ + "\tDomain: " + getDomain().toString() + "\n"
+ + "\tFunction: " + sFunction + "\n"
+ + "\tExtend: " + getExtend().toString() + "\n"
+ + "\tRaw Value:\n" +
+ DictShading.toString();
+
+ return s;
+ }
+
+}
--- /dev/null
+/*
+ * 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.color;
+
+import java.awt.color.ColorSpace;
+
+/**
+ * This class represents a CMYK color space.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class ColorSpaceCMYK extends ColorSpace
+{
+ /**
+ * IDfor serialization.
+ */
+ private static final long serialVersionUID = -6362864473145799405L;
+
+ /**
+ * Constructor.
+ */
+ public ColorSpaceCMYK()
+ {
+ super(ColorSpace.TYPE_CMYK,4);
+ }
+
+ /**
+ * Converts colorvalues from RGB-colorspace to CIEXYZ-colorspace.
+ * @param rgbvalue RGB colorvalues to be converted.
+ * @return Returns converted colorvalues.
+ */
+ private float[] fromRGBtoCIEXYZ(float[] rgbvalue)
+ {
+ ColorSpace colorspaceRGB = ColorSpace.getInstance(CS_sRGB);
+ return colorspaceRGB.toCIEXYZ(rgbvalue);
+ }
+
+ /**
+ * Converts colorvalues from CIEXYZ-colorspace to RGB-colorspace.
+ * @param rgbvalue CIEXYZ colorvalues to be converted.
+ * @return Returns converted colorvalues.
+ */
+ private float[] fromCIEXYZtoRGB(float[] xyzvalue)
+ {
+ ColorSpace colorspaceXYZ = ColorSpace.getInstance(CS_CIEXYZ);
+ return colorspaceXYZ.toRGB(xyzvalue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float[] fromCIEXYZ(float[] colorvalue)
+ {
+ if (colorvalue != null && colorvalue.length == 3)
+ {
+ // We have to convert from XYV to RGB to CMYK
+ return fromRGB(fromCIEXYZtoRGB(colorvalue));
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float[] fromRGB(float[] rgbvalue)
+ {
+ if (rgbvalue != null && rgbvalue.length == 3)
+ {
+ // First of all we have to convert from RGB to CMY
+ float c = 1 - rgbvalue[0];
+ float m = 1 - rgbvalue[1];
+ float y = 1 - rgbvalue[2];
+ // Now we have to convert from CMY to CMYK
+ float varK = 1;
+ float[] cmyk = new float[4];
+ if ( c < varK )
+ {
+ varK = c;
+ }
+ if ( m < varK )
+ {
+ varK = m;
+ }
+ if ( y < varK )
+ {
+ varK = y;
+ }
+ if ( varK == 1 )
+ {
+ cmyk[0] = cmyk[1] = cmyk[2] = 0;
+ }
+ else
+ {
+ cmyk[0] = ( c - varK ) / ( 1 - varK );
+ cmyk[1] = ( m - varK ) / ( 1 - varK );
+ cmyk[2] = ( y - varK ) / ( 1 - varK );
+ }
+ cmyk[3] = varK;
+ return cmyk;
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float[] toCIEXYZ(float[] colorvalue)
+ {
+ if (colorvalue != null && colorvalue.length == 4)
+ {
+ // We have to convert from CMYK to RGB to XYV
+ return fromRGBtoCIEXYZ(toRGB(colorvalue));
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float[] toRGB(float[] colorvalue)
+ {
+ if (colorvalue != null && colorvalue.length == 4)
+ {
+ // First of all we have to convert from CMYK to CMY
+ float k = colorvalue[3];
+ float c = ( colorvalue[0] * ( 1 - k ) + k );
+ float m = ( colorvalue[1] * ( 1 - k ) + k );
+ float y = ( colorvalue[2] * ( 1 - k ) + k );
+ // Now we have to convert from CMY to RGB
+ return new float[] { 1 - c, 1 - m, 1 - y };
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.color;
+
+import java.awt.color.ColorSpace;
+
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+
+/**
+ * This class represents a CalRGB color space.
+ *
+ * In the first place this implementation is needed to support CalRGB.
+ * To keep it simple, the CalRGB colorspace is treated similar to a DeviceRGB colorspace.
+ * There is no conversion including the gamma, whitepoint, blackpoint or matrix values yet.
+ * This should be suitable for displaying and simple printings.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class ColorSpaceCalRGB extends ColorSpace
+{
+ private PDGamma gamma = null;
+ private PDTristimulus whitepoint = null;
+ private PDTristimulus blackpoint = null;
+ private PDMatrix matrix = null;
+
+ /**
+ * ID for serialization.
+ */
+ private static final long serialVersionUID = -6362864473145799405L;
+
+ /**
+ * Constructor.
+ */
+ public ColorSpaceCalRGB()
+ {
+ super(ColorSpace.TYPE_3CLR,3);
+ }
+
+ /**
+ * Constructor.
+ * @param gammaValue Gamma
+ * @param whitept Whitepoint
+ * @param blackpt Blackpoint
+ * @param linearMatrix Matrix value
+ */
+ public ColorSpaceCalRGB(PDGamma gammaValue, PDTristimulus whitept, PDTristimulus blackpt, PDMatrix linearMatrix)
+ {
+ this();
+ this.gamma = gammaValue;
+ this.whitepoint = whitept;
+ this.blackpoint = blackpt;
+ this.matrix = linearMatrix;
+ }
+
+ /**
+ * Converts colorvalues from RGB-colorspace to CIEXYZ-colorspace.
+ * @param rgbvalue RGB colorvalues to be converted.
+ * @return Returns converted colorvalues.
+ */
+ private float[] fromRGBtoCIEXYZ(float[] rgbvalue)
+ {
+ ColorSpace colorspaceRGB = ColorSpace.getInstance(CS_sRGB);
+ return colorspaceRGB.toCIEXYZ(rgbvalue);
+ }
+
+ /**
+ * Converts colorvalues from CIEXYZ-colorspace to RGB-colorspace.
+ * @param rgbvalue CIEXYZ colorvalues to be converted.
+ * @return Returns converted colorvalues.
+ */
+ private float[] fromCIEXYZtoRGB(float[] xyzvalue)
+ {
+ ColorSpace colorspaceXYZ = ColorSpace.getInstance(CS_CIEXYZ);
+ return colorspaceXYZ.toRGB(xyzvalue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float[] fromCIEXYZ(float[] colorvalue)
+ {
+ if (colorvalue != null && colorvalue.length == 3)
+ {
+ // We have to convert from XYV to RGB
+ return fromCIEXYZtoRGB(colorvalue);
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float[] fromRGB(float[] rgbvalue)
+ {
+ if (rgbvalue != null && rgbvalue.length == 3)
+ {
+ return rgbvalue;
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float[] toCIEXYZ(float[] colorvalue)
+ {
+ if (colorvalue != null && colorvalue.length == 4)
+ {
+ // We have to convert from RGB to XYV
+ return fromRGBtoCIEXYZ(toRGB(colorvalue));
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public float[] toRGB(float[] colorvalue)
+ {
+ if (colorvalue != null && colorvalue.length == 3)
+ {
+ return colorvalue;
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+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 java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+import java.io.IOException;
+
+/**
+ * This class represents a Cal Gray color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDCalGray extends PDColorSpace
+{
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "CalGray";
+
+ private COSArray array;
+ private COSDictionary dictionary;
+
+ /**
+ * Constructor.
+ */
+ public PDCalGray()
+ {
+ array = new COSArray();
+ dictionary = new COSDictionary();
+ array.add( COSName.CALGRAY );
+ array.add( dictionary );
+ }
+
+ /**
+ * Constructor with array.
+ *
+ * @param gray The underlying color space.
+ */
+ public PDCalGray( COSArray gray )
+ {
+ array = gray;
+ dictionary = (COSDictionary)array.getObject( 1 );
+ }
+
+ /**
+ * This will get the number of components that this color space is made up of.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ return 1;
+ }
+
+ /**
+ * This will return the name of the color space.
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color space.
+ */
+ protected ColorSpace createColorSpace() throws IOException
+ {
+ throw new IOException( "Not implemented" );
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+ throw new IOException( "Not implemented" );
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return array;
+ }
+
+ /**
+ * This will get the gamma value. If none is present then the default of 1
+ * will be returned.
+ *
+ * @return The gamma value.
+ */
+ public float getGamma()
+ {
+ float retval = 1.0f;
+ COSNumber gamma = (COSNumber)dictionary.getDictionaryObject( COSName.GAMMA );
+ if( gamma != null )
+ {
+ retval = gamma.floatValue();
+ }
+ return retval;
+ }
+
+ /**
+ * Set the gamma value.
+ *
+ * @param value The new gamma value.
+ */
+ public void setGamma( float value )
+ {
+ dictionary.setItem( COSName.GAMMA, new COSFloat( value ) );
+ }
+
+ /**
+ * This will return the whitepoint tristimulus. As this is a required field
+ * this will never return null. A default of 1,1,1 will be returned if the
+ * pdf does not have any values yet.
+ *
+ * @return The whitepoint tristimulus.
+ */
+ public PDTristimulus getWhitepoint()
+ {
+ COSArray wp = (COSArray)dictionary.getDictionaryObject( COSName.WHITE_POINT );
+ if( wp == null )
+ {
+ wp = new COSArray();
+ wp.add( new COSFloat( 1.0f ) );
+ wp.add( new COSFloat( 1.0f ) );
+ wp.add( new COSFloat( 1.0f ) );
+ dictionary.setItem( COSName.WHITE_POINT, wp );
+ }
+ return new PDTristimulus( wp );
+ }
+
+ /**
+ * This will set the whitepoint tristimulus. As this is a required field
+ * this null should not be passed into this function.
+ *
+ * @param wp The whitepoint tristimulus.
+ */
+ public void setWhitepoint( PDTristimulus wp )
+ {
+ COSBase wpArray = wp.getCOSObject();
+ if( wpArray != null )
+ {
+ dictionary.setItem( COSName.WHITE_POINT, wpArray );
+ }
+ }
+
+ /**
+ * This will return the BlackPoint tristimulus. This is an optional field but
+ * has defaults so this will never return null.
+ * A default of 0,0,0 will be returned if the pdf does not have any values yet.
+ *
+ * @return The blackpoint tristimulus.
+ */
+ public PDTristimulus getBlackPoint()
+ {
+ COSArray bp = (COSArray)dictionary.getDictionaryObject( COSName.BLACK_POINT );
+ if( bp == null )
+ {
+ bp = new COSArray();
+ bp.add( new COSFloat( 0.0f ) );
+ bp.add( new COSFloat( 0.0f ) );
+ bp.add( new COSFloat( 0.0f ) );
+ dictionary.setItem( COSName.BLACK_POINT, bp );
+ }
+ return new PDTristimulus( bp );
+ }
+
+ /**
+ * This will set the BlackPoint tristimulus. As this is a required field
+ * this null should not be passed into this function.
+ *
+ * @param bp The BlackPoint tristimulus.
+ */
+ public void setBlackPoint( PDTristimulus bp )
+ {
+ COSBase bpArray = null;
+ if( bp != null )
+ {
+ bpArray = bp.getCOSObject();
+ }
+ dictionary.setItem( COSName.BLACK_POINT, bpArray );
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.io.IOException;
+
+/**
+ * This class represents a Cal RGB color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDCalRGB extends PDColorSpace
+{
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "CalRGB";
+
+ private COSArray array;
+ private COSDictionary dictionary;
+
+ /**
+ * Constructor.
+ */
+ public PDCalRGB()
+ {
+ array = new COSArray();
+ dictionary = new COSDictionary();
+ array.add( COSName.CALRGB );
+ array.add( dictionary );
+ }
+
+ /**
+ * Constructor with array.
+ *
+ * @param rgb The underlying color space.
+ */
+ public PDCalRGB( COSArray rgb )
+ {
+ array = rgb;
+ dictionary = (COSDictionary)array.getObject( 1 );
+ }
+
+ /**
+ * This will get the number of components that this color space is made up of.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ return 3;
+ }
+
+ /**
+ * This will return the name of the color space.
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ */
+ protected ColorSpace createColorSpace()
+ {
+ return new ColorSpaceCalRGB(getGamma(),getWhitepoint(),getBlackPoint(),getLinearInterpretation());
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+ int[] nBits = {bpc, bpc, bpc};
+ return new ComponentColorModel( getJavaColorSpace(),
+ nBits,
+ false,
+ false,
+ Transparency.OPAQUE,
+ DataBuffer.TYPE_BYTE);
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return array;
+ }
+
+ /**
+ * This will return the whitepoint tristimulus. As this is a required field
+ * this will never return null. A default of 1,1,1 will be returned if the
+ * pdf does not have any values yet.
+ *
+ * @return The whitepoint tristimulus.
+ */
+ public PDTristimulus getWhitepoint()
+ {
+ COSArray wp = (COSArray)dictionary.getDictionaryObject( COSName.WHITE_POINT );
+ if( wp == null )
+ {
+ wp = new COSArray();
+ wp.add( new COSFloat( 1.0f ) );
+ wp.add( new COSFloat( 1.0f ) );
+ wp.add( new COSFloat( 1.0f ) );
+ dictionary.setItem( COSName.WHITE_POINT, wp );
+ }
+ return new PDTristimulus( wp );
+ }
+
+ /**
+ * This will set the whitepoint tristimulus. As this is a required field
+ * this null should not be passed into this function.
+ *
+ * @param wp The whitepoint tristimulus.
+ */
+ public void setWhitepoint( PDTristimulus wp )
+ {
+ COSBase wpArray = wp.getCOSObject();
+ if( wpArray != null )
+ {
+ dictionary.setItem( COSName.WHITE_POINT, wpArray );
+ }
+ }
+
+ /**
+ * This will return the BlackPoint tristimulus. This is an optional field but
+ * has defaults so this will never return null.
+ * A default of 0,0,0 will be returned if the pdf does not have any values yet.
+ *
+ * @return The blackpoint tristimulus.
+ */
+ public PDTristimulus getBlackPoint()
+ {
+ COSArray bp = (COSArray)dictionary.getDictionaryObject( COSName.BLACK_POINT );
+ if( bp == null )
+ {
+ bp = new COSArray();
+ bp.add( new COSFloat( 0.0f ) );
+ bp.add( new COSFloat( 0.0f ) );
+ bp.add( new COSFloat( 0.0f ) );
+ dictionary.setItem( COSName.BLACK_POINT, bp );
+ }
+ return new PDTristimulus( bp );
+ }
+
+ /**
+ * This will set the BlackPoint tristimulus. As this is a required field
+ * this null should not be passed into this function.
+ *
+ * @param bp The BlackPoint tristimulus.
+ */
+ public void setBlackPoint( PDTristimulus bp )
+ {
+
+ COSBase bpArray = null;
+ if( bp != null )
+ {
+ bpArray = bp.getCOSObject();
+ }
+ dictionary.setItem( COSName.BLACK_POINT, bpArray );
+ }
+
+ /**
+ * This will get the gamma value. If none is present then the default of 1,1,1
+ * will be returned.
+ *
+ * @return The gamma value.
+ */
+ public PDGamma getGamma()
+ {
+ COSArray gamma = (COSArray)dictionary.getDictionaryObject( COSName.GAMMA );
+ if( gamma == null )
+ {
+ gamma = new COSArray();
+ gamma.add( new COSFloat( 1.0f ) );
+ gamma.add( new COSFloat( 1.0f ) );
+ gamma.add( new COSFloat( 1.0f ) );
+ dictionary.setItem( COSName.GAMMA, gamma );
+ }
+ return new PDGamma( gamma );
+ }
+
+ /**
+ * Set the gamma value.
+ *
+ * @param value The new gamma value.
+ */
+ public void setGamma( PDGamma value )
+ {
+ COSArray gamma = null;
+ if( value != null )
+ {
+ gamma = value.getCOSArray();
+ }
+ dictionary.setItem( COSName.GAMMA, gamma );
+ }
+
+ /**
+ * This will get the linear interpretation array. This is guaranteed to not
+ * return null. If the underlying dictionary contains null then the identity
+ * matrix will be returned.
+ *
+ * @return The linear interpretation matrix.
+ */
+ public PDMatrix getLinearInterpretation()
+ {
+ PDMatrix retval = null;
+ COSArray matrix = (COSArray)dictionary.getDictionaryObject( COSName.MATRIX );
+ if( matrix == null )
+ {
+ retval = new PDMatrix();
+ setLinearInterpretation( retval );
+ }
+ else
+ {
+ retval = new PDMatrix( matrix );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the linear interpretation matrix. Passing in null will
+ * clear the matrix.
+ *
+ * @param matrix The new linear interpretation matrix.
+ */
+ public void setLinearInterpretation( PDMatrix matrix )
+ {
+ COSArray matrixArray = null;
+ if( matrix != null )
+ {
+ matrixArray = matrix.getCOSArray();
+ }
+ dictionary.setItem( COSName.MATRIX, matrixArray );
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSArray;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import java.io.IOException;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+
+/**
+ * This class represents a color space in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public abstract class PDColorSpace implements COSObjectable
+{
+ /**
+ * array for the given parameters.
+ */
+ protected COSArray array;
+
+ /**
+ * Cached Java AWT color space.
+ *
+ * @see #getJavaColorSpace()
+ */
+ private ColorSpace colorSpace = null;
+
+ /**
+ * This will return the name of the color space.
+ *
+ * @return The name of the color space.
+ */
+ public abstract String getName();
+
+ /**
+ * This will get the number of components that this color space is made up of.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public abstract int getNumberOfComponents() throws IOException;
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return COSName.getPDFName( getName() );
+ }
+
+ /**
+ * Returns the Java AWT color space for this instance.
+ *
+ * @return Java AWT color space
+ * @throws IOException if the color space can not be created
+ */
+ public ColorSpace getJavaColorSpace() throws IOException {
+ if (colorSpace == null) {
+ colorSpace = createColorSpace();
+ }
+ return colorSpace;
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color space.
+ */
+ protected abstract ColorSpace createColorSpace() throws IOException;
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public abstract ColorModel createColorModel( int bpc ) throws IOException;
+
+ /*
+ Don't just tell me its color type -- tell me its contents!
+ */
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ return getName() + "{ " + (array==null? "" : array.toString() ) + " }";
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+
+/**
+ * This class represents a color space in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.11 $
+ */
+public final class PDColorSpaceFactory
+{
+ /**
+ * Private constructor for utility classes.
+ */
+ private PDColorSpaceFactory()
+ {
+ //utility class should not be implemented
+ }
+
+ /**
+ * This will create the correct color space given the name.
+ *
+ * @param colorSpace The color space object.
+ *
+ * @return The color space.
+ *
+ * @throws IOException If the color space name is unknown.
+ */
+ public static PDColorSpace createColorSpace( COSBase colorSpace ) throws IOException
+ {
+ return createColorSpace( colorSpace, null );
+ }
+
+ /**
+ * This will create the correct color space given the name.
+ *
+ * @param colorSpace The color space object.
+ * @param colorSpaces The ColorSpace dictionary from the current resources, if any.
+ *
+ * @return The color space.
+ *
+ * @throws IOException If the color space name is unknown.
+ */
+ public static PDColorSpace createColorSpace( COSBase colorSpace, Map<String, PDColorSpace> colorSpaces ) throws IOException
+ {
+ PDColorSpace retval = null;
+ if( colorSpace instanceof COSName )
+ {
+ retval = createColorSpace( ((COSName)colorSpace).getName(), colorSpaces );
+ }
+ else if( colorSpace instanceof COSArray )
+ {
+ COSArray array = (COSArray)colorSpace;
+ String name = ((COSName)array.getObject( 0 )).getName();
+ if( name.equals( PDCalGray.NAME ) )
+ {
+ retval = new PDCalGray( array );
+ }
+ else if( name.equals( PDDeviceRGB.NAME ) )
+ {
+ retval = PDDeviceRGB.INSTANCE;
+ }
+ else if( name.equals( PDDeviceGray.NAME ) )
+ {
+ retval = new PDDeviceGray();
+ }
+ else if( name.equals( PDDeviceCMYK.NAME ) )
+ {
+ retval = PDDeviceCMYK.INSTANCE;
+ }
+ else if( name.equals( PDCalRGB.NAME ) )
+ {
+ retval = new PDCalRGB( array );
+ }
+ else if( name.equals( PDDeviceN.NAME ) )
+ {
+ retval = new PDDeviceN( array );
+ }
+ else if( name.equals( PDIndexed.NAME ) ||
+ name.equals( PDIndexed.ABBREVIATED_NAME ))
+ {
+ retval = new PDIndexed( array );
+ }
+ else if( name.equals( PDLab.NAME ) )
+ {
+ retval = new PDLab( array );
+ }
+ else if( name.equals( PDSeparation.NAME ) )
+ {
+ retval = new PDSeparation( array );
+ }
+ else if( name.equals( PDICCBased.NAME ) )
+ {
+ retval = new PDICCBased( array );
+ }
+ else if( name.equals( PDPattern.NAME ) )
+ {
+ retval = new PDPattern( array );
+ }
+ else
+ {
+ throw new IOException( "Unknown colorspace array type:" + name );
+ }
+ }
+ else
+ {
+ throw new IOException( "Unknown colorspace type:" + colorSpace );
+ }
+ return retval;
+ }
+
+ /**
+ * This will create the correct color space given the name.
+ *
+ * @param colorSpaceName The name of the colorspace.
+ *
+ * @return The color space.
+ *
+ * @throws IOException If the color space name is unknown.
+ */
+ public static PDColorSpace createColorSpace( String colorSpaceName ) throws IOException
+ {
+ return createColorSpace(colorSpaceName, null);
+ }
+
+ /**
+ * This will create the correct color space given the name.
+ *
+ * @param colorSpaceName The name of the colorspace.
+ * @param colorSpaces The ColorSpace dictionary from the current resources, if any.
+ *
+ * @return The color space.
+ *
+ * @throws IOException If the color space name is unknown.
+ */
+ public static PDColorSpace createColorSpace( String colorSpaceName, Map<String, PDColorSpace> colorSpaces ) throws IOException
+ {
+ PDColorSpace cs = null;
+ if( colorSpaceName.equals( PDDeviceCMYK.NAME ) ||
+ colorSpaceName.equals( PDDeviceCMYK.ABBREVIATED_NAME ) )
+ {
+ cs = PDDeviceCMYK.INSTANCE;
+ }
+ else if( colorSpaceName.equals( PDDeviceRGB.NAME ) ||
+ colorSpaceName.equals( PDDeviceRGB.ABBREVIATED_NAME ) )
+ {
+ cs = PDDeviceRGB.INSTANCE;
+ }
+ else if( colorSpaceName.equals( PDDeviceGray.NAME ) ||
+ colorSpaceName.equals( PDDeviceGray.ABBREVIATED_NAME ))
+ {
+ cs = new PDDeviceGray();
+ }
+ else if( colorSpaces != null && colorSpaces.get( colorSpaceName ) != null )
+ {
+ cs = (PDColorSpace)colorSpaces.get( colorSpaceName );
+ }
+ else if( colorSpaceName.equals( PDLab.NAME ) )
+ {
+ cs = new PDLab();
+ }
+ else if( colorSpaceName.equals( PDPattern.NAME ) )
+ {
+ cs = new PDPattern();
+ }
+ else
+ {
+ throw new IOException( "Error: Unknown colorspace '" + colorSpaceName + "'" );
+ }
+ return cs;
+ }
+
+ /**
+ * This will create the correct color space from a java colorspace.
+ *
+ * @param doc The doc to potentiall write information to.
+ * @param cs The awt colorspace.
+ *
+ * @return The color space.
+ *
+ * @throws IOException If the color space name is unknown.
+ */
+ public static PDColorSpace createColorSpace( PDDocument doc, ColorSpace cs ) throws IOException
+ {
+ PDColorSpace retval = null;
+ if( cs.isCS_sRGB() )
+ {
+ retval = PDDeviceRGB.INSTANCE;
+ }
+ else if( cs instanceof ICC_ColorSpace )
+ {
+ ICC_ColorSpace ics = (ICC_ColorSpace)cs;
+ PDICCBased pdCS = new PDICCBased( doc );
+ retval = pdCS;
+ COSArray ranges = new COSArray();
+ for( int i=0; i<cs.getNumComponents(); i++ )
+ {
+ ranges.add( new COSFloat( ics.getMinValue( i ) ) );
+ ranges.add( new COSFloat( ics.getMaxValue( i ) ) );
+ }
+ PDStream iccData = pdCS.getPDStream();
+ OutputStream output = null;
+ try
+ {
+ output = iccData.createOutputStream();
+ output.write( ics.getProfile().getData() );
+ }
+ finally
+ {
+ if( output != null )
+ {
+ output.close();
+ }
+ }
+ pdCS.setNumberOfComponents( cs.getNumComponents() );
+ }
+ else
+ {
+ throw new IOException( "Not yet implemented:" + cs );
+ }
+ return retval;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.color;
+
+import java.awt.Color;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+
+/**
+ * This class represents a color space and the color value for that colorspace.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class PDColorState implements Cloneable
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDColorState.class);
+
+ /**
+ * The default color that can be set to replace all colors in
+ * {@link ICC_ColorSpace ICC color spaces}.
+ *
+ * @see #setIccOverrideColor(Color)
+ */
+ private static volatile Color iccOverrideColor =
+ Color.getColor("org.apache.pdfbox.ICC_override_color");
+
+ /**
+ * Sets the default color to replace all colors in
+ * {@link ICC_ColorSpace ICC color spaces}. This will work around
+ * a potential JVM crash caused by broken native ICC color manipulation
+ * code in the Sun class libraries.
+ * <p>
+ * The default override can be specified by setting the color code in
+ * <code>org.apache.pdfbox.ICC_override_color</code> system property
+ * (see {@link Color#getColor(String)}. If this system property is not
+ * specified, then the override is not enabled unless this method is
+ * explicitly called.
+ *
+ * @param color ICC override color,
+ * or <code>null</code> to disable the override
+ * @see <a href="https://issues.apache.org/jira/browse/PDFBOX-511">PDFBOX-511</a>
+ * @since Apache PDFBox 0.8.1
+ */
+ public static void setIccOverrideColor(Color color) {
+ iccOverrideColor = color;
+ }
+
+ private PDColorSpace colorSpace = new PDDeviceGray();
+ private COSArray colorSpaceValue = new COSArray();
+
+ /**
+ * Cached Java AWT color based on the current color space and value.
+ * The value is cleared whenever the color space or value is set.
+ *
+ * @see #getJavaColor()
+ */
+ private Color color = null;
+
+ /**
+ * Default constructor.
+ *
+ */
+ public PDColorState()
+ {
+ setColorSpaceValue( new float[] {0});
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object clone()
+ {
+ PDColorState retval = new PDColorState();
+ retval.colorSpace = this.colorSpace;
+ retval.colorSpaceValue.clear();
+ retval.colorSpaceValue.addAll( this.colorSpaceValue );
+ return retval;
+ }
+
+ /**
+ * Returns the Java AWT color based on the current color space and value.
+ *
+ * @return current Java AWT color
+ * @throws IOException if the current color can not be created
+ */
+ public Color getJavaColor() throws IOException {
+ if (color == null) {
+ color = createColor();
+ }
+ return color;
+ }
+
+ /**
+ * Create the current color from the colorspace and values.
+ * @return The current awt color.
+ * @throws IOException If there is an error creating the color.
+ */
+ private Color createColor() throws IOException
+ {
+ float[] components = colorSpaceValue.toFloatArray();
+ try
+ {
+ if( colorSpace.getName().equals(PDDeviceRGB.NAME) && components.length == 3 )
+ {
+ //for some reason, when using RGB and the RGB colorspace
+ //the new Color doesn't maintain exactly the same values
+ //I think some color conversion needs to take place first
+ //for now we will just make rgb a special case.
+ return new Color( components[0], components[1], components[2] );
+ }
+ else
+ {
+ if (components.length == 1)
+ {
+ if (colorSpace.getName().equals(PDSeparation.NAME))
+ {
+ //Use that component as a single-integer RGB value
+ return new Color((int)components[0]);
+ }
+ if (colorSpace.getName().equals(PDDeviceGray.NAME))
+ {
+ // Handling DeviceGray as a special case as with JVM 1.5.0_15
+ // and maybe others printing on Windows fails with an
+ // ArrayIndexOutOfBoundsException when selecting colors
+ // and strokes e.g. sun.awt.windows.WPrinterJob.setTextColor
+ return new Color(components[0],components[0],components[0]);
+ }
+ }
+ Color override = iccOverrideColor;
+ ColorSpace cs = colorSpace.getJavaColorSpace();
+ if (cs instanceof ICC_ColorSpace && override != null)
+ {
+ log.warn(
+ "Using an ICC override color to avoid a potential"
+ + " JVM crash (see PDFBOX-511)");
+ return override;
+ }
+ else
+ {
+ return new Color( cs, components, 1f );
+ }
+ }
+ }
+ // Catch IOExceptions from PDColorSpace.getJavaColorSpace(), but
+ // possibly also IllegalArgumentExceptions or other RuntimeExceptions
+ // from the potentially complex color management code.
+ catch (Exception e)
+ {
+ Color cGuess;
+ String sMsg = "Unable to create the color instance "
+ + Arrays.toString(components) + " in color space "
+ + colorSpace + "; guessing color ... ";
+ try
+ {
+ switch(components.length)
+ {
+ case 1://Use that component as a single-integer RGB value
+ cGuess = new Color((int)components[0]);
+ sMsg += "\nInterpretating as single-integer RGB";
+ break;
+ case 3: //RGB
+ cGuess = new Color(components[0],components[1],components[2]);
+ sMsg += "\nInterpretating as RGB";
+ break;
+ case 4: //CMYK
+ //do a rough conversion to RGB as I'm not getting the CMYK to work.
+ //http://www.codeproject.com/KB/applications/xcmyk.aspx
+ float R, G, B, K;
+ K = components[3];
+
+ R = components[0] * (1f - K) + K;
+ G = components[1] * (1f - K) + K;
+ B = components[2] * (1f - K) + K;
+
+ R = (1f - R);
+ G = (1f - G);
+ B = (1f - B);
+
+ cGuess = new Color( R,G,B );
+ sMsg += "\nInterpretating as CMYK";
+ break;
+ default:
+
+ sMsg += "\nUnable to guess using " + components.length + " components; using black instead";
+ cGuess = Color.BLACK;
+ }
+ }
+ catch (Exception e2)
+ {
+ sMsg += "\nColor interpolation failed; using black instead\n";
+ sMsg += e2.toString();
+ cGuess = Color.BLACK;
+ }
+ log.warn(sMsg, e);
+ return cGuess;
+ }
+ }
+
+ /**
+ * Constructor with an existing color set. Default colorspace is PDDeviceGray.
+ *
+ * @param csValues The color space values.
+ */
+ public PDColorState( COSArray csValues )
+ {
+ colorSpaceValue = csValues;
+ }
+
+
+ /**
+ * This will get the current colorspace.
+ *
+ * @return The current colorspace.
+ */
+ public PDColorSpace getColorSpace()
+ {
+ return colorSpace;
+ }
+
+ /**
+ * This will set the current colorspace.
+ *
+ * @param value The new colorspace.
+ */
+ public void setColorSpace(PDColorSpace value)
+ {
+ colorSpace = value;
+ // Clear color cache
+ color = null;
+ }
+
+ /**
+ * This will get the color space values. Either 1 for gray or 3 for RGB.
+ *
+ * @return The colorspace values.
+ */
+ public float[] getColorSpaceValue()
+ {
+ return colorSpaceValue.toFloatArray();
+ }
+
+ /**
+ * This will get the color space values. Either 1 for gray or 3 for RGB.
+ *
+ * @return The colorspace values.
+ */
+ public COSArray getCOSColorSpaceValue()
+ {
+ return colorSpaceValue;
+ }
+
+ /**
+ * This will update the colorspace values.
+ *
+ * @param value The new colorspace values.
+ */
+ public void setColorSpaceValue(float[] value)
+ {
+ colorSpaceValue.setFloatArray( value );
+ // Clear color cache
+ color = null;
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.io.IOException;
+
+import java.awt.Transparency;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+
+
+/**
+ * This class represents a CMYK color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDDeviceCMYK extends PDColorSpace
+{
+ /**
+ * The single instance of this class.
+ */
+ public static final PDDeviceCMYK INSTANCE = new PDDeviceCMYK();
+
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "DeviceCMYK";
+
+ /**
+ * The abbreviated name of this color space.
+ */
+ public static final String ABBREVIATED_NAME = "CMYK";
+
+ private PDDeviceCMYK()
+ {
+ }
+
+ /**
+ * This will return the name of the color space.
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * This will get the number of components that this color space is made up of.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ return 4;
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ */
+ protected ColorSpace createColorSpace()
+ {
+ return new ColorSpaceCMYK();
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+
+ int[] nbBits = { bpc, bpc, bpc, bpc };
+ ComponentColorModel componentColorModel =
+ new ComponentColorModel( getJavaColorSpace(),
+ nbBits,
+ false,
+ false,
+ Transparency.OPAQUE,
+ DataBuffer.TYPE_BYTE );
+ return componentColorModel;
+
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import java.awt.color.ColorSpace;
+
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.Transparency;
+
+import java.io.IOException;
+
+/**
+ * This class represents a Gray color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDDeviceGray extends PDColorSpace
+{
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "DeviceGray";
+
+ /**
+ * The abbreviated name of this color space.
+ */
+ public static final String ABBREVIATED_NAME = "G";
+
+ /**
+ * This will return the name of the color space.
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * This will get the number of components that this color space is made up of.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ return 1;
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ */
+ protected ColorSpace createColorSpace()
+ {
+ return ColorSpace.getInstance( ColorSpace.CS_GRAY );
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+ ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
+ int[] nBits = {bpc};
+ ColorModel colorModel = new ComponentColorModel(cs, nBits, false,false,
+ Transparency.OPAQUE,DataBuffer.TYPE_BYTE);
+ return colorModel;
+
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+import java.io.IOException;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.function.PDFunction;
+import org.apache.pdfbox.pdmodel.common.function.PDFunctionType4;
+
+/**
+ * This class represents a DeviceN color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDDeviceN extends PDColorSpace
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDDeviceN.class);
+
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "DeviceN";
+
+ private COSArray array;
+
+ /**
+ * Constructor.
+ */
+ public PDDeviceN()
+ {
+ array = new COSArray();
+ array.add( COSName.DEVICEN );
+ array.add( COSName.getPDFName( "" ) );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param separation The array containing all separation information.
+ */
+ public PDDeviceN( COSArray separation )
+ {
+ array = separation;
+ }
+
+ /**
+ * This will return the name of the color space. For a PDSeparation object
+ * this will always return "Separation"
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * This will get the number of components that this color space is made up of.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ return getColorantNames().size();
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color space.
+ */
+ protected ColorSpace createColorSpace() throws IOException
+ {
+ try
+ {
+ PDColorSpace alt = getAlternateColorSpace();
+ return alt.getJavaColorSpace();
+ }
+ catch (IOException ioexception)
+ {
+ log.error(ioexception, ioexception);
+ throw ioexception;
+ }
+ catch (Exception exception)
+ {
+ log.error(exception, exception);
+ throw new IOException("Failed to Create ColorSpace");
+ }
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+ log.info("About to create ColorModel for " + getAlternateColorSpace().toString());
+ return getAlternateColorSpace().createColorModel(bpc);
+ }
+
+ /**
+ * This will get the colorant names. A list of string objects.
+ *
+ * @return A list of colorants
+ */
+ public List<COSBase> getColorantNames()
+ {
+ COSArray names = (COSArray)array.getObject( 1 );
+ return COSArrayList.convertCOSNameCOSArrayToList( names );
+ }
+
+ /**
+ * This will set the list of colorants.
+ *
+ * @param names The list of colorant names.
+ */
+ public void setColorantNames( List<COSBase> names )
+ {
+ COSArray namesArray = COSArrayList.convertStringListToCOSNameCOSArray( names );
+ array.set( 1, namesArray );
+ }
+
+ /**
+ * This will get the alternate color space for this separation.
+ *
+ * @return The alternate color space.
+ *
+ * @throws IOException If there is an error getting the alternate color space.
+ */
+ public PDColorSpace getAlternateColorSpace() throws IOException
+ {
+ COSBase alternate = array.getObject( 2 );
+ return PDColorSpaceFactory.createColorSpace( alternate );
+ }
+
+ /**
+ * This will set the alternate color space.
+ *
+ * @param cs The alternate color space.
+ */
+ public void setAlternateColorSpace( PDColorSpace cs )
+ {
+ COSBase space = null;
+ if( cs != null )
+ {
+ space = cs.getCOSObject();
+ }
+ array.set( 2, space );
+ }
+
+ /**
+ * This will get the tint transform function.
+ *
+ * @return The tint transform function.
+ *
+ * @throws IOException if there is an error creating the function.
+ */
+ public PDFunction getTintTransform() throws IOException
+ {
+ return PDFunction.create( array.getObject( 3 ) );
+ }
+
+ /**
+ * This will set the tint transform function.
+ *
+ * @param tint The tint transform function.
+ */
+ public void setTintTransform( PDFunction tint )
+ {
+ array.set( 3, tint );
+ }
+
+ /**
+ * This will get the attributes that are associated with the deviceN
+ * color space.
+ *
+ * @return The DeviceN attributes.
+ */
+ public PDDeviceNAttributes getAttributes()
+ {
+ PDDeviceNAttributes retval = null;
+ if( array.size() <5)
+ {
+ retval = new PDDeviceNAttributes();
+ setAttributes( retval );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the color space attributes. If null is passed in then
+ * all attribute will be removed.
+ *
+ * @param attributes The color space attributes.
+ */
+ public void setAttributes( PDDeviceNAttributes attributes )
+ {
+ if( attributes == null )
+ {
+ array.remove( 4 );
+ }
+ else
+ {
+ //make sure array is large enough
+ while( array.size() < 5 )
+ {
+ array.add( COSNull.NULL );
+ }
+ array.set( 4, attributes.getCOSDictionary() );
+ }
+ }
+
+ /**
+ * Returns the components of the color in the alternate colorspace for the given tint value.
+ * @return COSArray with the color components
+ * @throws IOException If the tint function is not supported
+ */
+ public COSArray calculateColorValues(List<COSBase> tintValues) throws IOException
+ {
+ PDFunction tintTransform = getTintTransform();
+ if(tintTransform instanceof PDFunctionType4)
+ {
+ log.warn("Unsupported tint transformation type: "+tintTransform.getClass().getName()
+ + " in "+getClass().getName()+".getColorValues()"
+ + " using color black instead.");
+ int numberOfComponents = getAlternateColorSpace().getNumberOfComponents();
+ // To get black as color:
+ // 0.0f is used for the single value(s) if the colorspace is gray or RGB based
+ // 1.0f is used for the single value if the colorspace is CMYK based
+ float colorValue = numberOfComponents == 4 ? 1.0f : 0.0f;
+ COSArray retval = new COSArray();
+ for (int i=0;i<numberOfComponents;i++)
+ {
+ retval.add(new COSFloat(colorValue));
+ }
+ return retval;
+ }
+ else
+ {
+ COSArray tint = new COSArray();
+ tint.addAll(tintValues);
+ return tintTransform.eval(tint);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.color;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.COSDictionaryMap;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * This class represents attributes for a DeviceN color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDDeviceNAttributes
+{
+ private COSDictionary dictionary;
+
+ /**
+ * Constructor.
+ */
+ public PDDeviceNAttributes()
+ {
+ dictionary = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param attributes A dictionary that has all of the attributes.
+ */
+ public PDDeviceNAttributes( COSDictionary attributes )
+ {
+ dictionary = attributes;
+ }
+
+ /**
+ * This will get the underlying cos dictionary.
+ *
+ * @return The dictionary that this object wraps.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return dictionary;
+ }
+
+ /**
+ * This will get a map of colorants. See the PDF Reference for more details about
+ * this attribute. The map will contain a java.lang.String as the key, a colorant name,
+ * and a PDColorSpace as the value.
+ *
+ * @return The colorant map.
+ *
+ * @throws IOException If there is an error getting the colorspaces.
+ */
+ public Map getColorants() throws IOException
+ {
+ Map actuals = new HashMap();
+ COSDictionary colorants = (COSDictionary)dictionary.getDictionaryObject( COSName.COLORANTS );
+ if( colorants == null )
+ {
+ colorants = new COSDictionary();
+ dictionary.setItem( COSName.COLORANTS, colorants );
+ }
+ for( COSName name : colorants.keySet() )
+ {
+ COSBase value = colorants.getDictionaryObject( name );
+ actuals.put( name.getName(), PDColorSpaceFactory.createColorSpace( value ) );
+ }
+ return new COSDictionaryMap( actuals, colorants );
+ }
+
+ /**
+ * This will replace the existing colorant attribute. The key should be strings
+ * and the values should be PDColorSpaces.
+ *
+ * @param colorants The map of colorants.
+ */
+ public void setColorants( Map colorants )
+ {
+ COSDictionary colorantDict = null;
+ if( colorants != null )
+ {
+ colorantDict = COSDictionaryMap.convert( colorants );
+ }
+ dictionary.setItem( COSName.COLORANTS, colorantDict );
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import java.awt.Transparency;
+
+import java.awt.color.ColorSpace;
+
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+
+import java.io.IOException;
+
+/**
+ * This class represents an RGB color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.8 $
+ */
+public class PDDeviceRGB extends PDColorSpace
+{
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "DeviceRGB";
+
+ /**
+ * The abbreviated name of this color space.
+ */
+ public static final String ABBREVIATED_NAME = "RGB";
+
+ /**
+ * This is the single instance of this class.
+ */
+ public static final PDDeviceRGB INSTANCE = new PDDeviceRGB();
+
+ /**
+ * This class is immutable.
+ */
+ private PDDeviceRGB()
+ {
+ //only here to make immutable.
+ }
+
+ /**
+ * This will return the name of the color space.
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * This will get the number of components that this color space is made up of.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ return 3;
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ */
+ protected ColorSpace createColorSpace()
+ {
+ return ColorSpace.getInstance( ColorSpace.CS_sRGB );
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+ int[] nbBits = { bpc, bpc, bpc };
+ ComponentColorModel componentColorModel =
+ new ComponentColorModel( getJavaColorSpace(),
+ nbBits,
+ false,
+ false,
+ Transparency.OPAQUE,
+ DataBuffer.TYPE_BYTE );
+
+ return componentColorModel;
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * A gamma array, or collection of three floating point parameters used for
+ * color operations.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDGamma implements COSObjectable
+{
+ private COSArray values = null;
+
+ /**
+ * Constructor. Defaults all values to 0, 0, 0.
+ */
+ public PDGamma()
+ {
+ values = new COSArray();
+ values.add( new COSFloat( 0.0f ) );
+ values.add( new COSFloat( 0.0f ) );
+ values.add( new COSFloat( 0.0f ) );
+ }
+
+ /**
+ * Constructor from COS object.
+ *
+ * @param array The array containing the XYZ values.
+ */
+ public PDGamma( COSArray array )
+ {
+ values = array;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return values;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSArray getCOSArray()
+ {
+ return values;
+ }
+
+ /**
+ * This will get the r value of the tristimulus.
+ *
+ * @return The R value.
+ */
+ public float getR()
+ {
+ return ((COSNumber)values.get( 0 )).floatValue();
+ }
+
+ /**
+ * This will set the r value of the tristimulus.
+ *
+ * @param r The r value for the tristimulus.
+ */
+ public void setR( float r )
+ {
+ values.set( 0, new COSFloat( r ) );
+ }
+
+ /**
+ * This will get the g value of the tristimulus.
+ *
+ * @return The g value.
+ */
+ public float getG()
+ {
+ return ((COSNumber)values.get( 1 )).floatValue();
+ }
+
+ /**
+ * This will set the g value of the tristimulus.
+ *
+ * @param g The g value for the tristimulus.
+ */
+ public void setG( float g )
+ {
+ values.set( 1, new COSFloat( g ) );
+ }
+
+ /**
+ * This will get the b value of the tristimulus.
+ *
+ * @return The B value.
+ */
+ public float getB()
+ {
+ return ((COSNumber)values.get( 2 )).floatValue();
+ }
+
+ /**
+ * This will set the b value of the tristimulus.
+ *
+ * @param b The b value for the tristimulus.
+ */
+ public void setB( float b )
+ {
+ values.set( 2, new COSFloat( b ) );
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSInteger;
+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.PDDocument;
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDRange;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents a ICC profile color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDICCBased extends PDColorSpace
+{
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "ICCBased";
+
+ //private COSArray array;
+ private PDStream stream;
+
+ /**
+ * Default constructor, creates empty stream.
+ *
+ * @param doc The document to store the icc data.
+ */
+ public PDICCBased( PDDocument doc )
+ {
+ array = new COSArray();
+ array.add( COSName.ICCBASED );
+ array.add( new PDStream( doc ) );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param iccArray The ICC stream object.
+ */
+ public PDICCBased( COSArray iccArray )
+ {
+ array = iccArray;
+ stream = new PDStream( (COSStream)iccArray.getObject( 1 ) );
+ }
+
+ /**
+ * This will return the name of the color space.
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return array;
+ }
+
+ /**
+ * Get the pd stream for this icc color space.
+ *
+ * @return Get the stream for this icc based color space.
+ */
+ public PDStream getPDStream()
+ {
+ return stream;
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color space.
+ */
+ protected ColorSpace createColorSpace() throws IOException
+ {
+ InputStream profile = null;
+ ColorSpace cSpace = null;
+ try
+ {
+ profile = stream.createInputStream();
+ ICC_Profile iccProfile = ICC_Profile.getInstance( profile );
+ cSpace = new ICC_ColorSpace( iccProfile );
+ }
+ finally
+ {
+ if( profile != null )
+ {
+ profile.close();
+ }
+ }
+ return cSpace;
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+
+ int[] nbBits = { bpc, bpc, bpc, bpc }; //added 4th bpc to handle CMYK
+ ComponentColorModel componentColorModel =
+ new ComponentColorModel( getJavaColorSpace(),
+ nbBits,
+ false,
+ false,
+ Transparency.OPAQUE,
+ DataBuffer.TYPE_BYTE );
+
+ return componentColorModel;
+
+ }
+
+ /**
+ * This will return the number of color components. As of PDF 1.4 this will
+ * be 1,3,4.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ COSNumber n = (COSNumber)stream.getStream().getDictionaryObject( COSName.N );
+ return n.intValue();
+ }
+
+ /**
+ * This will set the number of color components.
+ *
+ * @param n The number of color components.
+ */
+ public void setNumberOfComponents( int n )
+ {
+ stream.getStream().setInt( COSName.N, n );
+ }
+
+ /**
+ * This will return a list of alternate color spaces(PDColorSpace) if the display application
+ * does not support this icc stream.
+ *
+ * @return A list of alternate color spaces.
+ *
+ * @throws IOException If there is an error getting the alternate color spaces.
+ */
+ public List getAlternateColorSpaces() throws IOException
+ {
+ COSBase alternate = stream.getStream().getDictionaryObject( COSName.ALTERNATE );
+ COSArray alternateArray = null;
+ if( alternate == null )
+ {
+ alternateArray = new COSArray();
+ int numComponents = getNumberOfComponents();
+ COSName csName = null;
+ if( numComponents == 1 )
+ {
+ csName = COSName.DEVICEGRAY;
+ }
+ else if( numComponents == 3 )
+ {
+ csName = COSName.DEVICERGB;
+ }
+ else if( numComponents == 4 )
+ {
+ csName = COSName.DEVICECMYK;
+ }
+ else
+ {
+ throw new IOException( "Unknown colorspace number of components:" + numComponents );
+ }
+ alternateArray.add( csName );
+ }
+ else
+ {
+ if( alternate instanceof COSArray )
+ {
+ alternateArray = (COSArray)alternate;
+ }
+ else if( alternate instanceof COSName )
+ {
+ alternateArray = new COSArray();
+ alternateArray.add( alternate );
+ }
+ else
+ {
+ throw new IOException( "Error: expected COSArray or COSName and not " +
+ alternate.getClass().getName() );
+ }
+ }
+ List retval = new ArrayList();
+ for( int i=0; i<alternateArray.size(); i++ )
+ {
+ retval.add( PDColorSpaceFactory.createColorSpace( alternateArray.get( i ) ) );
+ }
+ return new COSArrayList( retval, alternateArray );
+ }
+
+ /**
+ * This will set the list of alternate color spaces. This should be a list
+ * of PDColorSpace objects.
+ *
+ * @param list The list of colorspace objects.
+ */
+ public void setAlternateColorSpaces( List list )
+ {
+ COSArray altArray = null;
+ if( list != null )
+ {
+ altArray = COSArrayList.converterToCOSArray( list );
+ }
+ stream.getStream().setItem( COSName.ALTERNATE, altArray );
+ }
+
+ private COSArray getRangeArray( int n )
+ {
+ COSArray rangeArray = (COSArray)stream.getStream().getDictionaryObject( COSName.RANGE);
+ if( rangeArray == null )
+ {
+ rangeArray = new COSArray();
+ stream.getStream().setItem( COSName.RANGE, rangeArray );
+ while( rangeArray.size() < n*2 )
+ {
+ rangeArray.add( new COSFloat( -100 ) );
+ rangeArray.add( new COSFloat( 100 ) );
+ }
+ }
+ return rangeArray;
+ }
+
+ /**
+ * This will get the range for a certain component number. This is will never
+ * return null. If it is not present then the range -100 to 100 will
+ * be returned.
+ *
+ * @param n The component number to get the range for.
+ *
+ * @return The range for this component.
+ */
+ public PDRange getRangeForComponent( int n )
+ {
+ COSArray rangeArray = getRangeArray( n );
+ return new PDRange( rangeArray, n );
+ }
+
+ /**
+ * This will set the a range for this color space.
+ *
+ * @param range The new range for the a component.
+ * @param n The component to set the range for.
+ */
+ public void setRangeForComponent( PDRange range, int n )
+ {
+ COSArray rangeArray = getRangeArray( n );
+ rangeArray.set( n*2, new COSFloat( range.getMin() ) );
+ rangeArray.set( n*2+1, new COSFloat( range.getMax() ) );
+ }
+
+ /**
+ * This will get the metadata stream for this object. Null if there is no
+ * metadata stream.
+ *
+ * @return The metadata stream, if it exists.
+ */
+ public COSStream getMetadata()
+ {
+ return (COSStream)stream.getStream().getDictionaryObject( COSName.METADATA );
+ }
+
+ /**
+ * This will set the metadata stream that is associated with this color space.
+ *
+ * @param metadata The new metadata stream.
+ */
+ public void setMetadata( COSStream metadata )
+ {
+ stream.getStream().setItem( COSName.METADATA, metadata );
+ }
+
+ // Need more info on the ICCBased ones ... Array contains very little.
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ String retVal = super.toString() + "\n\t Number of Components: ";
+ try
+ {
+ retVal = retVal + getNumberOfComponents();
+ }
+ catch (IOException exception)
+ {
+ retVal = retVal + exception.toString();
+ }
+ return retVal;
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * This class represents an Indexed color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDIndexed extends PDColorSpace
+{
+
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "Indexed";
+
+ /**
+ * The abbreviated name of this color space.
+ */
+ public static final String ABBREVIATED_NAME = "I";
+
+ private COSArray array;
+
+ /**
+ * The lookup data as byte array.
+ */
+ private byte[] lookupData;
+
+ /**
+ * Constructor, default DeviceRGB, hival 255.
+ */
+ public PDIndexed()
+ {
+ array = new COSArray();
+ array.add( COSName.INDEXED );
+ array.add( COSName.DEVICERGB );
+ array.add( COSInteger.get( 255 ) );
+ array.add( org.apache.pdfbox.cos.COSNull.NULL );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param indexedArray The array containing the indexed parameters
+ */
+ public PDIndexed( COSArray indexedArray )
+ {
+ array = indexedArray;
+ }
+
+ /**
+ * This will return the number of color components. This will return the
+ * number of color components in the base color.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ return getBaseColorSpace().getNumberOfComponents();
+ }
+
+ /**
+ * This will return the name of the color space.
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color space.
+ */
+ protected ColorSpace createColorSpace() throws IOException
+ {
+ throw new IOException( "Not implemented" );
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+ int size = getHighValue();
+ byte[] index = getLookupData();
+ PDColorSpace baseColorSpace = getBaseColorSpace();
+ ColorModel cm = null;
+ if( baseColorSpace instanceof PDDeviceRGB )
+ {
+ cm = new IndexColorModel(bpc, size+1, index,0,false);
+ }
+ else
+ {
+ ColorModel baseColorModel = baseColorSpace.createColorModel(bpc);
+ if( baseColorModel.getTransferType() != DataBuffer.TYPE_BYTE )
+ {
+ throw new IOException( "Not implemented" );
+ }
+ byte[] r = new byte[size+1];
+ byte[] g = new byte[size+1];
+ byte[] b = new byte[size+1];
+ byte[] a = baseColorModel.hasAlpha() ? new byte[size+1] : null;
+ byte[] inData = new byte[baseColorModel.getNumComponents()];
+ for( int i = 0; i <= size; i++ )
+ {
+ System.arraycopy(index, i * inData.length, inData, 0, inData.length);
+ r[i] = (byte)baseColorModel.getRed(inData);
+ g[i] = (byte)baseColorModel.getGreen(inData);
+ b[i] = (byte)baseColorModel.getBlue(inData);
+ if(a != null)
+ {
+ a[i] = (byte)baseColorModel.getAlpha(inData);
+ }
+ }
+ cm = a == null ? new IndexColorModel(bpc, size+1, r, g, b) : new IndexColorModel(bpc, size+1, r, g, b, a);
+ }
+ return cm;
+ }
+
+ /**
+ * This will get the color space that acts as the index for this color space.
+ *
+ * @return The base color space.
+ *
+ * @throws IOException If there is error creating the base color space.
+ */
+ public PDColorSpace getBaseColorSpace() throws IOException
+ {
+ COSBase base = array.getObject( 1 );
+ return PDColorSpaceFactory.createColorSpace( base );
+ }
+
+ /**
+ * This will set the base color space.
+ *
+ * @param base The base color space to use as the index.
+ */
+ public void setBaseColorSpace( PDColorSpace base )
+ {
+ array.set( 1, base.getCOSObject() );
+ }
+
+ /**
+ * Get the highest value for the lookup.
+ *
+ * @return The hival entry.
+ */
+ public int getHighValue()
+ {
+ return ((COSNumber)array.getObject( 2 )).intValue();
+ }
+
+ /**
+ * This will set the highest value that is allowed. This cannot be higher
+ * than 255.
+ *
+ * @param high The highest value for the lookup table.
+ */
+ public void setHighValue( int high )
+ {
+ array.set( 2, high );
+ }
+
+ /**
+ * This will perform a lookup into the color lookup table.
+ *
+ * @param lookupIndex The zero-based index into the table, should not exceed the high value.
+ * @param componentNumber The component number, probably 1,2,3,3.
+ *
+ * @return The value that was from the lookup table.
+ *
+ * @throws IOException If there is an error looking up the color.
+ */
+ public int lookupColor( int lookupIndex, int componentNumber ) throws IOException
+ {
+ PDColorSpace baseColor = getBaseColorSpace();
+ byte[] data = getLookupData();
+ int numberOfComponents = baseColor.getNumberOfComponents();
+ return (data[lookupIndex*numberOfComponents + componentNumber]+256)%256;
+ }
+
+ /**
+ * Get the lookup data table.
+ *
+ * @return a byte array containing the the lookup data.
+ * @throws IOException if an error occurs.
+ */
+ public byte[] getLookupData() throws IOException
+ {
+ if ( lookupData == null)
+ {
+ COSBase lookupTable = array.getObject( 3 );
+ if( lookupTable instanceof COSString )
+ {
+ lookupData = ((COSString)lookupTable).getBytes();
+ }
+ else if( lookupTable instanceof COSStream )
+ {
+ //Data will be small so just load the whole thing into memory for
+ //easier processing
+ COSStream lookupStream = (COSStream)lookupTable;
+ InputStream input = lookupStream.getUnfilteredStream();
+ ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
+ byte[] buffer = new byte[ 1024 ];
+ int amountRead;
+ while( (amountRead = input.read(buffer, 0, buffer.length)) != -1 )
+ {
+ output.write( buffer, 0, amountRead );
+ }
+ lookupData = output.toByteArray();
+ }
+ else if( lookupTable == null )
+ {
+ lookupData = new byte[0];
+ }
+ else
+ {
+ throw new IOException( "Error: Unknown type for lookup table " + lookupTable );
+ }
+ }
+ return lookupData;
+ }
+
+ /**
+ * This will set a color in the color lookup table.
+ *
+ * @param lookupIndex The zero-based index into the table, should not exceed the high value.
+ * @param componentNumber The component number, probably 1,2,3,3.
+ * @param color The color that will go into the table.
+ *
+ * @throws IOException If there is an error looking up the color.
+ */
+ public void setLookupColor( int lookupIndex, int componentNumber, int color ) throws IOException
+ {
+ PDColorSpace baseColor = getBaseColorSpace();
+ int numberOfComponents = baseColor.getNumberOfComponents();
+ byte[] data = getLookupData();
+ data[lookupIndex*numberOfComponents + componentNumber] = (byte)color;
+ COSString string = new COSString( data );
+ array.set( 3, string );
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.PDRange;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+import java.io.IOException;
+
+/**
+ * This class represents a Lab color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDLab extends PDColorSpace
+{
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "Lab";
+
+ private COSArray array;
+ private COSDictionary dictionary;
+
+ /**
+ * Constructor.
+ */
+ public PDLab()
+ {
+ array = new COSArray();
+ dictionary = new COSDictionary();
+ array.add( COSName.LAB );
+ array.add( dictionary );
+ }
+
+ /**
+ * Constructor with array.
+ *
+ * @param lab The underlying color space.
+ */
+ public PDLab( COSArray lab )
+ {
+ array = lab;
+ dictionary = (COSDictionary)array.getObject( 1 );
+ }
+
+ /**
+ * This will return the name of the color space.
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return array;
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color space.
+ */
+ protected ColorSpace createColorSpace() throws IOException
+ {
+ throw new IOException( "Not implemented" );
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+ throw new IOException( "Not implemented" );
+ }
+
+ /**
+ * This will get the number of components that this color space is made up of.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ //BJL
+ //hmm is this correct, I am not 100% sure.
+ return 3;
+ }
+
+ /**
+ * This will return the whitepoint tristimulus. As this is a required field
+ * this will never return null. A default of 1,1,1 will be returned if the
+ * pdf does not have any values yet.
+ *
+ * @return The whitepoint tristimulus.
+ */
+ public PDTristimulus getWhitepoint()
+ {
+ COSArray wp = (COSArray)dictionary.getDictionaryObject( COSName.WHITE_POINT );
+ if( wp == null )
+ {
+ wp = new COSArray();
+ wp.add( new COSFloat( 1.0f ) );
+ wp.add( new COSFloat( 1.0f ) );
+ wp.add( new COSFloat( 1.0f ) );
+ dictionary.setItem( COSName.WHITE_POINT, wp );
+ }
+ return new PDTristimulus( wp );
+ }
+
+ /**
+ * This will set the whitepoint tristimulus. As this is a required field
+ * this null should not be passed into this function.
+ *
+ * @param wp The whitepoint tristimulus.
+ */
+ public void setWhitepoint( PDTristimulus wp )
+ {
+ COSBase wpArray = wp.getCOSObject();
+ if( wpArray != null )
+ {
+ dictionary.setItem( COSName.WHITE_POINT, wpArray );
+ }
+ }
+
+ /**
+ * This will return the BlackPoint tristimulus. This is an optional field but
+ * has defaults so this will never return null.
+ * A default of 0,0,0 will be returned if the pdf does not have any values yet.
+ *
+ * @return The blackpoint tristimulus.
+ */
+ public PDTristimulus getBlackPoint()
+ {
+ COSArray bp = (COSArray)dictionary.getDictionaryObject( COSName.BLACK_POINT );
+ if( bp == null )
+ {
+ bp = new COSArray();
+ bp.add( new COSFloat( 0.0f ) );
+ bp.add( new COSFloat( 0.0f ) );
+ bp.add( new COSFloat( 0.0f ) );
+ dictionary.setItem( COSName.BLACK_POINT, bp );
+ }
+ return new PDTristimulus( bp );
+ }
+
+ /**
+ * This will set the BlackPoint tristimulus. As this is a required field
+ * this null should not be passed into this function.
+ *
+ * @param bp The BlackPoint tristimulus.
+ */
+ public void setBlackPoint( PDTristimulus bp )
+ {
+
+ COSBase bpArray = null;
+ if( bp != null )
+ {
+ bpArray = bp.getCOSObject();
+ }
+ dictionary.setItem( COSName.BLACK_POINT, bpArray );
+ }
+
+ private COSArray getRangeArray()
+ {
+ COSArray range = (COSArray)dictionary.getDictionaryObject( COSName.RANGE );
+ if( range == null )
+ {
+ range = new COSArray();
+ dictionary.setItem( COSName.RANGE, array );
+ range.add( new COSFloat( -100 ) );
+ range.add( new COSFloat( 100 ) );
+ range.add( new COSFloat( -100 ) );
+ range.add( new COSFloat( 100 ) );
+ }
+ return range;
+ }
+
+ /**
+ * This will get the valid range for the a component. If none is found
+ * then the default will be returned, which is -100 to 100.
+ *
+ * @return The a range.
+ */
+ public PDRange getARange()
+ {
+ COSArray range = getRangeArray();
+ return new PDRange( range, 0 );
+ }
+
+ /**
+ * This will set the a range for this color space.
+ *
+ * @param range The new range for the a component.
+ */
+ public void setARange( PDRange range )
+ {
+ COSArray rangeArray = null;
+ //if null then reset to defaults
+ if( range == null )
+ {
+ rangeArray = getRangeArray();
+ rangeArray.set( 0, new COSFloat( -100 ) );
+ rangeArray.set( 1, new COSFloat( 100 ) );
+ }
+ else
+ {
+ rangeArray = range.getCOSArray();
+ }
+ dictionary.setItem( COSName.RANGE, rangeArray );
+ }
+
+ /**
+ * This will get the valid range for the b component. If none is found
+ * then the default will be returned, which is -100 to 100.
+ *
+ * @return The b range.
+ */
+ public PDRange getBRange()
+ {
+ COSArray range = getRangeArray();
+ return new PDRange( range, 1 );
+ }
+
+ /**
+ * This will set the b range for this color space.
+ *
+ * @param range The new range for the b component.
+ */
+ public void setBRange( PDRange range )
+ {
+ COSArray rangeArray = null;
+ //if null then reset to defaults
+ if( range == null )
+ {
+ rangeArray = getRangeArray();
+ rangeArray.set( 2, new COSFloat( -100 ) );
+ rangeArray.set( 3, new COSFloat( 100 ) );
+ }
+ else
+ {
+ rangeArray = range.getCOSArray();
+ }
+ dictionary.setItem( COSName.RANGE, rangeArray );
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSName;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+import java.io.IOException;
+
+/**
+ * This class represents a Pattern color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDPattern extends PDColorSpace
+{
+ private COSArray array;
+
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "Pattern";
+
+ /**
+ * Default constructor.
+ */
+ public PDPattern()
+ {
+ array = new COSArray();
+ array.add( COSName.PATTERN );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param pattern The pattern array.
+ */
+ public PDPattern( COSArray pattern)
+ {
+ array = pattern;
+ }
+
+ /**
+ * This will return the name of the color space.
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * This will get the number of components that this color space is made up of.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ return -1;
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color space.
+ */
+ protected ColorSpace createColorSpace() throws IOException
+ {
+ throw new IOException( "Not implemented" );
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+ throw new IOException( "Not implemented" );
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.function.PDFunction;
+import org.apache.pdfbox.pdmodel.common.function.PDFunctionType4;
+
+/**
+ * This class represents a Separation color space.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDSeparation extends PDColorSpace
+{
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDSeparation.class);
+
+ /**
+ * The name of this color space.
+ */
+ public static final String NAME = "Separation";
+
+
+ /**
+ * Constructor.
+ */
+ public PDSeparation()
+ {
+ array = new COSArray();
+ array.add( COSName.SEPARATION );
+ array.add( COSName.getPDFName( "" ) );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param separation The array containing all separation information.
+ */
+ public PDSeparation( COSArray separation )
+ {
+ array = separation;
+ }
+
+ /**
+ * This will return the name of the color space. For a PDSeparation object
+ * this will always return "Separation"
+ *
+ * @return The name of the color space.
+ */
+ public String getName()
+ {
+ return NAME;
+ }
+
+ /**
+ * This will get the number of components that this color space is made up of.
+ *
+ * @return The number of components in this color space.
+ *
+ * @throws IOException If there is an error getting the number of color components.
+ */
+ public int getNumberOfComponents() throws IOException
+ {
+ return getAlternateColorSpace().getNumberOfComponents();
+ }
+
+ /**
+ * Create a Java colorspace for this colorspace.
+ *
+ * @return A color space that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color space.
+ */
+ protected ColorSpace createColorSpace() throws IOException
+ {
+ try
+ {
+ PDColorSpace alt = getAlternateColorSpace();
+ return alt.getJavaColorSpace();
+ }
+ catch (IOException ioexception)
+ {
+ log.error(ioexception, ioexception);
+
+ throw ioexception;
+ }
+ catch (Exception exception)
+ {
+ log.error(exception, exception);
+ throw new IOException("Failed to Create ColorSpace");
+ }
+ }
+
+ /**
+ * Create a Java color model for this colorspace.
+ *
+ * @param bpc The number of bits per component.
+ *
+ * @return A color model that can be used for Java AWT operations.
+ *
+ * @throws IOException If there is an error creating the color model.
+ */
+ public ColorModel createColorModel( int bpc ) throws IOException
+ {
+ log.info("About to create ColorModel for " + getAlternateColorSpace().toString());
+ return getAlternateColorSpace().createColorModel(bpc);
+ }
+
+ /**
+ * This will get the separation name.
+ *
+ * @return The name in the separation.
+ */
+ public String getColorantName()
+ {
+ COSName name = (COSName)array.getObject( 1 );
+ return name.getName();
+ }
+
+ /**
+ * This will set the separation name.
+ *
+ * @param name The separation name.
+ */
+ public void setColorantName( String name )
+ {
+ array.set( 1, COSName.getPDFName( name ) );
+ }
+
+ /**
+ * This will get the alternate color space for this separation.
+ *
+ * @return The alternate color space.
+ *
+ * @throws IOException If there is an error getting the alternate color space.
+ */
+ public PDColorSpace getAlternateColorSpace() throws IOException
+ {
+ COSBase alternate = array.getObject( 2 );
+ PDColorSpace cs = PDColorSpaceFactory.createColorSpace( alternate );
+ return cs;
+ }
+
+ /**
+ * This will set the alternate color space.
+ *
+ * @param cs The alternate color space.
+ */
+ public void setAlternateColorSpace( PDColorSpace cs )
+ {
+ COSBase space = null;
+ if( cs != null )
+ {
+ space = cs.getCOSObject();
+ }
+ array.set( 2, space );
+ }
+
+ /**
+ * This will get the tint transform function.
+ *
+ * @return The tint transform function.
+ *
+ * @throws IOException If there is an error creating the PDFunction
+ */
+ public PDFunction getTintTransform() throws IOException
+ {
+ return PDFunction.create( array.getObject( 3 ) );
+ }
+
+ /**
+ * This will set the tint transform function.
+ *
+ * @param tint The tint transform function.
+ */
+ public void setTintTransform( PDFunction tint )
+ {
+ array.set( 3, tint );
+ }
+
+ /**
+ * Returns the components of the color in the alternate colorspace for the given tint value.
+ * @return COSArray with the color components
+ * @throws IOException If the tint function is not supported
+ */
+ public COSArray calculateColorValues(COSBase tintValue) throws IOException
+ {
+ PDFunction tintTransform = getTintTransform();
+ if(tintTransform instanceof PDFunctionType4)
+ {
+ log.warn("Unsupported tint transformation type: "+tintTransform.getClass().getName()
+ + " in "+getClass().getName()+".getColorValues()"
+ + " using color black instead.");
+ int numberOfComponents = getAlternateColorSpace().getNumberOfComponents();
+ // To get black as color:
+ // 0.0f is used for the single value(s) if the colorspace is gray or RGB based
+ // 1.0f is used for the single value if the colorspace is CMYK based
+ float colorValue = numberOfComponents == 4 ? 1.0f : 0.0f;
+ COSArray retval = new COSArray();
+ for (int i=0;i<numberOfComponents;i++)
+ {
+ retval.add(new COSFloat(colorValue));
+ }
+ return retval;
+ }
+ else
+ {
+ COSArray tint = new COSArray();
+ tint.add(tintValue);
+ return tintTransform.eval(tint);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.color;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * A tristimulus, or collection of three floating point parameters used for
+ * color operations.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDTristimulus implements COSObjectable
+{
+ private COSArray values = null;
+
+ /**
+ * Constructor. Defaults all values to 0, 0, 0.
+ */
+ public PDTristimulus()
+ {
+ values = new COSArray();
+ values.add( new COSFloat( 0.0f ) );
+ values.add( new COSFloat( 0.0f ) );
+ values.add( new COSFloat( 0.0f ) );
+ }
+
+ /**
+ * Constructor from COS object.
+ *
+ * @param array The array containing the XYZ values.
+ */
+ public PDTristimulus( COSArray array )
+ {
+ values = array;
+ }
+
+ /**
+ * Constructor from COS object.
+ *
+ * @param array The array containing the XYZ values.
+ */
+ public PDTristimulus( float[] array )
+ {
+ values = new COSArray();
+ for( int i=0; i<array.length && i<3; i++ )
+ {
+ values.add( new COSFloat( array[i] ) );
+ }
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return values;
+ }
+
+ /**
+ * This will get the x value of the tristimulus.
+ *
+ * @return The X value.
+ */
+ public float getX()
+ {
+ return ((COSNumber)values.get( 0 )).floatValue();
+ }
+
+ /**
+ * This will set the x value of the tristimulus.
+ *
+ * @param x The x value for the tristimulus.
+ */
+ public void setX( float x )
+ {
+ values.set( 0, new COSFloat( x ) );
+ }
+
+ /**
+ * This will get the y value of the tristimulus.
+ *
+ * @return The Y value.
+ */
+ public float getY()
+ {
+ return ((COSNumber)values.get( 1 )).floatValue();
+ }
+
+ /**
+ * This will set the y value of the tristimulus.
+ *
+ * @param y The y value for the tristimulus.
+ */
+ public void setY( float y )
+ {
+ values.set( 1, new COSFloat( y ) );
+ }
+
+ /**
+ * This will get the z value of the tristimulus.
+ *
+ * @return The Z value.
+ */
+ public float getZ()
+ {
+ return ((COSNumber)values.get( 2 )).floatValue();
+ }
+
+ /**
+ * This will set the z value of the tristimulus.
+ *
+ * @param z The z value for the tristimulus.
+ */
+ public void setZ( float z )
+ {
+ values.set( 2, new COSFloat( z ) );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package deals with colors that are stored in a PDF document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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() + ")";
+ }
+
+}
--- /dev/null
+/*
+ * 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());
+
+ //By default, add new group to the "Order" entry so it appears in the user interface
+ COSArray order = (COSArray)getD().getDictionaryObject(COSName.ORDER);
+ if (order == null)
+ {
+ order = new COSArray();
+ getD().setItem(COSName.ORDER, order);
+ }
+ order.add(ocg);
+ }
+
+ /**
+ * 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;
+ }
+
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The PDModel graphics package deals with graphics states, operations, and parameters within the PDF document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.predictor;
+
+/**
+ * We can use raw on the right hand side of
+ * the decoding formula because it is already decoded.
+ *
+ * <code>average(i,j) = raw(i,j) + (raw(i-1,j)+raw(i,j-1)/2</code>
+ *
+ * decoding
+ *
+ * <code>raw(i,j) = avarage(i,j) - (raw(i-1,j)+raw(i,j-1)/2</code>
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.3 $
+ */
+public class Average extends PredictorAlgorithm
+{
+ /**
+ * Not an optimal version, but close to the def.
+ *
+ * {@inheritDoc}
+ */
+ public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ int bpl = getWidth() * getBpp();
+ for (int x = 0; x < bpl; x++)
+ {
+ dest[x + destOffset] = (byte) (src[x + srcOffset] - ((leftPixel(
+ src, srcOffset, srcDy, x) + abovePixel(src, srcOffset,
+ srcDy, x)) >>> 2));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ int bpl = getWidth() * getBpp();
+ for (int x = 0; x < bpl; x++)
+ {
+ dest[x + destOffset] = (byte) (src[x + srcOffset] + ((leftPixel(
+ dest, destOffset, destDy, x) + abovePixel(dest,
+ destOffset, destDy, x)) >>> 2));
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.predictor;
+
+/**
+ * The none algorithm.
+ *
+ * <code>None(i,j) = Raw(i,j)</code>
+ *
+ * <code>Raw(i,j) = None(i,j)</code>
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.3 $
+ */
+public class None extends PredictorAlgorithm
+{
+ /**
+ * encode a byte array full of image data using the filter that this object
+ * implements.
+ *
+ * @param src
+ * buffer
+ * @param dest
+ * buffer
+ */
+ public void encode(byte[] src, byte[] dest)
+ {
+ checkBufsiz(dest, src);
+ System.arraycopy(src,0,dest,0,src.length);
+ }
+
+ /**
+ * decode a byte array full of image data using the filter that this object
+ * implements.
+ *
+ * @param src
+ * buffer
+ * @param dest
+ * buffer
+ */
+ public void decode(byte[] src, byte[] dest)
+ {
+ System.arraycopy(src,0,dest,0,src.length);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ int bpl = getWidth() * getBpp();
+ for (int x = 0; x < bpl; x++)
+ {
+ dest[destOffset + x] = src[srcOffset + x];
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ int bpl = getWidth() * getBpp();
+ for (int x = 0; x < bpl; x++)
+ {
+ dest[destOffset + x] = src[srcOffset + x];
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.predictor;
+
+/**
+ *
+ *
+ * In an Uptimum encoded image, each line takes up width*bpp+1 bytes. The first
+ * byte holds a number that signifies which algorithm encoded the line.
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.1 $
+ */
+public class Optimum extends PredictorAlgorithm
+{
+ /**
+ * {@inheritDoc}
+ */
+ public void checkBufsiz(byte[] filtered, byte[] raw)
+ {
+ if (filtered.length != (getWidth() * getBpp() + 1) * getHeight())
+ {
+
+ throw new IllegalArgumentException(
+ "filtered.length != (width*bpp + 1) * height, "
+ + filtered.length + " "
+ + (getWidth() * getBpp() + 1) * getHeight()
+ + "w,h,bpp=" + getWidth() + "," + getHeight() + ","
+ + getBpp());
+ }
+ if (raw.length != getWidth() * getHeight() * getBpp())
+ {
+ throw new IllegalArgumentException(
+ "raw.length != width * height * bpp, raw.length="
+ + raw.length + " w,h,bpp=" + getWidth() + ","
+ + getHeight() + "," + getBpp());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ throw new UnsupportedOperationException("encodeLine");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ throw new UnsupportedOperationException("decodeLine");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encode(byte[] src, byte[] dest)
+ {
+ checkBufsiz(dest, src);
+ throw new UnsupportedOperationException("encode");
+ }
+
+ /**
+ * Filter indexed by byte code.
+ */
+ PredictorAlgorithm[] filter = { new None(), new Sub(), new Up(), new Average(),
+ new Paeth() };
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setBpp(int bpp)
+ {
+ super.setBpp(bpp);
+ for (int i = 0; i < filter.length; i++)
+ {
+ filter[i].setBpp(bpp);
+ }
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public void setHeight(int height)
+ {
+ super.setHeight(height);
+ for (int i = 0; i < filter.length; i++)
+ {
+ filter[i].setHeight(height);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setWidth(int width)
+ {
+ super.setWidth(width);
+ for (int i = 0; i < filter.length; i++)
+ {
+ filter[i].setWidth(width);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decode(byte[] src, byte[] dest)
+ {
+ checkBufsiz(src, dest);
+ int bpl = getWidth() * getBpp();
+ int srcDy = bpl + 1;
+ for (int y = 0; y < getHeight(); y++)
+ {
+ PredictorAlgorithm f = filter[src[y * srcDy]];
+ int srcOffset = y * srcDy + 1;
+ f.decodeLine(src, dest, srcDy, srcOffset, bpl, y * bpl);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.predictor;
+
+/**
+ * From http://www.w3.org/TR/PNG-Filters.html: The Paeth filter computes a
+ * simple linear function of the three neighboring pixels (left, above, upper
+ * left), then chooses as predictor the neighboring pixel closest to the
+ * computed value. This technique is due to Alan W. Paeth [PAETH].
+ *
+ * To compute the Paeth filter, apply the following formula to each byte of the
+ * scanline:
+ *
+ * <code>Paeth(i,j) = Raw(i,j) - PaethPredictor(Raw(i-1,j), Raw(i,j-1), Raw(i-1,j-1))</code>
+ *
+ * To decode the Paeth filter
+ *
+ * <code>Raw(i,j) = Paeth(i,j) - PaethPredictor(Raw(i-1,j), Raw(i,j-1), Raw(i-1,j-1))</code>
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.3 $
+ */
+public class Paeth extends PredictorAlgorithm
+{
+ /**
+ * The paeth predictor function.
+ *
+ * This function is taken almost directly from the PNG definition on
+ * http://www.w3.org/TR/PNG-Filters.html
+ *
+ * @param a
+ * left
+ * @param b
+ * above
+ * @param c
+ * upper left
+ * @return The result of the paeth predictor.
+ */
+ public int paethPredictor(int a, int b, int c)
+ {
+ int p = a + b - c; // initial estimate
+ int pa = Math.abs(p - a); // distances to a, b, c
+ int pb = Math.abs(p - b);
+ int pc = Math.abs(p - c);
+ // return nearest of a,b,c,
+ // breaking ties in order a,b,c.
+ if (pa <= pb && pa <= pc)
+ {
+ return a;
+ }
+ else if (pb <= pc)
+ {
+ return b;
+ }
+ else
+ {
+ return c;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ int bpl = getWidth() * getBpp();
+ for (int x = 0; x < bpl; x++)
+ {
+ dest[x + destOffset] = (byte) (src[x + srcOffset] - paethPredictor(
+ leftPixel(src, srcOffset, srcDy, x), abovePixel(src,
+ srcOffset, srcDy, x), aboveLeftPixel(src,
+ srcOffset, srcDy, x)));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ int bpl = getWidth() * getBpp();
+ for (int x = 0; x < bpl; x++)
+ {
+ dest[x + destOffset] = (byte) (src[x + srcOffset] + paethPredictor(
+ leftPixel(dest, destOffset, destDy, x), abovePixel(dest,
+ destOffset, destDy, x), aboveLeftPixel(dest,
+ destOffset, destDy, x)));
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.predictor;
+
+import java.util.Random;
+
+/**
+ * Implements different PNG predictor algorithms that is used in PDF files.
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.4 $
+ * @see <a href="http://www.w3.org/TR/PNG-Filters.html">PNG Filters</a>
+ */
+public abstract class PredictorAlgorithm
+{
+ private int width;
+
+ private int height;
+
+ private int bpp;
+
+ /**
+ * check that buffer sizes matches width,height,bpp. This implementation is
+ * used by most of the filters, but not Uptimum.
+ *
+ * @param src The source buffer.
+ * @param dest The destination buffer.
+ */
+ public void checkBufsiz(byte[] src, byte[] dest)
+ {
+ if (src.length != dest.length)
+ {
+ throw new IllegalArgumentException("src.length != dest.length");
+ }
+ if (src.length != getWidth() * getHeight() * getBpp())
+ {
+ throw new IllegalArgumentException(
+ "src.length != width * height * bpp");
+ }
+ }
+
+ /**
+ * encode line of pixel data in src from srcOffset and width*bpp bytes
+ * forward, put the decoded bytes into dest.
+ *
+ * @param src
+ * raw image data
+ * @param dest
+ * encoded data
+ * @param srcDy
+ * byte offset between lines
+ * @param srcOffset
+ * beginning of line data
+ * @param destDy
+ * byte offset between lines
+ * @param destOffset
+ * beginning of line data
+ */
+ public abstract void encodeLine(byte[] src, byte[] dest, int srcDy,
+ int srcOffset, int destDy, int destOffset);
+
+ /**
+ * decode line of pixel data in src from src_offset and width*bpp bytes
+ * forward, put the decoded bytes into dest.
+ *
+ * @param src
+ * encoded image data
+ * @param dest
+ * raw data
+ * @param srcDy
+ * byte offset between lines
+ * @param srcOffset
+ * beginning of line data
+ * @param destDy
+ * byte offset between lines
+ * @param destOffset
+ * beginning of line data
+ */
+ public abstract void decodeLine(byte[] src, byte[] dest, int srcDy,
+ int srcOffset, int destDy, int destOffset);
+
+ /**
+ * Simple command line program to test the algorithm.
+ *
+ * @param args The command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ Random rnd = new Random();
+ int width = 5;
+ int height = 5;
+ int bpp = 3;
+ byte[] raw = new byte[width * height * bpp];
+ rnd.nextBytes(raw);
+ System.out.println("raw: ");
+ dump(raw);
+ for (int i = 10; i < 15; i++)
+ {
+ byte[] decoded = new byte[width * height * bpp];
+ byte[] encoded = new byte[width * height * bpp];
+
+ PredictorAlgorithm filter = PredictorAlgorithm.getFilter(i);
+ filter.setWidth(width);
+ filter.setHeight(height);
+ filter.setBpp(bpp);
+ filter.encode(raw, encoded);
+ filter.decode(encoded, decoded);
+ System.out.println(filter.getClass().getName());
+ dump(decoded);
+ }
+ }
+
+ /**
+ * Get the left pixel from the buffer.
+ *
+ * @param buf The buffer.
+ * @param offset The offset into the buffer.
+ * @param dy The dy value.
+ * @param x The x value.
+ *
+ * @return The left pixel.
+ */
+ public int leftPixel(byte[] buf, int offset, int dy, int x)
+ {
+ return x >= getBpp() ? buf[offset + x - getBpp()] : 0;
+ }
+
+ /**
+ * Get the above pixel from the buffer.
+ *
+ * @param buf The buffer.
+ * @param offset The offset into the buffer.
+ * @param dy The dy value.
+ * @param x The x value.
+ *
+ * @return The above pixel.
+ */
+ public int abovePixel(byte[] buf, int offset, int dy, int x)
+ {
+ return offset >= dy ? buf[offset + x - dy] : 0;
+ }
+
+ /**
+ * Get the above-left pixel from the buffer.
+ *
+ * @param buf The buffer.
+ * @param offset The offset into the buffer.
+ * @param dy The dy value.
+ * @param x The x value.
+ *
+ * @return The above-left pixel.
+ */
+ public int aboveLeftPixel(byte[] buf, int offset, int dy, int x)
+ {
+ return offset >= dy && x >= getBpp() ? buf[offset + x - dy - getBpp()]
+ : 0;
+ }
+
+ /**
+ * Simple helper to print out a buffer.
+ *
+ * @param raw The bytes to print out.
+ */
+ private static void dump(byte[] raw)
+ {
+ for (int i = 0; i < raw.length; i++)
+ {
+ System.out.print(raw[i] + " ");
+ }
+ System.out.println();
+ }
+
+ /**
+ * @return Returns the bpp.
+ */
+ public int getBpp()
+ {
+ return bpp;
+ }
+
+ /**
+ * @param newBpp
+ * The bpp to set.
+ */
+ public void setBpp(int newBpp)
+ {
+ bpp = newBpp;
+ }
+
+ /**
+ * @return Returns the height.
+ */
+ public int getHeight()
+ {
+ return height;
+ }
+
+ /**
+ * @param newHeight
+ * The height to set.
+ */
+ public void setHeight(int newHeight)
+ {
+ height = newHeight;
+ }
+
+ /**
+ * @return Returns the width.
+ */
+ public int getWidth()
+ {
+ return width;
+ }
+
+ /**
+ * @param newWidth
+ * The width to set.
+ */
+ public void setWidth(int newWidth)
+ {
+ this.width = newWidth;
+ }
+
+
+ /**
+ * encode a byte array full of image data using the filter that this object
+ * implements.
+ *
+ * @param src
+ * buffer
+ * @param dest
+ * buffer
+ */
+ public void encode(byte[] src, byte[] dest)
+ {
+ checkBufsiz(dest, src);
+ int dy = getWidth()*getBpp();
+ for (int y = 0; y < height; y++)
+ {
+ int yoffset = y * dy;
+ encodeLine(src, dest, dy, yoffset, dy, yoffset);
+ }
+ }
+
+ /**
+ * decode a byte array full of image data using the filter that this object
+ * implements.
+ *
+ * @param src
+ * buffer
+ * @param dest
+ * buffer
+ */
+ public void decode(byte[] src, byte[] dest)
+ {
+ checkBufsiz(src, dest);
+ int dy = width * bpp;
+ for (int y = 0; y < height; y++)
+ {
+ int yoffset = y * dy;
+ decodeLine(src, dest, dy, yoffset, dy, yoffset);
+ }
+ }
+
+ /**
+ * @param predictor
+ * <ul>
+ * <li>1 No prediction (the default value)
+ * <li>2 TIFF Predictor 2
+ * <li>10 PNG prediction (on encoding, PNG None on all rows)
+ * <li>11 PNG prediction (on encoding, PNG Sub on all rows)
+ * <li>12 PNG prediction (on encoding, PNG Up on all rows)
+ * <li>13 PNG prediction (on encoding, PNG Average on all rows)
+ * <li>14 PNG prediction (on encoding, PNG Paeth on all rows)
+ * <li>15 PNG prediction (on encoding, PNG optimum)
+ * </ul>
+ *
+ * @return The predictor class based on the predictor code.
+ */
+ public static PredictorAlgorithm getFilter(int predictor)
+ {
+ PredictorAlgorithm filter;
+ switch (predictor)
+ {
+ case 10:
+ filter = new None();
+ break;
+ case 11:
+ filter = new Sub();
+ break;
+ case 12:
+ filter = new Up();
+ break;
+ case 13:
+ filter = new Average();
+ break;
+ case 14:
+ filter = new Paeth();
+ break;
+ case 15:
+ filter = new Optimum();
+ break;
+ default:
+ filter = new None();
+ }
+ return filter;
+ }
+}
--- /dev/null
+/*
+ * 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.predictor;
+
+/**
+ * The sub algorithm.
+ *
+ * <code>Sub(i,j) = Raw(i,j) - Raw(i-1,j)</code>
+ *
+ * <code>Raw(i,j) = Sub(i,j) + Raw(i-1,j)</code>
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.3 $
+ */
+public class Sub extends PredictorAlgorithm
+{
+ /**
+ * {@inheritDoc}
+ */
+ public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ int bpl = getWidth()*getBpp();
+ int bpp = getBpp();
+ // case: x < bpp
+ for (int x = 0; x < bpl && x < bpp; x++)
+ {
+ dest[x + destOffset] = src[x + srcOffset];
+ }
+ // otherwise
+ for (int x = getBpp(); x < bpl; x++)
+ {
+ dest[x + destOffset] = (byte) (src[x + srcOffset] - src[x
+ + srcOffset - bpp]);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ int bpl = getWidth()*getBpp();
+ int bpp = getBpp();
+ // case: x < bpp
+ for (int x = 0; x < bpl && x < bpp; x++)
+ {
+ dest[x + destOffset] = src[x + srcOffset];
+ }
+ // otherwise
+ for (int x = getBpp(); x < bpl; x++)
+ {
+ dest[x + destOffset] = (byte) (src[x + srcOffset] + dest[x
+ + destOffset - bpp]);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.predictor;
+
+/**
+ * The up algorithm.
+ *
+ * <code>Up(i,j) = Raw(i,j) - Raw(i,j-1)</code>
+ *
+ * <code>Raw(i,j) = Up(i,j) + Raw(i,j-1)</code>
+ *
+ * @author xylifyx@yahoo.co.uk
+ * @version $Revision: 1.3 $
+ */
+public class Up extends PredictorAlgorithm
+{
+ /**
+ * {@inheritDoc}
+ */
+ public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ int bpl = getWidth()*getBpp();
+ // case: y = 0;
+ if (srcOffset - srcDy < 0)
+ {
+ if (0 < getHeight())
+ {
+ for (int x = 0; x < bpl; x++)
+ {
+ dest[destOffset + x] = src[srcOffset + x];
+ }
+ }
+ }
+ else
+ {
+ for (int x = 0; x < bpl; x++)
+ {
+ dest[destOffset + x] = (byte) (src[srcOffset + x] - src[srcOffset
+ + x - srcDy]);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset,
+ int destDy, int destOffset)
+ {
+ // case: y = 0;
+ int bpl = getWidth()*getBpp();
+ if (destOffset - destDy < 0)
+ {
+ if (0 < getHeight())
+ {
+ for (int x = 0; x < bpl; x++)
+ {
+ dest[destOffset + x] = src[srcOffset + x];
+ }
+ }
+ }
+ else
+ {
+ for (int x = 0; x < bpl; x++)
+ {
+ dest[destOffset + x] = (byte) (src[srcOffset + x] + dest[destOffset
+ + x - destDy]);
+ }
+ }
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The predictor package contains code for different PNG predictor algorithms that
+are present in PDF documents. These classes are used internally by PDFBox.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.xobject;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSArray;
+
+
+/**
+ * This class is responsible for combining a base image with an SMask-based transparency
+ * image to form a composite image.
+ * See section 11.5 of the pdf specification for details on Soft Masks.
+ * <p/>
+ * Briefly however, an Smask is a supplementary greyscale image whose RGB-values define
+ * a transparency mask which, when combined appropriately with the base image,
+ * allows per-pixel transparency to be applied.
+ * <p/>
+ * Note that Smasks are not required for any image and if the smask is not present
+ * in the pdf file, the image will have no transparent pixels.
+ *
+ * @author Neil McErlean
+ */
+public class CompositeImage
+{
+ private BufferedImage baseImage;
+ private BufferedImage smaskImage;
+
+ /**
+ * Standard constructor.
+ * @param baseImage the base Image.
+ * @param smaskImage the transparency image.
+ *
+ */
+ public CompositeImage(BufferedImage baseImage, BufferedImage smaskImage)
+ {
+ this.baseImage = baseImage;
+ this.smaskImage = smaskImage;
+ }
+
+ /**
+ * This method applies the specified transparency mask to a given image and returns a new BufferedImage
+ * whose alpha values are computed from the transparency mask (smask) image.
+ */
+ public BufferedImage createMaskedImage(COSArray decodeArray) throws IOException
+ {
+ // The decode array should only be [0 1] or [1 0]. See PDF spec.
+ // [0 1] means the smask's RGB values give transparency. Default: see PDF spec section 8.9.5.1
+ // [1 0] means the smask's RGB values give opacity.
+
+ boolean isOpaque = false;
+ if (decodeArray != null)
+ {
+ isOpaque = decodeArray.getInt(0) > decodeArray.getInt(1);
+ }
+
+ final int baseImageWidth = baseImage.getWidth();
+ final int baseImageHeight = baseImage.getHeight();
+
+ BufferedImage result = new BufferedImage(baseImageWidth, baseImageHeight, BufferedImage.TYPE_INT_ARGB);
+ for (int x = 0; x < baseImageWidth; x++)
+ {
+ for (int y = 0; y < baseImageHeight; y++)
+ {
+ int rgb = baseImage.getRGB(x, y);
+ int alpha = smaskImage.getRGB(x, y);
+
+ // The smask image defines a transparency mask but it has no alpha values itself, instead
+ // using the greyscale values to indicate transparency.
+ // 0xAARRGGBB
+
+ // We need to remove any alpha value in the main image.
+ int rgbOnly = 0x00FFFFFF & rgb;
+
+ // We need to use one of the rgb values as the new alpha value for the main image.
+ // It seems the mask is greyscale, so it shouldn't matter whether we use R, G or B
+ // as the indicator of transparency.
+ if (isOpaque)
+ {
+ alpha = ~alpha;
+ }
+ int alphaOnly = alpha << 24;
+
+ result.setRGB(x, y, rgbOnly | alphaOnly);
+ }
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * 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.xobject;
+
+import java.awt.Transparency;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBufferByte;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.io.RandomAccess;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+
+/**
+ * An image class for CCITT Fax.
+ *
+ * @author <a href="ben@benlitchfield.com">Ben Litchfield</a>
+ * @author paul king
+ * @version $Revision: 1.6 $
+ */
+public class PDCcitt extends PDXObjectImage
+{
+
+ private static final List<String> FAX_FILTERS = new ArrayList<String>();
+
+ static
+ {
+ FAX_FILTERS.add( COSName.CCITTFAX_DECODE.getName() );
+ FAX_FILTERS.add( COSName.CCITTFAX_DECODE_ABBREVIATION.getName() );
+ }
+
+ /**
+ * Standard constructor.
+ *
+ * @param ccitt The PDStream that already contains all ccitt information.
+ */
+ public PDCcitt(PDStream ccitt)
+ {
+ super(ccitt, "tiff");
+
+ }
+
+ /**
+ * Construct from a tiff file.
+ *
+ * @param doc The document to create the image as part of.
+ * @param raf The random access TIFF file which contains a suitable CCITT compressed image
+ * @throws IOException If there is an error reading the tiff data.
+ */
+
+ public PDCcitt( PDDocument doc, RandomAccess raf ) throws IOException
+ {
+ super( new PDStream(doc),"tiff");
+ // super( new PDStream( doc, null, true ), "tiff" );
+
+ COSDictionary decodeParms = new COSDictionary();
+
+ COSDictionary dic = getCOSStream();
+
+ extractFromTiff(raf, getCOSStream().createFilteredStream(),decodeParms);
+
+ dic.setItem( COSName.FILTER, COSName.CCITTFAX_DECODE);
+ dic.setItem( COSName.SUBTYPE, COSName.IMAGE);
+ dic.setItem( COSName.TYPE, COSName.XOBJECT );
+ dic.setItem( COSName.DECODE_PARMS, decodeParms);
+
+ setBitsPerComponent( 1 );
+ setColorSpace( new PDDeviceGray() );
+ setWidth( decodeParms.getInt(COSName.COLUMNS) );
+ setHeight( decodeParms.getInt(COSName.ROWS) );
+
+ }
+
+ /**
+ * Returns an image of the CCITT Fax, or null if TIFFs are not supported. (Requires additional JAI Image filters )
+ *
+ * {@inheritDoc}
+ */
+ public BufferedImage getRGBImage() throws IOException
+ {
+ COSStream stream = getCOSStream();
+ COSBase decodeP = stream.getDictionaryObject(COSName.DECODE_PARMS);
+ COSDictionary decodeParms = null;
+ if (decodeP instanceof COSDictionary)
+ {
+ decodeParms = (COSDictionary)decodeP;
+ }
+ else if (decodeP instanceof COSArray)
+ {
+ decodeParms = (COSDictionary)((COSArray)decodeP).get(0);
+ }
+ int cols = decodeParms.getInt(COSName.COLUMNS, 1728);
+ int rows = decodeParms.getInt(COSName.ROWS, 0);
+ int height = stream.getInt(COSName.HEIGHT, 0);
+ if (rows > 0 && height > 0)
+ {
+ // ensure that rows doesn't contain implausible data, see PDFBOX-771
+ rows = Math.min(rows, height);
+ }
+ else
+ {
+ // at least one of the values has to have a valid value
+ rows = Math.max(rows, height);
+ }
+ boolean blackIsOne = decodeParms.getBoolean(COSName.BLACK_IS_1, false);
+
+ byte[] map;
+ if (blackIsOne)
+ {
+ map = new byte[] {(byte)0x00, (byte)0xff};
+ }
+ else
+ {
+ map = new byte[] {(byte)0xff};
+ }
+ ColorModel cm = new IndexColorModel(1, map.length, map, map, map, Transparency.OPAQUE);
+ WritableRaster raster = cm.createCompatibleWritableRaster( cols, rows );
+ DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer();
+ byte[] bufferData = buffer.getData();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int bytesRead;
+ byte[] data = new byte[16384];
+ InputStream unfilteredStream = stream.getUnfilteredStream();
+ while ((bytesRead = unfilteredStream.read(data, 0, data.length)) != -1)
+ {
+ baos.write(data, 0, bytesRead);
+ }
+ baos.flush();
+ System.arraycopy( baos.toByteArray(), 0,bufferData, 0,
+ (baos.size() < bufferData.length ? baos.size() : bufferData.length) );
+ return new BufferedImage(cm, raster, false, null);
+ }
+
+ /**
+ * This writes a tiff to out.
+ *
+ * {@inheritDoc}
+ */
+ public void write2OutputStream(OutputStream out) throws IOException
+ {
+ // We should use another format than TIFF to get rid of the TiffWrapper
+ InputStream data = new TiffWrapper(getPDStream().getPartiallyFilteredStream( FAX_FILTERS ),getCOSStream());
+ byte[] buf = new byte[1024];
+ int amountRead = -1;
+ while( (amountRead = data.read( buf )) != -1 )
+ {
+ out.write( buf, 0, amountRead );
+ }
+ }
+
+ /**
+ * Extract the ccitt stream from the tiff file.
+ *
+ * @param raf - TIFF File
+ * @param os - Stream to write raw ccitt data two
+ * @param parms - COSDictionary which the encoding parameters are added to
+ * @throws IOException If there is an error reading/writing to/from the stream
+ */
+ private void extractFromTiff(RandomAccess raf, OutputStream os, COSDictionary parms) throws IOException
+ {
+ try
+ {
+
+ // First check the basic tiff header
+ raf.seek(0);
+ char endianess = (char) raf.read();
+ if ((char) raf.read() != endianess)
+ {
+ throw new IOException("Not a valid tiff file");
+ }
+ //ensure that endianess is either M or I
+ if (endianess != 'M' && endianess != 'I')
+ {
+ throw new IOException("Not a valid tiff file");
+ }
+ int magicNumber = readshort(endianess, raf);
+ if( magicNumber != 42)
+ {
+ throw new IOException("Not a valid tiff file");
+ }
+
+ // Relocate to the first set of tags
+ raf.seek(readlong(endianess, raf));
+
+ int numtags = readshort(endianess, raf);
+
+ // The number 50 is somewhat arbitary, it just stops us load up junk from somewhere and tramping on
+ if (numtags > 50)
+ {
+ throw new IOException("Not a valid tiff file");
+ }
+
+ // Loop through the tags, some will convert to items in the parms dictionary
+ // Other point us to where to find the data stream
+ // The only parm which might change as a result of other options is K, so
+ // We'll deal with that as a special;
+
+ int k=-1000; // Default Non CCITT compression
+ int dataoffset=0;
+ int datalength=0;
+
+ for (int i=0; i < numtags; i++)
+ {
+ int tag = readshort(endianess, raf);
+ int type = readshort(endianess, raf);
+ int count = readlong(endianess, raf);
+ int val = readlong(endianess, raf); // See note
+
+ // Note, we treated that value as a long. The value always occupies 4 bytes
+ // But it might only use the first byte or two. Depending on endianess we might need to correct
+ // Note we ignore all other types, they are of little interest for PDFs/CCITT Fax
+ if (endianess == 'M')
+ {
+ switch (type)
+ {
+ case 1:
+ {
+ val = val >> 24;
+ break; // byte value
+ }
+ case 3:
+ {
+ val = val >> 16;
+ break; // short value
+ }
+ case 4:
+ {
+ break; // long value
+ }
+ default:
+ {
+ //do nothing
+ }
+ }
+ }
+ switch (tag)
+ {
+ case 256:
+ {
+ parms.setInt(COSName.COLUMNS,val);
+ break;
+ }
+ case 257:
+ {
+ parms.setInt(COSName.ROWS,val);
+ break;
+ }
+ case 259:
+ {
+ if (val == 4)
+ {
+ k=-1;
+ }
+ if (val == 3)
+ {
+ k=0;
+ }
+ break; // T6/T4 Compression
+ }
+ case 262:
+ {
+ if (val == 1)
+ {
+ parms.setBoolean(COSName.BLACK_IS_1, true);
+ }
+ break;
+ }
+ case 273:
+ {
+ if (count == 1)
+ {
+ dataoffset=val;
+ }
+ break;
+ }
+ case 279:
+ {
+ if (count == 1)
+ {
+ datalength=val;
+ }
+ break;
+ }
+ case 292:
+ {
+ if (val == 1)
+ {
+ k=50; // T4 2D - arbitary K value
+ }
+ break;
+ }
+ case 324:
+ {
+ if (count == 1)
+ {
+ dataoffset=val;
+ }
+ break;
+ }
+ case 325:
+ {
+ if (count == 1)
+ {
+ datalength=val;
+ }
+ break;
+ }
+ default:
+ {
+ //do nothing
+ }
+ }
+ }
+
+ if (k == -1000)
+ {
+ throw new IOException("First image in tiff is not CCITT T4 or T6 compressed");
+ }
+ if (dataoffset == 0)
+ {
+ throw new IOException("First image in tiff is not a single tile/strip");
+ }
+
+ parms.setInt(COSName.K,k);
+
+ raf.seek(dataoffset);
+
+ byte[] buf = new byte[8192];
+ int amountRead = -1;
+ while( (amountRead = raf.read( buf,0, Math.min(8192,datalength) )) > 0 )
+ {
+ datalength -= amountRead;
+ os.write( buf, 0, amountRead );
+ }
+
+ }
+ finally
+ {
+ os.close();
+ }
+ }
+
+ private int readshort(char endianess, RandomAccess raf) throws IOException
+ {
+ if (endianess == 'I')
+ {
+ return raf.read() | (raf.read() << 8);
+ }
+ return (raf.read() << 8) | raf.read();
+ }
+
+ private int readlong(char endianess, RandomAccess raf) throws IOException
+ {
+ if (endianess == 'I')
+ {
+ return raf.read() | (raf.read() << 8) | (raf.read() << 16) | (raf.read() << 24);
+ }
+ return (raf.read() << 24) | (raf.read() << 16) | (raf.read() << 8) | raf.read();
+ }
+
+
+ /**
+ * Extends InputStream to wrap the data from the CCITT Fax with a suitable TIFF Header.
+ * For details see www.tiff.org, which contains useful information including pointers to the
+ * TIFF 6.0 Specification
+ *
+ */
+ private class TiffWrapper extends InputStream
+ {
+
+ private int currentOffset; // When reading, where in the tiffheader are we.
+ private byte[] tiffheader; // Byte array to store tiff header data
+ private InputStream datastream; // Original InputStream
+
+ private TiffWrapper(InputStream rawstream, COSDictionary options)
+ {
+ buildHeader(options);
+ currentOffset=0;
+ datastream = rawstream;
+ }
+
+ // Implement basic methods from InputStream
+ /**
+ * {@inheritDoc}
+ */
+ public boolean markSupported()
+ {
+ return false;
+ }
+ /**
+ * {@inheritDoc}
+ */
+ public void reset() throws IOException
+ {
+ throw new IOException("reset not supported");
+ }
+
+ /**
+ * For simple read, take a byte from the tiffheader array or pass through.
+ *
+ * {@inheritDoc}
+ */
+ public int read() throws IOException
+ {
+ if (currentOffset < tiffheader.length)
+ {
+ return tiffheader[currentOffset++];
+ }
+ return datastream.read();
+ }
+
+ /**
+ * For read methods only return as many bytes as we have left in the header
+ * if we've exhausted the header, pass through to the InputStream of the raw CCITT data.
+ *
+ * {@inheritDoc}
+ */
+ public int read(byte[] data) throws IOException
+ {
+ if (currentOffset < tiffheader.length)
+ {
+ int length = java.lang.Math.min(tiffheader.length - currentOffset, data.length);
+ if (length > 0)
+ {
+ System.arraycopy(tiffheader, currentOffset, data, 0, length);
+ }
+ currentOffset += length;
+ return length;
+ }
+ else
+ {
+ return datastream.read(data);
+ }
+ }
+
+ /**
+ * For read methods only return as many bytes as we have left in the header
+ * if we've exhausted the header, pass through to the InputStream of the raw CCITT data.
+ *
+ * {@inheritDoc}
+ */
+ public int read(byte[] data, int off, int len) throws IOException
+ {
+ if (currentOffset < tiffheader.length)
+ {
+ int length = java.lang.Math.min(tiffheader.length - currentOffset, len);
+ if (length > 0)
+ {
+ System.arraycopy(tiffheader, currentOffset, data, off, length);
+ }
+ currentOffset += length;
+ return length;
+ }
+ else
+ {
+ return datastream.read(data,off,len);
+ }
+ }
+
+ /**
+ * When skipping if any header data not yet read, only allow to skip what we've in the buffer
+ * Otherwise just pass through.
+ *
+ * {@inheritDoc}
+ */
+ public long skip(long n) throws IOException
+ {
+ if (currentOffset < tiffheader.length)
+ {
+ long length = Math.min(tiffheader.length - currentOffset, n);
+ currentOffset += length;
+ return length;
+ }
+ else
+ {
+ return datastream.skip(n);
+ }
+ }
+
+ // Static data for the beginning of the TIFF header
+ private final byte[] basicHeader = {
+ 'I','I',42,0,8,0,0,0, // File introducer and pointer to first IFD
+ 0,0}; // Number of tags start with two
+
+
+ private int additionalOffset; // Offset in header to additional data
+
+ // Builds up the tiffheader based on the options passed through.
+ private void buildHeader(COSDictionary options)
+ {
+
+ final int numOfTags = 10; // The maximum tags we'll fill
+ final int maxAdditionalData = 24; // The maximum amount of additional data
+ // outside the IFDs. (bytes)
+
+ // The length of the header will be the length of the basic header (10)
+ // plus 12 bytes for each IFD, 4 bytes as a pointer to the next IFD (will be 0)
+ // plus the length of the additional data
+
+ int ifdSize = 10 + (12 * numOfTags ) + 4;
+ tiffheader = new byte[ifdSize + maxAdditionalData];
+ java.util.Arrays.fill(tiffheader,(byte)0);
+ System.arraycopy(basicHeader,0,tiffheader,0,basicHeader.length);
+
+ // Additional data outside the IFD starts after the IFD's and pointer to the next IFD (0)
+ additionalOffset = ifdSize;
+
+ // Now work out the variable values from TIFF defaults,
+ // PDF Defaults and the Dictionary for this XObject
+ short cols = 1728;
+ short rows = 0;
+ short blackis1 = 0;
+ short comptype = 3; // T4 compression
+ long t4options = 0; // Will set if 1d or 2d T4
+
+ COSArray decode = getDecode();
+ // we have to invert the b/w-values,
+ // if the Decode array exists and consists of (1,0)
+ if (decode != null && decode.getInt(0) == 1)
+ {
+ blackis1 = 1;
+ }
+ COSBase dicOrArrayParms = options.getDictionaryObject(COSName.DECODE_PARMS);
+ COSDictionary decodeParms = null;
+ if( dicOrArrayParms instanceof COSDictionary )
+ {
+ decodeParms = (COSDictionary)dicOrArrayParms;
+ }
+ else
+ {
+ COSArray parmsArray = (COSArray)dicOrArrayParms;
+ if( parmsArray.size() == 1 )
+ {
+ decodeParms = (COSDictionary)parmsArray.getObject( 0 );
+ }
+ else
+ {
+ //else find the first dictionary with Row/Column info and use that.
+ for( int i=0; i<parmsArray.size() && decodeParms == null; i++ )
+ {
+ COSDictionary dic = (COSDictionary)parmsArray.getObject( i );
+ if (dic != null &&
+ ( dic.getDictionaryObject(COSName.COLUMNS) != null ||
+ dic.getDictionaryObject(COSName.ROWS) != null))
+ {
+ decodeParms = dic;
+ }
+ }
+ }
+ }
+
+ if (decodeParms != null)
+ {
+ cols = (short) decodeParms.getInt(COSName.COLUMNS, cols);
+ rows = (short) decodeParms.getInt(COSName.ROWS, rows);
+ if (decodeParms.getBoolean(COSName.BLACK_IS_1, false))
+ {
+ blackis1 = 1;
+ }
+ int k = decodeParms.getInt(COSName.K, 0); // Mandatory parm
+ if (k < 0)
+ {
+ //T6
+ comptype = 4;
+ }
+ if (k > 0)
+ {
+ //T4 2D
+ comptype = 3;
+ t4options = 1;
+ }
+ // else k = 0, leave as default T4 1D compression
+ }
+
+ // If we couldn't get the number of rows, use the main item from XObject
+ if (rows == 0)
+ {
+ rows = (short) options.getInt(COSName.HEIGHT, rows);
+ }
+
+ // Now put the tags into the tiffheader
+ // These musn't exceed the maximum set above, and by TIFF spec should be sorted into
+ // Numeric sequence.
+
+ addTag(256, cols); // Columns
+ addTag(257, rows); // Rows
+ addTag(259, comptype); // T6
+ addTag(262, blackis1); // Photometric Interpretation
+ addTag(273, tiffheader.length); // Offset to start of image data - updated below
+ addTag(279, options.getInt(COSName.LENGTH)); // Length of image data
+ addTag(282, 300, 1); // X Resolution 300 (default unit Inches) This is arbitary
+ addTag(283, 300, 1); // Y Resolution 300 (default unit Inches) This is arbitary
+ if (comptype == 3)
+ {
+ addTag(292, t4options);
+ }
+ addTag(305, "PDFBOX"); // Software generating image
+ }
+
+ /* Tiff types 1 = byte, 2=ascii, 3=short, 4=ulong 5=rational */
+
+ private void addTag(int tag,long value)
+ {
+ // Adds a tag of type 4 (ulong)
+ int count = ++tiffheader[8];
+ int offset = (count-1)*12 + 10;
+ tiffheader[offset]=(byte)(tag & 0xff);
+ tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
+ tiffheader[offset+2]=4; // Type Long
+ tiffheader[offset+4]=1; // One Value
+ tiffheader[offset+8]=(byte)(value & 0xff);
+ tiffheader[offset+9]=(byte)((value>>8) & 0xff);
+ tiffheader[offset+10]=(byte)((value>>16) & 0xff);
+ tiffheader[offset+11]=(byte)((value>>24) & 0xff);
+ }
+
+ private void addTag(int tag, short value)
+ {
+ // Adds a tag of type 3 (short)
+ int count = ++tiffheader[8];
+ int offset = (count-1)*12 + 10;
+ tiffheader[offset]=(byte)(tag & 0xff);
+ tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
+ tiffheader[offset+2]=3; // Type Short
+ tiffheader[offset+4]=1; // One Value
+ tiffheader[offset+8]=(byte)(value & 0xff);
+ tiffheader[offset+9]=(byte)((value>>8) & 0xff);
+ }
+
+ private void addTag(int tag, String value)
+ {
+ // Adds a tag of type 2 (ascii)
+ int count = ++tiffheader[8];
+ int offset = (count-1)*12 + 10;
+ tiffheader[offset]=(byte)(tag & 0xff);
+ tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
+ tiffheader[offset+2]=2; // Type Ascii
+ int len = value.length() + 1;
+ tiffheader[offset+4]=(byte)(len & 0xff);
+ tiffheader[offset+8]=(byte)(additionalOffset & 0xff);
+ tiffheader[offset+9]=(byte)((additionalOffset>>8) & 0xff);
+ tiffheader[offset+10]=(byte)((additionalOffset>>16) & 0xff);
+ tiffheader[offset+11]=(byte)((additionalOffset>>24) & 0xff);
+ try {
+ System.arraycopy(value.getBytes("US-ASCII"), 0,
+ tiffheader, additionalOffset, value.length());
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Incompatible VM without US-ASCII encoding", e);
+ }
+ additionalOffset += len;
+ }
+
+ private void addTag(int tag, long numerator, long denominator)
+ {
+ // Adds a tag of type 5 (rational)
+ int count = ++tiffheader[8];
+ int offset = (count-1)*12 + 10;
+ tiffheader[offset]=(byte)(tag & 0xff);
+ tiffheader[offset+1]=(byte)((tag>>8) & 0xff);
+ tiffheader[offset+2]=5; // Type Rational
+ tiffheader[offset+4]=1; // One Value
+ tiffheader[offset+8]=(byte)(additionalOffset & 0xff);
+ tiffheader[offset+9]=(byte)((additionalOffset>>8) & 0xff);
+ tiffheader[offset+10]=(byte)((additionalOffset>>16) & 0xff);
+ tiffheader[offset+11]=(byte)((additionalOffset>>24) & 0xff);
+ tiffheader[additionalOffset++]=(byte) ((numerator) & 0xFF);
+ tiffheader[additionalOffset++]=(byte) ((numerator>>8) & 0xFF);
+ tiffheader[additionalOffset++]=(byte) ((numerator>>16) & 0xFF);
+ tiffheader[additionalOffset++]=(byte) ((numerator>>24) & 0xFF);
+ tiffheader[additionalOffset++]=(byte) ((denominator) & 0xFF);
+ tiffheader[additionalOffset++]=(byte) ((denominator>>8) & 0xFF);
+ tiffheader[additionalOffset++]=(byte) ((denominator>>16) & 0xFF);
+ tiffheader[additionalOffset++]=(byte) ((denominator>>24) & 0xFF);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.xobject;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterManager;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.util.ImageParameters;
+
+/**
+ * This class represents an inlined image.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDInlinedImage
+{
+ private ImageParameters params;
+ private byte[] imageData;
+
+ /**
+ * This will get the image parameters.
+ *
+ * @return The image parameters.
+ */
+ public ImageParameters getImageParameters()
+ {
+ return params;
+ }
+
+ /**
+ * This will set the image parameters for this image.
+ *
+ * @param imageParams The imageParams.
+ */
+ public void setImageParameters( ImageParameters imageParams )
+ {
+ params = imageParams;
+ }
+
+ /**
+ * Get the bytes for the image.
+ *
+ * @return The image data.
+ */
+ public byte[] getImageData()
+ {
+ return imageData;
+ }
+
+ /**
+ * Set the bytes that make up the image.
+ *
+ * @param value The image data.
+ */
+ public void setImageData(byte[] value)
+ {
+ imageData = value;
+ }
+
+ /**
+ * This will take the inlined image information and create a java.awt.Image from
+ * it.
+ *
+ * @return The image that this object represents.
+ *
+ * @throws IOException If there is an error creating the image.
+ */
+ public BufferedImage createImage() throws IOException
+ {
+ return createImage( null );
+ }
+
+ /**
+ * This will take the inlined image information and create a java.awt.Image from
+ * it.
+ *
+ * @param colorSpaces The ColorSpace dictionary from the current resources, if any.
+ *
+ * @return The image that this object represents.
+ *
+ * @throws IOException If there is an error creating the image.
+ */
+ public BufferedImage createImage( Map colorSpaces ) throws IOException
+ {
+ /*
+ * This was the previous implementation, not sure which is better right now.
+ * byte[] transparentColors = new byte[]{(byte)0xFF,(byte)0xFF};
+ byte[] colors=new byte[]{0, (byte)0xFF};
+ IndexColorModel colorModel = new IndexColorModel( 1, 2, colors, colors, colors, transparentColors );
+ BufferedImage image = new BufferedImage(
+ params.getWidth(),
+ params.getHeight(),
+ BufferedImage.TYPE_BYTE_BINARY,
+ colorModel );
+ DataBufferByte buffer = new DataBufferByte( getImageData(), 1 );
+ WritableRaster raster =
+ Raster.createPackedRaster(
+ buffer,
+ params.getWidth(),
+ params.getHeight(),
+ params.getBitsPerComponent(),
+ new Point(0,0) );
+ image.setData( raster );
+ return image;
+ */
+
+
+ //verify again pci32.pdf before changing below
+ PDColorSpace pcs = params.getColorSpace( colorSpaces );
+ ColorModel colorModel = null;
+ if(pcs != null)
+ {
+ colorModel =
+ pcs.createColorModel(
+ params.getBitsPerComponent() );
+ }
+ else
+ {
+ byte[] transparentColors = new
+ byte[]{(byte)0xFF,(byte)0xFF};
+ byte[] colors=new byte[]{0, (byte)0xFF};
+ colorModel = new IndexColorModel( 1, 2,
+ colors, colors, colors, transparentColors );
+ }
+ List filters = params.getFilters();
+ byte[] finalData = null;
+ if( filters == null )
+ {
+ finalData = getImageData();
+ }
+ else
+ {
+ ByteArrayInputStream in = new ByteArrayInputStream( getImageData() );
+ ByteArrayOutputStream out = new ByteArrayOutputStream(getImageData().length);
+ FilterManager filterManager = new FilterManager();
+ for( int i=0; i<filters.size(); i++ )
+ {
+ out.reset();
+ Filter filter = filterManager.getFilter( (String)filters.get( i ) );
+ filter.decode( in, out, params.getDictionary(), i );
+ in = new ByteArrayInputStream( out.toByteArray() );
+ }
+ finalData = out.toByteArray();
+ }
+
+ WritableRaster raster = colorModel.createCompatibleWritableRaster( params.getWidth(), params.getHeight() );
+ /* Raster.createPackedRaster(
+ buffer,
+ params.getWidth(),
+ params.getHeight(),
+ params.getBitsPerComponent(),
+ new Point(0,0) );
+ */
+ DataBuffer rasterBuffer = raster.getDataBuffer();
+ if( rasterBuffer instanceof DataBufferByte )
+ {
+ DataBufferByte byteBuffer = (DataBufferByte)rasterBuffer;
+ byte[] data = byteBuffer.getData();
+ System.arraycopy( finalData, 0, data, 0, data.length );
+ }
+ else if( rasterBuffer instanceof DataBufferInt )
+ {
+ DataBufferInt byteBuffer = (DataBufferInt)rasterBuffer;
+ int[] data = byteBuffer.getData();
+ for( int i=0; i<finalData.length; i++ )
+ {
+ data[i] = (finalData[i]+256)%256;
+ }
+ }
+ BufferedImage image = new BufferedImage(
+ colorModel, raster, false, null );
+ image.setData( raster );
+ return image;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.xobject;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.IIOException;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
+import javax.imageio.stream.ImageOutputStream;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+
+/**
+ * An image class for JPegs.
+ *
+ * @author mathiak
+ * @version $Revision: 1.5 $
+ */
+public class PDJpeg extends PDXObjectImage
+{
+
+ private static final String JPG = "jpg";
+
+ private static final List<String> DCT_FILTERS = new ArrayList<String>();
+
+ private final static float defaultCompressionLevel = 0.75f;
+
+ static
+ {
+ DCT_FILTERS.add( COSName.DCT_DECODE.getName() );
+ DCT_FILTERS.add( COSName.DCT_DECODE_ABBREVIATION.getName() );
+ }
+
+ /**
+ * Standard constructor.
+ *
+ * @param jpeg The COSStream from which to extract the JPeg
+ */
+ public PDJpeg(PDStream jpeg)
+ {
+ super(jpeg, JPG);
+ }
+
+ /**
+ * Construct from a stream.
+ *
+ * @param doc The document to create the image as part of.
+ * @param is The stream that contains the jpeg data.
+ * @throws IOException If there is an error reading the jpeg data.
+ */
+ public PDJpeg( PDDocument doc, InputStream is ) throws IOException
+ {
+ super( new PDStream( doc, is, true ), JPG);
+ COSDictionary dic = getCOSStream();
+ dic.setItem( COSName.FILTER, COSName.DCT_DECODE );
+ dic.setItem( COSName.SUBTYPE, COSName.IMAGE);
+ dic.setItem( COSName.TYPE, COSName.XOBJECT );
+
+ BufferedImage image = getRGBImage();
+ if (image != null)
+ {
+ setBitsPerComponent( 8 );
+ setColorSpace( PDDeviceRGB.INSTANCE );
+ setHeight( image.getHeight() );
+ setWidth( image.getWidth() );
+ }
+
+ }
+
+ /**
+ * Construct from a buffered image.
+ * The default compression level of 0.75 will be used.
+ *
+ * @param doc The document to create the image as part of.
+ * @param bi The image to convert to a jpeg
+ * @throws IOException If there is an error processing the jpeg data.
+ */
+ public PDJpeg( PDDocument doc, BufferedImage bi ) throws IOException
+ {
+ super( new PDStream( doc ) , JPG);
+ createImageStream(doc, bi, defaultCompressionLevel);
+ }
+
+ /**
+ * Construct from a buffered image.
+ *
+ * @param doc The document to create the image as part of.
+ * @param bi The image to convert to a jpeg
+ * @param compressionQuality The quality level which is used to compress the image
+ * @throws IOException If there is an error processing the jpeg data.
+ */
+ public PDJpeg( PDDocument doc, BufferedImage bi, float compressionQuality ) throws IOException
+ {
+ super( new PDStream( doc ), JPG);
+ createImageStream(doc, bi, compressionQuality);
+ }
+
+ private void createImageStream(PDDocument doc, BufferedImage bi, float compressionQuality) throws IOException
+ {
+ BufferedImage alpha = null;
+ if (bi.getColorModel().hasAlpha())
+ {
+ alpha = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
+ Graphics2D g = alpha.createGraphics();
+ g.setColor(Color.BLACK);
+ g.drawRect(0, 0, bi.getWidth(), bi.getHeight());
+ g.setColor(Color.WHITE);
+ g.drawImage(bi, 0, 0, null);
+ int alphaHeight = alpha.getHeight();
+ int alphaWidth = alpha.getWidth();
+ int whiteRGB = (Color.WHITE).getRGB();
+ for(int y = 0; y < alphaHeight; y++)
+ {
+ for(int x = 0; x < alphaWidth; x++)
+ {
+ Color color = new Color(alpha.getRGB(x, y));
+ if(color.getRed() != 0 && color.getGreen() != 0 && color.getBlue() != 0)
+ {
+ alpha.setRGB(x, y, whiteRGB);
+ }
+ }
+ }
+ BufferedImage image = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_USHORT_565_RGB);
+ g = image.createGraphics();
+ g.drawImage(bi, 0, 0, null);
+ bi = image;
+ }
+
+ java.io.OutputStream os = getCOSStream().createFilteredStream();
+ try
+ {
+ ImageWriter writer = null;
+ Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(JPG);
+ if (iter.hasNext())
+ {
+ writer = iter.next();
+ }
+ ImageOutputStream ios = ImageIO.createImageOutputStream(os);
+ writer.setOutput(ios);
+
+ // Set the compression quality
+ JPEGImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
+ iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+ iwparam.setCompressionQuality(compressionQuality);
+
+ // Write the image
+ writer.write(null, new IIOImage(bi, null, null), iwparam);
+
+ writer.dispose();
+
+ COSDictionary dic = getCOSStream();
+ dic.setItem( COSName.FILTER, COSName.DCT_DECODE );
+ dic.setItem( COSName.SUBTYPE, COSName.IMAGE);
+ dic.setItem( COSName.TYPE, COSName.XOBJECT );
+ PDXObjectImage alphaPdImage = null;
+ if(alpha != null)
+ {
+ alphaPdImage = new PDJpeg(doc, alpha, compressionQuality);
+ dic.setItem(COSName.SMASK, alphaPdImage);
+ }
+ setBitsPerComponent( 8 );
+ if (bi.getColorModel().getNumComponents() == 3)
+ {
+ setColorSpace( PDDeviceRGB.INSTANCE );
+ }
+ else
+ {
+ if (bi.getColorModel().getNumComponents() == 1)
+ {
+ setColorSpace( new PDDeviceGray() );
+ }
+ else
+ {
+ throw new IllegalStateException();
+ }
+ }
+ setHeight( bi.getHeight() );
+ setWidth( bi.getWidth() );
+ }
+ finally
+ {
+ os.close();
+ }
+ }
+
+ /**
+ * Returns an image of the JPeg, or null if JPegs are not supported. (They should be. )
+ * {@inheritDoc}
+ */
+ public BufferedImage getRGBImage() throws IOException
+ { //TODO PKOCH
+ BufferedImage bi = null;
+ boolean readError = false;
+ try
+ {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ write2OutputStream(os);
+ os.close();
+ byte[] img = os.toByteArray();
+
+ // 1. try to read jpeg image
+ try
+ {
+ bi = ImageIO.read(new ByteArrayInputStream(img));
+ }
+ catch (IIOException iioe)
+ {
+ // cannot read jpeg
+ readError = true;
+ }
+ catch (Exception ignore)
+ {
+ }
+
+ // 2. try to read jpeg again. some jpegs have some strange header containing
+ // "Adobe " at some place. so just replace the header with a valid jpeg header.
+ // TODO : not sure if it works for all cases
+ if (bi == null && readError)
+ {
+ byte[] newImage = replaceHeader(img);
+
+ ByteArrayInputStream bai = new ByteArrayInputStream(newImage);
+
+ bi = ImageIO.read(bai);
+ }
+ }
+ finally
+ {
+ }
+
+ // If there is a 'soft mask' image then we use that as a transparency mask.
+ PDXObjectImage smask = getSMaskImage();
+ if (smask != null)
+ {
+ BufferedImage smaskBI = smask.getRGBImage();
+
+ COSArray decodeArray = smask.getDecode();
+ CompositeImage compositeImage = new CompositeImage(bi, smaskBI);
+ BufferedImage rgbImage = compositeImage.createMaskedImage(decodeArray);
+
+ return rgbImage;
+ }
+ else
+ {
+ // But if there is no soft mask, use the unaltered image.
+ return bi;
+ }
+ }
+
+ /**
+ * This writes the JPeg to out.
+ * {@inheritDoc}
+ */
+ public void write2OutputStream(OutputStream out) throws IOException
+ {
+ InputStream data = getPDStream().getPartiallyFilteredStream( DCT_FILTERS );
+ byte[] buf = new byte[1024];
+ int amountRead = -1;
+ while( (amountRead = data.read( buf )) != -1 )
+ {
+ out.write( buf, 0, amountRead );
+ }
+ }
+
+ /**
+ * Returns the given file as byte array.
+ * @param file File to be read
+ * @return given file as byte array
+ * @throws IOException if somethin went wrong during reading the file
+ */
+ public static byte[] getBytesFromFile(File file) throws IOException
+ {
+ InputStream is = new FileInputStream(file);
+ long length = file.length();
+
+ if (length > Integer.MAX_VALUE)
+ {
+ // File is too large
+ throw new IOException("File is tooo large");
+ }
+
+ // Create the byte array to hold the data
+ byte[] bytes = new byte[(int)length];
+
+ // Read in the bytes
+ int offset = 0;
+ int numRead = 0;
+
+ while (offset < bytes.length
+ && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0)
+ {
+ offset += numRead;
+ }
+
+ // Ensure all the bytes have been read in
+ if (offset < bytes.length)
+ {
+ throw new IOException("Could not completely read file "+file.getName());
+ }
+ is.close();
+ return bytes;
+ }
+
+ private int getHeaderEndPos(byte[] image)
+ {
+ for (int i = 0; i < image.length; i++)
+ {
+ byte b = image[i];
+ if (b == (byte) 0xDB)
+ {
+ // TODO : check for ff db
+ return i -2;
+ }
+ }
+ return 0;
+ }
+
+ private byte[] replaceHeader(byte[] image)
+ {
+ // get end position of wrong header respectively startposition of "real jpeg data"
+ int pos = getHeaderEndPos(image);
+
+ // simple correct header
+ byte[] header = new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF, (byte) 0xE0, (byte) 0x00,
+ (byte) 0x10, (byte) 0x4A, (byte) 0x46, (byte) 0x49, (byte) 0x46, (byte) 0x00, (byte) 0x01,
+ (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x60, (byte) 0x00, (byte) 0x60, (byte) 0x00, (byte) 0x00};
+
+ // concat
+ byte[] newImage = new byte[image.length - pos + header.length - 1];
+ System.arraycopy(header, 0, newImage, 0, header.length);
+ System.arraycopy(image, pos + 1, newImage, header.length, image.length - pos - 1);
+
+ return newImage;
+ }
+}
+
--- /dev/null
+/*
+ * 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.xobject;
+
+import java.awt.Transparency;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.imageio.ImageIO;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDIndexed;
+
+
+
+/**
+ * This class contains a PixelMap Image.
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author mathiak
+ * @version $Revision: 1.10 $
+ */
+public class PDPixelMap extends PDXObjectImage
+{
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDPixelMap.class);
+
+ private BufferedImage image = null;
+
+ /**
+ * Standard constructor. Basically does nothing.
+ * @param pdStream The stream that holds the pixel map.
+ */
+ public PDPixelMap(PDStream pdStream)
+ {
+ super(pdStream, "png");
+ }
+
+ /**
+ * Construct a pixel map image from an AWT image.
+ *
+ * @param doc The PDF document to embed the image in.
+ * @param awtImage The image to read data from.
+ *
+ * @throws IOException If there is an error while embedding this image.
+ */
+ /*
+ * This method is broken and needs to be implemented, any takers?
+ public PDPixelMap(PDDocument doc, BufferedImage awtImage) throws IOException
+ {
+ super( doc, "png");
+ image = awtImage;
+ setWidth( image.getWidth() );
+ setHeight( image.getHeight() );
+
+ ColorModel cm = image.getColorModel();
+ ColorSpace cs = cm.getColorSpace();
+ PDColorSpace pdColorSpace = PDColorSpaceFactory.createColorSpace( doc, cs );
+ setColorSpace( pdColorSpace );
+ //setColorSpace( )
+
+ PDStream stream = getPDStream();
+ OutputStream output = null;
+ try
+ {
+ output = stream.createOutputStream();
+ DataBuffer buffer = awtImage.getRaster().getDataBuffer();
+ if( buffer instanceof DataBufferByte )
+ {
+ DataBufferByte byteBuffer = (DataBufferByte)buffer;
+ byte[] data = byteBuffer.getData();
+ output.write( data );
+ }
+ setBitsPerComponent( cm.getPixelSize() );
+ }
+ finally
+ {
+ if( output != null )
+ {
+ output.close();
+ }
+ }
+ }*/
+
+ /**
+ * Returns a {@link java.awt.image.BufferedImage} of the COSStream
+ * set in the constructor or null if the COSStream could not be encoded.
+ *
+ * @return {@inheritDoc}
+ *
+ * @throws IOException {@inheritDoc}
+ */
+ public BufferedImage getRGBImage() throws IOException
+ {
+ if( image != null )
+ {
+ return image;
+ }
+
+ try
+ {
+ int width = getWidth();
+ int height = getHeight();
+ int bpc = getBitsPerComponent();
+
+ byte[] array = getPDStream().getByteArray();
+
+ // Get the ColorModel right
+ PDColorSpace colorspace = getColorSpace();
+ if (colorspace == null)
+ {
+ log.error("getColorSpace() returned NULL. Predictor = " + getPredictor());
+ return null;
+ }
+
+ ColorModel cm = null;
+ if (colorspace instanceof PDIndexed)
+ {
+ PDIndexed csIndexed = (PDIndexed)colorspace;
+ ColorModel baseColorModel = csIndexed.getBaseColorSpace().createColorModel(bpc);
+ int size = csIndexed.getHighValue();
+ byte[] index = csIndexed.getLookupData();
+ boolean hasAlpha = baseColorModel.hasAlpha();
+ COSArray maskArray = getMask();
+ if( baseColorModel.getTransferType() != DataBuffer.TYPE_BYTE )
+ {
+ throw new IOException( "Not implemented" );
+ }
+ byte[] r = new byte[size+1];
+ byte[] g = new byte[size+1];
+ byte[] b = new byte[size+1];
+ byte[] a = hasAlpha ? new byte[size+1] : null;
+ byte[] inData = new byte[baseColorModel.getNumComponents()];
+ for( int i = 0; i <= size; i++ )
+ {
+ System.arraycopy(index, i * inData.length, inData, 0, inData.length);
+ r[i] = (byte)baseColorModel.getRed(inData);
+ g[i] = (byte)baseColorModel.getGreen(inData);
+ b[i] = (byte)baseColorModel.getBlue(inData);
+ if( hasAlpha )
+ {
+ a[i] = (byte)baseColorModel.getAlpha(inData);
+ }
+ }
+ if (hasAlpha)
+ {
+ cm = new IndexColorModel(bpc, size+1, r, g, b, a);
+ }
+ else {
+ if (maskArray != null)
+ {
+ cm = new IndexColorModel(bpc, size+1, r, g, b, maskArray.getInt(0));
+ }
+ else
+ {
+ cm = new IndexColorModel(bpc, size+1, r, g, b);
+ }
+ }
+ }
+ else if (bpc == 1)
+ {
+ byte[] map = null;
+ if (colorspace instanceof PDDeviceGray)
+ {
+ COSArray decode = getDecode();
+ // we have to invert the b/w-values,
+ // if the Decode array exists and consists of (1,0)
+ if (decode != null && decode.getInt(0) == 1)
+ {
+ map = new byte[] {(byte)0xff};
+ }
+ else
+ {
+ map = new byte[] {(byte)0x00, (byte)0xff};
+ }
+ }
+ else if (colorspace instanceof PDICCBased)
+ {
+ if ( ((PDICCBased)colorspace).getNumberOfComponents() == 1)
+ {
+ map = new byte[] {(byte)0xff};
+ }
+ else
+ {
+ map = new byte[] {(byte)0x00, (byte)0xff};
+ }
+ }
+ else
+ {
+ map = new byte[] {(byte)0x00, (byte)0xff};
+ }
+ cm = new IndexColorModel(bpc, map.length, map, map, map, Transparency.OPAQUE);
+ }
+ else
+ {
+ if (colorspace instanceof PDICCBased)
+ {
+ if (((PDICCBased)colorspace).getNumberOfComponents() == 1)
+ {
+ byte[] map = new byte[] {(byte)0xff};
+ cm = new IndexColorModel(bpc, 1, map, map, map, Transparency.OPAQUE);
+ }
+ else
+ cm = colorspace.createColorModel( bpc );
+ }
+ else
+ cm = colorspace.createColorModel( bpc );
+ }
+
+ log.debug("ColorModel: " + cm.toString());
+ WritableRaster raster = cm.createCompatibleWritableRaster( width, height );
+ DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer();
+ byte[] bufferData = buffer.getData();
+
+ System.arraycopy( array, 0,bufferData, 0,
+ (array.length<bufferData.length?array.length: bufferData.length) );
+ image = new BufferedImage(cm, raster, false, null);
+
+ // If there is a 'soft mask' image then we use that as a transparency mask.
+ PDXObjectImage smask = getSMaskImage();
+ if (smask != null)
+ {
+ BufferedImage smaskBI = smask.getRGBImage();
+
+ COSArray decodeArray = smask.getDecode();
+
+ CompositeImage compositeImage = new CompositeImage(image, smaskBI);
+ BufferedImage rgbImage = compositeImage.createMaskedImage(decodeArray);
+
+ return rgbImage;
+ }
+ else
+ {
+ // But if there is no soft mask, use the unaltered image.
+ return image;
+ }
+ }
+ catch (Exception exception)
+ {
+ log.error(exception, exception);
+ //A NULL return is caught in pagedrawer.Invoke.process() so don't re-throw.
+ //Returning the NULL falls through to Phillip Koch's TODO section.
+ return null;
+ }
+ }
+
+ /**
+ * Writes the image as .png.
+ *
+ * {@inheritDoc}
+ */
+ public void write2OutputStream(OutputStream out) throws IOException
+ {
+ getRGBImage();
+ if (image!=null)
+ {
+ ImageIO.write(image, "png", out);
+ }
+ }
+
+ /**
+ * DecodeParms is an optional parameter for filters.
+ *
+ * It is provided if any of the filters has nondefault parameters. If there
+ * is only one filter it is a dictionary, if there are multiple filters it
+ * is an array with an entry for each filter. An array entry can hold a null
+ * value if only the default values are used or a dictionary with
+ * parameters.
+ *
+ * @return The decoding parameters.
+ *
+ */
+ public COSDictionary getDecodeParams()
+ {
+ COSBase decodeParms = getCOSStream().getDictionaryObject(COSName.DECODE_PARMS);
+ if (decodeParms != null)
+ {
+ if (decodeParms instanceof COSDictionary)
+ {
+ return (COSDictionary) decodeParms;
+ }
+ else if (decodeParms instanceof COSArray)
+ {
+ // not implemented yet, which index should we use?
+ return null;//(COSDictionary)((COSArray)decodeParms).get(0);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * A code that selects the predictor algorithm.
+ *
+ * <ul>
+ * <li>1 No prediction (the default value)
+ * <li>2 TIFF Predictor 2
+ * <li>10 PNG prediction (on encoding, PNG None on all rows)
+ * <li>11 PNG prediction (on encoding, PNG Sub on all rows)
+ * <li>12 PNG prediction (on encoding, PNG Up on all rows)
+ * <li>13 PNG prediction (on encoding, PNG Average on all rows)
+ * <li>14 PNG prediction (on encoding, PNG Path on all rows)
+ * <li>15 PNG prediction (on encoding, PNG optimum)
+ * </ul>
+ *
+ * Default value: 1.
+ *
+ * @return predictor algorithm code
+ */
+ public int getPredictor()
+ {
+ COSDictionary decodeParms = getDecodeParams();
+ if (decodeParms != null)
+ {
+ int i = decodeParms.getInt(COSName.PREDICTOR);
+ if (i != -1)
+ {
+ return i;
+ }
+ }
+ return 1;
+ }
+}
--- /dev/null
+/*
+ * 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.xobject;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+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.PDMetadata;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * The base class for all XObjects in the PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author mathiak
+ * @author Marcel Kammer
+ * @version $Revision: 1.14 $
+ */
+public abstract class PDXObject implements COSObjectable
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDXObject.class);
+
+ private PDStream xobject;
+
+ /**
+ * Standard constructor.
+ *
+ * @param xobj The XObject dictionary.
+ */
+ public PDXObject(COSStream xobj)
+ {
+ xobject = new PDStream( xobj );
+ xobject.getStream().setName( COSName.TYPE, "XObject" );
+ }
+
+ /**
+ * Standard constuctor.
+ *
+ * @param xobj The XObject dictionary.
+ */
+ public PDXObject(PDStream xobj)
+ {
+ xobject = xobj;
+ xobject.getStream().setName( COSName.TYPE, "XObject" );
+ }
+
+ /**
+ * Standard constuctor.
+ *
+ * @param doc The doc to store the object contents.
+ */
+ public PDXObject(PDDocument doc)
+ {
+ xobject = new PDStream(doc);
+ xobject.getStream().setName( COSName.TYPE, "XObject" );
+ }
+
+ /**
+ * Returns the stream.
+ *
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return xobject.getCOSObject();
+ }
+
+ /**
+ * Returns the stream.
+ * @return The stream for this object.
+ */
+ public COSStream getCOSStream()
+ {
+ return xobject.getStream();
+ }
+
+ /**
+ * Returns the stream.
+ * @return The stream for this object.
+ */
+ public PDStream getPDStream()
+ {
+ return xobject;
+ }
+
+ /**
+ * Create the correct xobject from the cos base.
+ *
+ * @param xobject The cos level xobject to create.
+ *
+ * @return a pdmodel xobject
+ * @throws IOException If there is an error creating the xobject.
+ */
+ public static PDXObject createXObject( COSBase xobject ) throws IOException
+ {
+ PDXObject retval = null;
+ if( xobject == null )
+ {
+ retval = null;
+ }
+ else if( xobject instanceof COSStream )
+ {
+ COSStream xstream = (COSStream)xobject;
+ String subtype = xstream.getNameAsString( COSName.SUBTYPE );
+ if( subtype.equals( PDXObjectImage.SUB_TYPE ) )
+ {
+ PDStream image = new PDStream( xstream );
+ // See if filters are DCT or JPX otherwise treat as Bitmap-like
+ // There might be a problem with several filters, but that's ToDo until
+ // I find an example
+ List filters = image.getFilters();
+ if( filters != null && filters.contains( COSName.DCT_DECODE.getName() ) )
+ {
+ return new PDJpeg(image);
+ }
+ else if ( filters != null && filters.contains( COSName.CCITTFAX_DECODE.getName() ) )
+ {
+ return new PDCcitt(image);
+ }
+ else if( filters != null && filters.contains(COSName.JPX_DECODE.getName()))
+ {
+ //throw new IOException( "JPXDecode has not been implemented for images" );
+ //JPX Decode is not really supported right now, but if we are just doing
+ //text extraction then we don't want to throw an exception, so for now
+ //just return a PDPixelMap, which will break later on if it is
+ //actually used, but for text extraction it is not used.
+ return new PDPixelMap( image );
+ }
+ else
+ {
+ retval = new PDPixelMap(image);
+ }
+ }
+ else if( subtype.equals( PDXObjectForm.SUB_TYPE ) )
+ {
+ retval = new PDXObjectForm( xstream );
+ }
+ else
+ {
+ log.warn( "Skipping unknown XObject subtype '" + subtype + "'" );
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * Get the metadata that is part of the document catalog. This will
+ * return null if there is no meta data for this object.
+ *
+ * @return The metadata for this object.
+ */
+ public PDMetadata getMetadata()
+ {
+ PDMetadata retval = null;
+ COSStream mdStream = (COSStream)xobject.getStream().getDictionaryObject( COSName.METADATA );
+ if( mdStream != null )
+ {
+ retval = new PDMetadata( mdStream );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the metadata for this object. This can be null.
+ *
+ * @param meta The meta data for this object.
+ */
+ public void setMetadata( PDMetadata meta )
+ {
+ xobject.getStream().setItem( COSName.METADATA, meta );
+ }
+}
--- /dev/null
+/*
+ * 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.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;
+import org.apache.pdfbox.util.Matrix;
+
+/**
+ * A form xobject.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public class PDXObjectForm extends PDXObject
+{
+ /**
+ * The XObject subtype.
+ */
+ public static final String SUB_TYPE = "Form";
+
+ /**
+ * Standard constuctor.
+ *
+ * @param formStream The XObject is passed as a COSStream.
+ */
+ public PDXObjectForm(PDStream formStream)
+ {
+ super( formStream );
+ getCOSStream().setName( COSName.SUBTYPE, SUB_TYPE );
+ }
+
+ /**
+ * Standard constuctor.
+ *
+ * @param formStream The XObject is passed as a COSStream.
+ */
+ public PDXObjectForm(COSStream formStream)
+ {
+ super( formStream );
+ getCOSStream().setName( COSName.SUBTYPE, SUB_TYPE );
+ }
+
+ /**
+ * This will get the form type, currently 1 is the only form type.
+ *
+ * @return The form type.
+ */
+ public int getFormType()
+ {
+ return getCOSStream().getInt( "FormType",1 );
+ }
+
+ /**
+ * Set the form type.
+ *
+ * @param formType The new form type.
+ */
+ public void setFormType( int formType )
+ {
+ getCOSStream().setInt( "FormType", formType );
+ }
+
+ /**
+ * This will get the resources at this page and not look up the hierarchy.
+ * This attribute is inheritable, and findResources() should probably used.
+ * This will return null if no resources are available at this level.
+ *
+ * @return The resources at this level in the hierarchy.
+ */
+ public PDResources getResources()
+ {
+ PDResources retval = null;
+ COSDictionary resources = (COSDictionary)getCOSStream().getDictionaryObject( COSName.RESOURCES );
+ if( resources != null )
+ {
+ retval = new PDResources( resources );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the resources for this page.
+ *
+ * @param resources The new resources for this page.
+ */
+ public void setResources( PDResources resources )
+ {
+ getCOSStream().setItem( COSName.RESOURCES, resources );
+ }
+
+ /**
+ * An array of four numbers in the form coordinate system (see
+ * below), giving the coordinates of the left, bottom, right, and top edges,
+ * respectively, of the form XObject's bounding box. These boundaries are used
+ * to clip the form XObject and to determine its size for caching.
+ *
+ * @return The BBox of the form.
+ */
+ public PDRectangle getBBox()
+ {
+ PDRectangle retval = null;
+ COSArray array = (COSArray)getCOSStream().getDictionaryObject( COSName.BBOX );
+ if( array != null )
+ {
+ retval = new PDRectangle( array );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the BBox (bounding box) for this form.
+ *
+ * @param bbox The new BBox for this form.
+ */
+ public void setBBox(PDRectangle bbox)
+ {
+ if( bbox == null )
+ {
+ getCOSStream().removeItem( COSName.BBOX );
+ }
+ else
+ {
+ 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()
+ {
+ Matrix retval = null;
+ COSArray array = (COSArray)getCOSStream().getDictionaryObject( COSName.MATRIX );
+ if( array != null )
+ {
+ retval = new Matrix();
+ retval.setValue(0, 0, ((COSNumber) array.get(0)).floatValue());
+ retval.setValue(0, 1, ((COSNumber) array.get(1)).floatValue());
+ retval.setValue(1, 0, ((COSNumber) array.get(2)).floatValue());
+ retval.setValue(1, 1, ((COSNumber) array.get(3)).floatValue());
+ retval.setValue(2, 0, ((COSNumber) array.get(4)).floatValue());
+ retval.setValue(2, 1, ((COSNumber) array.get(5)).floatValue());
+ }
+ 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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.xobject;
+
+import java.awt.image.BufferedImage;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.File;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+
+/**
+ * The prototype for all PDImages.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author mathiak
+ * @version $Revision: 1.9 $
+ */
+public abstract class PDXObjectImage extends PDXObject
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDXObjectImage.class);
+
+ /**
+ * The XObject subtype.
+ */
+ public static final String SUB_TYPE = "Image";
+
+ /**
+ * This contains the suffix used when writing to file.
+ */
+ private String suffix;
+
+ private PDGraphicsState graphicsState;
+
+ /**
+ * Standard constuctor.
+ *
+ * @param imageStream The XObject is passed as a COSStream.
+ * @param fileSuffix The file suffix, jpg/png.
+ */
+ public PDXObjectImage(PDStream imageStream, String fileSuffix)
+ {
+ super( imageStream );
+ suffix = fileSuffix;
+ }
+
+ /**
+ * Standard constuctor.
+ *
+ * @param doc The document to store the stream in.
+ * @param fileSuffix The file suffix, jpg/png.
+ */
+ public PDXObjectImage(PDDocument doc, String fileSuffix)
+ {
+ super( doc );
+ getCOSStream().setName( COSName.SUBTYPE, SUB_TYPE );
+ suffix = fileSuffix;
+ }
+
+ /**
+ * Returns an java.awt.Image, that can be used for display etc.
+ *
+ * @return This PDF object as an AWT image.
+ *
+ * @throws IOException If there is an error creating the image.
+ */
+ public abstract BufferedImage getRGBImage() throws IOException;
+
+ /**
+ * Returns a PDXObjectImage of the SMask image, if there is one.
+ * See section 11.5 of the pdf specification for details on Soft Masks.
+ *
+ * @return the PDXObjectImage of the SMask if there is one, else <code>null</code>.
+ */
+ public PDXObjectImage getSMaskImage() throws IOException
+ {
+ COSStream cosStream = getPDStream().getStream();
+ COSBase smask = cosStream.getDictionaryObject(COSName.SMASK);
+
+ if (smask == null)
+ {
+ return null;
+ }
+ else
+ {
+ return (PDXObjectImage)PDXObject.createXObject(smask);
+ }
+ }
+
+ /**
+ * Writes the Image to out.
+ * @param out the OutputStream that the Image is written to.
+ * @throws IOException when somethings wrong with out
+ */
+ public abstract void write2OutputStream(OutputStream out) throws IOException;
+
+ /**
+ * Writes the image to a file with the filename + an appropriate suffix, like "Image.jpg".
+ * The suffix is automatically set by the
+ * @param filename the filename
+ * @throws IOException When somethings wrong with the corresponding file.
+ */
+ public void write2file(String filename) throws IOException
+ {
+ FileOutputStream out = null;
+ try
+ {
+ out = new FileOutputStream(filename + "." + suffix);
+ write2OutputStream(out);
+ out.flush();
+ }
+ finally
+ {
+ if( out != null )
+ {
+ out.close();
+ }
+ }
+ }
+
+ /**
+ * Writes the image to a file with the filename + an appropriate
+suffix, like "Image.jpg".
+ * The suffix is automatically set by the
+ * @param file the file
+ * @throws IOException When somethings wrong with the corresponding
+file.
+ */
+ public void write2file(File file) throws IOException
+ {
+ FileOutputStream out = null;
+ try
+ {
+ out = new FileOutputStream(file);
+ write2OutputStream(out);
+ out.flush();
+ }
+ finally
+ {
+ if( out != null )
+ {
+ out.close();
+ }
+ }
+ }
+
+ /**
+ * Get the height of the image.
+ *
+ * @return The height of the image.
+ */
+ public int getHeight()
+ {
+ return getCOSStream().getInt( COSName.HEIGHT, -1 );
+ }
+
+ /**
+ * Set the height of the image.
+ *
+ * @param height The height of the image.
+ */
+ public void setHeight( int height )
+ {
+ getCOSStream().setInt( COSName.HEIGHT, height );
+ }
+
+ /**
+ * Get the width of the image.
+ *
+ * @return The width of the image.
+ */
+ public int getWidth()
+ {
+ return getCOSStream().getInt( COSName.WIDTH, -1 );
+ }
+
+ /**
+ * Set the width of the image.
+ *
+ * @param width The width of the image.
+ */
+ public void setWidth( int width )
+ {
+ getCOSStream().setInt( COSName.WIDTH, width );
+ }
+
+ /**
+ * The bits per component of this image. This will return -1 if one has not
+ * been set.
+ *
+ * @return The number of bits per component.
+ */
+ public int getBitsPerComponent()
+ {
+ return getCOSStream().getInt( new String[] { "BPC", "BitsPerComponent"}, -1 );
+ }
+
+ /**
+ * Set the number of bits per component.
+ *
+ * @param bpc The number of bits per component.
+ */
+ public void setBitsPerComponent( int bpc )
+ {
+ getCOSStream().setInt( "BitsPerComponent", bpc );
+ }
+
+ /**
+ * This will get the color space or null if none exists.
+ *
+ * @return The color space for this image.
+ *
+ * @throws IOException If there is an error getting the colorspace.
+ */
+ public PDColorSpace getColorSpace() throws IOException
+ {
+ COSBase cs = getCOSStream().getDictionaryObject( new String[]{ "CS", "ColorSpace" } );
+ PDColorSpace retval = null;
+ if( cs != null )
+ {
+ retval = PDColorSpaceFactory.createColorSpace( cs );
+ if (retval == null)
+ {
+ log.info("About to return NULL from createColorSpace branch");
+ }
+ }
+ else
+ {
+ //there are some cases where the 'required' CS value is not present
+ //but we know that it will be grayscale for a CCITT filter.
+ COSBase filter = getCOSStream().getDictionaryObject( "Filter" );
+ if( COSName.CCITTFAX_DECODE.equals( filter ) ||
+ COSName.CCITTFAX_DECODE_ABBREVIATION.equals( filter ) )
+ {
+ retval = new PDDeviceGray();
+ if (retval == null)
+ {
+ log.info("About to return NULL from CCITT branch");
+ }
+ }
+ else if( COSName.JBIG2_DECODE.equals( filter ) )
+ {
+ retval = new PDDeviceGray();
+ if (retval == null)
+ {
+ log.info("About to return NULL from JBIG2 branch");
+ }
+ }
+ else if (getImageMask())
+ {
+ //Stencil Mask branch. Section 4.8.5 of the reference, page 350 in version 1.7.
+ retval = graphicsState.getNonStrokingColor().getColorSpace();
+ log.info("Stencil Mask branch returning " + retval.toString());
+ //throw new IOException("Trace the Stencil Mask!!!!");
+
+ }
+ else
+ {
+ log.info("About to return NULL from unhandled branch."
+ + " filter = " + filter);
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the color space for this image.
+ *
+ * @param cs The color space for this image.
+ */
+ public void setColorSpace( PDColorSpace cs )
+ {
+ COSBase base = null;
+ if( cs != null )
+ {
+ base = cs.getCOSObject();
+ }
+ getCOSStream().setItem( COSName.COLORSPACE, base );
+ }
+
+ /**
+ * This will get the suffix for this image type, jpg/png.
+ *
+ * @return The image suffix.
+ */
+ public String getSuffix()
+ {
+ return suffix;
+ }
+
+ /**
+ * Get the ImageMask flag. Used in Stencil Masking. Section 4.8.5 of the spec.
+ *
+ * @return The ImageMask flag. This is optional and defaults to False, so if it does not exist, we return False
+ */
+ public boolean getImageMask()
+ {
+ return getCOSStream().getBoolean( COSName.IMAGE_MASK, false );
+ }
+
+ /**
+ * Allow the Invoke operator to set the graphics state so that,
+ * in the case of an Image Mask, we can get to the current nonstroking colorspace.
+ * @param newGS The new graphicstate
+ */
+ public void setGraphicsState(PDGraphicsState newGS)
+ {
+ graphicsState = newGS;
+ }
+
+ /**
+ * Returns the Decode Array of an XObjectImage.
+ * @return the decode array
+ */
+ public COSArray getDecode() {
+ COSBase decode = getCOSStream().getDictionaryObject( COSName.DECODE );
+ if (decode != null && decode instanceof COSArray)
+ {
+ return (COSArray)decode;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the optional mask of a XObjectImage if there is one.
+ *
+ * @return The mask as COSArray otherwise null.
+ */
+ public COSArray getMask()
+ {
+ COSBase mask = getCOSStream().getDictionaryObject(COSName.MASK);
+ if (mask != null)
+ {
+ return (COSArray)mask;
+ }
+ return null;
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package deals with images that are stored in a PDF document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.interactive.action;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionGoTo;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionJavaScript;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionLaunch;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionRemoteGoTo;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionURI;
+
+/**
+ * This class will take a dictionary and determine which type of action to create.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDActionFactory
+{
+ /**
+ * Utility Class.
+ */
+ private PDActionFactory()
+ {
+ //utility class
+ }
+
+ /**
+ * This will create the correct type of action based on the type specified
+ * in the dictionary.
+ *
+ * @param action An action dictionary.
+ *
+ * @return An action of the correct type.
+ */
+ public static PDAction createAction( COSDictionary action )
+ {
+ PDAction retval = null;
+ if( action != null )
+ {
+ String type = action.getNameAsString( "S" );
+ if( PDActionJavaScript.SUB_TYPE.equals( type ) )
+ {
+ retval = new PDActionJavaScript( action );
+ }
+ else if( PDActionGoTo.SUB_TYPE.equals( type ) )
+ {
+ retval = new PDActionGoTo( action );
+ }
+ else if( PDActionLaunch.SUB_TYPE.equals( type ) )
+ {
+ retval = new PDActionLaunch( action );
+ }
+ else if( PDActionRemoteGoTo.SUB_TYPE.equals( type ) )
+ {
+ retval = new PDActionRemoteGoTo( action );
+ }
+ else if( PDActionURI.SUB_TYPE.equals( type ) )
+ {
+ retval = new PDActionURI( action );
+ }
+ }
+ return retval;
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.action;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This represents a dictionary of actions that occur due to events.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDAdditionalActions implements COSObjectable
+{
+ private COSDictionary actions;
+
+ /**
+ * Default constructor.
+ */
+ public PDAdditionalActions()
+ {
+ actions = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The action dictionary.
+ */
+ public PDAdditionalActions( COSDictionary a )
+ {
+ actions = a;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return actions;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return actions;
+ }
+
+ /**
+ * Get the F action.
+ *
+ * @return The F action.
+ */
+ public PDAction getF()
+ {
+ return PDActionFactory.createAction( (COSDictionary)actions.getDictionaryObject("F" ) );
+ }
+
+ /**
+ * Set the F action.
+ *
+ * @param action Get the F action.
+ */
+ public void setF( PDAction action )
+ {
+ actions.setItem( "F", action );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.action;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This class represents an annotation's dictionary of actions
+ * that occur due to events.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.2 $
+ */
+public class PDAnnotationAdditionalActions implements COSObjectable
+{
+ private COSDictionary actions;
+
+ /**
+ * Default constructor.
+ */
+ public PDAnnotationAdditionalActions()
+ {
+ actions = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The action dictionary.
+ */
+ public PDAnnotationAdditionalActions( COSDictionary a )
+ {
+ actions = a;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return actions;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return actions;
+ }
+
+ /**
+ * This will get an action to be performed when the cursor
+ * enters the annotation's active area.
+ *
+ * @return The E entry of annotation's additional actions dictionary.
+ */
+ public PDAction getE()
+ {
+ COSDictionary e = (COSDictionary)actions.getDictionaryObject( "E" );
+ PDAction retval = null;
+ if( e != null )
+ {
+ retval = PDActionFactory.createAction( e );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the cursor
+ * enters the annotation's active area.
+ *
+ * @param e The action to be performed.
+ */
+ public void setE( PDAction e )
+ {
+ actions.setItem( "E", e );
+ }
+
+ /**
+ * This will get an action to be performed when the cursor
+ * exits the annotation's active area.
+ *
+ * @return The X entry of annotation's additional actions dictionary.
+ */
+ public PDAction getX()
+ {
+ COSDictionary x = (COSDictionary)actions.getDictionaryObject( "X" );
+ PDAction retval = null;
+ if( x != null )
+ {
+ retval = PDActionFactory.createAction( x );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the cursor
+ * exits the annotation's active area.
+ *
+ * @param x The action to be performed.
+ */
+ public void setX( PDAction x )
+ {
+ actions.setItem( "X", x );
+ }
+
+ /**
+ * This will get an action to be performed when the mouse button
+ * is pressed inside the annotation's active area.
+ * The name D stands for "down".
+ *
+ * @return The d entry of annotation's additional actions dictionary.
+ */
+ public PDAction getD()
+ {
+ COSDictionary d = (COSDictionary)actions.getDictionaryObject( "D" );
+ PDAction retval = null;
+ if( d != null )
+ {
+ retval = PDActionFactory.createAction( d );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the mouse button
+ * is pressed inside the annotation's active area.
+ * The name D stands for "down".
+ *
+ * @param d The action to be performed.
+ */
+ public void setD( PDAction d )
+ {
+ actions.setItem( "D", d );
+ }
+
+ /**
+ * This will get an action to be performed when the mouse button
+ * is released inside the annotation's active area.
+ * The name U stands for "up".
+ *
+ * @return The U entry of annotation's additional actions dictionary.
+ */
+ public PDAction getU()
+ {
+ COSDictionary u = (COSDictionary)actions.getDictionaryObject( "U" );
+ PDAction retval = null;
+ if( u != null )
+ {
+ retval = PDActionFactory.createAction( u );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the mouse button
+ * is released inside the annotation's active area.
+ * The name U stands for "up".
+ *
+ * @param u The action to be performed.
+ */
+ public void setU( PDAction u )
+ {
+ actions.setItem( "U", u );
+ }
+
+ /**
+ * This will get an action to be performed when the annotation
+ * receives the input focus.
+ *
+ * @return The Fo entry of annotation's additional actions dictionary.
+ */
+ public PDAction getFo()
+ {
+ COSDictionary fo = (COSDictionary)actions.getDictionaryObject( "Fo" );
+ PDAction retval = null;
+ if( fo != null )
+ {
+ retval = PDActionFactory.createAction( fo );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the annotation
+ * receives the input focus.
+ *
+ * @param fo The action to be performed.
+ */
+ public void setFo( PDAction fo )
+ {
+ actions.setItem( "Fo", fo );
+ }
+
+ /**
+ * This will get an action to be performed when the annotation
+ * loses the input focus.
+ * The name Bl stands for "blurred".
+ *
+ * @return The Bl entry of annotation's additional actions dictionary.
+ */
+ public PDAction getBl()
+ {
+ COSDictionary bl = (COSDictionary)actions.getDictionaryObject( "Bl" );
+ PDAction retval = null;
+ if( bl != null )
+ {
+ retval = PDActionFactory.createAction( bl );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the annotation
+ * loses the input focus.
+ * The name Bl stands for "blurred".
+ *
+ * @param bl The action to be performed.
+ */
+ public void setBl( PDAction bl )
+ {
+ actions.setItem( "Bl", bl );
+ }
+
+ /**
+ * This will get an action to be performed when the page containing
+ * the annotation is opened. The action is executed after the O action
+ * in the page's additional actions dictionary and the OpenAction entry
+ * in the document catalog, if such actions are present.
+ *
+ * @return The PO entry of annotation's additional actions dictionary.
+ */
+ public PDAction getPO()
+ {
+ COSDictionary po = (COSDictionary)actions.getDictionaryObject( "PO" );
+ PDAction retval = null;
+ if( po != null )
+ {
+ retval = PDActionFactory.createAction( po );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the page containing
+ * the annotation is opened. The action is executed after the O action
+ * in the page's additional actions dictionary and the OpenAction entry
+ * in the document catalog, if such actions are present.
+ *
+ * @param po The action to be performed.
+ */
+ public void setPO( PDAction po )
+ {
+ actions.setItem( "PO", po );
+ }
+
+ /**
+ * This will get an action to be performed when the page containing
+ * the annotation is closed. The action is executed before the C action
+ * in the page's additional actions dictionary, if present.
+ *
+ * @return The PC entry of annotation's additional actions dictionary.
+ */
+ public PDAction getPC()
+ {
+ COSDictionary pc = (COSDictionary)actions.getDictionaryObject( "PC" );
+ PDAction retval = null;
+ if( pc != null )
+ {
+ retval = PDActionFactory.createAction( pc );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the page containing
+ * the annotation is closed. The action is executed before the C action
+ * in the page's additional actions dictionary, if present.
+ *
+ * @param pc The action to be performed.
+ */
+ public void setPC( PDAction pc )
+ {
+ actions.setItem( "PC", pc );
+ }
+
+ /**
+ * This will get an action to be performed when the page containing
+ * the annotation becomes visible in the viewer application's user interface.
+ *
+ * @return The PV entry of annotation's additional actions dictionary.
+ */
+ public PDAction getPV()
+ {
+ COSDictionary pv = (COSDictionary)actions.getDictionaryObject( "PV" );
+ PDAction retval = null;
+ if( pv != null )
+ {
+ retval = PDActionFactory.createAction( pv );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the page containing
+ * the annotation becomes visible in the viewer application's user interface.
+ *
+ * @param pv The action to be performed.
+ */
+ public void setPV( PDAction pv )
+ {
+ actions.setItem( "PV", pv );
+ }
+
+ /**
+ * This will get an action to be performed when the page containing the annotation
+ * is no longer visible in the viewer application's user interface.
+ *
+ * @return The PI entry of annotation's additional actions dictionary.
+ */
+ public PDAction getPI()
+ {
+ COSDictionary pi = (COSDictionary)actions.getDictionaryObject( "PI" );
+ PDAction retval = null;
+ if( pi != null )
+ {
+ retval = PDActionFactory.createAction( pi );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the page containing the annotation
+ * is no longer visible in the viewer application's user interface.
+ *
+ * @param pi The action to be performed.
+ */
+ public void setPI( PDAction pi )
+ {
+ actions.setItem( "PI", pi );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.action;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This class represents a document catalog's dictionary of actions
+ * that occur due to events.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.2 $
+ */
+public class PDDocumentCatalogAdditionalActions implements COSObjectable
+{
+ private COSDictionary actions;
+
+ /**
+ * Default constructor.
+ */
+ public PDDocumentCatalogAdditionalActions()
+ {
+ actions = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The action dictionary.
+ */
+ public PDDocumentCatalogAdditionalActions( COSDictionary a )
+ {
+ actions = a;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return actions;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return actions;
+ }
+
+ /**
+ * This will get a JavaScript action to be performed
+ * before closing a document.
+ * The name WC stands for "will close".
+ *
+ * @return The WC entry of document catalog's additional actions dictionary.
+ */
+ public PDAction getWC()
+ {
+ COSDictionary wc = (COSDictionary)actions.getDictionaryObject( "WC" );
+ PDAction retval = null;
+ if( wc != null )
+ {
+ retval = PDActionFactory.createAction( wc );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a JavaScript action to be performed
+ * before closing a document.
+ * The name WC stands for "will close".
+ *
+ * @param wc The action to be performed.
+ */
+ public void setWC( PDAction wc )
+ {
+ actions.setItem( "WC", wc );
+ }
+
+ /**
+ * This will get a JavaScript action to be performed
+ * before saving a document.
+ * The name WS stands for "will save".
+ *
+ * @return The WS entry of document catalog's additional actions dictionary.
+ */
+ public PDAction getWS()
+ {
+ COSDictionary ws = (COSDictionary)actions.getDictionaryObject( "WS" );
+ PDAction retval = null;
+ if( ws != null )
+ {
+ retval = PDActionFactory.createAction( ws );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a JavaScript action to be performed
+ * before saving a document.
+ * The name WS stands for "will save".
+ *
+ * @param ws The action to be performed.
+ */
+ public void setWS( PDAction ws )
+ {
+ actions.setItem( "WS", ws );
+ }
+
+ /**
+ * This will get a JavaScript action to be performed
+ * after saving a document.
+ * The name DS stands for "did save".
+ *
+ * @return The DS entry of document catalog's additional actions dictionary.
+ */
+ public PDAction getDS()
+ {
+ COSDictionary ds = (COSDictionary)actions.getDictionaryObject( "DS" );
+ PDAction retval = null;
+ if( ds != null )
+ {
+ retval = PDActionFactory.createAction( ds );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a JavaScript action to be performed
+ * after saving a document.
+ * The name DS stands for "did save".
+ *
+ * @param ds The action to be performed.
+ */
+ public void setDS( PDAction ds )
+ {
+ actions.setItem( "DS", ds );
+ }
+
+ /**
+ * This will get a JavaScript action to be performed
+ * before printing a document.
+ * The name WP stands for "will print".
+ *
+ * @return The WP entry of document catalog's additional actions dictionary.
+ */
+ public PDAction getWP()
+ {
+ COSDictionary wp = (COSDictionary)actions.getDictionaryObject( "WP" );
+ PDAction retval = null;
+ if( wp != null )
+ {
+ retval = PDActionFactory.createAction( wp );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a JavaScript action to be performed
+ * before printing a document.
+ * The name WP stands for "will print".
+ *
+ * @param wp The action to be performed.
+ */
+ public void setWP( PDAction wp )
+ {
+ actions.setItem( "WP", wp );
+ }
+
+ /**
+ * This will get a JavaScript action to be performed
+ * after printing a document.
+ * The name DP stands for "did print".
+ *
+ * @return The DP entry of document catalog's additional actions dictionary.
+ */
+ public PDAction getDP()
+ {
+ COSDictionary dp = (COSDictionary)actions.getDictionaryObject( "DP" );
+ PDAction retval = null;
+ if( dp != null )
+ {
+ retval = PDActionFactory.createAction( dp );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a JavaScript action to be performed
+ * after printing a document.
+ * The name DP stands for "did print".
+ *
+ * @param dp The action to be performed.
+ */
+ public void setDP( PDAction dp )
+ {
+ actions.setItem( "DP", dp );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.action;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This class represents a form field's dictionary of actions
+ * that occur due to events.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.2 $
+ */
+public class PDFormFieldAdditionalActions implements COSObjectable
+{
+ private COSDictionary actions;
+
+ /**
+ * Default constructor.
+ */
+ public PDFormFieldAdditionalActions()
+ {
+ actions = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The action dictionary.
+ */
+ public PDFormFieldAdditionalActions( COSDictionary a )
+ {
+ actions = a;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return actions;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return actions;
+ }
+
+ /**
+ * This will get a JavaScript action to be performed when the user
+ * types a keystroke into a text field or combo box or modifies the
+ * selection in a scrollable list box. This allows the keystroke to
+ * be checked for validity and rejected or modified.
+ *
+ * @return The K entry of form field's additional actions dictionary.
+ */
+ public PDAction getK()
+ {
+ COSDictionary k = (COSDictionary)actions.getDictionaryObject( "K" );
+ PDAction retval = null;
+ if( k != null )
+ {
+ retval = PDActionFactory.createAction( k );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a JavaScript action to be performed when the user
+ * types a keystroke into a text field or combo box or modifies the
+ * selection in a scrollable list box. This allows the keystroke to
+ * be checked for validity and rejected or modified.
+ *
+ * @param k The action to be performed.
+ */
+ public void setK( PDAction k )
+ {
+ actions.setItem( "K", k );
+ }
+
+ /**
+ * This will get a JavaScript action to be performed before
+ * the field is formatted to display its current value. This
+ * allows the field's value to be modified before formatting.
+ *
+ * @return The F entry of form field's additional actions dictionary.
+ */
+ public PDAction getF()
+ {
+ COSDictionary f = (COSDictionary)actions.getDictionaryObject( "F" );
+ PDAction retval = null;
+ if( f != null )
+ {
+ retval = PDActionFactory.createAction( f );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a JavaScript action to be performed before
+ * the field is formatted to display its current value. This
+ * allows the field's value to be modified before formatting.
+ *
+ * @param f The action to be performed.
+ */
+ public void setF( PDAction f )
+ {
+ actions.setItem( "F", f );
+ }
+
+ /**
+ * This will get a JavaScript action to be performed
+ * when the field's value is changed. This allows the
+ * new value to be checked for validity.
+ * The name V stands for "validate".
+ *
+ * @return The V entry of form field's additional actions dictionary.
+ */
+ public PDAction getV()
+ {
+ COSDictionary v = (COSDictionary)actions.getDictionaryObject( "V" );
+ PDAction retval = null;
+ if( v != null )
+ {
+ retval = PDActionFactory.createAction( v );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a JavaScript action to be performed
+ * when the field's value is changed. This allows the
+ * new value to be checked for validity.
+ * The name V stands for "validate".
+ *
+ * @param v The action to be performed.
+ */
+ public void setV( PDAction v )
+ {
+ actions.setItem( "V", v );
+ }
+
+ /**
+ * This will get a JavaScript action to be performed in order to recalculate
+ * the value of this field when that of another field changes. The order in which
+ * the document's fields are recalculated is defined by the CO entry in the
+ * interactive form dictionary.
+ * The name C stands for "calculate".
+ *
+ * @return The C entry of form field's additional actions dictionary.
+ */
+ public PDAction getC()
+ {
+ COSDictionary c = (COSDictionary)actions.getDictionaryObject( "C" );
+ PDAction retval = null;
+ if( c != null )
+ {
+ retval = PDActionFactory.createAction( c );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a JavaScript action to be performed in order to recalculate
+ * the value of this field when that of another field changes. The order in which
+ * the document's fields are recalculated is defined by the CO entry in the
+ * interactive form dictionary.
+ * The name C stands for "calculate".
+ *
+ * @param c The action to be performed.
+ */
+ public void setC( PDAction c )
+ {
+ actions.setItem( "C", c );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.action;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This class represents a page object's dictionary of actions
+ * that occur due to events.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.2 $
+ */
+public class PDPageAdditionalActions implements COSObjectable
+{
+ private COSDictionary actions;
+
+ /**
+ * Default constructor.
+ */
+ public PDPageAdditionalActions()
+ {
+ actions = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The action dictionary.
+ */
+ public PDPageAdditionalActions( COSDictionary a )
+ {
+ actions = a;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return actions;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return actions;
+ }
+
+ /**
+ * This will get an action to be performed when the page
+ * is opened. This action is independent of any that may be
+ * defined by the OpenAction entry in the document catalog,
+ * and is executed after such an action.
+ *
+ * @return The O entry of page object's additional actions dictionary.
+ */
+ public PDAction getO()
+ {
+ COSDictionary o = (COSDictionary)actions.getDictionaryObject( "O" );
+ PDAction retval = null;
+ if( o != null )
+ {
+ retval = PDActionFactory.createAction( o );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the page
+ * is opened. This action is independent of any that may be
+ * defined by the OpenAction entry in the document catalog,
+ * and is executed after such an action.
+ *
+ * @param o The action to be performed.
+ */
+ public void setO( PDAction o )
+ {
+ actions.setItem( "O", o );
+ }
+
+ /**
+ * This will get an action to be performed when the page
+ * is closed. This action applies to the page being closed,
+ * and is executed before any other page opened.
+ *
+ * @return The C entry of page object's additional actions dictionary.
+ */
+ public PDAction getC()
+ {
+ COSDictionary c = (COSDictionary)actions.getDictionaryObject( "C" );
+ PDAction retval = null;
+ if( c != null )
+ {
+ retval = PDActionFactory.createAction( c );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set an action to be performed when the page
+ * is closed. This action applies to the page being closed,
+ * and is executed before any other page opened.
+ *
+ * @param c The action to be performed.
+ */
+ public void setC( PDAction c )
+ {
+ actions.setItem( "C", c );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package represents actions that can be performed in a PDF document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.interactive.action.type;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.PDDestinationOrAction;
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+
+/**
+ * This represents an action that can be executed in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.3 $
+ */
+public abstract class PDAction implements PDDestinationOrAction
+{
+ /**
+ * The type of PDF object.
+ */
+ public static final String TYPE = "Action";
+
+ /**
+ * The action dictionary.
+ */
+ protected COSDictionary action;
+
+ /**
+ * Default constructor.
+ */
+ public PDAction()
+ {
+ action = new COSDictionary();
+ setType( TYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The action dictionary.
+ */
+ public PDAction( COSDictionary a )
+ {
+ action = a;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return action;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return action;
+ }
+
+ /**
+ * This will get the type of PDF object that the actions dictionary describes.
+ * If present must be Action for an action dictionary.
+ *
+ * @return The Type of PDF object.
+ */
+ public String getType()
+ {
+ return action.getNameAsString( "Type" );
+ }
+
+ /**
+ * This will set the type of PDF object that the actions dictionary describes.
+ * If present must be Action for an action dictionary.
+ *
+ * @param type The new Type for the PDF object.
+ */
+ public void setType( String type )
+ {
+ action.setName( "Type", type );
+ }
+
+ /**
+ * This will get the type of action that the actions dictionary describes.
+ * If present, must be Action for an action dictionary.
+ *
+ * @return The S entry of actions dictionary.
+ */
+ public String getSubType()
+ {
+ return action.getNameAsString( "S" );
+ }
+
+ /**
+ * This will set the type of action that the actions dictionary describes.
+ * If present, must be Action for an action dictionary.
+ *
+ * @param s The new type of action.
+ */
+ public void setSubType( String s )
+ {
+ action.setName( "S", s );
+ }
+
+ /**
+ * This will get the next action, or sequence of actions, to be performed after this one.
+ * The value is either a single action dictionary or an array of action dictionaries
+ * to be performed in order.
+ *
+ * @return The Next action or sequence of actions.
+ */
+ public List getNext()
+ {
+ List retval = null;
+ COSBase next = action.getDictionaryObject( "Next" );
+ if( next instanceof COSDictionary )
+ {
+ PDAction pdAction = PDActionFactory.createAction( (COSDictionary) next );
+ retval = new COSArrayList(pdAction, next, action, COSName.getPDFName( "Next" ));
+ }
+ else if( next instanceof COSArray )
+ {
+ COSArray array = (COSArray)next;
+ List actions = new ArrayList();
+ for( int i=0; i<array.size(); i++ )
+ {
+ actions.add( PDActionFactory.createAction( (COSDictionary) array.getObject( i )));
+ }
+ retval = new COSArrayList( actions, array );
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set the next action, or sequence of actions, to be performed after this one.
+ * The value is either a single action dictionary or an array of action dictionaries
+ * to be performed in order.
+ *
+ * @param next The Next action or sequence of actions.
+ */
+ public void setNext( List next )
+ {
+ action.setItem( "Next", COSArrayList.converterToCOSArray( next ) );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.action.type;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+
+/**
+ * This represents a go-to action that can be executed in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.2 $
+ */
+public class PDActionGoTo extends PDAction
+{
+ /**
+ * This type of action this object represents.
+ */
+ public static final String SUB_TYPE = "GoTo";
+
+ /**
+ * Default constructor.
+ */
+ public PDActionGoTo()
+ {
+ super();
+ setSubType( SUB_TYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The action dictionary.
+ */
+ public PDActionGoTo( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * This will get the destination to jump to.
+ *
+ * @return The D entry of the specific go-to action dictionary.
+ *
+ * @throws IOException If there is an error creating the destination.
+ */
+ public PDDestination getDestination() throws IOException
+ {
+ return PDDestination.create( getCOSDictionary().getDictionaryObject( "D" ) );
+ }
+
+ /**
+ * This will set the destination to jump to.
+ *
+ * @param d The destination.
+ */
+ public void setDestination( PDDestination d )
+ {
+ getCOSDictionary().setItem( "D", d );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.action.type;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+
+/**
+ * This represents a JavaScript action.
+ *
+ * @author Michael Schwarzenberger (mi2kee@gmail.com)
+ * @version $Revision: 1.1 $
+ */
+public class PDActionJavaScript extends PDAction
+{
+ /**
+ * This type of action this object represents.
+ */
+ public static final String SUB_TYPE = "JavaScript";
+
+ /**
+ * Constructor #1.
+ */
+ public PDActionJavaScript()
+ {
+ super();
+ setSubType( SUB_TYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param js Some javascript code.
+ */
+ public PDActionJavaScript( String js )
+ {
+ this();
+ setAction( js );
+ }
+
+ /**
+ * Constructor #2.
+ *
+ * @param a The action dictionary.
+ */
+ public PDActionJavaScript(COSDictionary a)
+ {
+ super(a);
+ }
+
+ /**
+ * @param sAction The JavaScript.
+ */
+ public void setAction(PDTextStream sAction)
+ {
+ action.setItem("JS", sAction);
+ }
+
+ /**
+ * @param sAction The JavaScript.
+ */
+ public void setAction(String sAction)
+ {
+ action.setString("JS", sAction);
+ }
+
+ /**
+ * @return The Javascript Code.
+ */
+ public PDTextStream getAction()
+ {
+ return PDTextStream.createTextStream( action.getDictionaryObject("JS") );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.action.type;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * This represents a launch action that can be executed in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.5 $
+ */
+public class PDActionLaunch extends PDAction
+{
+
+ /**
+ * This type of action this object represents.
+ */
+ public static final String SUB_TYPE = "Launch";
+
+ /**
+ * Default constructor.
+ */
+ public PDActionLaunch()
+ {
+ super();
+ setSubType( SUB_TYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The action dictionary.
+ */
+ public PDActionLaunch( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * This will get the application to be launched or the document
+ * to be opened or printed. It is required if none of the entries
+ * Win, Mac or Unix is present. If this entry is absent and the
+ * viewer application does not understand any of the alternative
+ * entries it should do nothing.
+ *
+ * @return The F entry of the specific launch action dictionary.
+ *
+ * @throws IOException If there is an error creating the file spec.
+ */
+ public PDFileSpecification getFile() throws IOException
+ {
+ return PDFileSpecification.createFS( getCOSDictionary().getDictionaryObject( "F" ) );
+ }
+
+ /**
+ * This will set the application to be launched or the document
+ * to be opened or printed. It is required if none of the entries
+ * Win, Mac or Unix is present. If this entry is absent and the
+ * viewer application does not understand any of the alternative
+ * entries it should do nothing.
+ *
+ * @param fs The file specification.
+ */
+ public void setFile( PDFileSpecification fs )
+ {
+ getCOSDictionary().setItem( "F", fs );
+ }
+
+ /**
+ * This will get a dictionary containing Windows-specific launch parameters.
+ *
+ * @return The Win entry of of the specific launch action dictionary.
+ */
+ public PDWindowsLaunchParams getWinLaunchParams()
+ {
+ COSDictionary win = (COSDictionary)action.getDictionaryObject( "Win" );
+ PDWindowsLaunchParams retval = null;
+ if( win != null )
+ {
+ retval = new PDWindowsLaunchParams( win );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set a dictionary containing Windows-specific launch parameters.
+ *
+ * @param win The action to be performed.
+ */
+ public void setWinLaunchParams( PDWindowsLaunchParams win )
+ {
+ action.setItem( "Win", win );
+ }
+
+ /**
+ * This will get the file name to be launched or the document to be opened
+ * or printed, in standard Windows pathname format. If the name string includes
+ * a backslash character (\), the backslash must itself be preceded by a backslash.
+ * This value must be a single string; it is not a file specification.
+ *
+ * @return The F entry of the specific Windows launch parameter dictionary.
+ */
+ public String getF()
+ {
+ return action.getString( "F" );
+ }
+
+ /**
+ * This will set the file name to be launched or the document to be opened
+ * or printed, in standard Windows pathname format. If the name string includes
+ * a backslash character (\), the backslash must itself be preceded by a backslash.
+ * This value must be a single string; it is not a file specification.
+ *
+ * @param f The file name to be launched.
+ */
+ public void setF( String f )
+ {
+ action.setString( "F", f );
+ }
+
+ /**
+ * This will get the string specifying the default directory in standard DOS syntax.
+ *
+ * @return The D entry of the specific Windows launch parameter dictionary.
+ */
+ public String getD()
+ {
+ return action.getString( "D" );
+ }
+
+ /**
+ * This will set the string specifying the default directory in standard DOS syntax.
+ *
+ * @param d The default directory.
+ */
+ public void setD( String d )
+ {
+ action.setString( "D", d );
+ }
+
+ /**
+ * This will get the string specifying the operation to perform:
+ * open to open a document
+ * print to print a document
+ * If the F entry designates an application instead of a document, this entry
+ * is ignored and the application is launched. Default value: open.
+ *
+ * @return The O entry of the specific Windows launch parameter dictionary.
+ */
+ public String getO()
+ {
+ return action.getString( "O" );
+ }
+
+ /**
+ * This will set the string specifying the operation to perform:
+ * open to open a document
+ * print to print a document
+ * If the F entry designates an application instead of a document, this entry
+ * is ignored and the application is launched. Default value: open.
+ *
+ * @param o The operation to perform.
+ */
+ public void setO( String o )
+ {
+ action.setString( "O", o );
+ }
+
+ /**
+ * This will get a parameter string to be passed to the application designated by the F entry.
+ * This entry should be omitted if F designates a document.
+ *
+ * @return The P entry of the specific Windows launch parameter dictionary.
+ */
+ public String getP()
+ {
+ return action.getString( "P" );
+ }
+
+ /**
+ * This will set a parameter string to be passed to the application designated by the F entry.
+ * This entry should be omitted if F designates a document.
+ *
+ * @param p The parameter string.
+ */
+ public void setP( String p )
+ {
+ action.setString( "P", p );
+ }
+
+ /**
+ * This will specify whether to open the destination document in a new window.
+ * If this flag is false, the destination document will replace the current
+ * document in the same window. If this entry is absent, the viewer application
+ * should behave in accordance with the current user preference. This entry is
+ * ignored if the file designated by the F entry is not a PDF document.
+ *
+ * @return A flag specifying whether to open the destination document in a new window.
+ */
+ public boolean shouldOpenInNewWindow()
+ {
+ return action.getBoolean( "NewWindow", true );
+ }
+
+ /**
+ * This will specify the destination document to open in a new window.
+ *
+ * @param value The flag value.
+ */
+ public void setOpenInNewWindow( boolean value )
+ {
+ action.setBoolean( "NewWindow", value );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.action.type;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * This represents a remote go-to action that can be executed in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.4 $
+ */
+public class PDActionRemoteGoTo extends PDAction
+{
+ /**
+ * This type of action this object represents.
+ */
+ public static final String SUB_TYPE = "GoToR";
+
+ /**
+ * Default constructor.
+ */
+ public PDActionRemoteGoTo()
+ {
+ action = new COSDictionary();
+ setSubType( SUB_TYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The action dictionary.
+ */
+ public PDActionRemoteGoTo( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return action;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return action;
+ }
+
+ /**
+ * This will get the type of action that the actions dictionary describes.
+ * It must be GoToR for a remote go-to action.
+ *
+ * @return The S entry of the specific remote go-to action dictionary.
+ */
+ public String getS()
+ {
+ return action.getNameAsString( "S" );
+ }
+
+ /**
+ * This will set the type of action that the actions dictionary describes.
+ * It must be GoToR for a remote go-to action.
+ *
+ * @param s The remote go-to action.
+ */
+ public void setS( String s )
+ {
+ action.setName( "S", s );
+ }
+
+ /**
+ * This will get the file in which the destination is located.
+ *
+ * @return The F entry of the specific remote go-to action dictionary.
+ *
+ * @throws IOException If there is an error creating the file spec.
+ */
+ public PDFileSpecification getFile() throws IOException
+ {
+ return PDFileSpecification.createFS( action.getDictionaryObject( "F" ) );
+ }
+
+ /**
+ * This will set the file in which the destination is located.
+ *
+ * @param fs The file specification.
+ */
+ public void setFile( PDFileSpecification fs )
+ {
+ action.setItem( "F", fs );
+ }
+
+ /**
+ * This will get the destination to jump to.
+ * If the value is an array defining an explicit destination,
+ * its first element must be a page number within the remote
+ * document rather than an indirect reference to a page object
+ * in the current document. The first page is numbered 0.
+ *
+ * @return The D entry of the specific remote go-to action dictionary.
+ */
+
+ // Array or String.
+ public COSBase getD()
+ {
+ return action.getDictionaryObject( "D" );
+ }
+
+ /**
+ * This will set the destination to jump to.
+ * If the value is an array defining an explicit destination,
+ * its first element must be a page number within the remote
+ * document rather than an indirect reference to a page object
+ * in the current document. The first page is numbered 0.
+ *
+ * @param d The destination.
+ */
+
+ // In case the value is an array.
+ public void setD( COSBase d )
+ {
+ action.setItem( "D", d );
+ }
+
+ /**
+ * This will specify whether to open the destination document in a new window.
+ * If this flag is false, the destination document will replace the current
+ * document in the same window. If this entry is absent, the viewer application
+ * should behave in accordance with the current user preference.
+ *
+ * @return A flag specifying whether to open the destination document in a new window.
+ */
+ public boolean shouldOpenInNewWindow()
+ {
+ return action.getBoolean( "NewWindow", true );
+ }
+
+ /**
+ * This will specify the destination document to open in a new window.
+ *
+ * @param value The flag value.
+ */
+ public void setOpenInNewWindow( boolean value )
+ {
+ action.setBoolean( "NewWindow", value );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.action.type;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This represents a URI action that can be executed in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Panagiotis Toumasis (ptoumasis@mail.gr)
+ * @version $Revision: 1.3 $
+ */
+public class PDActionURI extends PDAction
+{
+ /**
+ * This type of action this object represents.
+ */
+ public static final String SUB_TYPE = "URI";
+
+ /**
+ * Default constructor.
+ */
+ public PDActionURI()
+ {
+ action = new COSDictionary();
+ setSubType( SUB_TYPE );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param a The action dictionary.
+ */
+ public PDActionURI( COSDictionary a )
+ {
+ super( a );
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return action;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return action;
+ }
+
+ /**
+ * This will get the type of action that the actions dictionary describes.
+ * It must be URI for a URI action.
+ *
+ * @return The S entry of the specific URI action dictionary.
+ */
+ public String getS()
+ {
+ return action.getNameAsString( "S" );
+ }
+
+ /**
+ * This will set the type of action that the actions dictionary describes.
+ * It must be URI for a URI action.
+ *
+ * @param s The URI action.
+ */
+ public void setS( String s )
+ {
+ action.setName( "S", s );
+ }
+
+ /**
+ * This will get the uniform resource identifier to resolve, encoded in 7-bit ASCII.
+ *
+ * @return The URI entry of the specific URI action dictionary.
+ */
+ public String getURI()
+ {
+ return action.getString( "URI" );
+ }
+
+ /**
+ * This will set the uniform resource identifier to resolve, encoded in 7-bit ASCII.
+ *
+ * @param uri The uniform resource identifier.
+ */
+ public void setURI( String uri )
+ {
+ action.setString( "URI", uri );
+ }
+
+ /**
+ * This will specify whether to track the mouse position when the URI is resolved.
+ * Default value: false.
+ * This entry applies only to actions triggered by the user's clicking an annotation;
+ * it is ignored for actions associated with outline items or with a document's OpenAction entry.
+ *
+ * @return A flag specifying whether to track the mouse position when the URI is resolved.
+ */
+ public boolean shouldTrackMousePosition()
+ {
+ return this.action.getBoolean("IsMap", false);
+ }
+
+ /**
+ * This will specify whether to track the mouse position when the URI is resolved.
+ *
+ * @param value The flag value.
+ */
+ public void setTrackMousePosition( boolean value )
+ {
+ this.action.setBoolean("IsMap", value);
+ }
+
+ // TODO this must go into PDURIDictionary
+ /**
+ * This will get the base URI to be used in resolving relative URI references.
+ * URI actions within the document may specify URIs in partial form, to be interpreted
+ * relative to this base address. If no base URI is specified, such partial URIs
+ * will be interpreted relative to the location of the document itself.
+ * The use of this entry is parallel to that of the body element <BASE>, as described
+ * in the HTML 4.01 Specification.
+ *
+ * @return The URI entry of the specific URI dictionary.
+ * @deprecated use {@link PDURIDictionary#getBase()} instead
+ */
+ public String getBase()
+ {
+ return action.getString( "Base" );
+ }
+
+ // TODO this must go into PDURIDictionary
+ /**
+ * This will set the base URI to be used in resolving relative URI references.
+ * URI actions within the document may specify URIs in partial form, to be interpreted
+ * relative to this base address. If no base URI is specified, such partial URIs
+ * will be interpreted relative to the location of the document itself.
+ * The use of this entry is parallel to that of the body element <BASE>, as described
+ * in the HTML 4.01 Specification.
+ *
+ * @param base The the base URI to be used.
+ * @deprecated use {@link PDURIDictionary#setBase(String)} instead
+ */
+ public void setBase( String base )
+ {
+ action.setString( "Base", base );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.action.type;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This is the implementation of an URI dictionary.
+ *
+ * @version $Revision: 1.0 $
+ *
+ */
+public class PDURIDictionary implements COSObjectable
+{
+
+ private COSDictionary uriDictionary;
+
+ /**
+ * Constructor.
+ *
+ */
+ public PDURIDictionary()
+ {
+ this.uriDictionary = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dictionary the corresponding dictionary
+ */
+ public PDURIDictionary(COSDictionary dictionary)
+ {
+ this.uriDictionary = dictionary;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return this.uriDictionary;
+ }
+
+ /**
+ * Returns the corresponding dictionary.
+ * @return the dictionary
+ */
+ public COSDictionary getDictionary()
+ {
+ return this.uriDictionary;
+ }
+
+ /**
+ * This will get the base URI to be used in resolving relative URI references.
+ * URI actions within the document may specify URIs in partial form, to be interpreted
+ * relative to this base address. If no base URI is specified, such partial URIs
+ * will be interpreted relative to the location of the document itself.
+ * The use of this entry is parallel to that of the body element <BASE>, as described
+ * in the HTML 4.01 Specification.
+ *
+ * @return The URI entry of the specific URI dictionary.
+ */
+ public String getBase()
+ {
+ return this.getDictionary().getString("Base");
+ }
+
+ /**
+ * This will set the base URI to be used in resolving relative URI references.
+ * URI actions within the document may specify URIs in partial form, to be interpreted
+ * relative to this base address. If no base URI is specified, such partial URIs
+ * will be interpreted relative to the location of the document itself.
+ * The use of this entry is parallel to that of the body element <BASE>, as described
+ * in the HTML 4.01 Specification.
+ *
+ * @param base The the base URI to be used.
+ */
+ public void setBase(String base)
+ {
+ this.getDictionary().setString("Base", base);
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.action.type;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * Launch paramaters for the windows OS.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDWindowsLaunchParams implements COSObjectable
+{
+ /**
+ * The open operation for the launch.
+ */
+ public static final String OPERATION_OPEN = "open";
+ /**
+ * The print operation for the lanuch.
+ */
+ public static final String OPERATION_PRINT = "print";
+
+ /**
+ * The params dictionary.
+ */
+ protected COSDictionary params;
+
+ /**
+ * Default constructor.
+ */
+ public PDWindowsLaunchParams()
+ {
+ params = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param p The params dictionary.
+ */
+ public PDWindowsLaunchParams( COSDictionary p )
+ {
+ params = p;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return params;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return params;
+ }
+
+ /**
+ * The file to launch.
+ *
+ * @return The executable/document to launch.
+ */
+ public String getFilename()
+ {
+ return params.getString( "F" );
+ }
+
+ /**
+ * Set the file to launch.
+ *
+ * @param file The executable/document to launch.
+ */
+ public void setFilename( String file )
+ {
+ params.setString( "F", file );
+ }
+
+ /**
+ * The dir to launch from.
+ *
+ * @return The dir of the executable/document to launch.
+ */
+ public String getDirectory()
+ {
+ return params.getString( "D" );
+ }
+
+ /**
+ * Set the dir to launch from.
+ *
+ * @param dir The dir of the executable/document to launch.
+ */
+ public void setDirectory( String dir )
+ {
+ params.setString( "D", dir );
+ }
+
+ /**
+ * Get the operation to perform on the file. This method will not return null,
+ * OPERATION_OPEN is the default.
+ *
+ * @return The operation to perform for the file.
+ * @see PDWindowsLaunchParams#OPERATION_OPEN
+ * @see PDWindowsLaunchParams#OPERATION_PRINT
+ */
+ public String getOperation()
+ {
+ return params.getString( "O", OPERATION_OPEN );
+ }
+
+ /**
+ * Set the operation to perform..
+ *
+ * @param op The operation to perform on the file.
+ */
+ public void setOperation( String op )
+ {
+ params.setString( "D", op );
+ }
+
+ /**
+ * A parameter to pass the executable.
+ *
+ * @return The parameter to pass the executable.
+ */
+ public String getExecuteParam()
+ {
+ return params.getString( "P" );
+ }
+
+ /**
+ * Set the parameter to pass the executable.
+ *
+ * @param param The parameter for the executable.
+ */
+ public void setExecuteParam( String param )
+ {
+ params.setString( "P", param );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains all of the available PDF action types.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+import org.apache.pdfbox.util.BitFlagHelper;
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This class represents a PDF annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.16 $
+ */
+public abstract class PDAnnotation implements COSObjectable
+{
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDAnnotation.class);
+
+ /**
+ * An annotation flag.
+ */
+ public static final int FLAG_INVISIBLE = 1 << 0;
+ /**
+ * An annotation flag.
+ */
+ public static final int FLAG_HIDDEN = 1 << 1;
+ /**
+ * An annotation flag.
+ */
+ public static final int FLAG_PRINTED = 1 << 2;
+ /**
+ * An annotation flag.
+ */
+ public static final int FLAG_NO_ZOOM = 1 << 3;
+ /**
+ * An annotation flag.
+ */
+ public static final int FLAG_NO_ROTATE = 1 << 4;
+ /**
+ * An annotation flag.
+ */
+ public static final int FLAG_NO_VIEW = 1 << 5;
+ /**
+ * An annotation flag.
+ */
+ public static final int FLAG_READ_ONLY = 1 << 6;
+ /**
+ * An annotation flag.
+ */
+ public static final int FLAG_LOCKED = 1 << 7;
+ /**
+ * An annotation flag.
+ */
+ public static final int FLAG_TOGGLE_NO_VIEW = 1 << 8;
+
+
+
+ private COSDictionary dictionary;
+
+ /**
+ * Create the correct annotation from the base COS object.
+ *
+ * @param base The COS object that is the annotation.
+ * @return The correctly typed annotation object.
+ * @throws IOException If there is an error while creating the annotation.
+ */
+ // TODO not yet implemented:
+ // Movie, Screen, PrinterMark, TrapNet, Watermark, 3D, Redact
+ public static PDAnnotation createAnnotation( COSBase base ) throws IOException
+ {
+ PDAnnotation annot = null;
+ if( base instanceof COSDictionary )
+ {
+ COSDictionary annotDic = (COSDictionary)base;
+ String subtype = annotDic.getNameAsString( COSName.SUBTYPE );
+ if( PDAnnotationFileAttachment.SUB_TYPE.equals(subtype) )
+ {
+ annot = new PDAnnotationFileAttachment( annotDic );
+ }
+ else if( PDAnnotationLine.SUB_TYPE.equals(subtype) )
+ {
+ annot = new PDAnnotationLine( annotDic );
+ }
+ else if( PDAnnotationLink.SUB_TYPE.equals(subtype) )
+ {
+ annot = new PDAnnotationLink(annotDic);
+ }
+ else if( PDAnnotationPopup.SUB_TYPE.equals(subtype) )
+ {
+ annot = new PDAnnotationPopup(annotDic);
+ }
+ else if( PDAnnotationRubberStamp.SUB_TYPE.equals(subtype) )
+ {
+ annot = new PDAnnotationRubberStamp(annotDic);
+ }
+ else if( PDAnnotationSquareCircle.SUB_TYPE_SQUARE.equals(subtype) ||
+ PDAnnotationSquareCircle.SUB_TYPE_CIRCLE.equals(subtype) )
+ {
+ annot = new PDAnnotationSquareCircle( annotDic );
+ }
+ else if( PDAnnotationText.SUB_TYPE.equals(subtype) )
+ {
+ annot = new PDAnnotationText( annotDic);
+ }
+ else if( PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT.equals(subtype) ||
+ PDAnnotationTextMarkup.SUB_TYPE_UNDERLINE.equals(subtype) ||
+ PDAnnotationTextMarkup.SUB_TYPE_SQUIGGLY.equals(subtype) ||
+ PDAnnotationTextMarkup.SUB_TYPE_STRIKEOUT.equals(subtype) )
+ {
+ annot = new PDAnnotationTextMarkup( annotDic );
+ }
+ else if( PDAnnotationLink.SUB_TYPE.equals(subtype) )
+ {
+ annot = new PDAnnotationLink( annotDic );
+ }
+ else if( PDAnnotationWidget.SUB_TYPE.equals(subtype) )
+ {
+ annot = new PDAnnotationWidget( annotDic );
+ }
+ else if( PDAnnotationMarkup.SUB_TYPE_FREETEXT.equals(subtype) ||
+ PDAnnotationMarkup.SUB_TYPE_POLYGON.equals(subtype) ||
+ PDAnnotationMarkup.SUB_TYPE_POLYLINE.equals(subtype) ||
+ PDAnnotationMarkup.SUB_TYPE_CARET.equals(subtype) ||
+ PDAnnotationMarkup.SUB_TYPE_INK.equals(subtype) ||
+ PDAnnotationMarkup.SUB_TYPE_SOUND.equals(subtype) )
+ {
+ annot = new PDAnnotationMarkup( annotDic );
+ }
+ else
+ {
+ annot = new PDAnnotationUnknown( annotDic );
+ log.debug("Unknown or unsupported annotation subtype "+subtype);
+ }
+ }
+ else
+ {
+ throw new IOException( "Error: Unknown annotation type " + base );
+ }
+
+ return annot;
+ }
+
+ /**
+ * Constructor.
+ */
+ public PDAnnotation()
+ {
+ dictionary = new COSDictionary();
+ dictionary.setItem( COSName.TYPE, COSName.ANNOT );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dict The annotations dictionary.
+ */
+ public PDAnnotation( COSDictionary dict )
+ {
+ dictionary = dict;
+ }
+
+ /**
+ * returns the dictionary.
+ * @return the dictionary
+ */
+ public COSDictionary getDictionary()
+ {
+ return dictionary;
+ }
+
+ /**
+ * The annotation rectangle, defining the location of the annotation
+ * on the page in default user space units. This is usually required and should
+ * not return null on valid PDF documents. But where this is a parent form field
+ * with children, such as radio button collections then the rectangle will be null.
+ *
+ * @return The Rect value of this annotation.
+ */
+ public PDRectangle getRectangle()
+ {
+ COSArray rectArray = (COSArray)dictionary.getDictionaryObject( COSName.RECT );
+ PDRectangle rectangle = null;
+ if( rectArray != null )
+ {
+ rectangle = new PDRectangle( rectArray );
+ }
+ return rectangle;
+ }
+
+ /**
+ * This will set the rectangle for this annotation.
+ *
+ * @param rectangle The new rectangle values.
+ */
+ public void setRectangle( PDRectangle rectangle )
+ {
+ dictionary.setItem( COSName.RECT, rectangle.getCOSArray() );
+ }
+
+ /**
+ * This will get the flags for this field.
+ *
+ * @return flags The set of flags.
+ */
+ public int getAnnotationFlags()
+ {
+ return getDictionary().getInt( COSName.F, 0 );
+ }
+
+ /**
+ * This will set the flags for this field.
+ *
+ * @param flags The new flags.
+ */
+ public void setAnnotationFlags( int flags )
+ {
+ getDictionary().setInt( COSName.F, flags );
+ }
+
+ /**
+ * Interface method for COSObjectable.
+ *
+ * @return This object as a standard COS object.
+ */
+ public COSBase getCOSObject()
+ {
+ return getDictionary();
+ }
+
+ /**
+ * This will get the name of the current appearance stream if any.
+ *
+ * @return The name of the appearance stream.
+ */
+ public String getAppearanceStream()
+ {
+ String retval = null;
+ COSName name = (COSName)getDictionary().getDictionaryObject( COSName.AS );
+ if( name != null )
+ {
+ retval = name.getName();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the annotations appearance stream name.
+ *
+ * @param as The name of the appearance stream.
+ */
+ public void setAppearanceStream( String as )
+ {
+ if( as == null )
+ {
+ getDictionary().removeItem( COSName.AS );
+ }
+ else
+ {
+ getDictionary().setItem( COSName.AS, COSName.getPDFName( as ) );
+ }
+ }
+
+ /**
+ * This will get the appearance dictionary associated with this annotation.
+ * This may return null.
+ *
+ * @return This annotations appearance.
+ */
+ public PDAppearanceDictionary getAppearance()
+ {
+ PDAppearanceDictionary ap = null;
+ COSDictionary apDic = (COSDictionary)dictionary.getDictionaryObject( COSName.AP );
+ if( apDic != null )
+ {
+ ap = new PDAppearanceDictionary( apDic );
+ }
+ return ap;
+ }
+
+ /**
+ * This will set the appearance associated with this annotation.
+ *
+ * @param appearance The appearance dictionary for this annotation.
+ */
+ public void setAppearance( PDAppearanceDictionary appearance )
+ {
+ COSDictionary ap = null;
+ if( appearance != null )
+ {
+ ap = appearance.getDictionary();
+ }
+ dictionary.setItem( COSName.AP, ap );
+ }
+
+ /**
+ * Get the invisible flag.
+ *
+ * @return The invisible flag.
+ */
+ public boolean isInvisible()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_INVISIBLE );
+ }
+
+ /**
+ * Set the invisible flag.
+ *
+ * @param invisible The new invisible flag.
+ */
+ public void setInvisible( boolean invisible )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_INVISIBLE, invisible );
+ }
+
+ /**
+ * Get the hidden flag.
+ *
+ * @return The hidden flag.
+ */
+ public boolean isHidden()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_HIDDEN );
+ }
+
+ /**
+ * Set the hidden flag.
+ *
+ * @param hidden The new hidden flag.
+ */
+ public void setHidden( boolean hidden )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_HIDDEN, hidden );
+ }
+
+ /**
+ * Get the printed flag.
+ *
+ * @return The printed flag.
+ */
+ public boolean isPrinted()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_PRINTED );
+ }
+
+ /**
+ * Set the printed flag.
+ *
+ * @param printed The new printed flag.
+ */
+ public void setPrinted( boolean printed )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_PRINTED, printed );
+ }
+
+ /**
+ * Get the noZoom flag.
+ *
+ * @return The noZoom flag.
+ */
+ public boolean isNoZoom()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_ZOOM );
+ }
+
+ /**
+ * Set the noZoom flag.
+ *
+ * @param noZoom The new noZoom flag.
+ */
+ public void setNoZoom( boolean noZoom )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_ZOOM, noZoom );
+ }
+
+ /**
+ * Get the noRotate flag.
+ *
+ * @return The noRotate flag.
+ */
+ public boolean isNoRotate()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_ROTATE );
+ }
+
+ /**
+ * Set the noRotate flag.
+ *
+ * @param noRotate The new noRotate flag.
+ */
+ public void setNoRotate( boolean noRotate )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_ROTATE, noRotate );
+ }
+
+ /**
+ * Get the noView flag.
+ *
+ * @return The noView flag.
+ */
+ public boolean isNoView()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_VIEW );
+ }
+
+ /**
+ * Set the noView flag.
+ *
+ * @param noView The new noView flag.
+ */
+ public void setNoView( boolean noView )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_VIEW, noView );
+ }
+
+ /**
+ * Get the readOnly flag.
+ *
+ * @return The readOnly flag.
+ */
+ public boolean isReadOnly()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_READ_ONLY );
+ }
+
+ /**
+ * Set the readOnly flag.
+ *
+ * @param readOnly The new readOnly flag.
+ */
+ public void setReadOnly( boolean readOnly )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_READ_ONLY, readOnly );
+ }
+
+ /**
+ * Get the locked flag.
+ *
+ * @return The locked flag.
+ */
+ public boolean isLocked()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_LOCKED );
+ }
+
+ /**
+ * Set the locked flag.
+ *
+ * @param locked The new locked flag.
+ */
+ public void setLocked( boolean locked )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_LOCKED, locked );
+ }
+
+ /**
+ * Get the toggleNoView flag.
+ *
+ * @return The toggleNoView flag.
+ */
+ public boolean isToggleNoView()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW );
+ }
+
+ /**
+ * Set the toggleNoView flag.
+ *
+ * @param toggleNoView The new toggleNoView flag.
+ */
+ public void setToggleNoView( boolean toggleNoView )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW, toggleNoView );
+ }
+
+ /**
+ * Get the "contents" of the field.
+ *
+ * @return the value of the contents.
+ */
+ public String getContents()
+ {
+ return dictionary.getString(COSName.CONTENTS);
+ }
+
+ /**
+ * Set the "contents" of the field.
+ *
+ * @param value the value of the contents.
+ */
+ public void setContents( String value)
+ {
+ dictionary.setString(COSName.CONTENTS, value);
+ }
+
+ /**
+ * This will retrieve the date and time the annotation was modified.
+ *
+ * @return the modified date/time (often in date format, but can be an arbitary string).
+ */
+ public String getModifiedDate()
+ {
+ return getDictionary().getString( COSName.M );
+ }
+
+ /**
+ * This will set the the date and time the annotation was modified.
+ *
+ * @param m
+ * the date and time the annotation was created.
+ */
+ public void setModifiedDate( String m )
+ {
+ getDictionary().setString( COSName.M, m );
+ }
+
+ /**
+ * This will get the name, a string intended to uniquely identify each annotation
+ * within a page. Not to be confused with some annotations Name entry which
+ * impact the default image drawn for them.
+ *
+ * @return The identifying name for the Annotation.
+ */
+ public String getAnnotationName()
+ {
+ return getDictionary().getString( COSName.NM );
+ }
+
+ /**
+ * This will set the name, a string intended to uniquely identify each annotation
+ * within a page. Not to be confused with some annotations Name entry which
+ * impact the default image drawn for them.
+ *
+ * @param nm The identifying name for the annotation.
+ */
+ public void setAnnotationName( String nm )
+ {
+ getDictionary().setString( COSName.NM, nm );
+ }
+
+ /**
+ * This will set the colour used in drawing various elements.
+ * As of PDF 1.6 these are : Background of icon when closed
+ * Title bar of popup window
+ * Border of a link annotation
+ *
+ * Colour is in DeviceRGB colourspace
+ *
+ * @param c
+ * colour in the DeviceRGB colourspace
+ *
+ */
+ public void setColour( PDGamma c )
+ {
+ getDictionary().setItem( COSName.C, c );
+ }
+
+ /**
+ * This will retrieve the colour used in drawing various elements.
+ * As of PDF 1.6 these are : Background of icon when closed
+ * Title bar of popup window
+ * Border of a link annotation
+ *
+ * Colour is in DeviceRGB colourspace
+ *
+ * @return PDGamma object representing the colour
+ *
+ */
+ public PDGamma getColour()
+ {
+ COSArray c = (COSArray) getDictionary().getItem(COSName.C );
+ if (c != null)
+ {
+ return new PDGamma( c );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * This will retrieve the subtype of the annotation.
+ *
+ * @return the subtype
+ */
+ public String getSubtype()
+ {
+ return this.getDictionary().getNameAsString(COSName.SUBTYPE);
+ }
+
+ /**
+ * This will set the corresponding page for this annotation.
+ *
+ * @param page is the corresponding page
+ */
+ public void setPage(PDPage page)
+ {
+ this.getDictionary().setItem(COSName.P, page);
+ }
+
+ /**
+ * This will retrieve the corresponding page of this annotation.
+ *
+ * @return the corresponding page
+ */
+ public PDPage getPage()
+ {
+ COSDictionary p = (COSDictionary) this.getDictionary().getDictionaryObject(COSName.P);
+ if (p != null)
+ {
+ return new PDPage(p);
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification;
+
+/**
+ * This is the class that represents a file attachement.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDAnnotationFileAttachment extends PDAnnotationMarkup
+{
+ /**
+ * See get/setAttachmentName.
+ */
+ public static final String ATTACHMENT_NAME_PUSH_PIN = "PushPin";
+ /**
+ * See get/setAttachmentName.
+ */
+ public static final String ATTACHMENT_NAME_GRAPH = "Graph";
+ /**
+ * See get/setAttachmentName.
+ */
+ public static final String ATTACHMENT_NAME_PAPERCLIP = "Paperclip";
+ /**
+ * See get/setAttachmentName.
+ */
+ public static final String ATTACHMENT_NAME_TAG = "Tag";
+
+ /**
+ * The type of annotation.
+ */
+ public static final String SUB_TYPE = "FileAttachment";
+
+ /**
+ * Constructor.
+ */
+ public PDAnnotationFileAttachment()
+ {
+ super();
+ getDictionary().setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+ }
+
+ /**
+ * Creates a Link annotation from a COSDictionary, expected to be
+ * a correct object definition.
+ *
+ * @param field the PDF objet to represent as a field.
+ */
+ public PDAnnotationFileAttachment(COSDictionary field)
+ {
+ super( field );
+ }
+
+ /**
+ * Return the attached file.
+ *
+ * @return The attached file.
+ *
+ * @throws IOException If there is an error creating the file spec.
+ */
+ public PDFileSpecification getFile() throws IOException
+ {
+ return PDFileSpecification.createFS( getDictionary().getDictionaryObject( "FS" ) );
+ }
+
+ /**
+ * Set the attached file.
+ *
+ * @param file The file that is attached.
+ */
+ public void setFile( PDFileSpecification file )
+ {
+ getDictionary().setItem( "FS", file );
+ }
+
+ /**
+ * This is the name used to draw the type of attachment.
+ * See the ATTACHMENT_NAME_XXX constants.
+ *
+ * @return The name that describes the visual cue for the attachment.
+ */
+ public String getAttachmentName()
+ {
+ return getDictionary().getNameAsString( "Name", ATTACHMENT_NAME_PUSH_PIN );
+ }
+
+ /**
+ * Set the name used to draw the attachement icon.
+ * See the ATTACHMENT_NAME_XXX constants.
+ *
+ * @param name The name of the visual icon to draw.
+ */
+ public void setAttachementName( String name )
+ {
+ getDictionary().setName( "Name", name );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+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.pdmodel.graphics.color.PDGamma;
+
+/**
+ * This is the class that represents a line annotation.
+ * Introduced in PDF 1.3 specification
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationLine extends PDAnnotationMarkup
+{
+
+
+ /*
+ * The various values for intent (get/setIT, see the PDF 1.6 reference Table
+ * 8.22
+ */
+
+ /**
+ * Constant for annotation intent of Arrow.
+ */
+ public static final String IT_LINE_ARROW = "LineArrow";
+
+ /**
+ * Constant for annotation intent of a dimension line.
+ */
+ public static final String IT_LINE_DIMENSION = "LineDimension";
+
+ /*
+ * The various values for line ending styles, see the PDF 1.6 reference
+ * Table 8.23
+ */
+
+ /**
+ * Constant for a square line ending.
+ */
+ public static final String LE_SQUARE = "Square";
+
+ /**
+ * Constant for a circle line ending.
+ */
+ public static final String LE_CIRCLE = "Circle";
+
+ /**
+ * Constant for a diamond line ending.
+ */
+ public static final String LE_DIAMOND = "Diamond";
+
+ /**
+ * Constant for a open arrow line ending.
+ */
+ public static final String LE_OPEN_ARROW = "OpenArrow";
+
+ /**
+ * Constant for a closed arrow line ending.
+ */
+ public static final String LE_CLOSED_ARROW = "ClosedArrow";
+
+ /**
+ * Constant for no line ending.
+ */
+ public static final String LE_NONE = "None";
+
+ /**
+ * Constant for a butt line ending.
+ */
+ public static final String LE_BUTT = "Butt";
+
+ /**
+ * Constant for a reversed open arrow line ending.
+ */
+ public static final String LE_R_OPEN_ARROW = "ROpenArrow";
+
+ /**
+ * Constant for a revered closed arrow line ending.
+ */
+ public static final String LE_R_CLOSED_ARROW = "RClosedArrow";
+
+ /**
+ * Constant for a slash line ending.
+ */
+ public static final String LE_SLASH = "Slash";
+
+ /**
+ * The type of annotation.
+ */
+ public static final String SUB_TYPE = "Line";
+
+ /**
+ * Constructor.
+ */
+ public PDAnnotationLine()
+ {
+ super();
+ getDictionary().setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+ // Dictionary value L is mandatory, fill in with arbitary value
+ setLine( new float[] { 0, 0, 0, 0 } );
+
+ }
+
+ /**
+ * Creates a Line annotation from a COSDictionary, expected to be a correct
+ * object definition.
+ *
+ * @param field
+ * the PDF object to represent as a field.
+ */
+ public PDAnnotationLine( COSDictionary field )
+ {
+ super( field );
+ }
+
+ /**
+ * This will set start and end coordinates of the line (or leader line if LL
+ * entry is set).
+ *
+ * @param l
+ * array of 4 floats [x1, y1, x2, y2] line start and end points
+ * in default user space.
+ */
+ public void setLine( float[] l )
+ {
+ COSArray newL = new COSArray();
+ newL.setFloatArray( l );
+ getDictionary().setItem( "L", newL );
+ }
+
+ /**
+ * This will retrieve the start and end coordinates of the line (or leader
+ * line if LL entry is set).
+ *
+ * @return array of floats [x1, y1, x2, y2] line start and end points in
+ * default user space.
+ */
+ public float[] getLine()
+ {
+ COSArray l = (COSArray) getDictionary().getDictionaryObject( "L" );
+ return l.toFloatArray();
+ }
+
+ /**
+ * This will set the line ending style for the start point,
+ * see the LE_ constants for the possible values.
+ *
+ * @param style The new style.
+ */
+ public void setStartPointEndingStyle( String style )
+ {
+ if( style == null )
+ {
+ style = LE_NONE;
+ }
+ COSArray array = (COSArray)getDictionary().getDictionaryObject( "LE" );
+ if( array == null )
+ {
+ array = new COSArray();
+ array.add( COSName.getPDFName( style ) );
+ array.add( COSName.getPDFName( LE_NONE ) );
+ getDictionary().setItem( "LE", array );
+ }
+ else
+ {
+ array.setName( 0, style );
+ }
+ }
+
+ /**
+ * This will retrieve the line ending style for the start point,
+ * possible values shown in the LE_ constants section.
+ *
+ * @return The ending style for the start point.
+ */
+ public String getStartPointEndingStyle()
+ {
+ String retval = LE_NONE;
+ COSArray array = (COSArray)getDictionary().getDictionaryObject( "LE" );
+ if( array != null )
+ {
+ retval = array.getName( 0 );
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set the line ending style for the end point,
+ * see the LE_ constants for the possible values.
+ *
+ * @param style The new style.
+ */
+ public void setEndPointEndingStyle( String style )
+ {
+ if( style == null )
+ {
+ style = LE_NONE;
+ }
+ COSArray array = (COSArray)getDictionary().getDictionaryObject( "LE" );
+ if( array == null )
+ {
+ array = new COSArray();
+ array.add( COSName.getPDFName( LE_NONE ) );
+ array.add( COSName.getPDFName( style ) );
+ getDictionary().setItem( "LE", array );
+ }
+ else
+ {
+ array.setName( 1, style );
+ }
+ }
+
+ /**
+ * This will retrieve the line ending style for the end point,
+ * possible values shown in the LE_ constants section.
+ *
+ * @return The ending style for the end point.
+ */
+ public String getEndPointEndingStyle()
+ {
+ String retval = LE_NONE;
+ COSArray array = (COSArray)getDictionary().getDictionaryObject( "LE" );
+ if( array != null )
+ {
+ retval = array.getName( 1 );
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set interior colour of the line endings defined in the LE
+ * entry. Colour is in DeviceRGB colourspace.
+ *
+ * @param ic
+ * colour in the DeviceRGB colourspace.
+ *
+ */
+ public void setInteriorColour( PDGamma ic )
+ {
+ getDictionary().setItem( "IC", ic );
+ }
+
+ /**
+ * This will retrieve the interior colour of the line endings defined in the
+ * LE entry. Colour is in DeviceRGB colourspace.
+ *
+ *
+ * @return PDGamma object representing the colour.
+ *
+ */
+ public PDGamma getInteriorColour()
+ {
+
+ COSArray ic = (COSArray) getDictionary().getDictionaryObject( "IC" );
+ if (ic != null)
+ {
+ return new PDGamma( ic );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * This will set if the contents are shown as a caption to the line.
+ *
+ * @param cap
+ * Boolean value.
+ */
+ public void setCaption( boolean cap )
+ {
+ getDictionary().setBoolean( "Cap", cap );
+ }
+
+ /**
+ * This will retrieve if the contents are shown as a caption or not.
+ *
+ * @return boolean if the content is shown as a caption.
+ */
+ public boolean getCaption()
+ {
+ return getDictionary().getBoolean( "Cap", false );
+ }
+
+ /**
+ * This will set the border style dictionary, specifying the width and dash
+ * pattern used in drawing the line.
+ *
+ * @param bs the border style dictionary to set.
+ *
+ */
+ public void setBorderStyle( PDBorderStyleDictionary bs )
+ {
+ this.getDictionary().setItem( "BS", bs);
+ }
+
+ /**
+ * This will retrieve the border style dictionary, specifying the width and
+ * dash pattern used in drawing the line.
+ *
+ * @return the border style dictionary.
+ */
+ public PDBorderStyleDictionary getBorderStyle()
+ {
+ COSDictionary bs = (COSDictionary) this.getDictionary().getItem(
+ COSName.getPDFName( "BS" ) );
+ if (bs != null)
+ {
+ return new PDBorderStyleDictionary( bs );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * This will retrieve the length of the leader line.
+ *
+ * @return the length of the leader line
+ */
+ public float getLeaderLineLength()
+ {
+ return this.getDictionary().getFloat("LL");
+ }
+
+ /**
+ * This will set the length of the leader line.
+ *
+ * @param leaderLineLength length of the leader line
+ */
+ public void setLeaderLineLength(float leaderLineLength)
+ {
+ this.getDictionary().setFloat("LL", leaderLineLength);
+ }
+
+ /**
+ * This will retrieve the length of the leader line extensions.
+ *
+ * @return the length of the leader line extensions
+ */
+ public float getLeaderLineExtensionLength()
+ {
+ return this.getDictionary().getFloat("LLE");
+ }
+
+ /**
+ * This will set the length of the leader line extensions.
+ *
+ * @param leaderLineExtensionLength length of the leader line extensions
+ */
+ public void setLeaderLineExtensionLength(float leaderLineExtensionLength)
+ {
+ this.getDictionary().setFloat("LLE", leaderLineExtensionLength);
+ }
+
+ /**
+ * This will retrieve the length of the leader line offset.
+ *
+ * @return the length of the leader line offset
+ */
+ public float getLeaderLineOffsetLength()
+ {
+ return this.getDictionary().getFloat("LLO");
+ }
+
+ /**
+ * This will set the length of the leader line offset.
+ *
+ * @param leaderLineOffsetLength length of the leader line offset
+ */
+ public void setLeaderLineOffsetLength(float leaderLineOffsetLength)
+ {
+ this.getDictionary().setFloat("LLO", leaderLineOffsetLength);
+ }
+
+ /**
+ * This will retrieve the caption positioning.
+ *
+ * @return the caption positioning
+ */
+ public String getCaptionPositioning()
+ {
+ return this.getDictionary().getString("CP");
+ }
+
+ /**
+ * This will set the caption positioning.
+ * Allowed values are: "Inline" and "Top"
+ *
+ * @param captionPositioning caption positioning
+ */
+ public void setCaptionPositioning(String captionPositioning)
+ {
+ this.getDictionary().setString("CP", captionPositioning);
+ }
+
+ /**
+ * This will set the horizontal offset of the caption.
+ *
+ * @param offset the horizontal offset of the caption
+ */
+ public void setCaptionHorizontalOffset( float offset )
+ {
+ COSArray array = (COSArray)this.getDictionary().getDictionaryObject( "CO" );
+ if( array == null )
+ {
+ array = new COSArray();
+ array.setFloatArray(new float[] {offset, 0.f});
+ this.getDictionary().setItem( "CO", array );
+ }
+ else
+ {
+ array.set(0, new COSFloat(offset) );
+ }
+ }
+
+ /**
+ * This will retrieve the horizontal offset of the caption.
+ *
+ * @return the the horizontal offset of the caption
+ */
+ public float getCaptionHorizontalOffset()
+ {
+ float retval = 0.f;
+ COSArray array = (COSArray)this.getDictionary().getDictionaryObject( "CO" );
+ if( array != null )
+ {
+ retval = array.toFloatArray()[0];
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set the vertical offset of the caption.
+ *
+ * @param offset vertical offset of the caption
+ */
+ public void setCaptionVerticalOffset( float offset )
+ {
+ COSArray array = (COSArray)this.getDictionary().getDictionaryObject( "CO" );
+ if( array == null )
+ {
+ array = new COSArray();
+ array.setFloatArray(new float[] {0.f, offset});
+ this.getDictionary().setItem( "CO", array );
+ }
+ else
+ {
+ array.set(1, new COSFloat(offset) );
+ }
+ }
+
+ /**
+ * This will retrieve the vertical offset of the caption.
+ *
+ * @return the vertical offset of the caption
+ */
+ public float getCaptionVerticalOffset()
+ {
+ float retval = 0.f;
+ COSArray array = (COSArray)this.getDictionary().getDictionaryObject( "CO" );
+ if( array != null )
+ {
+ retval = array.toFloatArray()[1];
+ }
+ return retval;
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionURI;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+
+/**
+ * This is the class that represents a link annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Paul King
+ * @version $Revision: 1.3 $
+ */
+public class PDAnnotationLink extends PDAnnotation
+{
+
+
+ /**
+ * Constant values of the Text as defined in the PDF 1.6 reference Table 8.19.
+ */
+ public static final String HIGHLIGHT_MODE_NONE = "N";
+ /**
+ * Constant values of the Text as defined in the PDF 1.6 reference Table 8.19.
+ */
+ public static final String HIGHLIGHT_MODE_INVERT = "I";
+ /**
+ * Constant values of the Text as defined in the PDF 1.6 reference Table 8.19.
+ */
+ public static final String HIGHLIGHT_MODE_OUTLINE = "O";
+ /**
+ * Constant values of the Text as defined in the PDF 1.6 reference Table 8.19.
+ */
+ public static final String HIGHLIGHT_MODE_PUSH = "P";
+
+
+ /**
+ * The type of annotation.
+ */
+ public static final String SUB_TYPE = "Link";
+
+ /**
+ * Constructor.
+ */
+ public PDAnnotationLink()
+ {
+ super();
+ getDictionary().setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+ }
+
+ /**
+ * Creates a Link annotation from a COSDictionary, expected to be
+ * a correct object definition.
+ *
+ * @param field the PDF objet to represent as a field.
+ */
+ public PDAnnotationLink(COSDictionary field)
+ {
+ super( field );
+ }
+
+ /**
+ * Get the action to be performed when this annotation is to be activated.
+ *
+ * @return The action to be performed when this annotation is activated.
+ *
+ * TODO not all annotations have an A entry
+ */
+ public PDAction getAction()
+ {
+ COSDictionary action = (COSDictionary)
+ this.getDictionary().getDictionaryObject( COSName.A );
+ return PDActionFactory.createAction( action );
+ }
+
+ /**
+ * Set the annotation action.
+ * As of PDF 1.6 this is only used for Widget Annotations
+ * @param action The annotation action.
+ * TODO not all annotations have an A entry
+ */
+ public void setAction( PDAction action )
+ {
+ this.getDictionary().setItem( COSName.A, action );
+ }
+
+ /**
+ * This will set the border style dictionary, specifying the width and dash
+ * pattern used in drawing the line.
+ *
+ * @param bs the border style dictionary to set.
+ * TODO not all annotations may have a BS entry
+ *
+ */
+ public void setBorderStyle( PDBorderStyleDictionary bs )
+ {
+ this.getDictionary().setItem( "BS", bs);
+ }
+
+ /**
+ * This will retrieve the border style dictionary, specifying the width and
+ * dash pattern used in drawing the line.
+ *
+ * @return the border style dictionary.
+ * TODO not all annotations may have a BS entry
+ */
+ public PDBorderStyleDictionary getBorderStyle()
+ {
+ COSDictionary bs = (COSDictionary) this.getDictionary().getItem(
+ COSName.getPDFName( "BS" ) );
+ if (bs != null)
+ {
+ return new PDBorderStyleDictionary( bs );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Get the destination to be displayed when the annotation is activated. Either
+ * this or the A should be set but not both.
+ *
+ * @return The destination for this annotation.
+ *
+ * @throws IOException If there is an error creating the destination.
+ */
+ public PDDestination getDestination() throws IOException
+ {
+ COSBase base = getDictionary().getDictionaryObject( COSName.DEST );
+ PDDestination retval = PDDestination.create( base );
+
+ return retval;
+ }
+
+ /**
+ * The new destination value.
+ *
+ * @param dest The updated destination.
+ */
+ public void setDestination( PDDestination dest )
+ {
+ getDictionary().setItem( COSName.DEST, dest );
+ }
+
+ /**
+ * Set the highlight mode for when the mouse is depressed.
+ * See the HIGHLIGHT_MODE_XXX constants.
+ *
+ * @return The string representation of the highlight mode.
+ */
+ public String getHighlightMode()
+ {
+ return getDictionary().getNameAsString( COSName.H, HIGHLIGHT_MODE_INVERT );
+ }
+
+ /**
+ * Set the highlight mode. See the HIGHLIGHT_MODE_XXX constants.
+ *
+ * @param mode The new highlight mode.
+ */
+ public void setHighlightMode( String mode )
+ {
+ getDictionary().setName( COSName.H, mode );
+ }
+
+ /**
+ * This will set the previous URI action, in case it
+ * needs to be retrieved at later date.
+ *
+ * @param pa The previous URI.
+ */
+ public void setPreviousURI( PDActionURI pa )
+ {
+ getDictionary().setItem( "PA", pa );
+ }
+
+ /**
+ * This will set the previous URI action, in case it's
+ * needed.
+ *
+ * @return The previous URI.
+ */
+ public PDActionURI getPreviousURI()
+ {
+ COSDictionary pa = (COSDictionary) getDictionary().getDictionaryObject("PA");
+ if ( pa != null )
+ {
+ return new PDActionURI( pa );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * This will set the set of quadpoints which encompass the areas of this
+ * annotation which will activate.
+ *
+ * @param quadPoints
+ * an array representing the set of area covered.
+ */
+ public void setQuadPoints( float[] quadPoints )
+ {
+ COSArray newQuadPoints = new COSArray();
+ newQuadPoints.setFloatArray( quadPoints );
+ getDictionary().setItem( "QuadPoints", newQuadPoints );
+ }
+
+ /**
+ * This will retrieve the set of quadpoints which encompass the areas of
+ * this annotation which will activate.
+ *
+ * @return An array of floats representing the quad points.
+ */
+ public float[] getQuadPoints()
+ {
+ COSArray quadPoints = (COSArray) getDictionary().getDictionaryObject( "QuadPoints" );
+ if (quadPoints != null)
+ {
+ return quadPoints.toFloatArray();
+ }
+ else
+ {
+ return null; // Should never happen as this is a required item
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.PDTextStream;
+import org.apache.pdfbox.cos.COSBase;
+
+import java.io.IOException;
+
+import java.util.Calendar;
+
+/**
+ * This class represents the additonal fields of a Markup type Annotation. See
+ * section 12.5.6 of ISO32000-1:2008 (starting with page 390) for details on
+ * annotation types.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationMarkup extends PDAnnotation
+{
+ /**
+ * Constant for a FreeText type of annotation.
+ */
+ public static final String SUB_TYPE_FREETEXT = "FreeText";
+ /**
+ * Constant for an Polygon type of annotation.
+ */
+ public static final String SUB_TYPE_POLYGON = "Polygon";
+ /**
+ * Constant for an PolyLine type of annotation.
+ */
+ public static final String SUB_TYPE_POLYLINE = "PolyLine";
+ /**
+ * Constant for an Caret type of annotation.
+ */
+ public static final String SUB_TYPE_CARET = "Caret";
+ /**
+ * Constant for an Ink type of annotation.
+ */
+ public static final String SUB_TYPE_INK = "Ink";
+ /**
+ * Constant for an Sound type of annotation.
+ */
+ public static final String SUB_TYPE_SOUND = "Sound";
+
+ /*
+ * The various values of the reply type as defined in the PDF 1.7 reference
+ * Table 170
+ */
+
+ /**
+ * Constant for an annotation reply type.
+ */
+ public static final String RT_REPLY = "R";
+
+ /**
+ * Constant for an annotation reply type.
+ */
+ public static final String RT_GROUP = "Group";
+
+ /**
+ * Constructor.
+ */
+ public PDAnnotationMarkup()
+ {
+ super();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dict
+ * The annotations dictionary.
+ */
+ public PDAnnotationMarkup( COSDictionary dict )
+ {
+ super( dict );
+ }
+
+ /**
+ * Retrieve the string used as the title of the popup window shown when open
+ * and active (by convention this identifies who added the annotation).
+ *
+ * @return The title of the popup.
+ */
+ public String getTitlePopup()
+ {
+ return getDictionary().getString( "T" );
+ }
+
+ /**
+ * Set the string used as the title of the popup window shown when open and
+ * active (by convention this identifies who added the annotation).
+ *
+ * @param t
+ * The title of the popup.
+ */
+ public void setTitlePopup( String t )
+ {
+ getDictionary().setString( "T", t );
+ }
+
+ /**
+ * This will retrieve the popup annotation used for entering/editing the
+ * text for this annotation.
+ *
+ * @return the popup annotation.
+ */
+ public PDAnnotationPopup getPopup()
+ {
+ COSDictionary popup = (COSDictionary) getDictionary().getDictionaryObject( "Popup" );
+ if (popup != null)
+ {
+ return new PDAnnotationPopup( popup );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * This will set the popup annotation used for entering/editing the text for
+ * this annotation.
+ *
+ * @param popup
+ * the popup annotation.
+ */
+ public void setPopup( PDAnnotationPopup popup )
+ {
+ getDictionary().setItem( "Popup", popup );
+ }
+
+ /**
+ * This will retrieve the constant opacity value used when rendering the
+ * annotation (excluing any popup).
+ *
+ * @return the constant opacity value.
+ */
+ public float getConstantOpacity()
+ {
+ return getDictionary().getFloat( "CA", 1 );
+ }
+
+ /**
+ * This will set the constant opacity value used when rendering the
+ * annotation (excluing any popup).
+ *
+ * @param ca
+ * the constant opacity value.
+ */
+ public void setConstantOpacity( float ca )
+ {
+ getDictionary().setFloat( "CA", ca );
+ }
+
+ /**
+ * This will retrieve the rich text stream which is displayed in the popup
+ * window.
+ *
+ * @return the rich text stream.
+ */
+ public PDTextStream getRichContents()
+ {
+ COSBase rc = getDictionary().getDictionaryObject( "RC" );
+ if (rc != null)
+ {
+ return PDTextStream.createTextStream( rc );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * This will set the rich text stream which is displayed in the popup window.
+ *
+ * @param rc
+ * the rich text stream.
+ */
+ public void setRichContents( PDTextStream rc )
+ {
+ getDictionary().setItem( "RC", rc);
+ }
+
+ /**
+ * This will retrieve the date and time the annotation was created.
+ *
+ * @return the creation date/time.
+ * @throws IOException
+ * if there is a format problem when converting the date.
+ */
+ public Calendar getCreationDate() throws IOException
+ {
+ return getDictionary().getDate( "CreationDate" );
+ }
+
+ /**
+ * This will set the the date and time the annotation was created.
+ *
+ * @param creationDate
+ * the date and time the annotation was created.
+ */
+ public void setCreationDate( Calendar creationDate )
+ {
+ getDictionary().setDate( "CreationDate", creationDate );
+ }
+
+ /**
+ * This will retrieve the annotation to which this one is "In Reply To" the
+ * actual relationship is specified by the RT entry.
+ *
+ * @return the other annotation.
+ * @throws IOException
+ * if there is an error with the annotation.
+ */
+ public PDAnnotation getInReplyTo() throws IOException
+ {
+ COSBase irt = getDictionary().getDictionaryObject( "IRT" );
+ return PDAnnotation.createAnnotation( irt );
+ }
+
+ /**
+ * This will set the annotation to which this one is "In Reply To" the
+ * actual relationship is specified by the RT entry.
+ *
+ * @param irt the annotation this one is "In Reply To".
+ */
+ public void setInReplyTo( PDAnnotation irt )
+ {
+ getDictionary().setItem( "IRT", irt );
+ }
+
+ /**
+ * This will retrieve the short description of the subject of the annotation.
+ *
+ * @return the subject.
+ */
+ public String getSubject()
+ {
+ return getDictionary().getString( "Subj" );
+ }
+
+ /**
+ * This will set the short description of the subject of the annotation.
+ *
+ * @param subj short description of the subject.
+ */
+ public void setSubject( String subj )
+ {
+ getDictionary().setString( "Subj", subj );
+ }
+
+ /**
+ * This will retrieve the Reply Type (relationship) with the annotation in
+ * the IRT entry See the RT_* constants for the available values.
+ *
+ * @return the relationship.
+ */
+ public String getReplyType()
+ {
+ return getDictionary().getNameAsString( "RT", RT_REPLY );
+ }
+
+ /**
+ * This will set the Reply Type (relationship) with the annotation in the
+ * IRT entry See the RT_* constants for the available values.
+ *
+ * @param rt the reply type.
+ */
+ public void setReplyType( String rt )
+ {
+ getDictionary().setName( "RT", rt );
+ }
+
+ /**
+ * This will retrieve the intent of the annotation The values and meanings
+ * are specific to the actual annotation See the IT_* constants for the
+ * annotation classes.
+ *
+ * @return the intent
+ */
+ public String getIntent()
+ {
+ return getDictionary().getNameAsString( "IT" );
+ }
+
+ /**
+ * This will set the intent of the annotation The values and meanings are
+ * specific to the actual annotation See the IT_* constants for the
+ * annotation classes.
+ *
+ * @param it the intent
+ */
+ public void setIntent( String it )
+ {
+ getDictionary().setName( "IT", it );
+ }
+
+ /**
+ * This will return the external data dictionary.
+ *
+ * @return the external data dictionary
+ */
+ public PDExternalDataDictionary getExternalData()
+ {
+ COSBase exData = this.getDictionary().getDictionaryObject("ExData");
+ if (exData instanceof COSDictionary)
+ {
+ return new PDExternalDataDictionary((COSDictionary) exData);
+ }
+ return null;
+ }
+
+ /**
+ * This will set the external data dictionary.
+ *
+ * @param externalData the external data dictionary
+ */
+ public void setExternalData(PDExternalDataDictionary externalData)
+ {
+ this.getDictionary().setItem("ExData", externalData);
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is the class that represents a popup annotation.
+ * Introduced in PDF 1.3 specification
+ *
+ * @author Paul King
+ * @version $Revision: 1.2 $
+ */
+public class PDAnnotationPopup extends PDAnnotation
+{
+
+ /**
+ * The type of annotation.
+ */
+ public static final String SUB_TYPE = "Popup";
+
+ /**
+ * Constructor.
+ */
+ public PDAnnotationPopup()
+ {
+ super();
+ getDictionary()
+ .setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+ }
+
+ /**
+ * Creates a popup annotation from a COSDictionary, expected to be a correct
+ * object definition.
+ *
+ * @param field
+ * the PDF objet to represent as a field.
+ */
+ public PDAnnotationPopup( COSDictionary field )
+ {
+ super( field );
+ }
+
+ /**
+ * This will set inital state of the annotation, open or closed.
+ *
+ * @param open
+ * Boolean value, true = open false = closed.
+ */
+ public void setOpen( boolean open )
+ {
+ getDictionary().setBoolean( "Open" , open );
+ }
+
+ /**
+ * This will retrieve the initial state of the annotation, open Or closed
+ * (default closed).
+ *
+ * @return The initial state, true = open false = closed.
+ */
+ public boolean getOpen()
+ {
+ return getDictionary().getBoolean( "Open" , false );
+ }
+
+ /**
+ * This will set the markup annotation which this popup relates to.
+ *
+ * @param annot
+ * the markup annotation.
+ */
+ public void setParent( PDAnnotationMarkup annot )
+ {
+ getDictionary().setItem( COSName.PARENT, annot.getDictionary() );
+ }
+
+ /**
+ * This will retrieve the markup annotation which this popup relates to.
+ *
+ * @return The parent markup annotation.
+ */
+ public PDAnnotationMarkup getParent()
+ {
+ PDAnnotationMarkup am = null;
+ try
+ {
+ am = (PDAnnotationMarkup)
+ PDAnnotation.createAnnotation( getDictionary().getDictionaryObject( "Parent", "P" ) );
+ }
+ catch (IOException ioe)
+ {
+ // Couldn't construct the annotation, so return null i.e. do nothing
+ }
+ return am;
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is the class that represents a rubber stamp annotation.
+ * Introduced in PDF 1.3 specification
+ *
+ * @author Paul King
+ * @version $Revision: 1.2 $
+ */
+public class PDAnnotationRubberStamp extends PDAnnotationMarkup
+{
+
+ /*
+ * The various values of the rubber stamp as defined in
+ * the PDF 1.6 reference Table 8.28
+ */
+
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_APPROVED = "Approved";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_EXPERIMENTAL = "Experimental";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_NOT_APPROVED = "NotApproved";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_AS_IS = "AsIs";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_EXPIRED = "Expired";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_NOT_FOR_PUBLIC_RELEASE = "NotForPublicRelease";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_FOR_PUBLIC_RELEASE = "ForPublicRelease";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_DRAFT = "Draft";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_FOR_COMMENT = "ForComment";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_TOP_SECRET = "TopSecret";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_DEPARTMENTAL = "Departmental";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_CONFIDENTIAL = "Confidential";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_FINAL = "Final";
+ /**
+ * Constant for the name of a rubber stamp.
+ */
+ public static final String NAME_SOLD = "Sold";
+
+ /**
+ * The type of annotation.
+ */
+ public static final String SUB_TYPE = "Stamp";
+
+ /**
+ * Constructor.
+ */
+ public PDAnnotationRubberStamp()
+ {
+ super();
+ getDictionary().setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+ }
+
+ /**
+ * Creates a Rubber Stamp annotation from a COSDictionary, expected to be
+ * a correct object definition.
+ *
+ * @param field the PDF objet to represent as a field.
+ */
+ public PDAnnotationRubberStamp(COSDictionary field)
+ {
+ super( field );
+ }
+
+ /**
+ * This will set the name (and hence appearance, AP taking precedence)
+ * For this annotation. See the NAME_XXX constants for valid values.
+ *
+ * @param name The name of the rubber stamp.
+ */
+ public void setName( String name )
+ {
+ getDictionary().setName(COSName.NAME, name);
+ }
+
+ /**
+ * This will retrieve the name (and hence appearance, AP taking precedence)
+ * For this annotation. The default is DRAFT.
+ *
+ * @return The name of this rubber stamp, see the NAME_XXX constants.
+ */
+ public String getName()
+ {
+ return getDictionary().getNameAsString(COSName.NAME, NAME_DRAFT);
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This is the class that represents a rectangular or eliptical annotation
+ * Introduced in PDF 1.3 specification .
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationSquareCircle extends PDAnnotationMarkup
+{
+
+ /**
+ * Constant for a Rectangular type of annotation.
+ */
+ public static final String SUB_TYPE_SQUARE = "Square";
+ /**
+ * Constant for an Eliptical type of annotation.
+ */
+ public static final String SUB_TYPE_CIRCLE = "Circle";
+
+ /**
+ * Creates a Circle or Square annotation of the specified sub type.
+ *
+ * @param subType the subtype the annotation represents.
+ */
+ public PDAnnotationSquareCircle( String subType )
+ {
+ super();
+ setSubtype( subType );
+ }
+
+ /**
+ * Creates a Line annotation from a COSDictionary, expected to be a correct
+ * object definition.
+ *
+ * @param field
+ * the PDF objet to represent as a field.
+ */
+ public PDAnnotationSquareCircle( COSDictionary field )
+ {
+ super( field );
+ }
+
+
+ /**
+ * This will set interior colour of the drawn area
+ * Colour is in DeviceRGB colourspace.
+ *
+ * @param ic
+ * colour in the DeviceRGB colourspace.
+ *
+ */
+ public void setInteriorColour( PDGamma ic )
+ {
+ getDictionary().setItem( "IC", ic );
+ }
+
+ /**
+ * This will retrieve the interior colour of the drawn area
+ * Colour is in DeviceRGB colourspace.
+ *
+ *
+ * @return PDGamma object representing the colour.
+ *
+ */
+ public PDGamma getInteriorColour()
+ {
+
+ COSArray ic = (COSArray) getDictionary().getItem(
+ COSName.getPDFName( "IC" ) );
+ if (ic != null)
+ {
+ return new PDGamma( ic );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+
+ /**
+ * This will set the border effect dictionary, specifying effects to be applied
+ * when drawing the line.
+ *
+ * @param be The border effect dictionary to set.
+ *
+ */
+ public void setBorderEffect( PDBorderEffectDictionary be )
+ {
+ getDictionary().setItem( "BE", be );
+ }
+
+ /**
+ * This will retrieve the border effect dictionary, specifying effects to be
+ * applied used in drawing the line.
+ *
+ * @return The border effect dictionary
+ */
+ public PDBorderEffectDictionary getBorderEffect()
+ {
+ COSDictionary be = (COSDictionary) getDictionary().getDictionaryObject( "BE" );
+ if (be != null)
+ {
+ return new PDBorderEffectDictionary( be );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * This will set the rectangle difference rectangle. Giving the difference
+ * between the annotations rectangle and where the drawing occurs.
+ * (To take account of any effects applied through the BE entry forexample)
+ *
+ * @param rd the rectangle difference
+ *
+ */
+ public void setRectDifference( PDRectangle rd )
+ {
+ getDictionary().setItem( "RD", rd );
+ }
+
+ /**
+ * This will get the rectangle difference rectangle. Giving the difference
+ * between the annotations rectangle and where the drawing occurs.
+ * (To take account of any effects applied through the BE entry forexample)
+ *
+ * @return the rectangle difference
+ */
+ public PDRectangle getRectDifference()
+ {
+ COSArray rd = (COSArray) getDictionary().getDictionaryObject( "RD" );
+ if (rd != null)
+ {
+ return new PDRectangle( rd );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * This will set the sub type (and hence appearance, AP taking precedence) For
+ * this annotation. See the SUB_TYPE_XXX constants for valid values.
+ *
+ * @param subType The subtype of the annotation
+ */
+ public void setSubtype( String subType )
+ {
+ getDictionary().setName( COSName.SUBTYPE, subType );
+ }
+
+ /**
+ * This will retrieve the sub type (and hence appearance, AP taking precedence)
+ * For this annotation.
+ *
+ * @return The subtype of this annotation, see the SUB_TYPE_XXX constants.
+ */
+ public String getSubtype()
+ {
+ return getDictionary().getNameAsString( COSName.SUBTYPE);
+ }
+
+ /**
+ * This will set the border style dictionary, specifying the width and dash
+ * pattern used in drawing the line.
+ *
+ * @param bs the border style dictionary to set.
+ * TODO not all annotations may have a BS entry
+ *
+ */
+ public void setBorderStyle( PDBorderStyleDictionary bs )
+ {
+ this.getDictionary().setItem( "BS", bs);
+ }
+
+ /**
+ * This will retrieve the border style dictionary, specifying the width and
+ * dash pattern used in drawing the line.
+ *
+ * @return the border style dictionary.
+ * TODO not all annotations may have a BS entry
+ */
+ public PDBorderStyleDictionary getBorderStyle()
+ {
+ COSDictionary bs = (COSDictionary) this.getDictionary().getItem(
+ COSName.getPDFName( "BS" ) );
+ if (bs != null)
+ {
+ return new PDBorderStyleDictionary( bs );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is the class that represents a text annotation.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationText extends PDAnnotationMarkup
+{
+
+ /*
+ * The various values of the Text as defined in the PDF 1.7 reference Table
+ * 172
+ */
+
+ /**
+ * Constant for the name of a text annotation.
+ */
+ public static final String NAME_COMMENT = "Comment";
+
+ /**
+ * Constant for the name of a text annotation.
+ */
+ public static final String NAME_KEY = "Key";
+
+ /**
+ * Constant for the name of a text annotation.
+ */
+ public static final String NAME_NOTE = "Note";
+
+ /**
+ * Constant for the name of a text annotation.
+ */
+ public static final String NAME_HELP = "Help";
+
+ /**
+ * Constant for the name of a text annotation.
+ */
+ public static final String NAME_NEW_PARAGRAPH = "NewParagraph";
+
+ /**
+ * Constant for the name of a text annotation.
+ */
+ public static final String NAME_PARAGRAPH = "Paragraph";
+
+ /**
+ * Constant for the name of a text annotation.
+ */
+ public static final String NAME_INSERT = "Insert";
+
+ /**
+ * The type of annotation.
+ */
+ public static final String SUB_TYPE = "Text";
+
+ /**
+ * Constructor.
+ */
+ public PDAnnotationText()
+ {
+ super();
+ getDictionary()
+ .setItem( COSName.SUBTYPE, COSName.getPDFName( SUB_TYPE ) );
+ }
+
+ /**
+ * Creates a Text annotation from a COSDictionary, expected to be a correct
+ * object definition.
+ *
+ * @param field
+ * the PDF object to represent as a field.
+ */
+ public PDAnnotationText( COSDictionary field )
+ {
+ super( field );
+ }
+
+ /**
+ * This will set initial state of the annotation, open or closed.
+ *
+ * @param open
+ * Boolean value, true = open false = closed
+ */
+ public void setOpen( boolean open )
+ {
+ getDictionary().setBoolean( COSName.getPDFName( "Open" ), open );
+ }
+
+ /**
+ * This will retrieve the initial state of the annotation, open Or closed
+ * (default closed).
+ *
+ * @return The initial state, true = open false = closed
+ */
+ public boolean getOpen()
+ {
+ return getDictionary().getBoolean( COSName.getPDFName( "Open" ), false );
+ }
+
+ /**
+ * This will set the name (and hence appearance, AP taking precedence) For
+ * this annotation. See the NAME_XXX constants for valid values.
+ *
+ * @param name
+ * The name of the annotation
+ */
+ public void setName( String name )
+ {
+ getDictionary().setName( COSName.NAME, name );
+ }
+
+ /**
+ * This will retrieve the name (and hence appearance, AP taking precedence)
+ * For this annotation. The default is NOTE.
+ *
+ * @return The name of this annotation, see the NAME_XXX constants.
+ */
+ public String getName()
+ {
+ return getDictionary().getNameAsString( COSName.NAME, NAME_NOTE );
+ }
+
+ /**
+ * This will retrieve the annotation state.
+ *
+ * @return the annotation state
+ */
+ public String getState()
+ {
+ return this.getDictionary().getString("State");
+ }
+
+ /**
+ * This will set the annotation state.
+ *
+ * @param state the annotation state
+ */
+ public void setState(String state)
+ {
+ this.getDictionary().setString("State", state);
+ }
+
+ /**
+ * This will retrieve the annotation state model.
+ *
+ * @return the annotation state model
+ */
+ public String getStateModel()
+ {
+ return this.getDictionary().getString("StateModel");
+ }
+
+ /**
+ * This will set the annotation state model.
+ * Allowed values are "Marked" and "Review"
+ *
+ * @param stateModel the annotation state model
+ */
+ public void setStateModel(String stateModel)
+ {
+ this.getDictionary().setString("StateModel", stateModel);
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This is the abstract class that represents a text markup annotation
+ * Introduced in PDF 1.3 specification, except Squiggly lines in 1.4.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationTextMarkup extends PDAnnotationMarkup
+{
+
+ /**
+ * The types of annotation.
+ */
+ public static final String SUB_TYPE_HIGHLIGHT = "Highlight";
+ /**
+ * The types of annotation.
+ */
+ public static final String SUB_TYPE_UNDERLINE = "Underline";
+ /**
+ * The types of annotation.
+ */
+ public static final String SUB_TYPE_SQUIGGLY = "Squiggly";
+ /**
+ * The types of annotation.
+ */
+ public static final String SUB_TYPE_STRIKEOUT = "StrikeOut";
+
+
+ private PDAnnotationTextMarkup()
+ {
+ // Must be constructed with a subType or dictionary parameter
+ }
+
+ /**
+ * Creates a TextMarkup annotation of the specified sub type.
+ *
+ * @param subType the subtype the annotation represents
+ */
+ public PDAnnotationTextMarkup(String subType)
+ {
+ super();
+ setSubtype( subType );
+
+ // Quad points are required, set and empty array
+ setQuadPoints( new float[0] );
+ }
+
+ /**
+ * Creates a TextMarkup annotation from a COSDictionary, expected to be a
+ * correct object definition.
+ *
+ * @param field the PDF objet to represent as a field.
+ */
+ public PDAnnotationTextMarkup( COSDictionary field )
+ {
+ super( field );
+ }
+
+ /**
+ * This will set the set of quadpoints which encompass the areas of this
+ * annotation.
+ *
+ * @param quadPoints
+ * an array representing the set of area covered
+ */
+ public void setQuadPoints( float[] quadPoints )
+ {
+ COSArray newQuadPoints = new COSArray();
+ newQuadPoints.setFloatArray( quadPoints );
+ getDictionary().setItem( "QuadPoints", newQuadPoints );
+ }
+
+ /**
+ * This will retrieve the set of quadpoints which encompass the areas of
+ * this annotation.
+ *
+ * @return An array of floats representing the quad points.
+ */
+ public float[] getQuadPoints()
+ {
+ COSArray quadPoints = (COSArray) getDictionary().getDictionaryObject( "QuadPoints" );
+ if (quadPoints != null)
+ {
+ return quadPoints.toFloatArray();
+ }
+ else
+ {
+ return null; // Should never happen as this is a required item
+ }
+ }
+
+ /**
+ * This will set the sub type (and hence appearance, AP taking precedence) For
+ * this annotation. See the SUB_TYPE_XXX constants for valid values.
+ *
+ * @param subType The subtype of the annotation
+ */
+ public void setSubtype( String subType )
+ {
+ getDictionary().setName( COSName.SUBTYPE, subType );
+ }
+
+ /**
+ * This will retrieve the sub type (and hence appearance, AP taking precedence)
+ * For this annotation.
+ *
+ * @return The subtype of this annotation, see the SUB_TYPE_XXX constants.
+ */
+ public String getSubtype()
+ {
+ return getDictionary().getNameAsString( COSName.SUBTYPE);
+ }
+
+
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This is the class that represents an arbitary Unknown Annotation type.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDAnnotationUnknown extends PDAnnotation
+{
+
+ /**
+ * Creates an arbitary annotation from a COSDictionary, expected to be
+ * a correct object definition for some sort of annotation.
+ *
+ * @param dic The dictionary which represents this Annotation.
+ */
+ public PDAnnotationUnknown(COSDictionary dic)
+ {
+ super( dic );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+import org.apache.pdfbox.pdmodel.interactive.action.PDAnnotationAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+
+/**
+ * This is the class that represents a widget.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDAnnotationWidget extends PDAnnotation
+{
+ /**
+ * The type of annotation.
+ */
+ public static final String SUB_TYPE = "Widget";
+
+
+ /**
+ * Constructor.
+ */
+ public PDAnnotationWidget()
+ {
+ super();
+ getDictionary().setName( COSName.SUBTYPE, SUB_TYPE);
+ }
+
+
+ /**
+ * Creates a PDWidget from a COSDictionary, expected to be
+ * a correct object definition for a field in PDF.
+ *
+ * @param field the PDF objet to represent as a field.
+ */
+ public PDAnnotationWidget(COSDictionary field)
+ {
+ super( field );
+ }
+
+ /**
+ * Returns the highlighting mode. Default value: <code>I</code>
+ * <dl>
+ * <dt><code>N</code></dt>
+ * <dd>(None) No highlighting.</dd>
+ * <dt><code>I</code></dt>
+ * <dd>(Invert) Invert the contents of the annotation rectangle.</dd>
+ * <dt><code>O</code></dt>
+ * <dd>(Outline) Invert the annotation's border.</dd>
+ * <dt><code>P</code></dt>
+ * <dd>(Push) Display the annotation's down appearance, if any. If no
+ * down appearance is defined, the contents of the annotation rectangle
+ * shall be offset to appear as if it were pushed below the surface of
+ * the page</dd>
+ * <dt><code>T</code></dt>
+ * <dd>(Toggle) Same as <code>P</code> (which is preferred).</dd>
+ * </dl>
+ *
+ * @return the highlighting mode
+ */
+ public String getHighlightingMode()
+ {
+ return this.getDictionary().getNameAsString(COSName.H, "I");
+ }
+
+ /**
+ * Sets the highlighting mode.
+ * <dl>
+ * <dt><code>N</code></dt>
+ * <dd>(None) No highlighting.</dd>
+ * <dt><code>I</code></dt>
+ * <dd>(Invert) Invert the contents of the annotation rectangle.</dd>
+ * <dt><code>O</code></dt>
+ * <dd>(Outline) Invert the annotation's border.</dd>
+ * <dt><code>P</code></dt>
+ * <dd>(Push) Display the annotation's down appearance, if any. If no
+ * down appearance is defined, the contents of the annotation rectangle
+ * shall be offset to appear as if it were pushed below the surface of
+ * the page</dd>
+ * <dt><code>T</code></dt>
+ * <dd>(Toggle) Same as <code>P</code> (which is preferred).</dd>
+ * </dl>
+ *
+ * @param highlightingMode the highlighting mode
+ * the defined values
+ */
+ public void setHighlightingMode(String highlightingMode)
+ {
+ if ((highlightingMode == null)
+ || "N".equals(highlightingMode) || "I".equals(highlightingMode)
+ || "O".equals(highlightingMode) || "P".equals(highlightingMode)
+ || "T".equals(highlightingMode))
+ {
+ this.getDictionary().setName(COSName.H, highlightingMode);
+ }
+ else
+ {
+ throw new IllegalArgumentException( "Valid values for highlighting mode are " +
+ "'N', 'N', 'O', 'P' or 'T'" );
+ }
+ }
+
+ /**
+ * Returns the appearance characteristics dictionary.
+ *
+ * @return the appearance characteristics dictionary
+ */
+ public PDAppearanceCharacteristicsDictionary getAppearanceCharacteristics()
+ {
+ COSBase mk = this.getDictionary().getDictionaryObject(COSName.getPDFName("MK"));
+ if (mk instanceof COSDictionary)
+ {
+ return new PDAppearanceCharacteristicsDictionary((COSDictionary) mk);
+ }
+ return null;
+ }
+
+ /**
+ * Sets the appearance characteristics dictionary.
+ *
+ * @param appearanceCharacteristics the appearance characteristics dictionary
+ */
+ public void setAppearanceCharacteristics(PDAppearanceCharacteristicsDictionary appearanceCharacteristics)
+ {
+ this.getDictionary().setItem("MK", appearanceCharacteristics);
+ }
+
+ /**
+ * Get the action to be performed when this annotation is to be activated.
+ *
+ * @return The action to be performed when this annotation is activated.
+ */
+ public PDAction getAction()
+ {
+ COSDictionary action = (COSDictionary)
+ this.getDictionary().getDictionaryObject( COSName.A );
+ return PDActionFactory.createAction( action );
+ }
+
+ /**
+ * Set the annotation action.
+ * As of PDF 1.6 this is only used for Widget Annotations
+ * @param action The annotation action.
+ */
+ public void setAction( PDAction action )
+ {
+ this.getDictionary().setItem( COSName.A, action );
+ }
+
+ /**
+ * Get the additional actions for this field. This will return null
+ * if there are no additional actions for this field.
+ * As of PDF 1.6 this is only used for Widget Annotations.
+ *
+ * @return The actions of the field.
+ */
+ public PDAnnotationAdditionalActions getActions()
+ {
+ COSDictionary aa = (COSDictionary)this.getDictionary().getDictionaryObject( "AA" );
+ PDAnnotationAdditionalActions retval = null;
+ if( aa != null )
+ {
+ retval = new PDAnnotationAdditionalActions( aa );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the actions of the field.
+ *
+ * @param actions The field actions.
+ */
+ public void setActions( PDAnnotationAdditionalActions actions )
+ {
+ this.getDictionary().setItem( "AA", actions );
+ }
+
+ /**
+ * This will set the border style dictionary, specifying the width and dash
+ * pattern used in drawing the line.
+ *
+ * @param bs the border style dictionary to set.
+ *
+ */
+ public void setBorderStyle( PDBorderStyleDictionary bs )
+ {
+ this.getDictionary().setItem( "BS", bs);
+ }
+
+ /**
+ * This will retrieve the border style dictionary, specifying the width and
+ * dash pattern used in drawing the line.
+ *
+ * @return the border style dictionary.
+ */
+ public PDBorderStyleDictionary getBorderStyle()
+ {
+ COSDictionary bs = (COSDictionary) this.getDictionary().getItem(
+ COSName.getPDFName( "BS" ) );
+ if (bs != null)
+ {
+ return new PDBorderStyleDictionary( bs );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ // TODO where to get acroForm from?
+// public PDField getParent() throws IOException
+// {
+// COSBase parent = this.getDictionary().getDictionaryObject(COSName.PARENT);
+// if (parent instanceof COSDictionary)
+// {
+// PDAcroForm acroForm = null;
+// return PDFieldFactory.createField(acroForm, (COSDictionary) parent);
+// }
+// return null;
+// }
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+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.COSStream;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.color.PDGamma;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+
+/**
+ * This class represents an appearance characteristics dictionary.
+ *
+ * @version $Revision: 1.0 $
+ *
+ */
+public class PDAppearanceCharacteristicsDictionary implements COSObjectable
+{
+
+ private COSDictionary dictionary;
+
+ /**
+ * Constructor.
+ *
+ * @param dict dictionary
+ */
+ public PDAppearanceCharacteristicsDictionary(COSDictionary dict)
+ {
+ this.dictionary = dict;
+ }
+
+
+ /**
+ * returns the dictionary.
+ * @return the dictionary
+ */
+ public COSDictionary getDictionary()
+ {
+ return this.dictionary;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ public COSBase getCOSObject()
+ {
+ return this.dictionary;
+ }
+
+ /**
+ * This will retrieve the rotation of the annotation widget.
+ * It must be a multiple of 90. Default is 0
+ * @return the rotation
+ */
+ public int getRotation()
+ {
+ return this.getDictionary().getInt(COSName.R, 0);
+ }
+
+ /**
+ * This will set the rotation.
+ *
+ * @param rotation the rotation as a multiple of 90
+ */
+ public void setRotation(int rotation)
+ {
+ this.getDictionary().setInt(COSName.R, rotation);
+ }
+
+ /**
+ * This will retrieve the border color.
+ *
+ * @return the border color.
+ */
+ public PDGamma getBorderColour()
+ {
+ COSBase c = this.getDictionary().getItem(COSName.getPDFName("BC"));
+ if (c instanceof COSArray)
+ {
+ return new PDGamma((COSArray) c);
+ }
+ return null;
+ }
+
+ /**
+ * This will set the border color.
+ *
+ * @param c the border color
+ */
+ public void setBorderColour(PDGamma c)
+ {
+ this.getDictionary().setItem("BC", c);
+ }
+
+ /**
+ * This will retrieve the background color.
+ *
+ * @return the background color.
+ */
+ public PDGamma getBackground()
+ {
+ COSBase c = this.getDictionary().getItem(COSName.getPDFName("BG"));
+ if (c instanceof COSArray)
+ {
+ return new PDGamma((COSArray) c);
+ }
+ return null;
+ }
+
+ /**
+ * This will set the background color.
+ *
+ * @param c the background color
+ */
+ public void setBackground(PDGamma c)
+ {
+ this.getDictionary().setItem("BG", c);
+ }
+
+ /**
+ * This will retrieve the normal caption.
+ *
+ * @return the normal caption.
+ */
+ public String getNormalCaption()
+ {
+ return this.getDictionary().getString("CA");
+ }
+
+ /**
+ * This will set the normal caption.
+ *
+ * @param caption the normal caption
+ */
+ public void setNormalCaption(String caption)
+ {
+ this.getDictionary().setString("CA", caption);
+ }
+
+ /**
+ * This will retrieve the rollover caption.
+ *
+ * @return the rollover caption.
+ */
+ public String getRolloverCaption()
+ {
+ return this.getDictionary().getString("RC");
+ }
+
+ /**
+ * This will set the rollover caption.
+ *
+ * @param caption the rollover caption
+ */
+ public void setRolloverCaption(String caption)
+ {
+ this.getDictionary().setString("RC", caption);
+ }
+
+ /**
+ * This will retrieve the alternate caption.
+ *
+ * @return the alternate caption.
+ */
+ public String getAlternateCaption()
+ {
+ return this.getDictionary().getString("AC");
+ }
+
+ /**
+ * This will set the alternate caption.
+ *
+ * @param caption the alternate caption
+ */
+ public void setAlternateCaption(String caption)
+ {
+ this.getDictionary().setString("AC", caption);
+ }
+
+ /**
+ * This will retrieve the normal icon.
+ *
+ * @return the normal icon.
+ */
+ public PDXObjectForm getNormalIcon()
+ {
+ COSBase i = this.getDictionary().getDictionaryObject("I");
+ if (i instanceof COSStream)
+ {
+ return new PDXObjectForm((COSStream) i);
+ }
+ return null;
+ }
+
+ /**
+ * This will retrieve the rollover icon.
+ *
+ * @return the rollover icon
+ */
+ public PDXObjectForm getRolloverIcon()
+ {
+ COSBase i = this.getDictionary().getDictionaryObject("RI");
+ if (i instanceof COSStream)
+ {
+ return new PDXObjectForm((COSStream) i);
+ }
+ return null;
+ }
+
+ /**
+ * This will retrieve the alternate icon.
+ *
+ * @return the alternate icon.
+ */
+ public PDXObjectForm getAlternateIcon()
+ {
+ COSBase i = this.getDictionary().getDictionaryObject("IX");
+ if (i instanceof COSStream)
+ {
+ return new PDXObjectForm((COSStream) i);
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+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.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.COSDictionaryMap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class represents a PDF /AP entry the appearance dictionary.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDAppearanceDictionary implements COSObjectable
+{
+ private COSDictionary dictionary;
+
+ /**
+ * Constructor.
+ */
+ public PDAppearanceDictionary()
+ {
+ dictionary = new COSDictionary();
+ //the N entry is required.
+ dictionary.setItem( COSName.getPDFName( "N" ), new COSDictionary() );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dict The annotations dictionary.
+ */
+ public PDAppearanceDictionary( COSDictionary dict )
+ {
+ dictionary = dict;
+ }
+
+ /**
+ * returns the dictionary.
+ * @return the dictionary
+ */
+ public COSDictionary getDictionary()
+ {
+ return dictionary;
+ }
+
+ /**
+ * returns the dictionary.
+ * @return the dictionary
+ */
+ public COSBase getCOSObject()
+ {
+ return dictionary;
+ }
+
+ /**
+ * This will return a list of appearances. In the case where there is
+ * only one appearance the map will contain one entry whose key is the string
+ * "default".
+ *
+ * @return A list of key(java.lang.String) value(PDAppearanceStream) pairs
+ */
+ public Map getNormalAppearance()
+ {
+ COSBase ap = dictionary.getDictionaryObject( COSName.getPDFName( "N" ) );
+ if ( ap == null )
+ {
+ return null;
+ }
+ else if( ap instanceof COSStream )
+ {
+ COSStream aux = (COSStream) ap;
+ ap = new COSDictionary();
+ ((COSDictionary)ap).setItem(COSName.getPDFName( "default" ), aux );
+ }
+ COSDictionary map = (COSDictionary)ap;
+ Map<String, PDAppearanceStream> actuals = new HashMap<String, PDAppearanceStream>();
+ Map retval = new COSDictionaryMap( actuals, map );
+ for( COSName asName : map.keySet() )
+ {
+ COSStream as = (COSStream)map.getDictionaryObject( asName );
+ actuals.put( asName.getName(), new PDAppearanceStream( as ) );
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set a list of appearances. If you would like to set the single
+ * appearance then you should use the key "default", and when the PDF is written
+ * back to the filesystem then there will only be one stream.
+ *
+ * @param appearanceMap The updated map with the appearance.
+ */
+ public void setNormalAppearance( Map appearanceMap )
+ {
+ dictionary.setItem( COSName.getPDFName( "N" ), COSDictionaryMap.convert( appearanceMap ) );
+ }
+
+ /**
+ * This will set the normal appearance when there is only one appearance
+ * to be shown.
+ *
+ * @param ap The appearance stream to show.
+ */
+ public void setNormalAppearance( PDAppearanceStream ap )
+ {
+ dictionary.setItem( COSName.getPDFName( "N" ), ap.getStream() );
+ }
+
+ /**
+ * This will return a list of appearances. In the case where there is
+ * only one appearance the map will contain one entry whose key is the string
+ * "default". If there is no rollover appearance then the normal appearance
+ * will be returned. Which means that this method will never return null.
+ *
+ * @return A list of key(java.lang.String) value(PDAppearanceStream) pairs
+ */
+ public Map getRolloverAppearance()
+ {
+ Map retval = null;
+ COSBase ap = dictionary.getDictionaryObject( COSName.getPDFName( "R" ) );
+ if( ap == null )
+ {
+ retval = getNormalAppearance();
+ }
+ else
+ {
+ if( ap instanceof COSStream )
+ {
+ COSStream aux = (COSStream) ap;
+ ap = new COSDictionary();
+ ((COSDictionary)ap).setItem(COSName.getPDFName( "default" ), aux );
+ }
+ COSDictionary map = (COSDictionary)ap;
+ Map<String, PDAppearanceStream> actuals = new HashMap<String, PDAppearanceStream>();
+ retval = new COSDictionaryMap( actuals, map );
+ for( COSName asName : map.keySet() )
+ {
+ COSStream as = (COSStream)map.getDictionaryObject( asName );
+ actuals.put( asName.getName(), new PDAppearanceStream( as ) );
+ }
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set a list of appearances. If you would like to set the single
+ * appearance then you should use the key "default", and when the PDF is written
+ * back to the filesystem then there will only be one stream.
+ *
+ * @param appearanceMap The updated map with the appearance.
+ */
+ public void setRolloverAppearance( Map appearanceMap )
+ {
+ dictionary.setItem( COSName.getPDFName( "R" ), COSDictionaryMap.convert( appearanceMap ) );
+ }
+
+ /**
+ * This will return a list of appearances. In the case where there is
+ * only one appearance the map will contain one entry whose key is the string
+ * "default". If there is no rollover appearance then the normal appearance
+ * will be returned. Which means that this method will never return null.
+ *
+ * @return A list of key(java.lang.String) value(PDAppearanceStream) pairs
+ */
+ public Map getDownAppearance()
+ {
+ Map retval = null;
+ COSBase ap = dictionary.getDictionaryObject( COSName.getPDFName( "D" ) );
+ if( ap == null )
+ {
+ retval = getNormalAppearance();
+ }
+ else
+ {
+ if( ap instanceof COSStream )
+ {
+ COSStream aux = (COSStream) ap;
+ ap = new COSDictionary();
+ ((COSDictionary)ap).setItem(COSName.getPDFName( "default" ), aux );
+ }
+ COSDictionary map = (COSDictionary)ap;
+ Map<String, PDAppearanceStream> actuals =
+ new HashMap<String, PDAppearanceStream>();
+ retval = new COSDictionaryMap( actuals, map );
+ for( COSName asName : map.keySet() )
+ {
+ COSStream as = (COSStream)map.getDictionaryObject( asName );
+ actuals.put( asName.getName(), new PDAppearanceStream( as ) );
+ }
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set a list of appearances. If you would like to set the single
+ * appearance then you should use the key "default", and when the PDF is written
+ * back to the filesystem then there will only be one stream.
+ *
+ * @param appearanceMap The updated map with the appearance.
+ */
+ public void setDownAppearance( Map appearanceMap )
+ {
+ dictionary.setItem( COSName.getPDFName( "D" ), COSDictionaryMap.convert( appearanceMap ) );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+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.COSStream;
+
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.pdfbox.pdmodel.PDResources;
+
+
+/**
+ * This class represents an appearance for an annotation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDAppearanceStream implements COSObjectable
+{
+ private COSStream stream = null;
+
+
+ /**
+ * Constructor.
+ *
+ * @param s The cos stream for this appearance.
+ */
+ public PDAppearanceStream( COSStream s )
+ {
+ stream = s;
+ }
+
+ /**
+ * This will return the underlying stream.
+ *
+ * @return The wrapped stream.
+ */
+ public COSStream getStream()
+ {
+ return stream;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return stream;
+ }
+
+ /**
+ * Get the bounding box for this appearance. This may return null in which
+ * case the Rectangle from the annotation should be used.
+ *
+ * @return The bounding box for this appearance.
+ */
+ public PDRectangle getBoundingBox()
+ {
+ PDRectangle box = null;
+ COSArray bbox = (COSArray)stream.getDictionaryObject( COSName.getPDFName( "BBox" ) );
+ if( bbox != null )
+ {
+ box = new PDRectangle( bbox );
+ }
+ return box;
+ }
+
+ /**
+ * This will set the bounding box for this appearance stream.
+ *
+ * @param rectangle The new bounding box.
+ */
+ public void setBoundingBox( PDRectangle rectangle )
+ {
+ COSArray array = null;
+ if( rectangle != null )
+ {
+ array = rectangle.getCOSArray();
+ }
+ stream.setItem( COSName.getPDFName( "BBox" ), array );
+ }
+
+ /**
+ * This will get the resources for this appearance stream.
+ *
+ * @return The appearance stream resources.
+ */
+ public PDResources getResources()
+ {
+ PDResources retval = null;
+ COSDictionary dict = (COSDictionary)stream.getDictionaryObject( COSName.RESOURCES );
+ if( dict != null )
+ {
+ retval = new PDResources( dict );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the new resources.
+ *
+ * @param resources The new resources.
+ */
+ public void setResources( PDResources resources )
+ {
+ COSDictionary dict = null;
+ if( resources != null )
+ {
+ dict = resources.getCOSDictionary();
+ }
+ stream.setItem( COSName.RESOURCES, dict );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents a PDF /BE entry the border effect dictionary.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDBorderEffectDictionary implements COSObjectable
+{
+
+ /*
+ * The various values of the effect applied to the border as defined in the
+ * PDF 1.6 reference Table 8.14
+ */
+
+ /**
+ * Constant for the name for no effect.
+ */
+ public static final String STYLE_SOLID = "S";
+
+ /**
+ * Constant for the name of a cloudy effect.
+ */
+ public static final String STYLE_CLOUDY = "C";
+
+ private COSDictionary dictionary;
+
+ /**
+ * Constructor.
+ */
+ public PDBorderEffectDictionary()
+ {
+ dictionary = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dict
+ * a border style dictionary.
+ */
+ public PDBorderEffectDictionary( COSDictionary dict )
+ {
+ dictionary = dict;
+ }
+
+ /**
+ * returns the dictionary.
+ *
+ * @return the dictionary
+ */
+ public COSDictionary getDictionary()
+ {
+ return dictionary;
+ }
+
+ /**
+ * returns the dictionary.
+ *
+ * @return the dictionary
+ */
+ public COSBase getCOSObject()
+ {
+ return dictionary;
+ }
+
+ /**
+ * This will set the intensity of the applied effect.
+ *
+ * @param i
+ * the intensity of the effect values 0 to 2
+ */
+ public void setIntensity( float i )
+ {
+ getDictionary().setFloat( "I", i );
+ }
+
+ /**
+ * This will retrieve the intensity of the applied effect.
+ *
+ * @return the intensity value 0 to 2
+ */
+ public float getIntensity()
+ {
+ return getDictionary().getFloat( "I", 0 );
+ }
+
+ /**
+ * This will set the border effect, see the STYLE_* constants for valid values.
+ *
+ * @param s
+ * the border effect to use
+ */
+ public void setStyle( String s )
+ {
+ getDictionary().setName( "S", s );
+ }
+
+ /**
+ * This will retrieve the border effect, see the STYLE_* constants for valid
+ * values.
+ *
+ * @return the effect of the border
+ */
+ public String getStyle()
+ {
+ return getDictionary().getNameAsString( "S", STYLE_SOLID );
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSInteger;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
+
+/**
+ * This class represents a PDF /BS entry the border style dictionary.
+ *
+ * @author Paul King
+ * @version $Revision: 1.1 $
+ */
+public class PDBorderStyleDictionary implements COSObjectable
+{
+
+ /*
+ * The various values of the style for the border as defined in the PDF 1.6
+ * reference Table 8.13
+ */
+
+ /**
+ * Constant for the name of a solid style.
+ */
+ public static final String STYLE_SOLID = "S";
+
+ /**
+ * Constant for the name of a dashed style.
+ */
+ public static final String STYLE_DASHED = "D";
+
+ /**
+ * Constant for the name of a beveled style.
+ */
+ public static final String STYLE_BEVELED = "B";
+
+ /**
+ * Constant for the name of a inset style.
+ */
+ public static final String STYLE_INSET = "I";
+
+ /**
+ * Constant for the name of a underline style.
+ */
+ public static final String STYLE_UNDERLINE = "U";
+
+ private COSDictionary dictionary;
+
+ /**
+ * Constructor.
+ */
+ public PDBorderStyleDictionary()
+ {
+ dictionary = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dict
+ * a border style dictionary.
+ */
+ public PDBorderStyleDictionary( COSDictionary dict )
+ {
+ dictionary = dict;
+ }
+
+ /**
+ * returns the dictionary.
+ *
+ * @return the dictionary
+ */
+ public COSDictionary getDictionary()
+ {
+ return dictionary;
+ }
+
+ /**
+ * returns the dictionary.
+ *
+ * @return the dictionary
+ */
+ public COSBase getCOSObject()
+ {
+ return dictionary;
+ }
+
+ /**
+ * This will set the border width in points, 0 = no border.
+ *
+ * @param w
+ * float the width in points
+ */
+ public void setWidth( float w )
+ {
+ getDictionary().setFloat( "W", w );
+ }
+
+ /**
+ * This will retrieve the border width in points, 0 = no border.
+ *
+ * @return flaot the width of the border in points
+ */
+ public float getWidth()
+ {
+ return getDictionary().getFloat( "W", 1 );
+ }
+
+ /**
+ * This will set the border style, see the STYLE_* constants for valid values.
+ *
+ * @param s
+ * the border style to use
+ */
+ public void setStyle( String s )
+ {
+ getDictionary().setName( "S", s );
+ }
+
+ /**
+ * This will retrieve the border style, see the STYLE_* constants for valid
+ * values.
+ *
+ * @return the style of the border
+ */
+ public String getStyle()
+ {
+ return getDictionary().getNameAsString( "S", STYLE_SOLID );
+ }
+
+ /**
+ * This will set the dash style used for drawing the border.
+ *
+ * @param d
+ * the dash style to use
+ */
+ public void setDashStyle( PDLineDashPattern d )
+ {
+ COSArray array = null;
+ if( d != null )
+ {
+ array = d.getCOSDashPattern();
+ }
+ getDictionary().setItem( "D", array );
+ }
+
+ /**
+ * This will retrieve the dash style used for drawing the border.
+ *
+ * @return the dash style of the border
+ */
+ public PDLineDashPattern getDashStyle()
+ {
+ COSArray d = (COSArray) getDictionary().getDictionaryObject( "D" );
+ if (d == null)
+ {
+ d = new COSArray();
+ d.add( COSInteger.THREE );
+ getDictionary().setItem( "D", d );
+ }
+ return new PDLineDashPattern( d, 0 );
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.annotation;
+
+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 external data dictionary.
+ *
+ * @version $Revision: 1.0 $
+ *
+ */
+public class PDExternalDataDictionary implements COSObjectable
+{
+
+ private COSDictionary dataDictionary;
+
+ /**
+ * Constructor.
+ */
+ public PDExternalDataDictionary()
+ {
+ this.dataDictionary = new COSDictionary();
+ this.dataDictionary.setName(COSName.TYPE, "ExData");
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dictionary Dictionary
+ */
+ public PDExternalDataDictionary(COSDictionary dictionary)
+ {
+ this.dataDictionary = dictionary;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return this.dataDictionary;
+ }
+
+ /**
+ * returns the dictionary.
+ *
+ * @return the dictionary
+ */
+ public COSDictionary getDictionary()
+ {
+ return this.dataDictionary;
+ }
+
+ /**
+ * returns the type of the external data dictionary.
+ * It must be "ExData", if present
+ * @return the type of the external data dictionary
+ */
+ public String getType()
+ {
+ return this.getDictionary().getNameAsString(COSName.TYPE, "ExData");
+ }
+
+ /**
+ * returns the subtype of the external data dictionary.
+ * @return the subtype of the external data dictionary
+ */
+ public String getSubtype()
+ {
+ return this.getDictionary().getNameAsString(COSName.SUBTYPE);
+ }
+
+ /**
+ * This will set the subtype of the external data dictionary.
+ * @param subtype the subtype of the external data dictionary
+ */
+ public void setSubtype(String subtype)
+ {
+ this.getDictionary().setName(COSName.SUBTYPE, subtype);
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The annotation package contains classes that work with PDF annotation elements.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.interactive.digitalsignature;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+
+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.COSString;
+import org.apache.pdfbox.pdfwriter.COSFilterInputStream;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents a digital signature that can be attached to a document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Thomas Chojecki
+ * @version $Revision: 1.2 $
+ */
+public class PDSignature implements COSObjectable
+{
+
+ private COSDictionary dictionary;
+
+ /**
+ * A signature filter value.
+ */
+ public static final COSName FILTER_ADOBE_PPKLITE = COSName.ADOBE_PPKLITE;
+
+ /**
+ * A signature filter value.
+ */
+ public static final COSName FILTER_ENTRUST_PPKEF = COSName.ENTRUST_PPKEF;
+
+ /**
+ * A signature filter value.
+ */
+ public static final COSName FILTER_CICI_SIGNIT = COSName.CICI_SIGNIT;
+
+ /**
+ * A signature filter value.
+ */
+ public static final COSName FILTER_VERISIGN_PPKVS = COSName.VERISIGN_PPKVS;
+
+ /**
+ * A signature subfilter value.
+ */
+ public static final COSName SUBFILTER_ADBE_X509_RSA_SHA1 = COSName.ADBE_X509_RSA_SHA1;
+
+ /**
+ * A signature subfilter value.
+ */
+ public static final COSName SUBFILTER_ADBE_PKCS7_DETACHED = COSName.ADBE_PKCS7_DETACHED;
+
+ /**
+ * A signature subfilter value.
+ */
+ public static final COSName SUBFILTER_ETSI_CADES_DETACHED = COSName.getPDFName("ETSI.CAdES.detached");
+
+ /**
+ * A signature subfilter value.
+ */
+ public static final COSName SUBFILTER_ADBE_PKCS7_SHA1 = COSName.ADBE_PKCS7_SHA1;
+
+ /**
+ * Default constructor.
+ */
+ public PDSignature()
+ {
+ dictionary = new COSDictionary();
+ dictionary.setItem(COSName.TYPE, COSName.SIG);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dict The signature dictionary.
+ */
+ public PDSignature(COSDictionary dict)
+ {
+ dictionary = dict;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return getDictionary();
+ }
+
+ /**
+ * Convert this standard java object to a COS dictionary.
+ *
+ * @return The COS dictionary that matches this Java object.
+ */
+ public COSDictionary getDictionary()
+ {
+ return dictionary;
+ }
+
+ public void setFilter(COSName filter)
+ {
+ dictionary.setItem(COSName.FILTER, filter);
+ }
+
+ /**
+ * Set a subfilter that specify the signature that should be used.
+ *
+ * @param subfilter the subfilter that shall be used.
+ */
+ public void setSubFilter(COSName subfilter)
+ {
+ dictionary.setItem(COSName.SUBFILTER, subfilter);
+ }
+
+ public void setName(String name)
+ {
+ dictionary.setString(COSName.NAME, name);
+ }
+
+ public void setLocation(String location)
+ {
+ dictionary.setString(COSName.LOCATION, location);
+ }
+
+ public void setReason(String reason)
+ {
+ dictionary.setString(COSName.REASON, reason);
+ }
+
+ public void setSignDate(Calendar cal)
+ {
+ dictionary.setDate("M", cal);
+ }
+
+ public String getFilter()
+ {
+ return ((COSName)dictionary.getItem(COSName.FILTER)).getName();
+ }
+
+ public String getSubFilter()
+ {
+ return ((COSName)dictionary.getItem(COSName.SUBFILTER)).getName();
+ }
+
+ public String getName()
+ {
+ return dictionary.getString(COSName.NAME);
+ }
+
+ public String getLocation()
+ {
+ return dictionary.getString(COSName.LOCATION);
+ }
+
+ public String getReason()
+ {
+ return dictionary.getString(COSName.REASON);
+ }
+
+ public Calendar getSignDate()
+ {
+ try
+ {
+ return dictionary.getDate("M");
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+
+ public void setByteRange(int[] range)
+ {
+ if (range.length!=4)
+ return;
+
+ COSArray ary = new COSArray();
+ for ( int i : range )
+ {
+ ary.add(COSInteger.get(i));
+ }
+
+ dictionary.setItem("ByteRange", ary);
+ }
+
+ /**
+ * Read out the byterange from the file
+ *
+ * @return a integer array with the byterange
+ */
+ public int[] getByteRange()
+ {
+ COSArray byteRange = (COSArray)dictionary.getDictionaryObject("ByteRange");
+ int[] ary = new int[byteRange.size()];
+ for (int i = 0; i<ary.length;++i)
+ ary[i] = byteRange.getInt(i);
+
+ return ary;
+ }
+
+ /**
+ * Will return the embedded signature between the byterange gap.
+ *
+ * @param pdfFile The signed pdf file as InputStream
+ * @return a byte array containing the signature
+ * @throws IOException if the pdfFile can't be read
+ */
+ public byte[] getContents(InputStream pdfFile) throws IOException
+ {
+ int[] byteRange = getByteRange();
+ int begin = byteRange[0]+byteRange[1]+1;
+ int end = byteRange[2]-begin;
+
+ return getContents(new COSFilterInputStream(pdfFile,new int[] {begin,end}));
+ }
+
+ /**
+ * Will return the embedded signature between the byterange gap.
+ *
+ * @param pdfFile The signed pdf file as byte array
+ * @return a byte array containing the signature
+ * @throws IOException if the pdfFile can't be read
+ */
+ public byte[] getContents(byte[] pdfFile) throws IOException
+ {
+ int[] byteRange = getByteRange();
+ int begin = byteRange[0]+byteRange[1]+1;
+ int end = byteRange[2]-begin;
+
+ return getContents(new COSFilterInputStream(pdfFile,new int[] {begin,end}));
+ }
+
+ private byte[] getContents(COSFilterInputStream fis) throws IOException
+ {
+ ByteArrayOutputStream byteOS = new ByteArrayOutputStream(1024);
+ byte[] buffer = new byte[1024];
+ int c;
+ while ((c = fis.read(buffer)) != -1)
+ {
+ // Filter < and (
+ if(buffer[0]==0x3C || buffer[0]==0x28)
+ byteOS.write(buffer, 1, c);
+ // Filter > and )
+ else if(buffer[c-1]==0x3E || buffer[c-1]==0x29)
+ byteOS.write(buffer, 0, c-1);
+ else
+ byteOS.write(buffer, 0, c);
+ }
+ fis.close();
+
+ return COSString.createFromHexString(byteOS.toString()).getBytes();
+ }
+
+ public void setContents(byte[] bytes)
+ {
+ COSString string = new COSString(bytes);
+ string.setForceHexForm(true);
+ dictionary.setItem("Contents", string);
+ }
+
+ /**
+ * Will return the signed content of the document.
+ *
+ * @param pdfFile The signed pdf file as InputStream
+ * @return a byte array containing only the signed part of the content
+ * @throws IOException if the pdfFile can't be read
+ */
+ public byte[] getSignedContent(InputStream pdfFile) throws IOException
+ {
+ COSFilterInputStream fis=null;
+
+ try
+ {
+ fis = new COSFilterInputStream(pdfFile,getByteRange());
+ return fis.toByteArray();
+ }
+ finally
+ {
+ if (fis != null)
+ fis.close();
+ }
+ }
+
+ /**
+ * Will return the signed content of the document.
+ *
+ * @param pdfFile The signed pdf file as byte array
+ * @return a byte array containing only the signed part of the content
+ * @throws IOException if the pdfFile can't be read
+ */
+ public byte[] getSignedContent(byte[] pdfFile) throws IOException
+ {
+ COSFilterInputStream fis=null;
+
+ try
+ {
+ fis = new COSFilterInputStream(pdfFile,getByteRange());
+ return fis.toByteArray();
+ }
+ finally
+ {
+ if (fis != null)
+ fis.close();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.digitalsignature;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.pdfbox.exceptions.SignatureException;
+
+/**
+ * Providing an interface for accessing necessary functions for signing a pdf document.
+ *
+ * @author <a href="mailto:mail@thomas-chojecki.de">Thomas Chojecki</a>
+ * @version $
+ */
+public interface SignatureInterface
+{
+
+ /**
+ * Creates a cms signature for the given content
+ *
+ * @param content is the content as a (Filter)InputStream
+ * @return signature as a byte array
+ */
+ public byte[] sign (InputStream content) throws SignatureException, IOException;
+
+}
--- /dev/null
+/*
+ * 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.interactive.digitalsignature;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.pdfparser.VisualSignatureParser;
+
+
+public class SignatureOptions
+{
+ private COSDocument visualSignature;
+
+ private int preferedSignatureSize;
+
+ private int pageNo;
+
+ /**
+ * Set the page number.
+ *
+ * @param pageNo the page number
+ *
+ */
+ public void setPage(int pageNo)
+ {
+ this.pageNo = pageNo;
+ }
+
+ /**
+ * Get the page number.
+ *
+ * @return the page number
+ */
+ public int getPage()
+ {
+ return pageNo;
+ }
+
+ /**
+ * Reads the visual signature from the given input stream.
+ *
+ * @param is the input stream containing the visual signature
+ *
+ * @throws IOException when something went wrong during parsing
+ */
+ public void setVisualSignature(InputStream is) throws IOException
+ {
+ VisualSignatureParser visParser = new VisualSignatureParser(is);
+ visParser.parse();
+ visualSignature = visParser.getDocument();
+ }
+
+ /**
+ * Get the visual signature.
+ *
+ * @return the visual signature
+ */
+ public COSDocument getVisualSignature()
+ {
+ return visualSignature;
+ }
+
+ /**
+ * Get the preferred size of the signature.
+ *
+ * @return the preferred size
+ */
+ public int getPreferedSignatureSize()
+ {
+ return preferedSignatureSize;
+ }
+
+ /**
+ * Set the preferred size of the signature.
+ *
+ * @param size the size of the signature
+ */
+ public void setPreferedSignatureSize(int size)
+ {
+ if (size > 0)
+ {
+ preferedSignatureSize = size;
+ }
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The digitial signature library will manage signatures that are stored in the PDF document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.destination;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import org.apache.pdfbox.pdmodel.common.PDDestinationOrAction;
+
+/**
+ * This represents a destination in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.6 $
+ */
+public abstract class PDDestination implements PDDestinationOrAction
+{
+
+ /**
+ * This will create a new destination depending on the type of COSBase
+ * that is passed in.
+ *
+ * @param base The base level object.
+ *
+ * @return A new destination.
+ *
+ * @throws IOException If the base cannot be converted to a Destination.
+ */
+ public static PDDestination create( COSBase base ) throws IOException
+ {
+ PDDestination retval = null;
+ if( base == null )
+ {
+ //this is ok, just return null.
+ }
+ else if( base instanceof COSArray && ((COSArray)base).size() > 0 )
+ {
+ COSArray array = (COSArray)base;
+ COSName type = (COSName)array.getObject( 1 );
+ String typeString = type.getName();
+ if( typeString.equals( PDPageFitDestination.TYPE ) ||
+ typeString.equals( PDPageFitDestination.TYPE_BOUNDED ))
+ {
+ retval = new PDPageFitDestination( array );
+ }
+ else if( typeString.equals( PDPageFitHeightDestination.TYPE ) ||
+ typeString.equals( PDPageFitHeightDestination.TYPE_BOUNDED ))
+ {
+ retval = new PDPageFitHeightDestination( array );
+ }
+ else if( typeString.equals( PDPageFitRectangleDestination.TYPE ) )
+ {
+ retval = new PDPageFitRectangleDestination( array );
+ }
+ else if( typeString.equals( PDPageFitWidthDestination.TYPE ) ||
+ typeString.equals( PDPageFitWidthDestination.TYPE_BOUNDED ))
+ {
+ retval = new PDPageFitWidthDestination( array );
+ }
+ else if( typeString.equals( PDPageXYZDestination.TYPE ) )
+ {
+ retval = new PDPageXYZDestination( array );
+ }
+ else
+ {
+ throw new IOException( "Unknown destination type:" + type );
+ }
+ }
+ else if( base instanceof COSString )
+ {
+ retval = new PDNamedDestination( (COSString)base );
+ }
+ else if( base instanceof COSName )
+ {
+ retval = new PDNamedDestination( (COSName)base );
+ }
+ else
+ {
+ throw new IOException( "Error: can't convert to Destination " + base );
+ }
+ return retval;
+ }
+
+ /**
+ * Return a string representation of this class.
+ *
+ * @return A human readable string.
+ */
+ public String toString()
+ {
+ return super.toString();
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.destination;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * This represents a destination to a page by referencing it with a name.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDNamedDestination extends PDDestination
+{
+ private COSBase namedDestination;
+
+ /**
+ * Constructor.
+ *
+ * @param dest The named destination.
+ */
+ public PDNamedDestination( COSString dest )
+ {
+ namedDestination = dest;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dest The named destination.
+ */
+ public PDNamedDestination( COSName dest )
+ {
+ namedDestination = dest;
+ }
+
+ /**
+ * Default constructor.
+ */
+ public PDNamedDestination()
+ {
+ //default, so do nothing
+ }
+
+ /**
+ * Default constructor.
+ *
+ * @param dest The named destination.
+ */
+ public PDNamedDestination( String dest )
+ {
+ namedDestination = new COSString( dest );
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return namedDestination;
+ }
+
+ /**
+ * This will get the name of the destination.
+ *
+ * @return The name of the destination.
+ */
+ public String getNamedDestination()
+ {
+ String retval = null;
+ if( namedDestination instanceof COSString )
+ {
+ retval = ((COSString)namedDestination).getString();
+ }
+ else if( namedDestination instanceof COSName )
+ {
+ retval = ((COSName)namedDestination).getName();
+ }
+
+ return retval;
+ }
+
+ /**
+ * Set the named destination.
+ *
+ * @param dest The new named destination.
+ *
+ * @throws IOException If there is an error setting the named destination.
+ */
+ public void setNamedDestination( String dest ) throws IOException
+ {
+ if( namedDestination instanceof COSString )
+ {
+ COSString string = ((COSString)namedDestination);
+ string.reset();
+ string.append( dest.getBytes("ISO-8859-1") );
+ }
+ else if( dest == null )
+ {
+ namedDestination = null;
+ }
+ else
+ {
+ namedDestination = new COSString( dest );
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.destination;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSNumber;
+
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageNode;
+
+/**
+ * This represents a destination to a page, see subclasses for specific parameters.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public abstract class PDPageDestination extends PDDestination
+{
+ /**
+ * Storage for the page destination.
+ */
+ protected COSArray array;
+
+ /**
+ * Constructor to create empty page destination.
+ *
+ */
+ protected PDPageDestination()
+ {
+ array = new COSArray();
+ }
+
+ /**
+ * Constructor to create empty page destination.
+ *
+ * @param arr A page destination array.
+ */
+ protected PDPageDestination( COSArray arr )
+ {
+ array = arr;
+ }
+
+ /**
+ * This will get the page for this destination. A page destination
+ * can either reference a page or a page number(when doing a remote destination to
+ * another PDF). If this object is referencing by page number then this method will
+ * return null and getPageNumber should be used.
+ *
+ * @return The page for this destination.
+ */
+ public PDPage getPage()
+ {
+ PDPage retval = null;
+ if( array.size() > 0 )
+ {
+ COSBase page = array.getObject( 0 );
+ if( page instanceof COSDictionary )
+ {
+ retval = new PDPage( (COSDictionary)page );
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * Set the page for this destination.
+ *
+ * @param page The page for the destination.
+ */
+ public void setPage( PDPage page )
+ {
+ array.set( 0, page );
+ }
+
+ /**
+ * This will get the page number for this destination. A page destination
+ * can either reference a page or a page number(when doing a remote destination to
+ * another PDF). If this object is referencing by page number then this method will
+ * return that number, otherwise -1 will be returned.
+ *
+ * @return The page number for this destination.
+ */
+ public int getPageNumber()
+ {
+ int retval = -1;
+ if( array.size() > 0 )
+ {
+ COSBase page = array.getObject( 0 );
+ if( page instanceof COSNumber )
+ {
+ retval = ((COSNumber)page).intValue();
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * Returns the page number for this destination, regardless of whether
+ * this is a page number or a reference to a page.
+ *
+ * @since Apache PDFBox 1.0.0
+ * @see PDOutlineItem
+ * @return page number, or -1 if the destination type is unknown
+ */
+ public int findPageNumber() {
+ int retval = -1;
+ if( array.size() > 0 )
+ {
+ COSBase page = array.getObject( 0 );
+ if( page instanceof COSNumber )
+ {
+ retval = ((COSNumber)page).intValue();
+ }
+ else if (page instanceof COSDictionary)
+ {
+ COSBase parent = page;
+ while (((COSDictionary) parent).getDictionaryObject("Parent", "P") != null) {
+ parent = ((COSDictionary) parent).getDictionaryObject( "Parent", "P" );
+ }
+ // now parent is the pages node
+ PDPageNode pages = new PDPageNode((COSDictionary) parent);
+ List<PDPage> allPages = new ArrayList<PDPage>();
+ pages.getAllKids(allPages);
+ retval = allPages.indexOf(new PDPage((COSDictionary) page)) + 1;
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * Set the page number for this destination.
+ *
+ * @param pageNumber The page for the destination.
+ */
+ public void setPageNumber( int pageNumber )
+ {
+ array.set( 0, pageNumber );
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return array;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSArray getCOSArray()
+ {
+ return array;
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.destination;
+
+import org.apache.pdfbox.cos.COSArray;
+
+/**
+ * This represents a destination to a page and the page contents will be magnified to just
+ * fit on the screen.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDPageFitDestination extends PDPageDestination
+{
+ /**
+ * The type of this destination.
+ */
+ protected static final String TYPE = "Fit";
+ /**
+ * The type of this destination.
+ */
+ protected static final String TYPE_BOUNDED = "FitB";
+
+ /**
+ * Default constructor.
+ *
+ */
+ public PDPageFitDestination()
+ {
+ super();
+ array.growToSize(2);
+ array.setName( 1, TYPE );
+
+ }
+
+ /**
+ * Constructor from an existing destination array.
+ *
+ * @param arr The destination array.
+ */
+ public PDPageFitDestination( COSArray arr )
+ {
+ super( arr );
+ }
+
+ /**
+ * A flag indicating if this page destination should just fit bounding box of the PDF.
+ *
+ * @return true If the destination should fit just the bounding box.
+ */
+ public boolean fitBoundingBox()
+ {
+ return TYPE_BOUNDED.equals( array.getName( 1 ) );
+ }
+
+ /**
+ * Set if this page destination should just fit the bounding box. The default is false.
+ *
+ * @param fitBoundingBox A flag indicating if this should fit the bounding box.
+ */
+ public void setFitBoundingBox( boolean fitBoundingBox )
+ {
+ array.growToSize( 2 );
+ if( fitBoundingBox )
+ {
+ array.setName( 1, TYPE_BOUNDED );
+ }
+ else
+ {
+ array.setName( 1, TYPE );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.destination;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This represents a destination to a page at a x location and the height is magnified
+ * to just fit on the screen.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDPageFitHeightDestination extends PDPageDestination
+{
+ /**
+ * The type of this destination.
+ */
+ protected static final String TYPE = "FitV";
+ /**
+ * The type of this destination.
+ */
+ protected static final String TYPE_BOUNDED = "FitBV";
+
+ /**
+ * Default constructor.
+ *
+ */
+ public PDPageFitHeightDestination()
+ {
+ super();
+ array.growToSize(3);
+ array.setName( 1, TYPE );
+
+ }
+
+ /**
+ * Constructor from an existing destination array.
+ *
+ * @param arr The destination array.
+ */
+ public PDPageFitHeightDestination( COSArray arr )
+ {
+ super( arr );
+ }
+
+ /**
+ * Get the left x coordinate. A return value of -1 implies that the current x-coordinate
+ * will be used.
+ *
+ * @return The left x coordinate.
+ */
+ public int getLeft()
+ {
+ return array.getInt( 2 );
+ }
+
+ /**
+ * Set the left x-coordinate, a value of -1 implies that the current x-coordinate
+ * will be used.
+ * @param x The left x coordinate.
+ */
+ public void setLeft( int x )
+ {
+ array.growToSize( 3 );
+ if( x == -1 )
+ {
+ array.set( 2, (COSBase)null );
+ }
+ else
+ {
+ array.setInt( 2, x );
+ }
+ }
+
+ /**
+ * A flag indicating if this page destination should just fit bounding box of the PDF.
+ *
+ * @return true If the destination should fit just the bounding box.
+ */
+ public boolean fitBoundingBox()
+ {
+ return TYPE_BOUNDED.equals( array.getName( 1 ) );
+ }
+
+ /**
+ * Set if this page destination should just fit the bounding box. The default is false.
+ *
+ * @param fitBoundingBox A flag indicating if this should fit the bounding box.
+ */
+ public void setFitBoundingBox( boolean fitBoundingBox )
+ {
+ array.growToSize( 2 );
+ if( fitBoundingBox )
+ {
+ array.setName( 1, TYPE_BOUNDED );
+ }
+ else
+ {
+ array.setName( 1, TYPE );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.destination;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This represents a destination to a page at a y location and the width is magnified
+ * to just fit on the screen.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDPageFitRectangleDestination extends PDPageDestination
+{
+ /**
+ * The type of this destination.
+ */
+ protected static final String TYPE = "FitR";
+
+ /**
+ * Default constructor.
+ *
+ */
+ public PDPageFitRectangleDestination()
+ {
+ super();
+ array.growToSize(6);
+ array.setName( 1, TYPE );
+
+ }
+
+ /**
+ * Constructor from an existing destination array.
+ *
+ * @param arr The destination array.
+ */
+ public PDPageFitRectangleDestination( COSArray arr )
+ {
+ super( arr );
+ }
+
+ /**
+ * Get the left x coordinate. A return value of -1 implies that the current x-coordinate
+ * will be used.
+ *
+ * @return The left x coordinate.
+ */
+ public int getLeft()
+ {
+ return array.getInt( 2 );
+ }
+
+ /**
+ * Set the left x-coordinate, a value of -1 implies that the current x-coordinate
+ * will be used.
+ * @param x The left x coordinate.
+ */
+ public void setLeft( int x )
+ {
+ array.growToSize( 3 );
+ if( x == -1 )
+ {
+ array.set( 2, (COSBase)null );
+ }
+ else
+ {
+ array.setInt( 2, x );
+ }
+ }
+
+ /**
+ * Get the bottom y coordinate. A return value of -1 implies that the current y-coordinate
+ * will be used.
+ *
+ * @return The bottom y coordinate.
+ */
+ public int getBottom()
+ {
+ return array.getInt( 3 );
+ }
+
+ /**
+ * Set the bottom y-coordinate, a value of -1 implies that the current y-coordinate
+ * will be used.
+ * @param y The bottom y coordinate.
+ */
+ public void setBottom( int y )
+ {
+ array.growToSize( 6 );
+ if( y == -1 )
+ {
+ array.set( 3, (COSBase)null );
+ }
+ else
+ {
+ array.setInt( 3, y );
+ }
+ }
+
+ /**
+ * Get the right x coordinate. A return value of -1 implies that the current x-coordinate
+ * will be used.
+ *
+ * @return The right x coordinate.
+ */
+ public int getRight()
+ {
+ return array.getInt( 4 );
+ }
+
+ /**
+ * Set the right x-coordinate, a value of -1 implies that the current x-coordinate
+ * will be used.
+ * @param x The right x coordinate.
+ */
+ public void setRight( int x )
+ {
+ array.growToSize( 6 );
+ if( x == -1 )
+ {
+ array.set( 4, (COSBase)null );
+ }
+ else
+ {
+ array.setInt( 4, x );
+ }
+ }
+
+
+ /**
+ * Get the top y coordinate. A return value of -1 implies that the current y-coordinate
+ * will be used.
+ *
+ * @return The top y coordinate.
+ */
+ public int getTop()
+ {
+ return array.getInt( 5 );
+ }
+
+ /**
+ * Set the top y-coordinate, a value of -1 implies that the current y-coordinate
+ * will be used.
+ * @param y The top ycoordinate.
+ */
+ public void setTop( int y )
+ {
+ array.growToSize( 6 );
+ if( y == -1 )
+ {
+ array.set( 5, (COSBase)null );
+ }
+ else
+ {
+ array.setInt( 5, y );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.destination;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This represents a destination to a page at a y location and the width is magnified
+ * to just fit on the screen.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDPageFitWidthDestination extends PDPageDestination
+{
+
+ /**
+ * The type of this destination.
+ */
+ protected static final String TYPE = "FitH";
+ /**
+ * The type of this destination.
+ */
+ protected static final String TYPE_BOUNDED = "FitBH";
+
+ /**
+ * Default constructor.
+ *
+ */
+ public PDPageFitWidthDestination()
+ {
+ super();
+ array.growToSize(3);
+ array.setName( 1, TYPE );
+
+ }
+
+ /**
+ * Constructor from an existing destination array.
+ *
+ * @param arr The destination array.
+ */
+ public PDPageFitWidthDestination( COSArray arr )
+ {
+ super( arr );
+ }
+
+
+ /**
+ * Get the top y coordinate. A return value of -1 implies that the current y-coordinate
+ * will be used.
+ *
+ * @return The top y coordinate.
+ */
+ public int getTop()
+ {
+ return array.getInt( 2 );
+ }
+
+ /**
+ * Set the top y-coordinate, a value of -1 implies that the current y-coordinate
+ * will be used.
+ * @param y The top ycoordinate.
+ */
+ public void setTop( int y )
+ {
+ array.growToSize( 3 );
+ if( y == -1 )
+ {
+ array.set( 2, (COSBase)null );
+ }
+ else
+ {
+ array.setInt( 2, y );
+ }
+ }
+
+ /**
+ * A flag indicating if this page destination should just fit bounding box of the PDF.
+ *
+ * @return true If the destination should fit just the bounding box.
+ */
+ public boolean fitBoundingBox()
+ {
+ return TYPE_BOUNDED.equals( array.getName( 1 ) );
+ }
+
+ /**
+ * Set if this page destination should just fit the bounding box. The default is false.
+ *
+ * @param fitBoundingBox A flag indicating if this should fit the bounding box.
+ */
+ public void setFitBoundingBox( boolean fitBoundingBox )
+ {
+ array.growToSize( 2 );
+ if( fitBoundingBox )
+ {
+ array.setName( 1, TYPE_BOUNDED );
+ }
+ else
+ {
+ array.setName( 1, TYPE );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.destination;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+
+/**
+ * This represents a destination to a page at an x,y coordinate with a zoom setting.
+ * The default x,y,z will be whatever is the current value in the viewer application and
+ * are not required.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDPageXYZDestination extends PDPageDestination
+{
+ /**
+ * The type of this destination.
+ */
+ protected static final String TYPE = "XYZ";
+
+ /**
+ * Default constructor.
+ *
+ */
+ public PDPageXYZDestination()
+ {
+ super();
+ array.growToSize(5);
+ array.setName( 1, TYPE );
+
+ }
+
+ /**
+ * Constructor from an existing destination array.
+ *
+ * @param arr The destination array.
+ */
+ public PDPageXYZDestination( COSArray arr )
+ {
+ super( arr );
+ }
+
+ /**
+ * Get the left x coordinate. A return value of -1 implies that the current x-coordinate
+ * will be used.
+ *
+ * @return The left x coordinate.
+ */
+ public int getLeft()
+ {
+ return array.getInt( 2 );
+ }
+
+ /**
+ * Set the left x-coordinate, a value of -1 implies that the current x-coordinate
+ * will be used.
+ * @param x The left x coordinate.
+ */
+ public void setLeft( int x )
+ {
+ array.growToSize( 3 );
+ if( x == -1 )
+ {
+ array.set( 2, (COSBase)null );
+ }
+ else
+ {
+ array.setInt( 2, x );
+ }
+ }
+
+ /**
+ * Get the top y coordinate. A return value of -1 implies that the current y-coordinate
+ * will be used.
+ *
+ * @return The top y coordinate.
+ */
+ public int getTop()
+ {
+ return array.getInt( 3 );
+ }
+
+ /**
+ * Set the top y-coordinate, a value of -1 implies that the current y-coordinate
+ * will be used.
+ * @param y The top ycoordinate.
+ */
+ public void setTop( int y )
+ {
+ array.growToSize( 4 );
+ if( y == -1 )
+ {
+ array.set( 3, (COSBase)null );
+ }
+ else
+ {
+ array.setInt( 3, y );
+ }
+ }
+
+ /**
+ * Get the zoom value. A return value of -1 implies that the current zoom
+ * will be used.
+ *
+ * @return The zoom value for the page.
+ */
+ public int getZoom()
+ {
+ return array.getInt( 4 );
+ }
+
+ /**
+ * Set the zoom value for the page, a value of -1 implies that the current zoom
+ * will be used.
+ * @param zoom The zoom value.
+ */
+ public void setZoom( int zoom )
+ {
+ array.growToSize( 5 );
+ if( zoom == -1 )
+ {
+ array.set( 4, (COSBase)null );
+ }
+ else
+ {
+ array.setInt( 4, zoom );
+ }
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The destination package allows destinations into a pdf document to be specified.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.outline;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This represents an outline in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDDocumentOutline extends PDOutlineNode
+{
+
+ /**
+ * Default Constructor.
+ */
+ public PDDocumentOutline()
+ {
+ super();
+ node.setName( "Type", "Outlines" );
+ }
+
+ /**
+ * Constructor for an existing document outline.
+ *
+ * @param dic The storage dictionary.
+ */
+ public PDDocumentOutline( COSDictionary dic )
+ {
+ super( dic );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.outline;
+
+import java.awt.Color;
+import java.io.IOException;
+import java.util.List;
+
+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.exceptions.OutlineNotLocalException;
+import org.apache.pdfbox.pdmodel.PDDestinationNameTreeNode;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction;
+import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionGoTo;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDNamedDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination;
+import org.apache.pdfbox.util.BitFlagHelper;
+
+/**
+ * This represents an outline in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class PDOutlineItem extends PDOutlineNode
+{
+
+ private static final int ITALIC_FLAG = 1;
+ private static final int BOLD_FLAG = 2;
+
+ /**
+ * Default Constructor.
+ */
+ public PDOutlineItem()
+ {
+ super();
+ }
+
+ /**
+ * Constructor for an existing outline item.
+ *
+ * @param dic The storage dictionary.
+ */
+ public PDOutlineItem( COSDictionary dic )
+ {
+ super( dic );
+ }
+
+ /**
+ * Insert a sibling after this node.
+ *
+ * @param item The item to insert.
+ */
+ public void insertSiblingAfter( PDOutlineItem item )
+ {
+ item.setParent( getParent() );
+ PDOutlineItem next = getNextSibling();
+ setNextSibling( item );
+ item.setPreviousSibling( this );
+ if( next != null )
+ {
+ item.setNextSibling( next );
+ next.setPreviousSibling( item );
+ }
+ updateParentOpenCount( 1 );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PDOutlineNode getParent()
+ {
+ return super.getParent();
+ }
+
+ /**
+ * Return the previous sibling or null if there is no sibling.
+ *
+ * @return The previous sibling.
+ */
+ public PDOutlineItem getPreviousSibling()
+ {
+ PDOutlineItem last = null;
+ COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( COSName.PREV );
+ if( lastDic != null )
+ {
+ last = new PDOutlineItem( lastDic );
+ }
+ return last;
+ }
+
+ /**
+ * Set the previous sibling, this will be maintained by this class.
+ *
+ * @param outlineNode The new previous sibling.
+ */
+ protected void setPreviousSibling( PDOutlineNode outlineNode )
+ {
+ node.setItem( COSName.PREV, outlineNode );
+ }
+
+ /**
+ * Return the next sibling or null if there is no next sibling.
+ *
+ * @return The next sibling.
+ */
+ public PDOutlineItem getNextSibling()
+ {
+ PDOutlineItem last = null;
+ COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( COSName.NEXT );
+ if( lastDic != null )
+ {
+ last = new PDOutlineItem( lastDic );
+ }
+ return last;
+ }
+
+ /**
+ * Set the next sibling, this will be maintained by this class.
+ *
+ * @param outlineNode The new next sibling.
+ */
+ protected void setNextSibling( PDOutlineNode outlineNode )
+ {
+ node.setItem( COSName.NEXT, outlineNode );
+ }
+
+ /**
+ * Get the title of this node.
+ *
+ * @return The title of this node.
+ */
+ public String getTitle()
+ {
+ return node.getString( COSName.TITLE );
+ }
+
+ /**
+ * Set the title for this node.
+ *
+ * @param title The new title for this node.
+ */
+ public void setTitle( String title )
+ {
+ node.setString( COSName.TITLE, title );
+ }
+
+ /**
+ * Get the page destination of this node.
+ *
+ * @return The page destination of this node.
+ * @throws IOException If there is an error creating the destination.
+ */
+ public PDDestination getDestination() throws IOException
+ {
+ return PDDestination.create( node.getDictionaryObject( COSName.DEST ) );
+ }
+
+ /**
+ * Set the page destination for this node.
+ *
+ * @param dest The new page destination for this node.
+ */
+ public void setDestination( PDDestination dest )
+ {
+ node.setItem( COSName.DEST, dest );
+ }
+
+ /**
+ * A convenience method that will create an XYZ destination using only the defaults.
+ *
+ * @param page The page to refer to.
+ */
+ public void setDestination( PDPage page )
+ {
+ PDPageXYZDestination dest = null;
+ if( page != null )
+ {
+ dest = new PDPageXYZDestination();
+ dest.setPage( page );
+ }
+ setDestination( dest );
+ }
+
+ /**
+ * This method will attempt to find the page in this PDF document that this outline points to.
+ * If the outline does not point to anything then this method will return null. If the outline
+ * is an action that is not a GoTo action then this methods will throw the OutlineNotLocationException
+ *
+ * @param doc The document to get the page from.
+ *
+ * @return The page that this outline will go to when activated or null if it does not point to anything.
+ * @throws IOException If there is an error when trying to find the page.
+ */
+ public PDPage findDestinationPage( PDDocument doc ) throws IOException
+ {
+ PDPage page = null;
+ PDDestination rawDest = getDestination();
+ if( rawDest == null )
+ {
+ PDAction outlineAction = getAction();
+ if( outlineAction instanceof PDActionGoTo )
+ {
+ rawDest = ((PDActionGoTo)outlineAction).getDestination();
+ }
+ else if( outlineAction == null )
+ {
+ //if the outline action is null then this outline does not refer
+ //to anything and we will just return null.
+ }
+ else
+ {
+ throw new OutlineNotLocalException( "Error: Outline does not reference a local page." );
+ }
+ }
+
+ PDPageDestination pageDest = null;
+ if( rawDest instanceof PDNamedDestination )
+ {
+ //if we have a named destination we need to lookup the PDPageDestination
+ PDNamedDestination namedDest = (PDNamedDestination)rawDest;
+ PDDocumentNameDictionary namesDict = doc.getDocumentCatalog().getNames();
+ if( namesDict != null )
+ {
+ PDDestinationNameTreeNode destsTree = namesDict.getDests();
+ if( destsTree != null )
+ {
+ pageDest = (PDPageDestination)destsTree.getValue( namedDest.getNamedDestination() );
+ }
+ }
+ }
+ else if( rawDest instanceof PDPageDestination)
+ {
+ pageDest = (PDPageDestination) rawDest;
+ }
+ else if( rawDest == null )
+ {
+ //if the destination is null then we will simply return a null page.
+ }
+ else
+ {
+ throw new IOException( "Error: Unknown destination type " + rawDest );
+ }
+
+ if( pageDest != null )
+ {
+ page = pageDest.getPage();
+ if( page == null )
+ {
+ int pageNumber = pageDest.getPageNumber();
+ if( pageNumber != -1 )
+ {
+ List allPages = doc.getDocumentCatalog().getAllPages();
+ page = (PDPage)allPages.get( pageNumber );
+ }
+ }
+ }
+
+ return page;
+ }
+
+ /**
+ * Get the action of this node.
+ *
+ * @return The action of this node.
+ */
+ public PDAction getAction()
+ {
+ return PDActionFactory.createAction( (COSDictionary)node.getDictionaryObject( COSName.A ) );
+ }
+
+ /**
+ * Set the action for this node.
+ *
+ * @param action The new action for this node.
+ */
+ public void setAction( PDAction action )
+ {
+ node.setItem( COSName.A, action );
+ }
+
+ /**
+ * Get the structure element of this node.
+ *
+ * @return The structure element of this node.
+ */
+ public PDStructureElement getStructureElement()
+ {
+ PDStructureElement se = null;
+ COSDictionary dic = (COSDictionary)node.getDictionaryObject( COSName.SE );
+ if( dic != null )
+ {
+ se = new PDStructureElement( dic );
+ }
+ return se;
+ }
+
+ /**
+ * Set the structure element for this node.
+ *
+ * @param structureElement The new structure element for this node.
+ */
+ public void setStructuredElement( PDStructureElement structureElement )
+ {
+ node.setItem( COSName.SE, structureElement );
+ }
+
+ /**
+ * Get the text color of this node. Default is black and this method
+ * will never return null.
+ *
+ * @return The structure element of this node.
+ */
+ public PDColorState getTextColor()
+ {
+ PDColorState retval = null;
+ COSArray csValues = (COSArray)node.getDictionaryObject( COSName.C );
+ if( csValues == null )
+ {
+ csValues = new COSArray();
+ csValues.growToSize( 3, new COSFloat( 0 ) );
+ node.setItem( COSName.C, csValues );
+ }
+ retval = new PDColorState(csValues);
+ retval.setColorSpace( PDDeviceRGB.INSTANCE );
+ return retval;
+ }
+
+ /**
+ * Set the text color for this node. The colorspace must be a PDDeviceRGB.
+ *
+ * @param textColor The text color for this node.
+ */
+ public void setTextColor( PDColorState textColor )
+ {
+ node.setItem( COSName.C, textColor.getCOSColorSpaceValue() );
+ }
+
+ /**
+ * Set the text color for this node. The colorspace must be a PDDeviceRGB.
+ *
+ * @param textColor The text color for this node.
+ */
+ public void setTextColor( Color textColor )
+ {
+ COSArray array = new COSArray();
+ array.add( new COSFloat( textColor.getRed()/255f));
+ array.add( new COSFloat( textColor.getGreen()/255f));
+ array.add( new COSFloat( textColor.getBlue()/255f));
+ node.setItem( COSName.C, array );
+ }
+
+ /**
+ * A flag telling if the text should be italic.
+ *
+ * @return The italic flag.
+ */
+ public boolean isItalic()
+ {
+ return BitFlagHelper.getFlag( node, COSName.F, ITALIC_FLAG );
+ }
+
+ /**
+ * Set the italic property of the text.
+ *
+ * @param italic The new italic flag.
+ */
+ public void setItalic( boolean italic )
+ {
+ BitFlagHelper.setFlag( node, COSName.F, ITALIC_FLAG, italic );
+ }
+
+ /**
+ * A flag telling if the text should be bold.
+ *
+ * @return The bold flag.
+ */
+ public boolean isBold()
+ {
+ return BitFlagHelper.getFlag( node, COSName.F, BOLD_FLAG );
+ }
+
+ /**
+ * Set the bold property of the text.
+ *
+ * @param bold The new bold flag.
+ */
+ public void setBold( boolean bold )
+ {
+ BitFlagHelper.setFlag( node, COSName.F, BOLD_FLAG, bold );
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.documentnavigation.outline;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This represents an node in an outline in a pdf document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDOutlineNode implements COSObjectable
+{
+ /**
+ * The dictionary for this node.
+ */
+ protected COSDictionary node;
+
+ /**
+ * Default Constructor.
+ */
+ public PDOutlineNode()
+ {
+ node = new COSDictionary();
+ }
+
+ /**
+ * Default Constructor.
+ *
+ * @param dict The dictionary storage.
+ */
+ public PDOutlineNode( COSDictionary dict)
+ {
+ node = dict;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return node;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSDictionary getCOSDictionary()
+ {
+ return node;
+ }
+
+ /**
+ * Get the parent of this object. This will either be a DocumentOutline or an OutlineItem.
+ *
+ * @return The parent of this object, or null if this is the document outline and there
+ * is no parent.
+ */
+ protected PDOutlineNode getParent()
+ {
+ PDOutlineNode retval = null;
+ COSDictionary parent = (COSDictionary)node.getDictionaryObject( "Parent", "P" );
+ if( parent != null )
+ {
+ if( parent.getDictionaryObject( "Parent", "P" ) == null )
+ {
+ retval = new PDDocumentOutline( parent );
+ }
+ else
+ {
+ retval = new PDOutlineItem( parent );
+ }
+ }
+
+ return retval;
+ }
+
+ /**
+ * Set the parent of this object, this is maintained by these objects and should not
+ * be called by any clients of PDFBox code.
+ *
+ * @param parent The parent of this object.
+ */
+ protected void setParent( PDOutlineNode parent )
+ {
+ node.setItem( "Parent", parent );
+ }
+
+ /**
+ * append a child node to this node.
+ *
+ * @param outlineNode The node to add.
+ */
+ public void appendChild( PDOutlineItem outlineNode )
+ {
+ outlineNode.setParent( this );
+ if( getFirstChild() == null )
+ {
+ int currentOpenCount = getOpenCount();
+ setFirstChild( outlineNode );
+ //1 for the the item we are adding;
+ int numberOfOpenNodesWeAreAdding = 1;
+ if( outlineNode.isNodeOpen() )
+ {
+ numberOfOpenNodesWeAreAdding += outlineNode.getOpenCount();
+ }
+ if( isNodeOpen() )
+ {
+ setOpenCount( currentOpenCount + numberOfOpenNodesWeAreAdding );
+ }
+ else
+ {
+ setOpenCount( currentOpenCount - numberOfOpenNodesWeAreAdding );
+ }
+ updateParentOpenCount( numberOfOpenNodesWeAreAdding );
+ }
+ else
+ {
+ PDOutlineItem previousLastChild = getLastChild();
+ previousLastChild.insertSiblingAfter( outlineNode );
+ }
+
+ PDOutlineItem lastNode = outlineNode;
+ while(lastNode.getNextSibling() != null)
+ {
+ lastNode = lastNode.getNextSibling();
+ }
+ setLastChild( lastNode );
+ }
+
+ /**
+ * Return the first child or null if there is no child.
+ *
+ * @return The first child.
+ */
+ public PDOutlineItem getFirstChild()
+ {
+ PDOutlineItem last = null;
+ COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( "First" );
+ if( lastDic != null )
+ {
+ last = new PDOutlineItem( lastDic );
+ }
+ return last;
+ }
+
+ /**
+ * Set the first child, this will be maintained by this class.
+ *
+ * @param outlineNode The new first child.
+ */
+ protected void setFirstChild( PDOutlineNode outlineNode )
+ {
+ node.setItem( "First", outlineNode );
+ }
+
+ /**
+ * Return the last child or null if there is no child.
+ *
+ * @return The last child.
+ */
+ public PDOutlineItem getLastChild()
+ {
+ PDOutlineItem last = null;
+ COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( "Last" );
+ if( lastDic != null )
+ {
+ last = new PDOutlineItem( lastDic );
+ }
+ return last;
+ }
+
+ /**
+ * Set the last child, this will be maintained by this class.
+ *
+ * @param outlineNode The new last child.
+ */
+ protected void setLastChild( PDOutlineNode outlineNode )
+ {
+ node.setItem( "Last", outlineNode );
+ }
+
+ /**
+ * Get the number of open nodes. Or a negative number if this node
+ * is closed. See PDF Reference for more details. This value
+ * is updated as you append children and siblings.
+ *
+ * @return The Count attribute of the outline dictionary.
+ */
+ public int getOpenCount()
+ {
+ return node.getInt( "Count", 0 );
+ }
+
+ /**
+ * Set the open count. This number is automatically managed for you
+ * when you add items to the outline.
+ *
+ * @param openCount The new open cound.
+ */
+ protected void setOpenCount( int openCount )
+ {
+ node.setInt( "Count", openCount );
+ }
+
+ /**
+ * This will set this node to be open when it is shown in the viewer. By default, when
+ * a new node is created it will be closed.
+ * This will do nothing if the node is already open.
+ */
+ public void openNode()
+ {
+ //if the node is already open then do nothing.
+ if( !isNodeOpen() )
+ {
+ int openChildrenCount = 0;
+ PDOutlineItem currentChild = getFirstChild();
+ while( currentChild != null )
+ {
+ //first increase by one for the current child
+ openChildrenCount++;
+ //then increase by the number of open nodes the child has
+ if( currentChild.isNodeOpen() )
+ {
+ openChildrenCount += currentChild.getOpenCount();
+ }
+ currentChild = currentChild.getNextSibling();
+ }
+ setOpenCount( openChildrenCount );
+ updateParentOpenCount( openChildrenCount );
+ }
+ }
+
+ /**
+ * Close this node.
+ *
+ */
+ public void closeNode()
+ {
+ //if the node is already closed then do nothing.
+ if( isNodeOpen() )
+ {
+ int openCount = getOpenCount();
+ updateParentOpenCount( -openCount );
+ setOpenCount( -openCount );
+ }
+ }
+
+ /**
+ * Node is open if the open count is greater than zero.
+ * @return true if this node is open.
+ */
+ public boolean isNodeOpen()
+ {
+ return getOpenCount() > 0;
+ }
+
+ /**
+ * The count parameter needs to be updated when you add or remove elements to
+ * the outline. When you add an element at a lower level then you need to
+ * increase all of the parents.
+ *
+ * @param amount The amount to update by.
+ */
+ protected void updateParentOpenCount( int amount )
+ {
+ PDOutlineNode parent = getParent();
+ if( parent != null )
+ {
+ int currentCount = parent.getOpenCount();
+ //if the currentCount is negative or it is absent then
+ //we will treat it as negative. The default is to be negative.
+ boolean negative = currentCount < 0 ||
+ parent.getCOSDictionary().getDictionaryObject( "Count" ) == null;
+ currentCount = Math.abs( currentCount );
+ currentCount += amount;
+ if( negative )
+ {
+ currentCount = -currentCount;
+ }
+ parent.setOpenCount( currentCount );
+ //recursively call parent to update count, but the parents count is only
+ //updated if this is an open node
+ if( !negative )
+ {
+ parent.updateParentOpenCount( amount );
+ }
+ }
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The outline package allows for a PDF outline(bookmarks) to be created.
+</body>
+</html>
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+A package to allow access to document level navigation within a PDF document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.interactive.form;
+
+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.COSString;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDResources;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.fdf.FDFDictionary;
+import org.apache.pdfbox.pdmodel.fdf.FDFDocument;
+import org.apache.pdfbox.pdmodel.fdf.FDFCatalog;
+import org.apache.pdfbox.pdmodel.fdf.FDFField;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class represents the acroform of a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class PDAcroForm implements COSObjectable
+{
+ private COSDictionary acroForm;
+ private PDDocument document;
+
+ private Map fieldCache;
+
+ /**
+ * Constructor.
+ *
+ * @param doc The document that this form is part of.
+ */
+ public PDAcroForm( PDDocument doc )
+ {
+ document = doc;
+ acroForm = new COSDictionary();
+ COSArray fields = new COSArray();
+ acroForm.setItem( COSName.getPDFName( "Fields" ), fields );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param doc The document that this form is part of.
+ * @param form The existing acroForm.
+ */
+ public PDAcroForm( PDDocument doc, COSDictionary form )
+ {
+ document = doc;
+ acroForm = form;
+ }
+
+ /**
+ * This will get the document associated with this form.
+ *
+ * @return The PDF document.
+ */
+ public PDDocument getDocument()
+ {
+ return document;
+ }
+
+ /**
+ * This will get the dictionary that this form wraps.
+ *
+ * @return The dictionary for this form.
+ */
+ public COSDictionary getDictionary()
+ {
+ return acroForm;
+ }
+
+ /**
+ * This method will import an entire FDF document into the PDF document
+ * that this acroform is part of.
+ *
+ * @param fdf The FDF document to import.
+ *
+ * @throws IOException If there is an error doing the import.
+ */
+ public void importFDF( FDFDocument fdf ) throws IOException
+ {
+ List fields = fdf.getCatalog().getFDF().getFields();
+ if( fields != null )
+ {
+ for( int i=0; i<fields.size(); i++ )
+ {
+ FDFField fdfField = (FDFField)fields.get( i );
+ PDField docField = getField( fdfField.getPartialFieldName() );
+ if( docField != null )
+ {
+ docField.importFDF( fdfField );
+ }
+ }
+ }
+ }
+
+ /**
+ * This will export all FDF form data.
+ *
+ * @return An FDF document used to export the document.
+ * @throws IOException If there is an error when exporting the document.
+ */
+ public FDFDocument exportFDF() throws IOException
+ {
+ FDFDocument fdf = new FDFDocument();
+ FDFCatalog catalog = fdf.getCatalog();
+ FDFDictionary fdfDict = new FDFDictionary();
+ catalog.setFDF( fdfDict );
+
+ List fdfFields = new ArrayList();
+ List fields = getFields();
+ Iterator fieldIter = fields.iterator();
+ while( fieldIter.hasNext() )
+ {
+ PDField docField = (PDField)fieldIter.next();
+ addFieldAndChildren( docField, fdfFields );
+ }
+ fdfDict.setID( document.getDocument().getDocumentID() );
+ if( fdfFields.size() > 0 )
+ {
+ fdfDict.setFields( fdfFields );
+ }
+ return fdf;
+ }
+
+ private void addFieldAndChildren( PDField docField, List fdfFields ) throws IOException
+ {
+ Object fieldValue = docField.getValue();
+ FDFField fdfField = new FDFField();
+ fdfField.setPartialFieldName( docField.getPartialName() );
+ fdfField.setValue( fieldValue );
+ List kids = docField.getKids();
+ List childFDFFields = new ArrayList();
+ if( kids != null )
+ {
+
+ for( int i=0; i<kids.size(); i++ )
+ {
+ addFieldAndChildren( (PDField)kids.get( i ), childFDFFields );
+ }
+ if( childFDFFields.size() > 0 )
+ {
+ fdfField.setKids( childFDFFields );
+ }
+ }
+ if( fieldValue != null || childFDFFields.size() > 0 )
+ {
+ fdfFields.add( fdfField );
+ }
+ }
+
+ /**
+ * This will return all of the fields in the document. The type
+ * will be a org.apache.pdfbox.pdmodel.field.PDField.
+ *
+ * @return A list of all the fields.
+ * @throws IOException If there is an error while getting the list of fields.
+ */
+ public List getFields() throws IOException
+ {
+ List retval = null;
+ COSArray fields =
+ (COSArray) acroForm.getDictionaryObject(
+ COSName.getPDFName("Fields"));
+
+ if( fields != null )
+ {
+ List actuals = new ArrayList();
+ for (int i = 0; i < fields.size(); i++)
+ {
+ COSDictionary element = (COSDictionary) fields.getObject(i);
+ if (element != null)
+ {
+ PDField field = PDFieldFactory.createField( this, element );
+ if( field != null )
+ {
+ actuals.add(field);
+ }
+ }
+ }
+ retval = new COSArrayList( actuals, fields );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the fields that are part of this AcroForm.
+ *
+ * @param fields The fields that are part of this form.
+ */
+ public void setFields( List fields )
+ {
+ acroForm.setItem( "Fields", COSArrayList.converterToCOSArray( fields ));
+ }
+
+ /**
+ * This will tell this form to cache the fields into a Map structure
+ * for fast access via the getField method. The default is false. You would
+ * want this to be false if you were changing the COSDictionary behind the scenes,
+ * otherwise setting this to true is acceptable.
+ *
+ * @param cache A boolean telling if we should cache the fields.
+ * @throws IOException If there is an error while caching the fields.
+ */
+ public void setCacheFields( boolean cache ) throws IOException
+ {
+ if( cache )
+ {
+ fieldCache = new HashMap();
+ List fields = getFields();
+ Iterator fieldIter = fields.iterator();
+ while( fieldIter.hasNext() )
+ {
+ PDField next = (PDField)fieldIter.next();
+ fieldCache.put( next.getFullyQualifiedName(), next );
+ }
+ }
+ else
+ {
+ fieldCache = null;
+ }
+ }
+
+ /**
+ * This will tell if this acro form is caching the fields.
+ *
+ * @return true if the fields are being cached.
+ */
+ public boolean isCachingFields()
+ {
+ return fieldCache != null;
+ }
+
+ /**
+ * This will get a field by name, possibly using the cache if setCache is true.
+ *
+ * @param name The name of the field to get.
+ *
+ * @return The field with that name of null if one was not found.
+ *
+ * @throws IOException If there is an error getting the field type.
+ */
+ public PDField getField( String name ) throws IOException
+ {
+ PDField retval = null;
+ if( fieldCache != null )
+ {
+ retval = (PDField)fieldCache.get( name );
+ }
+ else
+ {
+ String[] nameSubSection = name.split( "\\." );
+ COSArray fields =
+ (COSArray) acroForm.getDictionaryObject(
+ COSName.getPDFName("Fields"));
+
+ for (int i = 0; i < fields.size() && retval == null; i++)
+ {
+ COSDictionary element = (COSDictionary) fields.getObject(i);
+ if( element != null )
+ {
+ COSString fieldName =
+ (COSString)element.getDictionaryObject( COSName.getPDFName( "T" ) );
+ if( fieldName.getString().equals( name ) ||
+ fieldName.getString().equals( nameSubSection[0] ) )
+ {
+ PDField root = PDFieldFactory.createField( this, element );
+
+ if( nameSubSection.length > 1 )
+ {
+ PDField kid = root.findKid( nameSubSection, 1 );
+ if( kid != null )
+ {
+ retval = kid;
+ }
+ else
+ {
+ retval = root;
+ }
+ }
+ else
+ {
+ retval = root;
+ }
+ }
+ }
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will get the default resources for the acro form.
+ *
+ * @return The default resources.
+ */
+ public PDResources getDefaultResources()
+ {
+ PDResources retval = null;
+ COSDictionary dr = (COSDictionary)acroForm.getDictionaryObject( COSName.getPDFName( "DR" ) );
+ if( dr != null )
+ {
+ retval = new PDResources( dr );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the default resources for the acroform.
+ *
+ * @param dr The new default resources.
+ */
+ public void setDefaultResources( PDResources dr )
+ {
+ COSDictionary drDict = null;
+ if( dr != null )
+ {
+ drDict = dr.getCOSDictionary();
+ }
+ acroForm.setItem( COSName.getPDFName( "DR" ), drDict );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return acroForm;
+ }
+
+ /**
+ * Get the XFA resource, the XFA resource is only used for PDF 1.5+ forms.
+ *
+ * @return The xfa resource or null if it does not exist.
+ */
+ public PDXFA getXFA()
+ {
+ PDXFA xfa = null;
+ COSBase base = acroForm.getDictionaryObject( "XFA" );
+ if( base != null )
+ {
+ xfa = new PDXFA( base );
+ }
+ return xfa;
+ }
+
+ /**
+ * Set the XFA resource, this is only used for PDF 1.5+ forms.
+ *
+ * @param xfa The xfa resource.
+ */
+ public void setXFA( PDXFA xfa )
+ {
+ acroForm.setItem( "XFA", xfa );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+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.cos.COSString;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
+
+import org.apache.pdfbox.pdmodel.PDResources;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
+import org.apache.pdfbox.pdmodel.font.PDSimpleFont;
+
+import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
+
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * This one took me a while, but i'm proud to say that it handles
+ * the appearance of a textbox. This allows you to apply a value to
+ * a field in the document and handle the appearance so that the
+ * value is actually visible too.
+ * The problem was described by Ben Litchfield, the author of the
+ * example: org.apache.pdfbox.examlpes.fdf.ImportFDF. So Ben, here is the
+ * solution.
+ *
+ * @author sug
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.20 $
+ */
+public class PDAppearance
+{
+ private PDVariableText parent;
+
+ private String value;
+ private COSString defaultAppearance;
+
+ private PDAcroForm acroForm;
+ private List<COSObjectable> widgets = new ArrayList<COSObjectable>();
+
+
+ /**
+ * Constructs a COSAppearnce from the given field.
+ *
+ * @param theAcroForm the acro form that this field is part of.
+ * @param field the field which you wish to control the appearance of
+ * @throws IOException If there is an error creating the appearance.
+ */
+ public PDAppearance( PDAcroForm theAcroForm, PDVariableText field ) throws IOException
+ {
+ acroForm = theAcroForm;
+ parent = field;
+
+ widgets = field.getKids();
+ if( widgets == null )
+ {
+ widgets = new ArrayList<COSObjectable>();
+ widgets.add( field.getWidget() );
+ }
+
+ defaultAppearance = getDefaultAppearance();
+
+
+ }
+
+ /**
+ * Returns the default apperance of a textbox. If the textbox
+ * does not have one, then it will be taken from the AcroForm.
+ * @return The DA element
+ */
+ private COSString getDefaultAppearance()
+ {
+
+ COSString dap = parent.getDefaultAppearance();
+ if (dap == null)
+ {
+ COSArray kids = (COSArray)parent.getDictionary().getDictionaryObject( COSName.KIDS );
+ if( kids != null && kids.size() > 0 )
+ {
+ COSDictionary firstKid = (COSDictionary)kids.getObject( 0 );
+ dap = (COSString)firstKid.getDictionaryObject( COSName.DA );
+ }
+ if( dap == null )
+ {
+ dap = (COSString) acroForm.getDictionary().getDictionaryObject(COSName.DA);
+ }
+ }
+ return dap;
+ }
+
+ private int getQ()
+ {
+ int q = parent.getQ();
+ if( parent.getDictionary().getDictionaryObject( COSName.Q ) == null )
+ {
+ COSArray kids = (COSArray)parent.getDictionary().getDictionaryObject( COSName.KIDS );
+ if( kids != null && kids.size() > 0 )
+ {
+ COSDictionary firstKid = (COSDictionary)kids.getObject( 0 );
+ COSNumber qNum = (COSNumber)firstKid.getDictionaryObject( COSName.Q );
+ if( qNum != null )
+ {
+ q = qNum.intValue();
+ }
+ }
+ }
+ return q;
+ }
+
+ /**
+ * Extracts the original appearance stream into a list of tokens.
+ *
+ * @return The tokens in the original appearance stream
+ */
+ private List getStreamTokens( PDAppearanceStream appearanceStream ) throws IOException
+ {
+ List tokens = null;
+ if( appearanceStream != null )
+ {
+ tokens = getStreamTokens( appearanceStream.getStream() );
+ }
+ return tokens;
+ }
+
+ private List getStreamTokens( COSString string ) throws IOException
+ {
+ PDFStreamParser parser;
+
+ List tokens = null;
+ if( string != null )
+ {
+ ByteArrayInputStream stream = new ByteArrayInputStream( string.getBytes() );
+ parser = new PDFStreamParser( stream, acroForm.getDocument().getDocument().getScratchFile() );
+ parser.parse();
+ tokens = parser.getTokens();
+ }
+ return tokens;
+ }
+
+ private List getStreamTokens( COSStream stream ) throws IOException
+ {
+ PDFStreamParser parser;
+
+ List tokens = null;
+ if( stream != null )
+ {
+ parser = new PDFStreamParser( stream );
+ parser.parse();
+ tokens = parser.getTokens();
+ }
+ return tokens;
+ }
+
+ /**
+ * Tests if the apperance stream already contains content.
+ *
+ * @return true if it contains any content
+ */
+ private boolean containsMarkedContent( List stream )
+ {
+ return stream.contains( PDFOperator.getOperator( "BMC" ) );
+ }
+
+ /**
+ * This is the public method for setting the appearance stream.
+ *
+ * @param apValue the String value which the apperance shoud represent
+ *
+ * @throws IOException If there is an error creating the stream.
+ */
+ public void setAppearanceValue(String apValue) throws IOException
+ {
+ // MulitLine check and set
+ if ( parent.isMultiline() && apValue.indexOf('\n') != -1 )
+ {
+ apValue = convertToMultiLine( apValue );
+ }
+
+ value = apValue;
+ Iterator<COSObjectable> widgetIter = widgets.iterator();
+ while( widgetIter.hasNext() )
+ {
+ COSObjectable next = widgetIter.next();
+ PDField field = null;
+ PDAnnotationWidget widget = null;
+ if( next instanceof PDField )
+ {
+ field = (PDField)next;
+ widget = field.getWidget();
+ }
+ else
+ {
+ widget = (PDAnnotationWidget)next;
+ }
+ PDFormFieldAdditionalActions actions = null;
+ if( field != null )
+ {
+ actions = field.getActions();
+ }
+ if( actions != null &&
+ actions.getF() != null &&
+ widget.getDictionary().getDictionaryObject( COSName.AP ) ==null)
+ {
+ //do nothing because the field will be formatted by acrobat
+ //when it is opened. See FreedomExpressions.pdf for an example of this.
+ }
+ else
+ {
+
+ PDAppearanceDictionary appearance = widget.getAppearance();
+ if( appearance == null )
+ {
+ appearance = new PDAppearanceDictionary();
+ widget.setAppearance( appearance );
+ }
+
+ Map normalAppearance = appearance.getNormalAppearance();
+ PDAppearanceStream appearanceStream = (PDAppearanceStream)normalAppearance.get( "default" );
+ if( appearanceStream == null )
+ {
+ COSStream cosStream = new COSStream( acroForm.getDocument().getDocument().getScratchFile() );
+ appearanceStream = new PDAppearanceStream( cosStream );
+ appearanceStream.setBoundingBox( widget.getRectangle().createRetranslatedRectangle() );
+ appearance.setNormalAppearance( appearanceStream );
+ }
+
+ List tokens = getStreamTokens( appearanceStream );
+ List daTokens = getStreamTokens( getDefaultAppearance() );
+ PDFont pdFont = getFontAndUpdateResources( tokens, appearanceStream );
+
+ if (!containsMarkedContent( tokens ))
+ {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ //BJL 9/25/2004 Must prepend existing stream
+ //because it might have operators to draw things like
+ //rectangles and such
+ ContentStreamWriter writer = new ContentStreamWriter( output );
+ writer.writeTokens( tokens );
+
+ output.write( " /Tx BMC\n".getBytes("ISO-8859-1") );
+ insertGeneratedAppearance( widget, output, pdFont, tokens, appearanceStream );
+ output.write( " EMC".getBytes("ISO-8859-1") );
+ writeToStream( output.toByteArray(), appearanceStream );
+ }
+ else
+ {
+ if( tokens != null )
+ {
+ if( daTokens != null )
+ {
+ int bmcIndex = tokens.indexOf( PDFOperator.getOperator( "BMC" ));
+ int emcIndex = tokens.indexOf( PDFOperator.getOperator( "EMC" ));
+ if( bmcIndex != -1 && emcIndex != -1 &&
+ emcIndex == bmcIndex+1 )
+ {
+ //if the EMC immediately follows the BMC index then should
+ //insert the daTokens inbetween the two markers.
+ tokens.addAll( emcIndex, daTokens );
+ }
+ }
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ ContentStreamWriter writer = new ContentStreamWriter( output );
+ float fontSize = calculateFontSize( pdFont, appearanceStream.getBoundingBox(), tokens, null );
+ boolean foundString = false;
+ for( int i=0; i<tokens.size(); i++ )
+ {
+ if( tokens.get( i ) instanceof COSString )
+ {
+ foundString = true;
+ COSString drawnString =((COSString)tokens.get(i));
+ drawnString.reset();
+ drawnString.append( apValue.getBytes("ISO-8859-1") );
+ }
+ }
+ int setFontIndex = tokens.indexOf( PDFOperator.getOperator( "Tf" ));
+ tokens.set( setFontIndex-1, new COSFloat( fontSize ) );
+ if( foundString )
+ {
+ writer.writeTokens( tokens );
+ }
+ else
+ {
+ int bmcIndex = tokens.indexOf( PDFOperator.getOperator( "BMC" ) );
+ int emcIndex = tokens.indexOf( PDFOperator.getOperator( "EMC" ) );
+
+ if( bmcIndex != -1 )
+ {
+ writer.writeTokens( tokens, 0, bmcIndex+1 );
+ }
+ else
+ {
+ writer.writeTokens( tokens );
+ }
+ output.write( "\n".getBytes("ISO-8859-1") );
+ insertGeneratedAppearance( widget, output,
+ pdFont, tokens, appearanceStream );
+ if( emcIndex != -1 )
+ {
+ writer.writeTokens( tokens, emcIndex, tokens.size() );
+ }
+ }
+ writeToStream( output.toByteArray(), appearanceStream );
+ }
+ else
+ {
+ //hmm?
+ }
+ }
+ }
+ }
+ }
+
+ private void insertGeneratedAppearance( PDAnnotationWidget fieldWidget, OutputStream output,
+ PDFont pdFont, List tokens, PDAppearanceStream appearanceStream ) throws IOException
+ {
+ PrintWriter printWriter = new PrintWriter( output, true );
+ float fontSize = 0.0f;
+ PDRectangle boundingBox = null;
+ boundingBox = appearanceStream.getBoundingBox();
+ if( boundingBox == null )
+ {
+ boundingBox = fieldWidget.getRectangle().createRetranslatedRectangle();
+ }
+ printWriter.println( "BT" );
+ if( defaultAppearance != null )
+ {
+ String daString = defaultAppearance.getString();
+ PDFStreamParser daParser = new PDFStreamParser(new ByteArrayInputStream( daString.getBytes("ISO-8859-1") ), null );
+ daParser.parse();
+ List<Object> daTokens = daParser.getTokens();
+ fontSize = calculateFontSize( pdFont, boundingBox, tokens, daTokens );
+ int fontIndex = daTokens.indexOf( PDFOperator.getOperator( "Tf" ) );
+ if(fontIndex != -1 )
+ {
+ daTokens.set( fontIndex-1, new COSFloat( fontSize ) );
+ }
+ ContentStreamWriter daWriter = new ContentStreamWriter(output);
+ daWriter.writeTokens( daTokens );
+ }
+ printWriter.println( getTextPosition( boundingBox, pdFont, fontSize, tokens ) );
+ int q = getQ();
+ if( q == PDTextbox.QUADDING_LEFT )
+ {
+ //do nothing because left is default
+ }
+ else if( q == PDTextbox.QUADDING_CENTERED ||
+ q == PDTextbox.QUADDING_RIGHT )
+ {
+ float fieldWidth = boundingBox.getWidth();
+ float stringWidth = (pdFont.getStringWidth( value )/1000)*fontSize;
+ float adjustAmount = fieldWidth - stringWidth - 4;
+
+ if( q == PDTextbox.QUADDING_CENTERED )
+ {
+ adjustAmount = adjustAmount/2.0f;
+ }
+
+ printWriter.println( adjustAmount + " 0 Td" );
+ }
+ else
+ {
+ throw new IOException( "Error: Unknown justification value:" + q );
+ }
+ printWriter.println("(" + value + ") Tj");
+ printWriter.println("ET" );
+ printWriter.flush();
+ }
+
+ private PDFont getFontAndUpdateResources( List tokens, PDAppearanceStream appearanceStream ) throws IOException
+ {
+
+ PDFont retval = null;
+ PDResources streamResources = appearanceStream.getResources();
+ PDResources formResources = acroForm.getDefaultResources();
+ if( formResources != null )
+ {
+ if( streamResources == null )
+ {
+ streamResources = new PDResources();
+ appearanceStream.setResources( streamResources );
+ }
+
+ COSString da = getDefaultAppearance();
+ if( da != null )
+ {
+ String data = da.getString();
+ PDFStreamParser streamParser = new PDFStreamParser(
+ new ByteArrayInputStream( data.getBytes("ISO-8859-1") ), null );
+ streamParser.parse();
+ tokens = streamParser.getTokens();
+ }
+
+ int setFontIndex = tokens.indexOf( PDFOperator.getOperator( "Tf" ));
+ COSName cosFontName = (COSName)tokens.get( setFontIndex-2 );
+ String fontName = cosFontName.getName();
+ retval = (PDFont)streamResources.getFonts().get( fontName );
+ if( retval == null )
+ {
+ retval = (PDFont)formResources.getFonts().get( fontName );
+ streamResources.getFonts().put( fontName, retval );
+ }
+ }
+ return retval;
+ }
+
+ private String convertToMultiLine( String line )
+ {
+ int currIdx = 0;
+ int lastIdx = 0;
+ StringBuffer result = new StringBuffer(line.length() + 64);
+ while( (currIdx = line.indexOf('\n',lastIdx )) > -1 )
+ {
+ result.append(line.substring(lastIdx,currIdx));
+ result.append(" ) Tj\n0 -13 Td\n(");
+ lastIdx = currIdx + 1;
+ }
+ result.append(line.substring(lastIdx));
+ return result.toString();
+ }
+
+ /**
+ * Writes the stream to the actual stream in the COSStream.
+ *
+ * @throws IOException If there is an error writing to the stream
+ */
+ private void writeToStream( byte[] data, PDAppearanceStream appearanceStream ) throws IOException
+ {
+ OutputStream out = appearanceStream.getStream().createUnfilteredStream();
+ out.write( data );
+ out.flush();
+ }
+
+
+ /**
+ * w in an appearance stream represents the lineWidth.
+ * @return the linewidth
+ */
+ private float getLineWidth( List tokens )
+ {
+
+ float retval = 1;
+ if( tokens != null )
+ {
+ int btIndex = tokens.indexOf(PDFOperator.getOperator( "BT" ));
+ int wIndex = tokens.indexOf(PDFOperator.getOperator( "w" ));
+ //the w should only be used if it is before the first BT.
+ if( (wIndex > 0) && (wIndex < btIndex) )
+ {
+ retval = ((COSNumber)tokens.get(wIndex-1)).floatValue();
+ }
+ }
+ return retval;
+ }
+
+ private PDRectangle getSmallestDrawnRectangle( PDRectangle boundingBox, List tokens )
+ {
+ PDRectangle smallest = boundingBox;
+ for( int i=0; i<tokens.size(); i++ )
+ {
+ Object next = tokens.get( i );
+ if( next == PDFOperator.getOperator( "re" ) )
+ {
+ COSNumber x = (COSNumber)tokens.get( i-4 );
+ COSNumber y = (COSNumber)tokens.get( i-3 );
+ COSNumber width = (COSNumber)tokens.get( i-2 );
+ COSNumber height = (COSNumber)tokens.get( i-1 );
+ PDRectangle potentialSmallest = new PDRectangle();
+ potentialSmallest.setLowerLeftX( x.floatValue() );
+ potentialSmallest.setLowerLeftY( y.floatValue() );
+ potentialSmallest.setUpperRightX( x.floatValue() + width.floatValue() );
+ potentialSmallest.setUpperRightY( y.floatValue() + height.floatValue() );
+ if( smallest == null ||
+ smallest.getLowerLeftX() < potentialSmallest.getLowerLeftX() ||
+ smallest.getUpperRightY() > potentialSmallest.getUpperRightY() )
+ {
+ smallest = potentialSmallest;
+ }
+
+ }
+ }
+ return smallest;
+ }
+
+ /**
+ * My "not so great" method for calculating the fontsize.
+ * It does not work superb, but it handles ok.
+ * @return the calculated font-size
+ *
+ * @throws IOException If there is an error getting the font height.
+ */
+ private float calculateFontSize( PDFont pdFont, PDRectangle boundingBox, List tokens, List daTokens )
+ throws IOException
+ {
+ float fontSize = 0;
+ if( daTokens != null )
+ {
+ //daString looks like "BMC /Helv 3.4 Tf EMC"
+
+ int fontIndex = daTokens.indexOf( PDFOperator.getOperator( "Tf" ) );
+ if(fontIndex != -1 )
+ {
+ fontSize = ((COSNumber)daTokens.get(fontIndex-1)).floatValue();
+ }
+ }
+
+ float widthBasedFontSize = Float.MAX_VALUE;
+
+ if( parent.doNotScroll() )
+ {
+ //if we don't scroll then we will shrink the font to fit into the text area.
+ float widthAtFontSize1 = pdFont.getStringWidth( value )/1000.f;
+ float availableWidth = getAvailableWidth(boundingBox, getLineWidth(tokens));
+ widthBasedFontSize = availableWidth / widthAtFontSize1;
+ }
+ else if( fontSize == 0 )
+ {
+ float lineWidth = getLineWidth( tokens );
+ float stringWidth = pdFont.getStringWidth( value );
+ float height = 0;
+ if( pdFont instanceof PDSimpleFont )
+ {
+ height = ((PDSimpleFont)pdFont).getFontDescriptor().getFontBoundingBox().getHeight();
+ }
+ else
+ {
+ //now much we can do, so lets assume font is square and use width
+ //as the height
+ height = pdFont.getAverageFontWidth();
+ }
+ height = height/1000f;
+
+ float availHeight = getAvailableHeight( boundingBox, lineWidth );
+ fontSize = Math.min((availHeight/height), widthBasedFontSize);
+ }
+ return fontSize;
+ }
+
+ /**
+ * Calculates where to start putting the text in the box.
+ * The positioning is not quite as accurate as when Acrobat
+ * places the elements, but it works though.
+ *
+ * @return the sting for representing the start position of the text
+ *
+ * @throws IOException If there is an error calculating the text position.
+ */
+ private String getTextPosition( PDRectangle boundingBox, PDFont pdFont, float fontSize, List tokens )
+ throws IOException
+ {
+ float lineWidth = getLineWidth( tokens );
+ float pos = 0.0f;
+ if(parent.isMultiline())
+ {
+ int rows = (int) (getAvailableHeight( boundingBox, lineWidth ) / ((int) fontSize));
+ pos = ((rows)*fontSize)-fontSize;
+ }
+ else
+ {
+ if( pdFont instanceof PDSimpleFont )
+ {
+ //BJL 9/25/2004
+ //This algorithm is a little bit of black magic. It does
+ //not appear to be documented anywhere. Through examining a few
+ //PDF documents and the value that Acrobat places in there I
+ //have determined that the below method of computing the position
+ //is correct for certain documents, but maybe not all. It does
+ //work f1040ez.pdf and Form_1.pdf
+ PDFontDescriptor fd = ((PDSimpleFont)pdFont).getFontDescriptor();
+ float bBoxHeight = boundingBox.getHeight();
+ float fontHeight = fd.getFontBoundingBox().getHeight() + 2 * fd.getDescent();
+ fontHeight = (fontHeight/1000) * fontSize;
+ pos = (bBoxHeight - fontHeight)/2;
+ }
+ else
+ {
+ throw new IOException( "Error: Don't know how to calculate the position for non-simple fonts" );
+ }
+ }
+ PDRectangle innerBox = getSmallestDrawnRectangle( boundingBox, tokens );
+ float xInset = 2+ 2*(boundingBox.getWidth() - innerBox.getWidth());
+ return Math.round(xInset) + " "+ pos + " Td";
+ }
+
+ /**
+ * calculates the available width of the box.
+ * @return the calculated available width of the box
+ */
+ private float getAvailableWidth( PDRectangle boundingBox, float lineWidth )
+ {
+ return boundingBox.getWidth() - 2 * lineWidth;
+ }
+
+ /**
+ * calculates the available height of the box.
+ * @return the calculated available height of the box
+ */
+ private float getAvailableHeight( PDRectangle boundingBox, float lineWidth )
+ {
+ return boundingBox.getHeight() - 2 * lineWidth;
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import java.io.IOException;
+
+/**
+ * A class for handling the PDF field as a checkbox.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author sug
+ * @version $Revision: 1.11 $
+ */
+public class PDCheckbox extends PDChoiceButton
+{
+ private static final COSName KEY = COSName.getPDFName("AS");
+ private static final COSName OFF_VALUE = COSName.getPDFName("Off");
+
+ private COSName value;
+
+ /**
+ * @see PDField#PDField(PDAcroForm,COSDictionary)
+ *
+ * @param theAcroForm The acroForm for this field.
+ * @param field The checkbox field dictionary
+ */
+ public PDCheckbox( PDAcroForm theAcroForm, COSDictionary field)
+ {
+ super( theAcroForm, field);
+ COSDictionary ap = (COSDictionary) field.getDictionaryObject(COSName.getPDFName("AP"));
+ if( ap != null )
+ {
+ COSBase n = ap.getDictionaryObject(COSName.getPDFName("N"));
+
+ if( n instanceof COSDictionary )
+ {
+ for( COSName name : ((COSDictionary)n).keySet() )
+ {
+ if( !name.equals( OFF_VALUE ))
+ {
+ value = name;
+ }
+ }
+
+ }
+ }
+ else
+ {
+ value = (COSName)getDictionary().getDictionaryObject( "V" );
+ }
+ }
+
+ /**
+ * This will tell if this radio button is currently checked or not.
+ *
+ * @return true If the radio button is checked.
+ */
+ public boolean isChecked()
+ {
+ boolean retval = false;
+ String onValue = getOnValue();
+ COSName radioValue = (COSName)getDictionary().getDictionaryObject( KEY );
+ if( radioValue != null && value != null && radioValue.getName().equals( onValue ) )
+ {
+ retval = true;
+ }
+
+ return retval;
+ }
+
+ /**
+ * Checks the radiobutton.
+ */
+ public void check()
+ {
+ getDictionary().setItem(KEY, value);
+ }
+
+ /**
+ * Unchecks the radiobutton.
+ */
+ public void unCheck()
+ {
+ getDictionary().setItem(KEY, OFF_VALUE);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setValue(String newValue)
+ {
+ getDictionary().setName( "V", newValue );
+ if( newValue == null )
+ {
+ getDictionary().setItem( KEY, OFF_VALUE );
+ }
+ else
+ {
+ getDictionary().setName( KEY, newValue );
+ }
+ }
+
+ /**
+ * This will get the value of the radio button.
+ *
+ * @return The value of the radio button.
+ */
+ public String getOffValue()
+ {
+ return OFF_VALUE.getName();
+ }
+
+ /**
+ * This will get the value of the radio button.
+ *
+ * @return The value of the radio button.
+ */
+ public String getOnValue()
+ {
+ String retval = null;
+ COSDictionary ap = (COSDictionary) getDictionary().getDictionaryObject(COSName.getPDFName("AP"));
+ COSBase n = ap.getDictionaryObject(COSName.getPDFName("N"));
+
+ //N can be a COSDictionary or a COSStream
+ if( n instanceof COSDictionary )
+ {
+ for( COSName key :((COSDictionary)n).keySet() )
+ {
+ if( !key.equals( OFF_VALUE) )
+ {
+ retval = key.getName();
+ }
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * getValue gets the fields value to as a string.
+ *
+ * @return The string value of this field.
+ *
+ * @throws IOException If there is an error getting the value.
+ */
+ public String getValue() throws IOException
+ {
+ return getDictionary().getNameAsString( "V" );
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+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.common.COSArrayList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This holds common functionality for check boxes and radio buttons.
+ *
+ * @author sug
+ * @version $Revision: 1.4 $
+ */
+public abstract class PDChoiceButton extends PDField
+{
+
+ /**
+ * @see PDField#PDField(PDAcroForm,org.apache.pdfbox.cos.COSDictionary)
+ *
+ * @param theAcroForm The acroForm for this field.
+ * @param field The field for this button.
+ */
+ public PDChoiceButton( PDAcroForm theAcroForm, COSDictionary field)
+ {
+ super(theAcroForm, field);
+ }
+
+ /**
+ * This will get the option values "Opt" entry of the pdf button.
+ *
+ * @return A list of java.lang.String values.
+ */
+ public List getOptions()
+ {
+ List retval = null;
+ COSArray array = (COSArray)getDictionary().getDictionaryObject( COSName.getPDFName( "Opt" ) );
+ if( array != null )
+ {
+ List strings = new ArrayList();
+ for( int i=0; i<array.size(); i++ )
+ {
+ strings.add( ((COSString)array.getObject( i )).getString() );
+ }
+ retval = new COSArrayList( strings, array );
+ }
+ return retval;
+ }
+
+ /**
+ * This will will set the list of options for this button.
+ *
+ * @param options The list of options for the button.
+ */
+ public void setOptions( List options )
+ {
+ getDictionary().setItem(
+ COSName.getPDFName( "Opt" ),
+ COSArrayList.converterToCOSArray( options ) );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import java.io.IOException;
+
+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.COSString;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDVariableText;
+
+/**
+ * A class for handling the PDF field as a choicefield.
+ *
+ * @author sug
+ * @version $Revision: 1.7 $
+ */
+public class PDChoiceField extends PDVariableText
+{
+
+ /**
+ * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#PDField(PDAcroForm,COSDictionary)
+ *
+ * @param theAcroForm The acroForm for this field.
+ * @param field The field for this choice field.
+ */
+ public PDChoiceField( PDAcroForm theAcroForm, COSDictionary field)
+ {
+ super(theAcroForm, field);
+ }
+
+ /**
+ * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#setValue(java.lang.String)
+ *
+ * @param optionValue The new value for this text field.
+ *
+ * @throws IOException If there is an error calculating the appearance stream or the value in not one
+ * of the existing options.
+ */
+ public void setValue(String optionValue) throws IOException
+ {
+ int indexSelected = -1;
+ COSArray options = (COSArray)getDictionary().getDictionaryObject( "Opt" );
+ if( options.size() == 0 )
+ {
+ throw new IOException( "Error: You cannot set a value for a choice field if there are no options." );
+ }
+ else
+ {
+ // YXJ: Changed the order of the loops. Acrobat produces PDF's
+ // where sometimes there is 1 string and the rest arrays.
+ // This code works either way.
+ for( int i=0; i<options.size() && indexSelected == -1; i++ ) {
+ COSBase option = options.getObject( i );
+ if( option instanceof COSArray )
+ {
+ COSArray keyValuePair = (COSArray)option;
+ COSString key = (COSString)keyValuePair.getObject( 0 );
+ COSString value = (COSString)keyValuePair.getObject( 1 );
+ if( optionValue.equals( key.getString() ) || optionValue.equals( value.getString() ) )
+ {
+ //have the parent draw the appearance stream with the value
+ super.setValue( value.getString() );
+ //but then use the key as the V entry
+ getDictionary().setItem( COSName.getPDFName( "V" ), key );
+ indexSelected = i;
+ }
+ }
+ else
+ {
+ COSString value = (COSString)option;
+ if( optionValue.equals( value.getString() ) )
+ {
+ super.setValue( optionValue );
+ indexSelected = i;
+ }
+ }
+ }
+ }
+ if( indexSelected == -1 )
+ {
+ throw new IOException( "Error: '" + optionValue + "' was not an available option.");
+ }
+ else
+ {
+ COSArray indexArray = (COSArray)getDictionary().getDictionaryObject( "I" );
+ if( indexArray != null )
+ {
+ indexArray.clear();
+ indexArray.add( COSInteger.get( indexSelected ) );
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+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.pdmodel.common.PDTextStream;
+
+import org.apache.pdfbox.pdmodel.fdf.FDFField;
+import org.apache.pdfbox.util.BitFlagHelper;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is the superclass for a Field element in a PDF.
+ * Based on the COS object model from PDFBox.
+ *
+ * @author sug
+ * @version $Revision: 1.23 $
+ */
+public abstract class PDField implements COSObjectable
+{
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_READ_ONLY = 1;
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_REQUIRED = 1 << 1;
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_NO_EXPORT = 1 << 2;
+
+
+ private PDAcroForm acroForm;
+
+ private COSDictionary dictionary;
+
+ /**
+ * Constructor.
+ *
+ * @param theAcroForm The form that this field is part of.
+ */
+ public PDField( PDAcroForm theAcroForm )
+ {
+ acroForm = theAcroForm;
+ dictionary = new COSDictionary();
+ //no required fields in base field class
+ }
+
+
+ /**
+ * Creates a COSField from a COSDictionary, expected to be
+ * a correct object definition for a field in PDF.
+ *
+ * @param theAcroForm The form that this field is part of.
+ * @param field the PDF objet to represent as a field.
+ */
+ public PDField(PDAcroForm theAcroForm, COSDictionary field)
+ {
+ acroForm = theAcroForm;
+ dictionary = field;
+ }
+
+ /**
+ * Returns the partial name of the field.
+ *
+ * @return the name of the field
+ */
+ public String getPartialName()
+ {
+ return getDictionary().getString( COSName.T );
+ }
+
+ /**
+ * This will set the partial name of the field.
+ *
+ * @param name The new name for the field.
+ */
+ public void setPartialName( String name )
+ {
+ getDictionary().setString( COSName.T, name );
+ }
+
+ /**
+ * Returns the fully qualified name of the field, which is a concatenation of
+ * the names of all the parents fields.
+ *
+ * @return the name of the field
+ *
+ * @throws IOException If there is an error generating the fully qualified name.
+ */
+ public String getFullyQualifiedName() throws IOException
+ {
+ PDField parent = getParent();
+ String parentName = null;
+ if( parent != null )
+ {
+ parentName = parent.getFullyQualifiedName();
+ }
+ String finalName = getPartialName();
+ if( parentName != null )
+ {
+ finalName = parentName + "." + finalName;
+ }
+ return finalName;
+ }
+
+ /**
+ * Gets the alternate name of the field.
+ *
+ * @return the alternate name of the field
+ */
+ public String getAlternateFieldName()
+ {
+ return this.getDictionary().getString(COSName.TU);
+ }
+
+ /**
+ * This will set the alternate name of the field.
+ *
+ * @param alternateFieldName the alternate name of the field
+ */
+ public void setAlternateFieldName(String alternateFieldName)
+ {
+ this.getDictionary().setString(COSName.TU, alternateFieldName);
+ }
+
+ /**
+ * Get the FT entry of the field. This is a read only field and is set depending
+ * on the actual type. The field type is an inheritable attribute. This method will
+ * return only the direct value on this object. Use the findFieldType for an upward
+ * recursive search.
+ *
+ * @return The Field type.
+ *
+ * @see PDField#findFieldType()
+ */
+ public String getFieldType()
+ {
+ return getDictionary().getNameAsString( COSName.FT );
+ }
+
+ /**
+ * Find the field type and optionally do a recursive upward search. Sometimes the fieldtype
+ * will be specified on the parent instead of the direct object. This will look at this
+ * object for the field type, if none is specified then it will look to the parent if there
+ * is a parent. If there is no parent and no field type has been found then this
+ * will return null.
+ *
+ * @return The field type or null if none was found.
+ */
+ public String findFieldType()
+ {
+ return findFieldType( getDictionary() );
+ }
+
+ private String findFieldType( COSDictionary dic )
+ {
+ String retval = dic.getNameAsString( COSName.FT );
+ if( retval == null )
+ {
+ COSDictionary parent = (COSDictionary)dic.getDictionaryObject( COSName.PARENT, COSName.P );
+ if( parent != null )
+ {
+ retval = findFieldType( parent );
+ }
+ }
+ return retval;
+
+ }
+
+
+ /**
+ * setValue sets the fields value to a given string.
+ *
+ * @param value the string value
+ *
+ * @throws IOException If there is an error creating the appearance stream.
+ */
+ public abstract void setValue(String value) throws IOException;
+
+ /**
+ * getValue gets the fields value to as a string.
+ *
+ * @return The string value of this field.
+ *
+ * @throws IOException If there is an error getting the value.
+ */
+ public abstract String getValue() throws IOException;
+
+ /**
+ * sets the field to be read-only.
+ *
+ * @param readonly The new flag for readonly.
+ */
+ public void setReadonly(boolean readonly)
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_READ_ONLY, readonly );
+ }
+
+ /**
+ *
+ * @return true if the field is readonly
+ */
+ public boolean isReadonly()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_READ_ONLY );
+ }
+
+ /**
+ * sets the field to be required.
+ *
+ * @param required The new flag for required.
+ */
+ public void setRequired(boolean required)
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_REQUIRED, required );
+ }
+
+ /**
+ *
+ * @return true if the field is required
+ */
+ public boolean isRequired()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_REQUIRED );
+ }
+
+ /**
+ * sets the field to be not exported..
+ *
+ * @param noExport The new flag for noExport.
+ */
+ public void setNoExport(boolean noExport)
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_NO_EXPORT, noExport );
+ }
+
+ /**
+ *
+ * @return true if the field is not to be exported.
+ */
+ public boolean isNoExport()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_NO_EXPORT );
+ }
+
+ /**
+ * This will get the flags for this field.
+ *
+ * @return flags The set of flags.
+ */
+ public int getFieldFlags()
+ {
+ int retval = 0;
+ COSInteger ff = (COSInteger)getDictionary().getDictionaryObject( COSName.FF );
+ if( ff != null )
+ {
+ retval = ff.intValue();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the flags for this field.
+ *
+ * @param flags The new flags.
+ */
+ public void setFieldFlags( int flags )
+ {
+ getDictionary().setInt( COSName.FF, flags );
+ }
+
+ /**
+ * This will import a fdf field from a fdf document.
+ *
+ * @param fdfField The fdf field to import.
+ *
+ * @throws IOException If there is an error importing the data for this field.
+ */
+ public void importFDF( FDFField fdfField ) throws IOException
+ {
+ Object fieldValue = fdfField.getValue();
+ int fieldFlags = getFieldFlags();
+
+ if( fieldValue != null )
+ {
+ if( fieldValue instanceof String )
+ {
+ setValue( (String)fieldValue );
+ }
+ else if( fieldValue instanceof PDTextStream )
+ {
+ setValue( ((PDTextStream)fieldValue).getAsString() );
+ }
+ else
+ {
+ throw new IOException( "Unknown field type:" + fieldValue.getClass().getName() );
+ }
+ }
+ Integer ff = fdfField.getFieldFlags();
+ if( ff != null )
+ {
+ setFieldFlags( ff.intValue() );
+ }
+ else
+ {
+ //these are suppose to be ignored if the Ff is set.
+ Integer setFf = fdfField.getSetFieldFlags();
+
+ if( setFf != null )
+ {
+ int setFfInt = setFf.intValue();
+ fieldFlags = fieldFlags | setFfInt;
+ setFieldFlags( fieldFlags );
+ }
+
+ Integer clrFf = fdfField.getClearFieldFlags();
+ if( clrFf != null )
+ {
+ //we have to clear the bits of the document fields for every bit that is
+ //set in this field.
+ //
+ //Example:
+ //docFf = 1011
+ //clrFf = 1101
+ //clrFfValue = 0010;
+ //newValue = 1011 & 0010 which is 0010
+ int clrFfValue = clrFf.intValue();
+ clrFfValue ^= 0xFFFFFFFF;
+ fieldFlags = fieldFlags & clrFfValue;
+ setFieldFlags( fieldFlags );
+ }
+ }
+
+ PDAnnotationWidget widget = getWidget();
+ if( widget != null )
+ {
+ int annotFlags = widget.getAnnotationFlags();
+ Integer f = fdfField.getWidgetFieldFlags();
+ if( f != null && widget != null )
+ {
+ widget.setAnnotationFlags( f.intValue() );
+ }
+ else
+ {
+ //these are suppose to be ignored if the F is set.
+ Integer setF = fdfField.getSetWidgetFieldFlags();
+ if( setF != null )
+ {
+ annotFlags = annotFlags | setF.intValue();
+ widget.setAnnotationFlags( annotFlags );
+ }
+
+ Integer clrF = fdfField.getClearWidgetFieldFlags();
+ if( clrF != null )
+ {
+ //we have to clear the bits of the document fields for every bit that is
+ //set in this field.
+ //
+ //Example:
+ //docF = 1011
+ //clrF = 1101
+ //clrFValue = 0010;
+ //newValue = 1011 & 0010 which is 0010
+ int clrFValue = clrF.intValue();
+ clrFValue ^= 0xFFFFFFFFL;
+ annotFlags = annotFlags & clrFValue;
+ widget.setAnnotationFlags( annotFlags );
+ }
+ }
+ }
+ List<FDFField> fdfKids = fdfField.getKids();
+ List<COSObjectable> pdKids = getKids();
+ for( int i=0; fdfKids != null && i<fdfKids.size(); i++ )
+ {
+ FDFField fdfChild = fdfKids.get( i );
+ String fdfName = fdfChild.getPartialFieldName();
+ for( int j=0; j<pdKids.size(); j++ )
+ {
+ Object pdChildObj = pdKids.get( j );
+ if( pdChildObj instanceof PDField )
+ {
+ PDField pdChild = (PDField)pdChildObj;
+ if( fdfName != null && fdfName.equals( pdChild.getPartialName() ) )
+ {
+ pdChild.importFDF( fdfChild );
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This will get the single associated widget that is part of this field. This
+ * occurs when the Widget is embedded in the fields dictionary. Sometimes there
+ * are multiple sub widgets associated with this field, in which case you want to
+ * use getKids(). If the kids entry is specified, then the first entry in that
+ * list will be returned.
+ *
+ * @return The widget that is associated with this field.
+ * @throws IOException If there is an error getting the widget object.
+ */
+ public PDAnnotationWidget getWidget() throws IOException
+ {
+ PDAnnotationWidget retval = null;
+ List<COSObjectable> kids = getKids();
+ if( kids == null )
+ {
+ retval = new PDAnnotationWidget( getDictionary() );
+ }
+ else if( kids.size() > 0 )
+ {
+ Object firstKid = kids.get( 0 );
+ if( firstKid instanceof PDAnnotationWidget )
+ {
+ retval = (PDAnnotationWidget)firstKid;
+ }
+ else
+ {
+ retval = ((PDField)firstKid).getWidget();
+ }
+ }
+ else
+ {
+ retval = null;
+ }
+ return retval;
+ }
+
+ /**
+ * Get the parent field to this field, or null if none exists.
+ *
+ * @return The parent field.
+ *
+ * @throws IOException If there is an error creating the parent field.
+ */
+ public PDField getParent() throws IOException
+ {
+ PDField parent = null;
+ COSDictionary parentDic = (COSDictionary)getDictionary().getDictionaryObject( COSName.PARENT, COSName.P );
+ if( parentDic != null )
+ {
+ parent = PDFieldFactory.createField( getAcroForm(), parentDic );
+ }
+ return parent;
+ }
+
+ /**
+ * Set the parent of this field.
+ *
+ * @param parent The parent to this field.
+ */
+ public void setParent( PDField parent )
+ {
+ getDictionary().setItem( "Parent", parent );
+ }
+
+ /**
+ * This will find one of the child elements. The name array are the components
+ * of the name to search down the tree of names. The nameIndex is where to
+ * start in that array. This method is called recursively until it finds
+ * the end point based on the name array.
+ *
+ * @param name An array that picks the path to the field.
+ * @param nameIndex The index into the array.
+ * @return The field at the endpoint or null if none is found.
+ * @throws IOException If there is an error creating the field.
+ */
+ public PDField findKid( String[] name, int nameIndex ) throws IOException
+ {
+ PDField retval = null;
+ COSArray kids = (COSArray)getDictionary().getDictionaryObject( COSName.KIDS );
+ if( kids != null )
+ {
+ for (int i = 0; retval == null && i < kids.size(); i++)
+ {
+ COSDictionary kidDictionary = (COSDictionary)kids.getObject(i);
+ if( name[nameIndex].equals( kidDictionary.getString( "T" ) ) )
+ {
+ retval = PDFieldFactory.createField( acroForm, kidDictionary );
+ if( name.length > nameIndex+1 )
+ {
+ retval = retval.findKid( name, nameIndex+1 );
+ }
+ }
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * This will get all the kids of this field. The values in the list
+ * will either be PDWidget or PDField. Normally they will be PDWidget objects
+ * unless this is a non-terminal field and they will be child PDField objects.
+ *
+ * @return A list of either PDWidget or PDField objects.
+ * @throws IOException If there is an error retrieving the kids.
+ */
+ public List<COSObjectable> getKids() throws IOException
+ {
+ List<COSObjectable> retval = null;
+ COSArray kids = (COSArray)getDictionary().getDictionaryObject(COSName.KIDS);
+ if( kids != null )
+ {
+ List<COSObjectable> kidsList = new ArrayList<COSObjectable>();
+ for (int i = 0; i < kids.size(); i++)
+ {
+ COSDictionary kidDictionary = (COSDictionary)kids.getObject(i);
+ COSDictionary parent = (COSDictionary)kidDictionary.getDictionaryObject( COSName.PARENT, COSName.P );
+ if( kidDictionary.getDictionaryObject( COSName.FT ) != null ||
+ (parent != null && parent.getDictionaryObject( COSName.FT ) != null ) )
+ {
+ kidsList.add( PDFieldFactory.createField( acroForm, kidDictionary ));
+ }
+ else if( "Widget".equals( kidDictionary.getNameAsString( COSName.SUBTYPE ) ) )
+ {
+ kidsList.add( new PDAnnotationWidget( kidDictionary ) );
+ }
+ else
+ {
+ //
+ kidsList.add( PDFieldFactory.createField( acroForm, kidDictionary ));
+ }
+ }
+ retval = new COSArrayList( kidsList, kids );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the list of kids.
+ *
+ * @param kids The list of child widgets.
+ */
+ public void setKids( List<COSObjectable> kids )
+ {
+ COSArray kidsArray = COSArrayList.converterToCOSArray( kids );
+ getDictionary().setItem( COSName.KIDS, kidsArray );
+ }
+
+ /**
+ * This will return a string representation of this field.
+ *
+ * @return A string representation of this field.
+ */
+ @Override
+ public String toString()
+ {
+ return "" + getDictionary().getDictionaryObject( COSName.V );
+ }
+
+ /**
+ * This will get the acroform that this field is part of.
+ *
+ * @return The form this field is on.
+ */
+ public PDAcroForm getAcroForm()
+ {
+ return acroForm;
+ }
+
+ /**
+ * This will set the form this field is on.
+ *
+ * @param value The new form to use.
+ */
+ public void setAcroForm(PDAcroForm value)
+ {
+ acroForm = value;
+ }
+
+ /**
+ * This will get the dictionary associated with this field.
+ *
+ * @return The dictionary that this class wraps.
+ */
+ public COSDictionary getDictionary()
+ {
+ return dictionary;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return dictionary;
+ }
+
+ /**
+ * Get the additional actions for this field. This will return null
+ * if there are no additional actions for this field.
+ *
+ * @return The actions of the field.
+ */
+ public PDFormFieldAdditionalActions getActions()
+ {
+ COSDictionary aa = (COSDictionary)dictionary.getDictionaryObject( COSName.AA );
+ PDFormFieldAdditionalActions retval = null;
+ if( aa != null )
+ {
+ retval = new PDFormFieldAdditionalActions( aa );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the actions of the field.
+ *
+ * @param actions The field actions.
+ */
+ public void setActions( PDFormFieldAdditionalActions actions )
+ {
+ dictionary.setItem( COSName.AA, actions );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
+
+import java.io.IOException;
+
+import java.util.List;
+
+/**
+ * This is the Factory for creating and returning the correct
+ * field elements.
+ *
+ * @author sug
+ * @version $Revision: 1.8 $
+ */
+public class PDFieldFactory
+{
+ private static final int RADIO_BITMASK = 32768;
+ private static final int PUSHBUTTON_BITMASK = 65536;
+ private static final int RADIOS_IN_UNISON_BITMASK = 33554432;
+
+ private static final String FIELD_TYPE_BTN = "Btn";
+ private static final String FIELD_TYPE_TX = "Tx";
+ private static final String FIELD_TYPE_CH = "Ch";
+ private static final String FIELD_TYPE_SIG = "Sig";
+
+ /**
+ * Utility class so no constructor.
+ */
+ private PDFieldFactory()
+ {
+ //do nothing.
+ }
+
+ /**
+ * This method creates a COSField subclass from the given field.
+ * The field is a PDF Dictionary object that must represent a
+ * field element. - othewise null is returned
+ *
+ * @param acroForm The form that the field will be part of.
+ * @param field The dictionary representing a field element
+ *
+ * @return a subclass to COSField according to the kind of field passed to createField
+ * @throws IOException If there is an error determining the field type.
+ */
+ public static PDField createField( PDAcroForm acroForm, COSDictionary field) throws IOException
+ {
+ PDField pdField = new PDUnknownField( acroForm, field );
+ if( isButton(pdField) )
+ {
+ int flags = pdField.getFieldFlags();
+ //BJL, I have found that the radio flag bit is not always set
+ //and that sometimes there is just a kids dictionary.
+ //so, if there is a kids dictionary then it must be a radio button
+ //group.
+ COSArray kids = (COSArray)field.getDictionaryObject( COSName.getPDFName( "Kids" ) );
+ if( kids != null || isRadio(flags) )
+ {
+ pdField = new PDRadioCollection( acroForm, field );
+ }
+ else if( isPushButton( flags ) )
+ {
+ pdField = new PDPushButton( acroForm, field );
+ }
+ else
+ {
+ pdField = new PDCheckbox( acroForm, field );
+ }
+
+ }
+ else if (isChoiceField(pdField))
+ {
+ pdField = new PDChoiceField( acroForm, field );
+ }
+ else if (isTextbox(pdField))
+ {
+ pdField = new PDTextbox( acroForm, field );
+ }
+ else if( isSignature( pdField ) )
+ {
+ pdField = new PDSignatureField( acroForm, field );
+ }
+ else
+ {
+ //do nothing and return an unknown field type.
+ }
+ return pdField;
+ }
+
+ /**
+ * This method determines if the given
+ * field is a radiobutton collection.
+ *
+ * @param flags The field flags.
+ *
+ * @return the result of the determination
+ */
+ private static boolean isRadio( int flags )
+ {
+ return (flags & RADIO_BITMASK) > 0;
+ }
+
+ /**
+ * This method determines if the given
+ * field is a pushbutton.
+ *
+ * @param flags The field flags.
+ *
+ * @return the result of the determination
+ */
+ private static boolean isPushButton( int flags )
+ {
+ return (flags & PUSHBUTTON_BITMASK) > 0;
+ }
+
+ /**
+ * This method determines if the given field is a choicefield
+ * Choicefields are either listboxes or comboboxes.
+ *
+ * @param field the field to determine
+ * @return the result of the determination
+ */
+ private static boolean isChoiceField(PDField field) throws IOException
+ {
+ return FIELD_TYPE_CH.equals(field.findFieldType());
+ }
+
+ /**
+ * This method determines if the given field is a button.
+ *
+ * @param field the field to determine
+ * @return the result of the determination
+ *
+ * @throws IOException If there is an error determining the field type.
+ */
+ private static boolean isButton(PDField field) throws IOException
+ {
+ String ft = field.findFieldType();
+ boolean retval = FIELD_TYPE_BTN.equals( ft );
+ List kids = field.getKids();
+ if( ft == null && kids != null && kids.size() > 0)
+ {
+ //sometimes if it is a button the type is only defined by one
+ //of the kids entries
+ Object obj = kids.get( 0 );
+ COSDictionary kidDict = null;
+ if( obj instanceof PDField )
+ {
+ kidDict = ((PDField)obj).getDictionary();
+ }
+ else if( obj instanceof PDAnnotationWidget )
+ {
+ kidDict = ((PDAnnotationWidget)obj).getDictionary();
+ }
+ else
+ {
+ throw new IOException( "Error:Unexpected type of kids field:" + obj );
+ }
+ retval = isButton( new PDUnknownField( field.getAcroForm(), kidDict ) );
+ }
+ return retval;
+ }
+
+ /**
+ * This method determines if the given field is a signature.
+ *
+ * @param field the field to determine
+ * @return the result of the determination
+ */
+ private static boolean isSignature(PDField field) throws IOException
+ {
+ return FIELD_TYPE_SIG.equals(field.findFieldType());
+ }
+
+ /**
+ * This method determines if the given field is a Textbox.
+ *
+ * @param field the field to determine
+ * @return the result of the determination
+ */
+ private static boolean isTextbox(PDField field) throws IOException
+ {
+ return FIELD_TYPE_TX.equals(field.findFieldType());
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSString;
+
+import java.io.IOException;
+
+/**
+ * A class for handling the PDF field as a PDPushButton.
+ *
+ * @author sug
+ * @version $Revision: 1.3 $
+ */
+public class PDPushButton extends PDField
+{
+
+ /**
+ * @see org.apache.pdfbox.pdmodel.field.PDField#COSField(org.apache.pdfbox.cos.COSDictionary)
+ *
+ * @param theAcroForm The acroForm for this field.
+ * @param field The field for this push button.
+ */
+ public PDPushButton( PDAcroForm theAcroForm, COSDictionary field)
+ {
+ super(theAcroForm, field);
+ }
+
+ /**
+ * @see as.interactive.pdf.form.cos.COSField#setValue(java.lang.String)
+ *
+ * @param value The new value for the field.
+ *
+ * @throws IOException If there is an error creating the appearance stream.
+ */
+ public void setValue(String value) throws IOException
+ {
+ COSString fieldValue = new COSString(value);
+ getDictionary().setItem( COSName.getPDFName( "V" ), fieldValue );
+ getDictionary().setItem( COSName.getPDFName( "DV" ), fieldValue );
+ }
+
+ /**
+ * getValue gets the fields value to as a string.
+ *
+ * @return The string value of this field.
+ *
+ * @throws IOException If there is an error getting the value.
+ */
+ public String getValue() throws IOException
+ {
+ return getDictionary().getString( "V" );
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.util.BitFlagHelper;
+
+/**
+ * A class for handling the PDF field as a Radio Collection.
+ * This class automatically keeps track of the child radio buttons
+ * in the collection.
+ *
+ * @see PDCheckbox
+ * @author sug
+ * @version $Revision: 1.13 $
+ */
+public class PDRadioCollection extends PDChoiceButton
+{
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_RADIOS_IN_UNISON = 1 << 25;
+
+ /**
+ * @param theAcroForm The acroForm for this field.
+ * @param field The field that makes up the radio collection.
+ *
+ * {@inheritDoc}
+ */
+ public PDRadioCollection( PDAcroForm theAcroForm, COSDictionary field)
+ {
+ super(theAcroForm,field);
+ }
+
+ /**
+ * From the PDF Spec <br/>
+ * If set, a group of radio buttons within a radio button field that
+ * use the same value for the on state will turn on and off in unison; that is if
+ * one is checked, they are all checked. If clear, the buttons are mutually exclusive
+ * (the same behavior as HTML radio buttons).
+ *
+ * @param radiosInUnison The new flag for radiosInUnison.
+ */
+ public void setRadiosInUnison(boolean radiosInUnison)
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_RADIOS_IN_UNISON, radiosInUnison );
+ }
+
+ /**
+ *
+ * @return true If the flag is set for radios in unison.
+ */
+ public boolean isRadiosInUnison()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_RADIOS_IN_UNISON );
+ }
+
+ /**
+ * This setValue method iterates the collection of radiobuttons
+ * and checks or unchecks each radiobutton according to the
+ * given value.
+ * If the value is not represented by any of the radiobuttons,
+ * then none will be checked.
+ *
+ * {@inheritDoc}
+ */
+ public void setValue(String value) throws IOException
+ {
+ getDictionary().setString( COSName.V, value );
+ List kids = getKids();
+ for (int i = 0; i < kids.size(); i++)
+ {
+ PDField field = (PDField)kids.get(i);
+ if ( field instanceof PDCheckbox )
+ {
+ PDCheckbox btn = (PDCheckbox)field;
+ if( btn.getOnValue().equals(value) )
+ {
+ btn.check();
+ }
+ else
+ {
+ btn.unCheck();
+ }
+ }
+ }
+ }
+
+ /**
+ * getValue gets the fields value to as a string.
+ *
+ * @return The string value of this field.
+ *
+ * @throws IOException If there is an error getting the value.
+ */
+ public String getValue()throws IOException
+ {
+ String retval = null;
+ List kids = getKids();
+ for (int i = 0; i < kids.size(); i++)
+ {
+ PDField kid = (PDField)kids.get(i);
+ if ( kid instanceof PDCheckbox )
+ {
+ PDCheckbox btn = (PDCheckbox)kid;
+ if( btn.isChecked() )
+ {
+ retval = btn.getOnValue();
+ }
+ }
+ }
+ if( retval == null )
+ {
+ retval = getDictionary().getNameAsString( COSName.V );
+ }
+ return retval;
+ }
+
+
+ /**
+ * This will return a list of PDField objects that are part of this radio collection.
+ *
+ * @see PDField#getWidget()
+ * @return A list of PDWidget objects.
+ * @throws IOException if there is an error while creating the children objects.
+ */
+ @SuppressWarnings("unchecked")
+ public List getKids() throws IOException
+ {
+ List retval = null;
+ COSArray kids = (COSArray)getDictionary().getDictionaryObject(COSName.KIDS);
+ if( kids != null )
+ {
+ List kidsList = new ArrayList();
+ for (int i = 0; i < kids.size(); i++)
+ {
+ kidsList.add( PDFieldFactory.createField( getAcroForm(), (COSDictionary)kids.getObject(i) ) );
+ }
+ retval = new COSArrayList( kidsList, kids );
+ }
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+import java.io.IOException;
+
+/**
+ * A class for handling the PDF field as a signature.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDSignature extends PDField
+{
+
+ /**
+ * @see PDField#PDField(PDAcroForm,COSDictionary)
+ *
+ * @param theAcroForm The acroForm for this field.
+ * @param field The dictionary for the signature.
+ */
+ public PDSignature( PDAcroForm theAcroForm, COSDictionary field)
+ {
+ super(theAcroForm,field);
+ }
+
+ /**
+ * @see PDField#setValue(java.lang.String)
+ *
+ * @param value The new value for the field.
+ *
+ * @throws IOException If there is an error creating the appearance stream.
+ */
+ public void setValue(String value) throws IOException
+ {
+ throw new RuntimeException( "Not yet implemented" );
+ }
+
+ /**
+ * @see PDField#setValue(java.lang.String)
+ *
+ * @return The string value of this field.
+ *
+ * @throws IOException If there is an error creating the appearance stream.
+ */
+ public String getValue() throws IOException
+ {
+ throw new RuntimeException( "Not yet implemented" );
+ }
+
+ /**
+ * Return a string rep of this object.
+ *
+ * @return A string rep of this object.
+ */
+ public String toString()
+ {
+ return "PDSignature";
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A class for handling the PDF field as a signature.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Thomas Chojecki
+ * @version $Revision: 1.5 $
+ */
+public class PDSignatureField extends PDField
+{
+
+ /**
+ * @see PDField#PDField(PDAcroForm,COSDictionary)
+ *
+ * @param theAcroForm The acroForm for this field.
+ * @param field The dictionary for the signature.
+ * @throws IOException If there is an error while resolving partital name for the signature field
+ */
+ public PDSignatureField( PDAcroForm theAcroForm, COSDictionary field) throws IOException
+ {
+ super(theAcroForm,field);
+ // dirty hack to avoid npe caused through getWidget() method
+ getDictionary().setName( COSName.TYPE, "Annot" );
+ getDictionary().setName( COSName.SUBTYPE, PDAnnotationWidget.SUB_TYPE);
+ }
+
+ /**
+ * @see PDField#PDField(PDAcroForm)
+ *
+ * @param theAcroForm The acroForm for this field.
+ * @throws IOException If there is an error while resolving partial name for the signature field
+ */
+ public PDSignatureField( PDAcroForm theAcroForm) throws IOException
+ {
+ super( theAcroForm );
+ getDictionary().setName("FT", "Sig");
+ getWidget().setLocked(true);
+ getWidget().setPrinted(true);
+ setPartialName(generatePartialName());
+ getDictionary().setName( COSName.TYPE, "Annot" );
+ getDictionary().setName( COSName.SUBTYPE, PDAnnotationWidget.SUB_TYPE);
+ }
+
+ /**
+ * Generate a unique name for the signature
+ * @return
+ * @throws IOException
+ */
+ private String generatePartialName() throws IOException
+ {
+ PDAcroForm acroForm = getAcroForm();
+ List fields = acroForm.getFields();
+
+ String fieldName = "Signature";
+ int i = 1;
+
+ Set<String> sigNames = new HashSet<String>();
+
+ for ( Object object : fields )
+ {
+ if(object instanceof PDSignatureField)
+ {
+ sigNames.add(((PDSignatureField)object).getPartialName());
+ }
+ }
+
+ while(sigNames.contains(fieldName+i))
+ ++i;
+
+ return fieldName+i;
+ }
+
+ /**
+ * @see PDField#setValue(java.lang.String)
+ *
+ * @param value The new value for the field.
+ *
+ * @throws IOException If there is an error creating the appearance stream.
+ * @deprecated use setSignature(PDSignature) instead
+ */
+ @Override
+ @Deprecated
+ public void setValue(String value) throws IOException
+ {
+ throw new RuntimeException( "Can't set signature as String, use setSignature(PDSignature) instead" );
+ }
+
+ /**
+ * @see PDField#setValue(java.lang.String)
+ *
+ * @return The string value of this field.
+ *
+ * @throws IOException If there is an error creating the appearance stream.
+ * @deprecated use getSignature() instead
+ */
+ @Override
+ @Deprecated
+ public String getValue() throws IOException
+ {
+ throw new RuntimeException( "Can't get signature as String, use getSignature() instead." );
+ }
+
+ /**
+ * Return a string rep of this object.
+ *
+ * @return A string rep of this object.
+ */
+ @Override
+ public String toString()
+ {
+ return "PDSignature";
+ }
+
+ /**
+ * Add a signature dictionary to the signature field
+ *
+ * @param value is the PDSignature
+ */
+ public void setSignature(PDSignature value)
+ {
+ getDictionary().setItem("V", value);
+ }
+
+ /**
+ * Get the signature dictionary
+ *
+ * @return the signature dictionary
+ *
+ */
+ public PDSignature getSignature()
+ {
+ COSBase dictionary = getDictionary().getDictionaryObject(COSName.V);
+ if (dictionary == null)
+ return null;
+ return new PDSignature((COSDictionary)dictionary);
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * A class for handling the PDF field as a textbox.
+ *
+ * @author sug
+ * @version $Revision: 1.9 $
+ */
+public class PDTextbox extends PDVariableText
+{
+
+ /**
+ * @see PDField#PDField(PDAcroForm,COSDictionary)
+ *
+ * @param theAcroForm The acroform.
+ */
+ public PDTextbox( PDAcroForm theAcroForm )
+ {
+ super( theAcroForm );
+ }
+
+ /**
+ * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#PDField(PDAcroForm,COSDictionary)
+ *
+ * @param theAcroForm The acroForm for this field.
+ * @param field The field's dictionary.
+ */
+ public PDTextbox( PDAcroForm theAcroForm, COSDictionary field)
+ {
+ super( theAcroForm, field);
+ }
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSDictionary;
+
+/**
+ * This class represents a form field with an unknown type.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDUnknownField extends PDField
+{
+ /**
+ * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#PDField(PDAcroForm, COSDictionary)
+ *
+ * @param theAcroForm The acroForm for this field.
+ * @param field The field's dictionary.
+ */
+ public PDUnknownField( PDAcroForm theAcroForm, COSDictionary field)
+ {
+ super( theAcroForm, field);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setValue(String value) throws IOException
+ {
+ //do nothing
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getValue() throws IOException
+ {
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+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.COSString;
+import org.apache.pdfbox.util.BitFlagHelper;
+
+import java.io.IOException;
+
+/**
+ * A class for handling PDF fields that display text.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public abstract class PDVariableText extends PDField
+{
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_MULTILINE = 1 << 12;
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_PASSWORD = 1 << 13;
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_FILE_SELECT = 1 << 20;
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_DO_NOT_SPELL_CHECK = 1 << 22;
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_DO_NOT_SCROLL = 1 << 23;
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_COMB = 1 << 24;
+ /**
+ * A Ff flag.
+ */
+ public static final int FLAG_RICH_TEXT = 1 << 25;
+
+
+ /**
+ * DA Default appearance.
+ */
+ private COSString da;
+
+ private PDAppearance appearance;
+
+
+ /**
+ * A Q value.
+ */
+ public static final int QUADDING_LEFT = 0;
+
+ /**
+ * A Q value.
+ */
+ public static final int QUADDING_CENTERED = 1;
+
+ /**
+ * A Q value.
+ */
+ public static final int QUADDING_RIGHT = 2;
+
+ /**
+ * @see PDField#PDField(PDAcroForm,COSDictionary)
+ *
+ * @param theAcroForm The acroform.
+ */
+ public PDVariableText( PDAcroForm theAcroForm )
+ {
+ super( theAcroForm );
+ }
+
+ /**
+ * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#PDField(PDAcroForm,COSDictionary)
+ *
+ * @param theAcroForm The acroForm for this field.
+ * @param field The field's dictionary.
+ */
+ public PDVariableText( PDAcroForm theAcroForm, COSDictionary field)
+ {
+ super( theAcroForm, field);
+ da = (COSString) field.getDictionaryObject(COSName.DA);
+ }
+
+ /**
+ * @see org.apache.pdfbox.pdmodel.interactive.form.PDField#setValue(java.lang.String)
+ *
+ * @param value The new value for this text field.
+ *
+ * @throws IOException If there is an error calculating the appearance stream.
+ */
+ public void setValue(String value) throws IOException
+ {
+ COSString fieldValue = new COSString(value);
+ getDictionary().setItem( COSName.V, fieldValue );
+
+ //hmm, not sure what the case where the DV gets set to the field
+ //value, for now leave blank until we can come up with a case
+ //where it needs to be in there
+ //getDictionary().setItem( COSName.getPDFName( "DV" ), fieldValue );
+ if(appearance == null)
+ {
+ this.appearance = new PDAppearance( getAcroForm(), this );
+ }
+ appearance.setAppearanceValue(value);
+ }
+
+ /**
+ * getValue gets the fields value to as a string.
+ *
+ * @return The string value of this field.
+ *
+ * @throws IOException If there is an error getting the value.
+ */
+ public String getValue() throws IOException
+ {
+ return getDictionary().getString( COSName.V );
+ }
+
+ /**
+ * @return true if the field is multiline
+ */
+ public boolean isMultiline()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_MULTILINE );
+ }
+
+ /**
+ * Set the multiline bit.
+ *
+ * @param multiline The value for the multiline.
+ */
+ public void setMultiline( boolean multiline )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_MULTILINE, multiline );
+ }
+
+ /**
+ * @return true if the field is a password field.
+ */
+ public boolean isPassword()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_PASSWORD );
+ }
+
+ /**
+ * Set the password bit.
+ *
+ * @param password The value for the password.
+ */
+ public void setPassword( boolean password )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_PASSWORD, password );
+ }
+
+ /**
+ * @return true if the field is a file select field.
+ */
+ public boolean isFileSelect()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_FILE_SELECT );
+ }
+
+ /**
+ * Set the file select bit.
+ *
+ * @param fileSelect The value for the fileSelect.
+ */
+ public void setFileSelect( boolean fileSelect )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_FILE_SELECT, fileSelect );
+ }
+
+ /**
+ * @return true if the field is not suppose to spell check.
+ */
+ public boolean doNotSpellCheck()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_DO_NOT_SPELL_CHECK );
+ }
+
+ /**
+ * Set the doNotSpellCheck bit.
+ *
+ * @param doNotSpellCheck The value for the doNotSpellCheck.
+ */
+ public void setDoNotSpellCheck( boolean doNotSpellCheck )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_DO_NOT_SPELL_CHECK, doNotSpellCheck );
+ }
+
+ /**
+ * @return true if the field is not suppose to scroll.
+ */
+ public boolean doNotScroll()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_DO_NOT_SCROLL );
+ }
+
+ /**
+ * Set the doNotScroll bit.
+ *
+ * @param doNotScroll The value for the doNotScroll.
+ */
+ public void setDoNotScroll( boolean doNotScroll )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_DO_NOT_SCROLL, doNotScroll );
+ }
+
+ /**
+ * @return true if the field is not suppose to comb the text display.
+ */
+ public boolean shouldComb()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_COMB );
+ }
+
+ /**
+ * Set the comb bit.
+ *
+ * @param comb The value for the comb.
+ */
+ public void setComb( boolean comb )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_COMB, comb );
+ }
+
+ /**
+ * @return true if the field is a rich text field.
+ */
+ public boolean isRichText()
+ {
+ return BitFlagHelper.getFlag( getDictionary(), COSName.FF, FLAG_RICH_TEXT );
+ }
+
+ /**
+ * Set the richText bit.
+ *
+ * @param richText The value for the richText.
+ */
+ public void setRichText( boolean richText )
+ {
+ BitFlagHelper.setFlag( getDictionary(), COSName.FF, FLAG_RICH_TEXT, richText );
+ }
+
+ /**
+ * @return the DA element of the dictionary object
+ */
+ protected COSString getDefaultAppearance()
+ {
+ return da;
+ }
+
+ /**
+ * This will get the 'quadding' or justification of the text to be displayed.
+ * 0 - Left(default)<br/>
+ * 1 - Centered<br />
+ * 2 - Right<br />
+ * Please see the QUADDING_CONSTANTS.
+ *
+ * @return The justification of the text strings.
+ */
+ public int getQ()
+ {
+ int retval = 0;
+ COSNumber number = (COSNumber)getDictionary().getDictionaryObject( COSName.Q );
+ if( number != null )
+ {
+ retval = number.intValue();
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the quadding/justification of the text. See QUADDING constants.
+ *
+ * @param q The new text justification.
+ */
+ public void setQ( int q )
+ {
+ getDictionary().setInt( COSName.Q, q );
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.form;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This class represents an XML Forms Architecture Data packet.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDXFA implements COSObjectable
+{
+ private COSBase xfa;
+
+ /**
+ * Constructor.
+ *
+ * @param xfaBase The xfa resource.
+ */
+ public PDXFA( COSBase xfaBase )
+ {
+ xfa = xfaBase;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return xfa;
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The interactive package contains classes that deal with interactive annotations such as textfields and buttons.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.interactive.measurement;
+
+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 a measure dictionary.
+ *
+ * @version $Revision: 1.0 $
+ *
+ */
+public class PDMeasureDictionary implements COSObjectable
+{
+ /**
+ * The type of the dictionary.
+ */
+ public static final String TYPE = "Measure";
+
+ private COSDictionary measureDictionary;
+
+ /**
+ * Constructor.
+ */
+ protected PDMeasureDictionary()
+ {
+ this.measureDictionary = new COSDictionary();
+ this.getDictionary().setName(COSName.TYPE, TYPE);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dictionary the corresponding dictionary
+ */
+ public PDMeasureDictionary(COSDictionary dictionary)
+ {
+ this.measureDictionary = dictionary;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return this.measureDictionary;
+ }
+
+ /**
+ * This will return the corresponding dictionary.
+ *
+ * @return the measure dictionary
+ */
+ public COSDictionary getDictionary()
+ {
+ return this.measureDictionary;
+ }
+
+ /**
+ * This will return the type of the measure dictionary.
+ * It must be "Measure"
+ *
+ * @return the type
+ */
+ public String getType()
+ {
+ return TYPE;
+ }
+
+ /**
+ * returns the subtype of the measure dictionary.
+ * @return the subtype of the measure data dictionary
+ */
+
+ public String getSubtype()
+ {
+ return this.getDictionary().getNameAsString(COSName.SUBTYPE,
+ PDRectlinearMeasureDictionary.SUBTYPE);
+ }
+
+ /**
+ * This will set the subtype of the measure dictionary.
+ * @param subtype the subtype of the measure dictionary
+ */
+ protected void setSubtype(String subtype)
+ {
+ this.getDictionary().setName(COSName.SUBTYPE, subtype);
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.measurement;
+
+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 a number format dictionary.
+ *
+ * @version $Revision: 1.0$
+ *
+ */
+public class PDNumberFormatDictionary implements COSObjectable
+{
+
+ /**
+ * The type of the dictionary.
+ */
+ public static final String TYPE = "NumberFormat";
+
+ /**
+ * Constant indicating that the label specified by U is a suffix to the value.
+ */
+ public static final String LABEL_SUFFIX_TO_VALUE = "S";
+ /**
+ * Constant indicating that the label specified by U is a postfix to the value.
+ */
+ public static final String LABEL_PREFIX_TO_VALUE = "P";
+
+ /**
+ * Constant for showing a fractional value as decimal to the precision specified by the D entry.
+ */
+ public static final String FRACTIONAL_DISPLAY_DECIMAL = "D";
+ /**
+ * Constant for showing a fractional value as a fraction with denominator specified by the D entry.
+ */
+ public static final String FRACTIONAL_DISPLAY_FRACTION = "F";
+ /**
+ * Constant for showing a fractional value without fractional part; round to the nearest whole unit.
+ */
+ public static final String FRACTIONAL_DISPLAY_ROUND = "R";
+ /**
+ * Constant for showing a fractional value without fractional part; truncate to achieve whole units.
+ */
+ public static final String FRACTIONAL_DISPLAY_TRUNCATE = "T";
+
+ private COSDictionary numberFormatDictionary;
+
+ /**
+ * Constructor.
+ */
+ public PDNumberFormatDictionary()
+ {
+ this.numberFormatDictionary = new COSDictionary();
+ this.numberFormatDictionary.setName(COSName.TYPE, TYPE);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dictionary the corresponding dictionary
+ */
+ public PDNumberFormatDictionary(COSDictionary dictionary)
+ {
+ this.numberFormatDictionary = dictionary;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return this.numberFormatDictionary;
+ }
+
+ /**
+ * This will return the dictionary.
+ *
+ * @return the number format dictionary
+ */
+ public COSDictionary getDictionary()
+ {
+ return this.numberFormatDictionary;
+ }
+
+ /**
+ * This will return the type of the number format dictionary.
+ * It must be "NumberFormat"
+ *
+ * @return the type
+ */
+ public String getType()
+ {
+ return TYPE;
+ }
+
+ /**
+ * This will return the label for the units.
+ *
+ * @return the label for the units
+ */
+ public String getUnits()
+ {
+ return this.getDictionary().getString("U");
+ }
+
+ /**
+ * This will set the label for the units.
+ *
+ * @param units the label for the units
+ */
+ public void setUnits(String units)
+ {
+ this.getDictionary().setString("U", units);
+ }
+
+ /**
+ * This will return the conversion factor.
+ *
+ * @return the conversion factor
+ */
+ public float getConversionFactor()
+ {
+ return this.getDictionary().getFloat("C");
+ }
+
+ /**
+ * This will set the conversion factor.
+ *
+ * @param conversionFactor the conversion factor
+ */
+ public void setConversionFactor(float conversionFactor)
+ {
+ this.getDictionary().setFloat("C", conversionFactor);
+ }
+
+ /**
+ * This will return the value for the manner to display a fractional value.
+ *
+ * @return the manner to display a fractional value
+ */
+ public String getFractionalDisplay()
+ {
+ return this.getDictionary().getString("F", FRACTIONAL_DISPLAY_DECIMAL);
+ }
+
+ /**
+ * This will set the value for the manner to display a fractional value.
+ * Allowed values are "D", "F", "R" and "T"
+ * @param fractionalDisplay the manner to display a fractional value
+ */
+ public void setFractionalDisplay(String fractionalDisplay)
+ {
+ if ((fractionalDisplay == null)
+ || FRACTIONAL_DISPLAY_DECIMAL.equals(fractionalDisplay)
+ || FRACTIONAL_DISPLAY_FRACTION.equals(fractionalDisplay)
+ || FRACTIONAL_DISPLAY_ROUND.equals(fractionalDisplay)
+ || FRACTIONAL_DISPLAY_TRUNCATE.equals(fractionalDisplay))
+ {
+ this.getDictionary().setString("F", fractionalDisplay);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Value must be \"D\", \"F\", \"R\", or \"T\", (or null).");
+ }
+ }
+
+ /**
+ * This will return the precision or denominator of a fractional amount.
+ *
+ * @return the precision or denominator
+ */
+ public int getDenominator()
+ {
+ return this.getDictionary().getInt("D");
+ }
+
+ /**
+ * This will set the precision or denominator of a fractional amount.
+ *
+ * @param denominator the precision or denominator
+ */
+ public void setDenominator(int denominator)
+ {
+ this.getDictionary().setInt("D", denominator);
+ }
+
+ /**
+ * This will return the value indication if the denominator of the fractional value is reduced/truncated .
+ *
+ * @return fd
+ */
+ public boolean isFD()
+ {
+ return this.getDictionary().getBoolean("FD", false);
+ }
+
+ /**
+ * This will set the value indication if the denominator of the fractional value is reduced/truncated .
+ * The denominator may not be reduced/truncated if true
+ * @param fd fd
+ */
+ public void setFD(boolean fd)
+ {
+ this.getDictionary().setBoolean("FD", fd);
+ }
+
+ /**
+ * This will return the text to be used between orders of thousands in display of numerical values.
+ *
+ * @return thousands separator
+ */
+ public String getThousandsSeparator()
+ {
+ return this.getDictionary().getString("RT", ",");
+ }
+
+ /**
+ * This will set the text to be used between orders of thousands in display of numerical values.
+ *
+ * @param thousandsSeparator thousands separator
+ */
+ public void setThousandsSeparator(String thousandsSeparator)
+ {
+ this.getDictionary().setString("RT", thousandsSeparator);
+ }
+
+ /**
+ * This will return the text to be used as the decimal point in displaying numerical values.
+ *
+ * @return decimal separator
+ */
+ public String getDecimalSeparator()
+ {
+ return this.getDictionary().getString("RD", ".");
+ }
+
+ /**
+ * This will set the text to be used as the decimal point in displaying numerical values.
+ *
+ * @param decimalSeparator decimal separator
+ */
+ public void setDecimalSeparator(String decimalSeparator)
+ {
+ this.getDictionary().setString("RD", decimalSeparator);
+ }
+
+ /**
+ * This will return the text to be concatenated to the left of the label specified by U.
+ * @return label prefix
+ */
+ public String getLabelPrefixString()
+ {
+ return this.getDictionary().getString("PS", " ");
+ }
+
+ /**
+ * This will set the text to be concatenated to the left of the label specified by U.
+ * @param labelPrefixString label prefix
+ */
+ public void setLabelPrefixString(String labelPrefixString)
+ {
+ this.getDictionary().setString("PS", labelPrefixString);
+ }
+
+ /**
+ * This will return the text to be concatenated after the label specified by U.
+ *
+ * @return label suffix
+ */
+ public String getLabelSuffixString()
+ {
+ return this.getDictionary().getString("SS", " ");
+ }
+
+ /**
+ * This will set the text to be concatenated after the label specified by U.
+ *
+ * @param labelSuffixString label suffix
+ */
+ public void setLabelSuffixString(String labelSuffixString)
+ {
+ this.getDictionary().setString("SS", labelSuffixString);
+ }
+
+ /**
+ * This will return a value indicating the ordering of the label specified by U to the calculated unit value.
+ *
+ * @return label position
+ */
+ public String getLabelPositionToValue()
+ {
+ return this.getDictionary().getString("O", LABEL_SUFFIX_TO_VALUE);
+ }
+
+ /**
+ * This will set the value indicating the ordering of the label specified by U to the calculated unit value.
+ * Possible values are "S" and "P"
+ *
+ * @param labelPositionToValue label position
+ */
+ public void setLabelPositionToValue(String labelPositionToValue)
+ {
+ if ((labelPositionToValue == null)
+ || LABEL_PREFIX_TO_VALUE.equals(labelPositionToValue)
+ || LABEL_SUFFIX_TO_VALUE.equals(labelPositionToValue))
+ {
+ this.getDictionary().setString("O", labelPositionToValue);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Value must be \"S\", or \"P\" (or null).");
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.measurement;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This class represents a rectlinear measure dictionary.
+ *
+ * @version $Revision: 1.0 $
+ *
+ */
+public class PDRectlinearMeasureDictionary extends PDMeasureDictionary
+{
+
+ /**
+ * The subtype of the rectlinear measure dictionary.
+ */
+ public static final String SUBTYPE = "RL";
+
+ /**
+ * Constructor.
+ */
+ public PDRectlinearMeasureDictionary()
+ {
+ this.setSubtype(SUBTYPE);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dictionary the corresponding dictionary
+ */
+ public PDRectlinearMeasureDictionary(COSDictionary dictionary)
+ {
+ super(dictionary);
+ }
+
+ /**
+ * This will return the scale ration.
+ *
+ * @return the scale ratio.
+ */
+ public String getScaleRatio()
+ {
+ return this.getDictionary().getString(COSName.R);
+ }
+
+ /**
+ * This will set the scale ration.
+ *
+ * @param scaleRatio the scale ratio.
+ */
+ public void setScaleRatio(String scaleRatio)
+ {
+ this.getDictionary().setString(COSName.R, scaleRatio);
+ }
+
+ /**
+ * This will return the changes along the x-axis.
+ *
+ * @return changes along the x-axis
+ */
+ public PDNumberFormatDictionary[] getChangeXs()
+ {
+ COSArray x = (COSArray)this.getDictionary().getDictionaryObject("X");
+ if (x != null)
+ {
+ PDNumberFormatDictionary[] retval =
+ new PDNumberFormatDictionary[x.size()];
+ for (int i = 0; i < x.size(); i++)
+ {
+ COSDictionary dic = (COSDictionary) x.get(i);
+ retval[i] = new PDNumberFormatDictionary(dic);
+ }
+ return retval;
+ }
+ return null;
+ }
+
+ /**
+ * This will set the changes along the x-axis.
+ *
+ * @param changeXs changes along the x-axis
+ */
+ public void setChangeXs(PDNumberFormatDictionary[] changeXs)
+ {
+ COSArray array = new COSArray();
+ for (int i = 0; i < changeXs.length; i++)
+ {
+ array.add(changeXs[i]);
+ }
+ this.getDictionary().setItem("X", array);
+ }
+
+ /**
+ * This will return the changes along the y-axis.
+ *
+ * @return changes along the y-axis
+ */
+ public PDNumberFormatDictionary[] getChangeYs()
+ {
+ COSArray y = (COSArray)this.getDictionary().getDictionaryObject("Y");
+ if (y != null)
+ {
+ PDNumberFormatDictionary[] retval =
+ new PDNumberFormatDictionary[y.size()];
+ for (int i = 0; i < y.size(); i++)
+ {
+ COSDictionary dic = (COSDictionary) y.get(i);
+ retval[i] = new PDNumberFormatDictionary(dic);
+ }
+ return retval;
+ }
+ return null;
+ }
+
+ /**
+ * This will set the changes along the y-axis.
+ *
+ * @param changeYs changes along the y-axis
+ */
+ public void setChangeYs(PDNumberFormatDictionary[] changeYs)
+ {
+ COSArray array = new COSArray();
+ for (int i = 0; i < changeYs.length; i++)
+ {
+ array.add(changeYs[i]);
+ }
+ this.getDictionary().setItem("Y", array);
+ }
+
+ /**
+ * This will return the distances.
+ *
+ * @return distances
+ */
+ public PDNumberFormatDictionary[] getDistances()
+ {
+ COSArray d = (COSArray)this.getDictionary().getDictionaryObject("D");
+ if (d != null)
+ {
+ PDNumberFormatDictionary[] retval =
+ new PDNumberFormatDictionary[d.size()];
+ for (int i = 0; i < d.size(); i++)
+ {
+ COSDictionary dic = (COSDictionary) d.get(i);
+ retval[i] = new PDNumberFormatDictionary(dic);
+ }
+ return retval;
+ }
+ return null;
+ }
+
+ /**
+ * This will set the distances.
+ *
+ * @param distances distances
+ */
+ public void setDistances(PDNumberFormatDictionary[] distances)
+ {
+ COSArray array = new COSArray();
+ for (int i = 0; i < distances.length; i++)
+ {
+ array.add(distances[i]);
+ }
+ this.getDictionary().setItem("D", array);
+ }
+
+ /**
+ * This will return the areas.
+ *
+ * @return areas
+ */
+ public PDNumberFormatDictionary[] getAreas()
+ {
+ COSArray a = (COSArray)this.getDictionary().getDictionaryObject(COSName.A);
+ if (a != null)
+ {
+ PDNumberFormatDictionary[] retval =
+ new PDNumberFormatDictionary[a.size()];
+ for (int i = 0; i < a.size(); i++)
+ {
+ COSDictionary dic = (COSDictionary) a.get(i);
+ retval[i] = new PDNumberFormatDictionary(dic);
+ }
+ return retval;
+ }
+ return null;
+ }
+
+ /**
+ * This will set the areas.
+ *
+ * @param areas areas
+ */
+ public void setAreas(PDNumberFormatDictionary[] areas)
+ {
+ COSArray array = new COSArray();
+ for (int i = 0; i < areas.length; i++)
+ {
+ array.add(areas[i]);
+ }
+ this.getDictionary().setItem(COSName.A, array);
+ }
+
+ /**
+ * This will return the angles.
+ *
+ * @return angles
+ */
+ public PDNumberFormatDictionary[] getAngles()
+ {
+ COSArray t = (COSArray)this.getDictionary().getDictionaryObject("T");
+ if (t != null)
+ {
+ PDNumberFormatDictionary[] retval =
+ new PDNumberFormatDictionary[t.size()];
+ for (int i = 0; i < t.size(); i++)
+ {
+ COSDictionary dic = (COSDictionary) t.get(i);
+ retval[i] = new PDNumberFormatDictionary(dic);
+ }
+ return retval;
+ }
+ return null;
+ }
+
+ /**
+ * This will set the angles.
+ *
+ * @param angles angles
+ */
+ public void setAngles(PDNumberFormatDictionary[] angles)
+ {
+ COSArray array = new COSArray();
+ for (int i = 0; i < angles.length; i++)
+ {
+ array.add(angles[i]);
+ }
+ this.getDictionary().setItem("T", array);
+ }
+
+ /**
+ * This will return the sloaps of a line.
+ *
+ * @return the sloaps of a line
+ */
+ public PDNumberFormatDictionary[] getLineSloaps()
+ {
+ COSArray s = (COSArray)this.getDictionary().getDictionaryObject("S");
+ if (s != null)
+ {
+ PDNumberFormatDictionary[] retval =
+ new PDNumberFormatDictionary[s.size()];
+ for (int i = 0; i < s.size(); i++)
+ {
+ COSDictionary dic = (COSDictionary) s.get(i);
+ retval[i] = new PDNumberFormatDictionary(dic);
+ }
+ return retval;
+ }
+ return null;
+ }
+
+ /**
+ * This will set the sloaps of a line.
+ *
+ * @param lineSloaps the sloaps of a line
+ */
+ public void setLineSloaps(PDNumberFormatDictionary[] lineSloaps)
+ {
+ COSArray array = new COSArray();
+ for (int i = 0; i < lineSloaps.length; i++)
+ {
+ array.add(lineSloaps[i]);
+ }
+ this.getDictionary().setItem("S", array);
+ }
+
+ /**
+ * This will return the origin of the coordinate system.
+ *
+ * @return the origin
+ */
+ public float[] getCoordSystemOrigin()
+ {
+ COSArray o = (COSArray)this.getDictionary().getDictionaryObject("O");
+ if (o != null)
+ {
+ return o.toFloatArray();
+ }
+ return null;
+ }
+
+ /**
+ * This will set the origin of the coordinate system.
+ *
+ * @param coordSystemOrigin the origin
+ */
+ public void setCoordSystemOrigin(float[] coordSystemOrigin)
+ {
+ COSArray array = new COSArray();
+ array.setFloatArray(coordSystemOrigin);
+ this.getDictionary().setItem("O", array);
+ }
+
+ /**
+ * This will return the CYX factor.
+ *
+ * @return CYX factor
+ */
+ public float getCYX()
+ {
+ return this.getDictionary().getFloat("CYX");
+ }
+
+ /**
+ * This will set the CYX factor.
+ *
+ * @param cyx CYX factor
+ */
+ public void setCYX(float cyx)
+ {
+ this.getDictionary().setFloat("CYX", cyx);
+ }
+
+}
--- /dev/null
+/*
+ * 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.interactive.measurement;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This class represents a viewport dictionary.
+ *
+ * @version $Revision: 1.0 $
+ *
+ */
+public class PDViewportDictionary implements COSObjectable
+{
+
+ /**
+ * The type of this annotation.
+ */
+ public static final String TYPE = "Viewport";
+
+ private COSDictionary viewportDictionary;
+
+ /**
+ * Constructor.
+ */
+ public PDViewportDictionary()
+ {
+ this.viewportDictionary = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param dictionary the dictionary
+ */
+ public PDViewportDictionary(COSDictionary dictionary)
+ {
+ this.viewportDictionary = dictionary;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public COSBase getCOSObject()
+ {
+ return this.viewportDictionary;
+ }
+
+ /**
+ * This will return the corresponding dictionary.
+ *
+ * @return the viewport dictionary
+ */
+ public COSDictionary getDictionary()
+ {
+ return this.viewportDictionary;
+ }
+
+ /**
+ * Returns the type of the viewport dictionary.
+ * It must be "Viewport"
+ * @return the type of the external data dictionary
+ */
+
+ public String getType()
+ {
+ return TYPE;
+ }
+
+ /**
+ * This will retrieve the rectangle specifying the location of the viewport.
+ *
+ * @return the location
+ */
+ public PDRectangle getBBox()
+ {
+ COSArray bbox = (COSArray)this.getDictionary().getDictionaryObject("BBox");
+ if (bbox != null)
+ {
+ return new PDRectangle(bbox);
+ }
+ return null;
+ }
+
+ /**
+ * This will set the rectangle specifying the location of the viewport.
+ *
+ * @param rectangle the rectangle specifying the location.
+ */
+ public void setBBox(PDRectangle rectangle)
+ {
+ this.getDictionary().setItem("BBox", rectangle);
+ }
+
+ /**
+ * This will retrieve the name of the viewport.
+ *
+ * @return the name of the viewport
+ */
+ public String getName()
+ {
+ return this.getDictionary().getNameAsString(COSName.NAME);
+ }
+
+ /**
+ * This will set the name of the viewport.
+ *
+ * @param name the name of the viewport
+ */
+ public void setName(String name)
+ {
+ this.getDictionary().setName(COSName.NAME, name);
+ }
+
+ /**
+ * This will retrieve the measure dictionary.
+ *
+ * @return the measure dictionary
+ */
+ public PDMeasureDictionary getMeasure()
+ {
+ COSDictionary measure = (COSDictionary)this.getDictionary().getDictionaryObject("Measure");
+ if (measure != null)
+ {
+ return new PDMeasureDictionary(measure);
+ }
+ return null;
+ }
+
+ /**
+ * This will set the measure dictionary.
+ *
+ * @param measure the measure dictionary
+ */
+ public void setMeasure(PDMeasureDictionary measure)
+ {
+ this.getDictionary().setItem("Measure", measure);
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The measurement package contains classes that work with elements specifying measure properties.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.interactive.pagenavigation;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This a single thread in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class PDThread implements COSObjectable
+{
+
+
+ private COSDictionary thread;
+
+ /**
+ * Constructor that is used for a preexisting dictionary.
+ *
+ * @param t The underlying dictionary.
+ */
+ public PDThread( COSDictionary t )
+ {
+ thread = t;
+ }
+
+ /**
+ * Default constructor.
+ *
+ */
+ public PDThread()
+ {
+ thread = new COSDictionary();
+ thread.setName( "Type", "Thread" );
+ }
+
+ /**
+ * This will get the underlying dictionary that this object wraps.
+ *
+ * @return The underlying info dictionary.
+ */
+ public COSDictionary getDictionary()
+ {
+ return thread;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return thread;
+ }
+
+ /**
+ * Get info about the thread, or null if there is nothing.
+ *
+ * @return The thread information.
+ */
+ public PDDocumentInformation getThreadInfo()
+ {
+ PDDocumentInformation retval = null;
+ COSDictionary info = (COSDictionary)thread.getDictionaryObject( "I" );
+ if( info != null )
+ {
+ retval = new PDDocumentInformation( info );
+ }
+
+ return retval;
+ }
+
+ /**
+ * Set the thread info, can be null.
+ *
+ * @param info The info dictionary about this thread.
+ */
+ public void setThreadInfo( PDDocumentInformation info )
+ {
+ thread.setItem( "I", info );
+ }
+
+ /**
+ * Get the first bead in the thread, or null if it has not been set yet. This
+ * is a required field for this object.
+ *
+ * @return The first bead in the thread.
+ */
+ public PDThreadBead getFirstBead()
+ {
+ PDThreadBead retval = null;
+ COSDictionary bead = (COSDictionary)thread.getDictionaryObject( "F" );
+ if( bead != null )
+ {
+ retval = new PDThreadBead( bead );
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will set the first bead in the thread. When this is set it will
+ * also set the thread property of the bead object.
+ *
+ * @param bead The first bead in the thread.
+ */
+ public void setFirstBead( PDThreadBead bead )
+ {
+ if( bead != null )
+ {
+ bead.setThread( this );
+ }
+ thread.setItem( "F", bead );
+ }
+
+
+}
--- /dev/null
+/*
+ * 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.interactive.pagenavigation;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ * This a single bead in a thread in a PDF document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDThreadBead implements COSObjectable
+{
+
+
+ private COSDictionary bead;
+
+ /**
+ * Constructor that is used for a preexisting dictionary.
+ *
+ * @param b The underlying dictionary.
+ */
+ public PDThreadBead( COSDictionary b )
+ {
+ bead = b;
+ }
+
+ /**
+ * Default constructor.
+ *
+ */
+ public PDThreadBead()
+ {
+ bead = new COSDictionary();
+ bead.setName( "Type", "Bead" );
+ setNextBead( this );
+ setPreviousBead( this );
+ }
+
+ /**
+ * This will get the underlying dictionary that this object wraps.
+ *
+ * @return The underlying info dictionary.
+ */
+ public COSDictionary getDictionary()
+ {
+ return bead;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return bead;
+ }
+
+ /**
+ * This will get the thread that this bead is part of. This is only required
+ * for the first bead in a thread, so other beads 'may' return null.
+ *
+ * @return The thread that this bead is part of.
+ */
+ public PDThread getThread()
+ {
+ PDThread retval = null;
+ COSDictionary dic = (COSDictionary)bead.getDictionaryObject( "T" );
+ if( dic != null )
+ {
+ retval = new PDThread( dic );
+ }
+ return retval;
+ }
+
+ /**
+ * Set the thread that this bead is part of. This is only required for the
+ * first bead in a thread. Note: This property is set for you by the PDThread.setFirstBead() method.
+ *
+ * @param thread The thread that this bead is part of.
+ */
+ public void setThread( PDThread thread )
+ {
+ bead.setItem( "T", thread );
+ }
+
+ /**
+ * This will get the next bead. If this bead is the last bead in the list then this
+ * will return the first bead.
+ *
+ * @return The next bead in the list or the first bead if this is the last bead.
+ */
+ public PDThreadBead getNextBead()
+ {
+ return new PDThreadBead( (COSDictionary) bead.getDictionaryObject( "N" ) );
+ }
+
+ /**
+ * Set the next bead in the thread.
+ *
+ * @param next The next bead.
+ */
+ protected void setNextBead( PDThreadBead next )
+ {
+ bead.setItem( "N", next );
+ }
+
+ /**
+ * This will get the previous bead. If this bead is the first bead in the list then this
+ * will return the last bead.
+ *
+ * @return The previous bead in the list or the last bead if this is the first bead.
+ */
+ public PDThreadBead getPreviousBead()
+ {
+ return new PDThreadBead( (COSDictionary) bead.getDictionaryObject( "V" ) );
+ }
+
+ /**
+ * Set the previous bead in the thread.
+ *
+ * @param previous The previous bead.
+ */
+ protected void setPreviousBead( PDThreadBead previous )
+ {
+ bead.setItem( "V", previous );
+ }
+
+ /**
+ * Append a bead after this bead. This will correctly set the next/previous beads in the
+ * linked list.
+ *
+ * @param append The bead to insert.
+ */
+ public void appendBead( PDThreadBead append )
+ {
+ PDThreadBead nextBead = getNextBead();
+ nextBead.setPreviousBead( append );
+ append.setNextBead( nextBead );
+ setNextBead( append );
+ append.setPreviousBead( this );
+ }
+
+ /**
+ * Get the page that this bead is part of.
+ *
+ * @return The page that this bead is part of.
+ */
+ public PDPage getPage()
+ {
+ PDPage page = null;
+ COSDictionary dic = (COSDictionary)bead.getDictionaryObject( "P" );
+ if( dic != null )
+ {
+ page = new PDPage( dic );
+ }
+ return page;
+ }
+
+ /**
+ * Set the page that this bead is part of. This is a required property and must be
+ * set when creating a new bead. The PDPage object also has a list of beads in the natural
+ * reading order. It is recommended that you add this object to that list as well.
+ *
+ * @param page The page that this bead is on.
+ */
+ public void setPage( PDPage page )
+ {
+ bead.setItem( "P", page );
+ }
+
+ /**
+ * The rectangle on the page that this bead is part of.
+ *
+ * @return The part of the page that this bead covers.
+ */
+ public PDRectangle getRectangle()
+ {
+ PDRectangle rect = null;
+ COSArray array = (COSArray)bead.getDictionaryObject( COSName.R );
+ if( array != null )
+ {
+ rect = new PDRectangle( array );
+ }
+ return rect;
+ }
+
+ /**
+ * Set the rectangle on the page that this bead covers.
+ *
+ * @param rect The portion of the page that this bead covers.
+ */
+ public void setRectangle( PDRectangle rect )
+ {
+ bead.setItem( COSName.R, rect );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+A package to allow provide access to PDF page navigation functionality.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.interactive.viewerpreferences;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+/**
+ * This is the document viewing preferences.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDViewerPreferences implements COSObjectable
+{
+ /**
+ * From PDF Reference: "Neither document outline nor thumbnail images visible".
+ */
+ public static final String NON_FULL_SCREEN_PAGE_MODE_USE_NONE = "UseNone";
+ /**
+ * From PDF Reference: "Document outline visible".
+ */
+ public static final String NON_FULL_SCREEN_PAGE_MODE_USE_OUTLINES = "UseOutlines";
+ /**
+ * From PDF Reference: "Thumbnail images visible".
+ */
+ public static final String NON_FULL_SCREEN_PAGE_MODE_USE_THUMBS = "UseThumbs";
+ /**
+ * From PDF Reference: "Optional content group panel visible".
+ */
+ public static final String NON_FULL_SCREEN_PAGE_MODE_USE_OPTIONAL_CONTENT = "UseOC";
+
+ /**
+ * Reading direction.
+ */
+ public static final String READING_DIRECTION_L2R = "L2R";
+ /**
+ * Reading direction.
+ */
+ public static final String READING_DIRECTION_R2L = "R2L";
+
+ /**
+ * Boundary constant.
+ */
+ public static final String BOUNDARY_MEDIA_BOX = "MediaBox";
+ /**
+ * Boundary constant.
+ */
+ public static final String BOUNDARY_CROP_BOX = "CropBox";
+ /**
+ * Boundary constant.
+ */
+ public static final String BOUNDARY_BLEED_BOX = "BleedBox";
+ /**
+ * Boundary constant.
+ */
+ public static final String BOUNDARY_TRIM_BOX = "TrimBox";
+ /**
+ * Boundary constant.
+ */
+ public static final String BOUNDARY_ART_BOX = "ArtBox";
+
+
+ private COSDictionary prefs;
+
+ /**
+ * Constructor that is used for a preexisting dictionary.
+ *
+ * @param dic The underlying dictionary.
+ */
+ public PDViewerPreferences( COSDictionary dic )
+ {
+ prefs = dic;
+ }
+
+ /**
+ * This will get the underlying dictionary that this object wraps.
+ *
+ * @return The underlying info dictionary.
+ */
+ public COSDictionary getDictionary()
+ {
+ return prefs;
+ }
+
+ /**
+ * Convert this standard java object to a COS object.
+ *
+ * @return The cos object that matches this Java object.
+ */
+ public COSBase getCOSObject()
+ {
+ return prefs;
+ }
+
+ /**
+ * Get the toolbar preference.
+ *
+ * @return the toolbar preference.
+ */
+ public boolean hideToolbar()
+ {
+ return prefs.getBoolean( "HideToolbar", false );
+ }
+
+ /**
+ * Set the toolbar preference.
+ *
+ * @param value Set the toolbar preference.
+ */
+ public void setHideToolbar( boolean value )
+ {
+ prefs.setBoolean( "HideToolbar", value );
+ }
+
+ /**
+ * Get the menubar preference.
+ *
+ * @return the menubar preference.
+ */
+ public boolean hideMenubar()
+ {
+ return prefs.getBoolean( "HideMenubar", false );
+ }
+
+ /**
+ * Set the menubar preference.
+ *
+ * @param value Set the menubar preference.
+ */
+ public void setHideMenubar( boolean value )
+ {
+ prefs.setBoolean( "HideMenubar", value );
+ }
+
+ /**
+ * Get the window UI preference.
+ *
+ * @return the window UI preference.
+ */
+ public boolean hideWindowUI()
+ {
+ return prefs.getBoolean( "HideWindowUI", false );
+ }
+
+ /**
+ * Set the window UI preference.
+ *
+ * @param value Set the window UI preference.
+ */
+ public void setHideWindowUI( boolean value )
+ {
+ prefs.setBoolean( "HideWindowUI", value );
+ }
+
+ /**
+ * Get the fit window preference.
+ *
+ * @return the fit window preference.
+ */
+ public boolean fitWindow()
+ {
+ return prefs.getBoolean( "FitWindow", false );
+ }
+
+ /**
+ * Set the fit window preference.
+ *
+ * @param value Set the fit window preference.
+ */
+ public void setFitWindow( boolean value )
+ {
+ prefs.setBoolean( "FitWindow", value );
+ }
+
+ /**
+ * Get the center window preference.
+ *
+ * @return the center window preference.
+ */
+ public boolean centerWindow()
+ {
+ return prefs.getBoolean( "CenterWindow", false );
+ }
+
+ /**
+ * Set the center window preference.
+ *
+ * @param value Set the center window preference.
+ */
+ public void setCenterWindow( boolean value )
+ {
+ prefs.setBoolean( "CenterWindow", value );
+ }
+
+ /**
+ * Get the display doc title preference.
+ *
+ * @return the display doc title preference.
+ */
+ public boolean displayDocTitle()
+ {
+ return prefs.getBoolean( "DisplayDocTitle", false );
+ }
+
+ /**
+ * Set the display doc title preference.
+ *
+ * @param value Set the display doc title preference.
+ */
+ public void setDisplayDocTitle( boolean value )
+ {
+ prefs.setBoolean( "DisplayDocTitle", value );
+ }
+
+ /**
+ * Get the non full screen page mode preference.
+ *
+ * @return the non full screen page mode preference.
+ */
+ public String getNonFullScreenPageMode()
+ {
+ return prefs.getNameAsString( "NonFullScreenPageMode", NON_FULL_SCREEN_PAGE_MODE_USE_NONE);
+ }
+
+ /**
+ * Set the non full screen page mode preference.
+ *
+ * @param value Set the non full screen page mode preference.
+ */
+ public void setNonFullScreenPageMode( String value )
+ {
+ prefs.setName( "NonFullScreenPageMode", value );
+ }
+
+ /**
+ * Get the reading direction preference.
+ *
+ * @return the reading direction preference.
+ */
+ public String getReadingDirection()
+ {
+ return prefs.getNameAsString( "Direction", READING_DIRECTION_L2R);
+ }
+
+ /**
+ * Set the reading direction preference.
+ *
+ * @param value Set the reading direction preference.
+ */
+ public void setReadingDirection( String value )
+ {
+ prefs.setName( "Direction", value );
+ }
+
+ /**
+ * Get the ViewArea preference. See BOUNDARY_XXX constants.
+ *
+ * @return the ViewArea preference.
+ */
+ public String getViewArea()
+ {
+ return prefs.getNameAsString( "ViewArea", BOUNDARY_CROP_BOX);
+ }
+
+ /**
+ * Set the ViewArea preference. See BOUNDARY_XXX constants.
+ *
+ * @param value Set the ViewArea preference.
+ */
+ public void setViewArea( String value )
+ {
+ prefs.setName( "ViewArea", value );
+ }
+
+ /**
+ * Get the ViewClip preference. See BOUNDARY_XXX constants.
+ *
+ * @return the ViewClip preference.
+ */
+ public String getViewClip()
+ {
+ return prefs.getNameAsString( "ViewClip", BOUNDARY_CROP_BOX);
+ }
+
+ /**
+ * Set the ViewClip preference. See BOUNDARY_XXX constants.
+ *
+ * @param value Set the ViewClip preference.
+ */
+ public void setViewClip( String value )
+ {
+ prefs.setName( "ViewClip", value );
+ }
+
+ /**
+ * Get the PrintArea preference. See BOUNDARY_XXX constants.
+ *
+ * @return the PrintArea preference.
+ */
+ public String getPrintArea()
+ {
+ return prefs.getNameAsString( "PrintArea", BOUNDARY_CROP_BOX);
+ }
+
+ /**
+ * Set the PrintArea preference. See BOUNDARY_XXX constants.
+ *
+ * @param value Set the PrintArea preference.
+ */
+ public void setPrintArea( String value )
+ {
+ prefs.setName( "PrintArea", value );
+ }
+
+ /**
+ * Get the PrintClip preference. See BOUNDARY_XXX constants.
+ *
+ * @return the PrintClip preference.
+ */
+ public String getPrintClip()
+ {
+ return prefs.getNameAsString( "PrintClip", BOUNDARY_CROP_BOX);
+ }
+
+ /**
+ * Set the PrintClip preference. See BOUNDARY_XXX constants.
+ *
+ * @param value Set the PrintClip preference.
+ */
+ public void setPrintClip( String value )
+ {
+ prefs.setName( "PrintClip", value );
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+A package to allow access to document viewing preferences.
+</body>
+</html>
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The PDModel package represents a high level API for creating and manipulating PDF documents.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.text;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+
+/**
+ * This class will hold the current state of the text parameters when executing a
+ * content stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class PDTextState implements Cloneable
+{
+ /**
+ * See PDF Reference 1.5 Table 5.3.
+ */
+ public static final int RENDERING_MODE_FILL_TEXT = 0;
+ /**
+ * See PDF Reference 1.5 Table 5.3.
+ */
+ public static final int RENDERING_MODE_STROKE_TEXT = 1;
+ /**
+ * See PDF Reference 1.5 Table 5.3.
+ */
+ public static final int RENDERING_MODE_FILL_THEN_STROKE_TEXT = 2;
+ /**
+ * See PDF Reference 1.5 Table 5.3.
+ */
+ public static final int RENDERING_MODE_NEITHER_FILL_NOR_STROKE_TEXT = 3;
+ /**
+ * See PDF Reference 1.5 Table 5.3.
+ */
+ public static final int RENDERING_MODE_FILL_TEXT_AND_ADD_TO_PATH_FOR_CLIPPING = 4;
+ /**
+ * See PDF Reference 1.5 Table 5.3.
+ */
+ public static final int RENDERING_MODE_STROKE_TEXT_AND_ADD_TO_PATH_FOR_CLIPPING = 5;
+ /**
+ * See PDF Reference 1.5 Table 5.3.
+ */
+ public static final int RENDERING_MODE_FILL_THEN_STROKE_TEXT_AND_ADD_TO_PATH_FOR_CLIPPING = 6;
+ /**
+ * See PDF Reference 1.5 Table 5.3.
+ */
+ public static final int RENDERING_MODE_ADD_TEXT_TO_PATH_FOR_CLIPPING = 7;
+
+
+ //these are set default according to PDF Reference 1.5 section 5.2
+ private float characterSpacing = 0;
+ private float wordSpacing = 0;
+ private float horizontalScaling = 100;
+ private float leading = 0;
+ private PDFont font;
+ private float fontSize;
+ private int renderingMode = 0;
+ private float rise = 0;
+ private boolean knockout = true;
+
+ /**
+ * Get the value of the characterSpacing.
+ *
+ * @return The current characterSpacing.
+ */
+ public float getCharacterSpacing()
+ {
+ return characterSpacing;
+ }
+
+ /**
+ * Set the value of the characterSpacing.
+ *
+ * @param value The characterSpacing.
+ */
+ public void setCharacterSpacing(float value)
+ {
+ characterSpacing = value;
+ }
+
+ /**
+ * Get the value of the wordSpacing.
+ *
+ * @return The wordSpacing.
+ */
+ public float getWordSpacing()
+ {
+ return wordSpacing;
+ }
+
+ /**
+ * Set the value of the wordSpacing.
+ *
+ * @param value The wordSpacing.
+ */
+ public void setWordSpacing(float value)
+ {
+ wordSpacing = value;
+ }
+
+ /**
+ * Get the value of the horizontalScaling. The default is 100. This value
+ * is the percentage value 0-100 and not 0-1. So for mathematical operations
+ * you will probably need to divide by 100 first.
+ *
+ * @return The horizontalScaling.
+ */
+ public float getHorizontalScalingPercent()
+ {
+ return horizontalScaling;
+ }
+
+ /**
+ * Set the value of the horizontalScaling.
+ *
+ * @param value The horizontalScaling.
+ */
+ public void setHorizontalScalingPercent(float value)
+ {
+ horizontalScaling = value;
+ }
+
+ /**
+ * Get the value of the leading.
+ *
+ * @return The leading.
+ */
+ public float getLeading()
+ {
+ return leading;
+ }
+
+ /**
+ * Set the value of the leading.
+ *
+ * @param value The leading.
+ */
+ public void setLeading(float value)
+ {
+ leading = value;
+ }
+
+ /**
+ * Get the value of the font.
+ *
+ * @return The font.
+ */
+ public PDFont getFont()
+ {
+ return font;
+ }
+
+ /**
+ * Set the value of the font.
+ *
+ * @param value The font.
+ */
+ public void setFont(PDFont value)
+ {
+ font = value;
+ }
+
+ /**
+ * Get the value of the fontSize.
+ *
+ * @return The fontSize.
+ */
+ public float getFontSize()
+ {
+ return fontSize;
+ }
+
+ /**
+ * Set the value of the fontSize.
+ *
+ * @param value The fontSize.
+ */
+ public void setFontSize(float value)
+ {
+ fontSize = value;
+ }
+
+ /**
+ * Get the value of the renderingMode.
+ *
+ * @return The renderingMode.
+ */
+ public int getRenderingMode()
+ {
+ return renderingMode;
+ }
+
+ /**
+ * Set the value of the renderingMode.
+ *
+ * @param value The renderingMode.
+ */
+ public void setRenderingMode(int value)
+ {
+ renderingMode = value;
+ }
+
+ /**
+ * Get the value of the rise.
+ *
+ * @return The rise.
+ */
+ public float getRise()
+ {
+ return rise;
+ }
+
+ /**
+ * Set the value of the rise.
+ *
+ * @param value The rise.
+ */
+ public void setRise(float value)
+ {
+ rise = value;
+ }
+
+ /**
+ * Get the value of the knockout.
+ *
+ * @return The knockout.
+ */
+ public boolean getKnockoutFlag()
+ {
+ return knockout;
+ }
+
+ /**
+ * Set the value of the knockout.
+ *
+ * @param value The knockout.
+ */
+ public void setKnockoutFlag(boolean value)
+ {
+ knockout = value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object clone()
+ {
+ try
+ {
+ return super.clone();
+ }
+ catch (CloneNotSupportedException ignore)
+ {
+ //ignore
+ }
+ return null;
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+The PDModel text package deals with text states, operations, and parameters within the PDF document.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.persistence.util;
+
+import org.apache.pdfbox.util.StringUtil;
+
+/**
+ * helper type for faster mapping of bytes to their hex equivalent.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.4 $
+ */
+public final class COSHEXTable
+{
+ private COSHEXTable()
+ {
+ }
+ /**
+ * ASCII byte values for the hex strings.
+ */
+ public static final byte[][] TABLE =
+ {
+ StringUtil.getBytes("00"),
+ StringUtil.getBytes("01"),
+ StringUtil.getBytes("02"),
+ StringUtil.getBytes("03"),
+ StringUtil.getBytes("04"),
+ StringUtil.getBytes("05"),
+ StringUtil.getBytes("06"),
+ StringUtil.getBytes("07"),
+ StringUtil.getBytes("08"),
+ StringUtil.getBytes("09"),
+ StringUtil.getBytes("0A"),
+ StringUtil.getBytes("0B"),
+ StringUtil.getBytes("0C"),
+ StringUtil.getBytes("0D"),
+ StringUtil.getBytes("0E"),
+ StringUtil.getBytes("0F"),
+ StringUtil.getBytes("10"),
+ StringUtil.getBytes("11"),
+ StringUtil.getBytes("12"),
+ StringUtil.getBytes("13"),
+ StringUtil.getBytes("14"),
+ StringUtil.getBytes("15"),
+ StringUtil.getBytes("16"),
+ StringUtil.getBytes("17"),
+ StringUtil.getBytes("18"),
+ StringUtil.getBytes("19"),
+ StringUtil.getBytes("1A"),
+ StringUtil.getBytes("1B"),
+ StringUtil.getBytes("1C"),
+ StringUtil.getBytes("1D"),
+ StringUtil.getBytes("1E"),
+ StringUtil.getBytes("1F"),
+ StringUtil.getBytes("20"),
+ StringUtil.getBytes("21"),
+ StringUtil.getBytes("22"),
+ StringUtil.getBytes("23"),
+ StringUtil.getBytes("24"),
+ StringUtil.getBytes("25"),
+ StringUtil.getBytes("26"),
+ StringUtil.getBytes("27"),
+ StringUtil.getBytes("28"),
+ StringUtil.getBytes("29"),
+ StringUtil.getBytes("2A"),
+ StringUtil.getBytes("2B"),
+ StringUtil.getBytes("2C"),
+ StringUtil.getBytes("2D"),
+ StringUtil.getBytes("2E"),
+ StringUtil.getBytes("2F"),
+ StringUtil.getBytes("30"),
+ StringUtil.getBytes("31"),
+ StringUtil.getBytes("32"),
+ StringUtil.getBytes("33"),
+ StringUtil.getBytes("34"),
+ StringUtil.getBytes("35"),
+ StringUtil.getBytes("36"),
+ StringUtil.getBytes("37"),
+ StringUtil.getBytes("38"),
+ StringUtil.getBytes("39"),
+ StringUtil.getBytes("3A"),
+ StringUtil.getBytes("3B"),
+ StringUtil.getBytes("3C"),
+ StringUtil.getBytes("3D"),
+ StringUtil.getBytes("3E"),
+ StringUtil.getBytes("3F"),
+ StringUtil.getBytes("40"),
+ StringUtil.getBytes("41"),
+ StringUtil.getBytes("42"),
+ StringUtil.getBytes("43"),
+ StringUtil.getBytes("44"),
+ StringUtil.getBytes("45"),
+ StringUtil.getBytes("46"),
+ StringUtil.getBytes("47"),
+ StringUtil.getBytes("48"),
+ StringUtil.getBytes("49"),
+ StringUtil.getBytes("4A"),
+ StringUtil.getBytes("4B"),
+ StringUtil.getBytes("4C"),
+ StringUtil.getBytes("4D"),
+ StringUtil.getBytes("4E"),
+ StringUtil.getBytes("4F"),
+ StringUtil.getBytes("50"),
+ StringUtil.getBytes("51"),
+ StringUtil.getBytes("52"),
+ StringUtil.getBytes("53"),
+ StringUtil.getBytes("54"),
+ StringUtil.getBytes("55"),
+ StringUtil.getBytes("56"),
+ StringUtil.getBytes("57"),
+ StringUtil.getBytes("58"),
+ StringUtil.getBytes("59"),
+ StringUtil.getBytes("5A"),
+ StringUtil.getBytes("5B"),
+ StringUtil.getBytes("5C"),
+ StringUtil.getBytes("5D"),
+ StringUtil.getBytes("5E"),
+ StringUtil.getBytes("5F"),
+ StringUtil.getBytes("60"),
+ StringUtil.getBytes("61"),
+ StringUtil.getBytes("62"),
+ StringUtil.getBytes("63"),
+ StringUtil.getBytes("64"),
+ StringUtil.getBytes("65"),
+ StringUtil.getBytes("66"),
+ StringUtil.getBytes("67"),
+ StringUtil.getBytes("68"),
+ StringUtil.getBytes("69"),
+ StringUtil.getBytes("6A"),
+ StringUtil.getBytes("6B"),
+ StringUtil.getBytes("6C"),
+ StringUtil.getBytes("6D"),
+ StringUtil.getBytes("6E"),
+ StringUtil.getBytes("6F"),
+ StringUtil.getBytes("70"),
+ StringUtil.getBytes("71"),
+ StringUtil.getBytes("72"),
+ StringUtil.getBytes("73"),
+ StringUtil.getBytes("74"),
+ StringUtil.getBytes("75"),
+ StringUtil.getBytes("76"),
+ StringUtil.getBytes("77"),
+ StringUtil.getBytes("78"),
+ StringUtil.getBytes("79"),
+ StringUtil.getBytes("7A"),
+ StringUtil.getBytes("7B"),
+ StringUtil.getBytes("7C"),
+ StringUtil.getBytes("7D"),
+ StringUtil.getBytes("7E"),
+ StringUtil.getBytes("7F"),
+ StringUtil.getBytes("80"),
+ StringUtil.getBytes("81"),
+ StringUtil.getBytes("82"),
+ StringUtil.getBytes("83"),
+ StringUtil.getBytes("84"),
+ StringUtil.getBytes("85"),
+ StringUtil.getBytes("86"),
+ StringUtil.getBytes("87"),
+ StringUtil.getBytes("88"),
+ StringUtil.getBytes("89"),
+ StringUtil.getBytes("8A"),
+ StringUtil.getBytes("8B"),
+ StringUtil.getBytes("8C"),
+ StringUtil.getBytes("8D"),
+ StringUtil.getBytes("8E"),
+ StringUtil.getBytes("8F"),
+ StringUtil.getBytes("90"),
+ StringUtil.getBytes("91"),
+ StringUtil.getBytes("92"),
+ StringUtil.getBytes("93"),
+ StringUtil.getBytes("94"),
+ StringUtil.getBytes("95"),
+ StringUtil.getBytes("96"),
+ StringUtil.getBytes("97"),
+ StringUtil.getBytes("98"),
+ StringUtil.getBytes("99"),
+ StringUtil.getBytes("9A"),
+ StringUtil.getBytes("9B"),
+ StringUtil.getBytes("9C"),
+ StringUtil.getBytes("9D"),
+ StringUtil.getBytes("9E"),
+ StringUtil.getBytes("9F"),
+ StringUtil.getBytes("A0"),
+ StringUtil.getBytes("A1"),
+ StringUtil.getBytes("A2"),
+ StringUtil.getBytes("A3"),
+ StringUtil.getBytes("A4"),
+ StringUtil.getBytes("A5"),
+ StringUtil.getBytes("A6"),
+ StringUtil.getBytes("A7"),
+ StringUtil.getBytes("A8"),
+ StringUtil.getBytes("A9"),
+ StringUtil.getBytes("AA"),
+ StringUtil.getBytes("AB"),
+ StringUtil.getBytes("AC"),
+ StringUtil.getBytes("AD"),
+ StringUtil.getBytes("AE"),
+ StringUtil.getBytes("AF"),
+ StringUtil.getBytes("B0"),
+ StringUtil.getBytes("B1"),
+ StringUtil.getBytes("B2"),
+ StringUtil.getBytes("B3"),
+ StringUtil.getBytes("B4"),
+ StringUtil.getBytes("B5"),
+ StringUtil.getBytes("B6"),
+ StringUtil.getBytes("B7"),
+ StringUtil.getBytes("B8"),
+ StringUtil.getBytes("B9"),
+ StringUtil.getBytes("BA"),
+ StringUtil.getBytes("BB"),
+ StringUtil.getBytes("BC"),
+ StringUtil.getBytes("BD"),
+ StringUtil.getBytes("BE"),
+ StringUtil.getBytes("BF"),
+ StringUtil.getBytes("C0"),
+ StringUtil.getBytes("C1"),
+ StringUtil.getBytes("C2"),
+ StringUtil.getBytes("C3"),
+ StringUtil.getBytes("C4"),
+ StringUtil.getBytes("C5"),
+ StringUtil.getBytes("C6"),
+ StringUtil.getBytes("C7"),
+ StringUtil.getBytes("C8"),
+ StringUtil.getBytes("C9"),
+ StringUtil.getBytes("CA"),
+ StringUtil.getBytes("CB"),
+ StringUtil.getBytes("CC"),
+ StringUtil.getBytes("CD"),
+ StringUtil.getBytes("CE"),
+ StringUtil.getBytes("CF"),
+ StringUtil.getBytes("D0"),
+ StringUtil.getBytes("D1"),
+ StringUtil.getBytes("D2"),
+ StringUtil.getBytes("D3"),
+ StringUtil.getBytes("D4"),
+ StringUtil.getBytes("D5"),
+ StringUtil.getBytes("D6"),
+ StringUtil.getBytes("D7"),
+ StringUtil.getBytes("D8"),
+ StringUtil.getBytes("D9"),
+ StringUtil.getBytes("DA"),
+ StringUtil.getBytes("DB"),
+ StringUtil.getBytes("DC"),
+ StringUtil.getBytes("DD"),
+ StringUtil.getBytes("DE"),
+ StringUtil.getBytes("DF"),
+ StringUtil.getBytes("E0"),
+ StringUtil.getBytes("E1"),
+ StringUtil.getBytes("E2"),
+ StringUtil.getBytes("E3"),
+ StringUtil.getBytes("E4"),
+ StringUtil.getBytes("E5"),
+ StringUtil.getBytes("E6"),
+ StringUtil.getBytes("E7"),
+ StringUtil.getBytes("E8"),
+ StringUtil.getBytes("E9"),
+ StringUtil.getBytes("EA"),
+ StringUtil.getBytes("EB"),
+ StringUtil.getBytes("EC"),
+ StringUtil.getBytes("ED"),
+ StringUtil.getBytes("EE"),
+ StringUtil.getBytes("EF"),
+ StringUtil.getBytes("F0"),
+ StringUtil.getBytes("F1"),
+ StringUtil.getBytes("F2"),
+ StringUtil.getBytes("F3"),
+ StringUtil.getBytes("F4"),
+ StringUtil.getBytes("F5"),
+ StringUtil.getBytes("F6"),
+ StringUtil.getBytes("F7"),
+ StringUtil.getBytes("F8"),
+ StringUtil.getBytes("F9"),
+ StringUtil.getBytes("FA"),
+ StringUtil.getBytes("FB"),
+ StringUtil.getBytes("FC"),
+ StringUtil.getBytes("FD"),
+ StringUtil.getBytes("FE"),
+ StringUtil.getBytes("FF")
+ };
+
+ /**
+ * ASCII byte values for the hex strings.
+ */
+ public static final String[] HEX_TABLE =
+ {
+ "00",
+ "01",
+ "02",
+ "03",
+ "04",
+ "05",
+ "06",
+ "07",
+ "08",
+ "09",
+ "0A",
+ "0B",
+ "0C",
+ "0D",
+ "0E",
+ "0F",
+ "10",
+ "11",
+ "12",
+ "13",
+ "14",
+ "15",
+ "16",
+ "17",
+ "18",
+ "19",
+ "1A",
+ "1B",
+ "1C",
+ "1D",
+ "1E",
+ "1F",
+ "20",
+ "21",
+ "22",
+ "23",
+ "24",
+ "25",
+ "26",
+ "27",
+ "28",
+ "29",
+ "2A",
+ "2B",
+ "2C",
+ "2D",
+ "2E",
+ "2F",
+ "30",
+ "31",
+ "32",
+ "33",
+ "34",
+ "35",
+ "36",
+ "37",
+ "38",
+ "39",
+ "3A",
+ "3B",
+ "3C",
+ "3D",
+ "3E",
+ "3F",
+ "40",
+ "41",
+ "42",
+ "43",
+ "44",
+ "45",
+ "46",
+ "47",
+ "48",
+ "49",
+ "4A",
+ "4B",
+ "4C",
+ "4D",
+ "4E",
+ "4F",
+ "50",
+ "51",
+ "52",
+ "53",
+ "54",
+ "55",
+ "56",
+ "57",
+ "58",
+ "59",
+ "5A",
+ "5B",
+ "5C",
+ "5D",
+ "5E",
+ "5F",
+ "60",
+ "61",
+ "62",
+ "63",
+ "64",
+ "65",
+ "66",
+ "67",
+ "68",
+ "69",
+ "6A",
+ "6B",
+ "6C",
+ "6D",
+ "6E",
+ "6F",
+ "70",
+ "71",
+ "72",
+ "73",
+ "74",
+ "75",
+ "76",
+ "77",
+ "78",
+ "79",
+ "7A",
+ "7B",
+ "7C",
+ "7D",
+ "7E",
+ "7F",
+ "80",
+ "81",
+ "82",
+ "83",
+ "84",
+ "85",
+ "86",
+ "87",
+ "88",
+ "89",
+ "8A",
+ "8B",
+ "8C",
+ "8D",
+ "8E",
+ "8F",
+ "90",
+ "91",
+ "92",
+ "93",
+ "94",
+ "95",
+ "96",
+ "97",
+ "98",
+ "99",
+ "9A",
+ "9B",
+ "9C",
+ "9D",
+ "9E",
+ "9F",
+ "A0",
+ "A1",
+ "A2",
+ "A3",
+ "A4",
+ "A5",
+ "A6",
+ "A7",
+ "A8",
+ "A9",
+ "AA",
+ "AB",
+ "AC",
+ "AD",
+ "AE",
+ "AF",
+ "B0",
+ "B1",
+ "B2",
+ "B3",
+ "B4",
+ "B5",
+ "B6",
+ "B7",
+ "B8",
+ "B9",
+ "BA",
+ "BB",
+ "BC",
+ "BD",
+ "BE",
+ "BF",
+ "C0",
+ "C1",
+ "C2",
+ "C3",
+ "C4",
+ "C5",
+ "C6",
+ "C7",
+ "C8",
+ "C9",
+ "CA",
+ "CB",
+ "CC",
+ "CD",
+ "CE",
+ "CF",
+ "D0",
+ "D1",
+ "D2",
+ "D3",
+ "D4",
+ "D5",
+ "D6",
+ "D7",
+ "D8",
+ "D9",
+ "DA",
+ "DB",
+ "DC",
+ "DD",
+ "DE",
+ "DF",
+ "E0",
+ "E1",
+ "E2",
+ "E3",
+ "E4",
+ "E5",
+ "E6",
+ "E7",
+ "E8",
+ "E9",
+ "EA",
+ "EB",
+ "EC",
+ "ED",
+ "EE",
+ "EF",
+ "F0",
+ "F1",
+ "F2",
+ "F3",
+ "F4",
+ "F5",
+ "F6",
+ "F7",
+ "F8",
+ "F9",
+ "FA",
+ "FB",
+ "FC",
+ "FD",
+ "FE",
+ "FF"
+ };
+}
--- /dev/null
+/*
+ * 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.persistence.util;
+
+import org.apache.pdfbox.cos.COSObject;
+
+/**
+ * Object representing the physical reference to an indirect pdf object.
+ *
+ * @author Michael Traut
+ * @version $Revision: 1.5 $
+ */
+public class COSObjectKey implements Comparable<COSObjectKey>
+{
+ private long number;
+ private long generation;
+
+ /**
+ * PDFObjectKey constructor comment.
+ *
+ * @param object The object that this key will represent.
+ */
+ public COSObjectKey(COSObject object)
+ {
+ this( object.getObjectNumber().longValue(), object.getGenerationNumber().longValue() );
+ }
+
+ /**
+ * PDFObjectKey constructor comment.
+ *
+ * @param num The object number.
+ * @param gen The object generation number.
+ */
+ public COSObjectKey(long num, long gen)
+ {
+ setNumber(num);
+ setGeneration(gen);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean equals(Object obj)
+ {
+ return (obj instanceof COSObjectKey) &&
+ ((COSObjectKey)obj).getNumber() == getNumber() &&
+ ((COSObjectKey)obj).getGeneration() == getGeneration();
+ }
+
+ /**
+ * This will get the generation number.
+ *
+ * @return The objects generation number.
+ */
+ public long getGeneration()
+ {
+ return generation;
+ }
+ /**
+ * This will get the objects id.
+ *
+ * @return The object's id.
+ */
+ public long getNumber()
+ {
+ return number;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int hashCode()
+ {
+ return (int)(number + generation);
+ }
+ /**
+ * This will set the objects generation number.
+ *
+ * @param newGeneration The objects generation number.
+ */
+ public void setGeneration(long newGeneration)
+ {
+ generation = newGeneration;
+ }
+ /**
+ * This will set the objects id.
+ *
+ * @param newNumber The objects number.
+ */
+ public void setNumber(long newNumber)
+ {
+ number = newNumber;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString()
+ {
+ 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;
+ }
+ }
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+These are utilities used by the persistence layer.
+</body>
+</html>
--- /dev/null
+/*
+ * 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 org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+
+/**
+ * This class will be used for bit flag operations.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class BitFlagHelper
+{
+ private BitFlagHelper()
+ {
+ //helper class should not be constructed
+ }
+
+ /**
+ * Sets the given boolean value at bitPos in the flags.
+ *
+ * @param dic The dictionary to set the value into.
+ * @param field The name of the field to set the value into.
+ * @param bitFlag the bit position to set the value in.
+ * @param value the value the bit position should have.
+ *
+ * @deprecated use {@link #setFlag(COSDictionary, COSName, int, boolean)} using COSName constants instead
+ */
+ public static final void setFlag( COSDictionary dic, String field, int bitFlag, boolean value )
+ {
+ setFlag(dic, COSName.getPDFName(field), bitFlag, value);
+ }
+
+ /**
+ * Sets the given boolean value at bitPos in the flags.
+ *
+ * @param dic The dictionary to set the value into.
+ * @param field The COSName of the field to set the value into.
+ * @param bitFlag the bit position to set the value in.
+ * @param value the value the bit position should have.
+ */
+ public static final void setFlag( COSDictionary dic, COSName field, int bitFlag, boolean value )
+ {
+ int currentFlags = dic.getInt( field, 0 );
+ if( value )
+ {
+ currentFlags = currentFlags | bitFlag;
+ }
+ else
+ {
+ currentFlags = currentFlags &= ~bitFlag;
+ }
+ dic.setInt( field, currentFlags );
+ }
+
+ /**
+ * Gets the boolean value from the flags at the given bit
+ * position.
+ *
+ * @param dic The dictionary to get the field from.
+ * @param field The name of the field to get the flag from.
+ * @param bitFlag the bitPosition to get the value from.
+ *
+ * @return true if the number at bitPos is '1'
+ *
+ * @deprecated use {@link #getFlag(COSDictionary, COSName, boolean)} using COSName constants instead
+ */
+ public static final boolean getFlag(COSDictionary dic, String field, int bitFlag)
+ {
+ return getFlag(dic, COSName.getPDFName(field), bitFlag);
+ }
+
+ /**
+ * Gets the boolean value from the flags at the given bit
+ * position.
+ *
+ * @param dic The dictionary to get the field from.
+ * @param field The COSName of the field to get the flag from.
+ * @param bitFlag the bitPosition to get the value from.
+ *
+ * @return true if the number at bitPos is '1'
+ */
+ public static final boolean getFlag(COSDictionary dic, COSName field, int bitFlag)
+ {
+ int ff = dic.getInt( field, 0 );
+ return (ff & bitFlag) == bitFlag;
+ }
+}
--- /dev/null
+/*
+ * 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.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import java.io.IOException;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * This class is used to convert dates to strings and back using the PDF
+ * date standards. Date are described in PDFReference1.4 section 3.8.2
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class DateConverter
+{
+ //The Date format is supposed to be the PDF_DATE_FORMAT, but not all PDF documents
+ //will use that date, so I have added a couple other potential formats
+ //to try if the original one does not work.
+ private static final SimpleDateFormat[] POTENTIAL_FORMATS = new SimpleDateFormat[] {
+ new SimpleDateFormat("EEEE, dd MMM yyyy hh:mm:ss a", Locale.ENGLISH),
+ new SimpleDateFormat("EEEE, MMM dd, yyyy hh:mm:ss a", Locale.ENGLISH),
+ new SimpleDateFormat("MM/dd/yyyy hh:mm:ss", Locale.ENGLISH),
+ new SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH),
+ new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH),
+ new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz", Locale.ENGLISH),
+ new SimpleDateFormat("EEEE, MMM dd, yyyy", Locale.ENGLISH), // Acrobat Distiller 1.0.2 for Macintosh
+ new SimpleDateFormat("EEEE MMM dd, yyyy HH:mm:ss", Locale.ENGLISH), // ECMP5
+ new SimpleDateFormat("EEEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH), // GNU Ghostscript 7.0.7
+ new SimpleDateFormat("EEEE, MMM dd, yyyy 'at' hh:mma", Locale.ENGLISH), // Acrobat Net Distiller 1.0 for Windows
+ new SimpleDateFormat("d/MM/yyyy hh:mm:ss", Locale.ENGLISH), // PDFBOX-164
+ new SimpleDateFormat("dd/MM/yyyy hh:mm:ss", Locale.ENGLISH), // PDFBOX-170
+ new SimpleDateFormat("EEEEEEEEEE, MMMMMMMMMMMM dd, yyyy", Locale.ENGLISH), // PDFBOX-465
+ new SimpleDateFormat("dd MMM yyyy hh:mm:ss", Locale.ENGLISH), // for 26 May 2000 11:25:00
+ new SimpleDateFormat("dd MMM yyyy hh:mm", Locale.ENGLISH), // for 26 May 2000 11:25
+ new SimpleDateFormat("M/dd/yyyy hh:mm:ss", Locale.ENGLISH),
+ new SimpleDateFormat("MM/d/yyyy hh:mm:ss", Locale.ENGLISH),
+ new SimpleDateFormat("M/dd/yyyy", Locale.ENGLISH),
+ new SimpleDateFormat("MM/d/yyyy", Locale.ENGLISH),
+ new SimpleDateFormat("M/d/yyyy hh:mm:ss", Locale.ENGLISH),
+ new SimpleDateFormat("M/d/yyyy", Locale.ENGLISH),
+ new SimpleDateFormat("M/d/yy hh:mm:ss", Locale.ENGLISH),
+ new SimpleDateFormat("M/d/yy", Locale.ENGLISH),
+ new SimpleDateFormat("yyyymmdd hh:mm:ss Z"), //
+ new SimpleDateFormat("yyyymmdd hh:mm:ss"), //
+ new SimpleDateFormat("yyyymmdd'+00''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+01''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+02''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+03''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+04''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+05''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+06''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+07''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+08''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+09''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+10''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+11''00'''"), //
+ new SimpleDateFormat("yyyymmdd'+12''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-01''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-02''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-03''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-04''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-05''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-06''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-07''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-08''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-09''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-10''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-11''00'''"), //
+ new SimpleDateFormat("yyyymmdd'-12''00'''"), //
+ new SimpleDateFormat("yyyymmdd"), // for 20090401+0200
+ };
+
+ private DateConverter()
+ {
+ //utility class should not be constructed.
+ }
+
+ /**
+ * This will convert the calendar to a string.
+ *
+ * @param date The date to convert to a string.
+ *
+ * @return The date as a String to be used in a PDF document.
+ */
+ public static String toString( Calendar date )
+ {
+ String retval = null;
+ if( date != null )
+ {
+ StringBuffer buffer = new StringBuffer();
+ TimeZone zone = date.getTimeZone();
+ long offsetInMinutes = zone.getOffset( date.getTimeInMillis() )/1000/60;
+ long hours = Math.abs( offsetInMinutes/60 );
+ long minutes = Math.abs( offsetInMinutes%60 );
+ buffer.append( "D:" );
+ // PDFBOX-402 , SimpleDateFormat is not thread safe, created it when you use it.
+ buffer.append( new SimpleDateFormat( "yyyyMMddHHmmss" , Locale.ENGLISH).format( date.getTime() ) );
+ if( offsetInMinutes == 0 )
+ {
+ buffer.append( "Z" );
+ }
+ else if( offsetInMinutes < 0 )
+ {
+ buffer.append( "-" );
+ }
+ else
+ {
+ buffer.append( "+" );
+ }
+ if( hours < 10 )
+ {
+ buffer.append( "0" );
+ }
+ buffer.append( hours );
+ buffer.append( "'" );
+ if( minutes < 10 )
+ {
+ buffer.append( "0" );
+ }
+ buffer.append( minutes );
+ buffer.append( "'" );
+ retval = buffer.toString();
+
+ }
+ return retval;
+ }
+
+ /**
+ * This will convert a string to a calendar.
+ *
+ * @param date The string representation of the calendar.
+ *
+ * @return The calendar that this string represents.
+ *
+ * @throws IOException If the date string is not in the correct format.
+ */
+ public static Calendar toCalendar( COSString date ) throws IOException
+ {
+ Calendar retval = null;
+ if( date != null )
+ {
+ retval = toCalendar( date.getString() );
+ }
+
+ return retval;
+ }
+
+ /**
+ * This will convert a string to a calendar.
+ *
+ * @param date The string representation of the calendar.
+ *
+ * @return The calendar that this string represents.
+ *
+ * @throws IOException If the date string is not in the correct format.
+ */
+ public static Calendar toCalendar( String date ) throws IOException
+ {
+ Calendar retval = null;
+ if( date != null && date.trim().length() > 0 )
+ {
+ //these are the default values
+ int year = 0;
+ int month = 1;
+ int day = 1;
+ int hour = 0;
+ int minute = 0;
+ int second = 0;
+ //first string off the prefix if it exists
+ try
+ {
+ SimpleTimeZone zone = null;
+ if( date.startsWith( "D:" ) )
+ {
+ date = date.substring( 2, date.length() );
+ }
+ if( date.length() < 4 )
+ {
+ throw new IOException( "Error: Invalid date format '" + date + "'" );
+ }
+ year = Integer.parseInt( date.substring( 0, 4 ) );
+ if( date.length() >= 6 )
+ {
+ month = Integer.parseInt( date.substring( 4, 6 ) );
+ }
+ if( date.length() >= 8 )
+ {
+ day = Integer.parseInt( date.substring( 6, 8 ) );
+ }
+ if( date.length() >= 10 )
+ {
+ hour = Integer.parseInt( date.substring( 8, 10 ) );
+ }
+ if( date.length() >= 12 )
+ {
+ minute = Integer.parseInt( date.substring( 10, 12 ) );
+ }
+ if( date.length() >= 14 )
+ {
+ second = Integer.parseInt( date.substring( 12, 14 ) );
+ }
+
+ if( date.length() >= 15 )
+ {
+ char sign = date.charAt( 14 );
+ if( sign == 'Z' )
+ {
+ zone = new SimpleTimeZone(0,"Unknown");
+ }
+ else
+ {
+ int hours = 0;
+ int minutes = 0;
+ if( date.length() >= 17 )
+ {
+ if( sign == '+' )
+ {
+ //parseInt cannot handle the + sign
+ hours = Integer.parseInt( date.substring( 15, 17 ) );
+ }
+ else if (sign == '-')
+ {
+ hours = -Integer.parseInt(date.substring(15,17));
+ }
+ else
+ {
+ hours = -Integer.parseInt( date.substring( 14, 16 ) );
+ }
+ }
+ if( date.length() > 20 )
+ {
+ minutes = Integer.parseInt( date.substring( 18, 20 ) );
+ }
+ zone = new SimpleTimeZone( hours*60*60*1000 + minutes*60*1000, "Unknown" );
+ }
+ }
+ if( zone != null )
+ {
+ retval = new GregorianCalendar( zone );
+ }
+ else
+ {
+ retval = new GregorianCalendar();
+ }
+
+ retval.set(year, month-1, day, hour, minute, second );
+ // PDFBOX-598: PDF dates are only accurate up to a second
+ retval.set(Calendar.MILLISECOND, 0);
+ }
+ catch( NumberFormatException e )
+ {
+ for( int i=0; retval == null && i<POTENTIAL_FORMATS.length; i++ )
+ {
+ try
+ {
+ Date utilDate = POTENTIAL_FORMATS[i].parse( date );
+ retval = new GregorianCalendar();
+ retval.setTime( utilDate );
+ }
+ catch( ParseException pe )
+ {
+ //ignore and move to next potential format
+ }
+ }
+ if( retval == null )
+ {
+ //we didn't find a valid date format so throw an exception
+ throw new IOException( "Error converting date:" + date );
+ }
+ }
+ }
+ return retval;
+ }
+
+ private static final void zeroAppend( StringBuffer out, int number )
+ {
+ if( number < 10 )
+ {
+ out.append( "0" );
+ }
+ out.append( number );
+ }
+
+ /**
+ * Convert the date to iso 8601 string format.
+ *
+ * @param cal The date to convert.
+ * @return The date represented as an ISO 8601 string.
+ */
+ public static String toISO8601( Calendar cal )
+ {
+ StringBuffer retval = new StringBuffer();
+
+ retval.append( cal.get( Calendar.YEAR ) );
+ retval.append( "-" );
+ zeroAppend( retval, cal.get( Calendar.MONTH )+1 );
+ retval.append( "-" );
+ zeroAppend( retval, cal.get( Calendar.DAY_OF_MONTH ) );
+ retval.append( "T" );
+ zeroAppend( retval, cal.get( Calendar.HOUR_OF_DAY ));
+ retval.append( ":" );
+ zeroAppend( retval, cal.get( Calendar.MINUTE ));
+ retval.append( ":" );
+ zeroAppend( retval, cal.get( Calendar.SECOND ));
+
+ int timeZone = cal.get( Calendar.ZONE_OFFSET ) + cal.get(Calendar.DST_OFFSET );
+ if( timeZone < 0 )
+ {
+ retval.append( "-" );
+ }
+ else
+ {
+ retval.append( "+" );
+ }
+ timeZone = Math.abs( timeZone );
+ //milliseconds/1000 = seconds = seconds / 60 = minutes = minutes/60 = hours
+ int hours = timeZone/1000/60/60;
+ int minutes = (timeZone - (hours*1000*60*60))/1000/1000;
+ if( hours < 10 )
+ {
+ retval.append( "0" );
+ }
+ retval.append( Integer.toString( hours ) );
+ retval.append( ":" );
+ if( minutes < 10 )
+ {
+ retval.append( "0" );
+ }
+ retval.append( Integer.toString( minutes ) );
+
+ return retval.toString();
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+/**
+ * This class deals with some logging that is not handled by the log4j replacement.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ErrorLogger
+{
+ /**
+ * Utility class, should not be instantiated.
+ *
+ */
+ private ErrorLogger()
+ {
+ }
+
+ /**
+ * Log an error message. This is only used for log4j replacement and
+ * should never be used when writing code.
+ *
+ * @param errorMessage The error message.
+ */
+ public static void log( String errorMessage )
+ {
+ System.err.println( errorMessage );
+ }
+
+ /**
+ * Log an error message. This is only used for log4j replacement and
+ * should never be used when writing code.
+ *
+ * @param errorMessage The error message.
+ * @param t The exception.
+ */
+ public static void log( String errorMessage, Throwable t )
+ {
+ System.err.println( errorMessage );
+ t.printStackTrace();
+ }
+}
--- /dev/null
+/*
+ * 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.File;
+
+import javax.swing.filechooser.FileFilter;
+
+
+/**
+ * A FileFilter that will only accept files of a certain extension.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class ExtensionFileFilter extends FileFilter
+{
+ private String[] extensions = null;
+ private String desc;
+
+ /**
+ * Constructor.
+ *
+ * @param ext A list of filename extensions, ie new String[] { "PDF"}.
+ * @param description A description of the files.
+ */
+ public ExtensionFileFilter( String[] ext, String description )
+ {
+ extensions = ext;
+ desc = description;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean accept(File pathname)
+ {
+ if (pathname.isDirectory())
+ {
+ return true;
+ }
+ boolean acceptable = false;
+ String name = pathname.getName().toUpperCase();
+ for( int i=0; !acceptable && i<extensions.length; i++ )
+ {
+ if( name.endsWith( extensions[i].toUpperCase() ) )
+ {
+ acceptable = true;
+ }
+ }
+ return acceptable;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getDescription()
+ {
+ return desc;
+ }
+}
--- /dev/null
+/*
+ * 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 com.ibm.icu.text.Bidi;
+import com.ibm.icu.text.Normalizer;
+
+/**
+ * This class is an implementation the the ICU4J class. TextNormalize
+ * will call this only if the ICU4J library exists in the classpath.
+ * @author <a href="mailto:carrier@digital-evidence.org">Brian Carrier</a>
+ * @version $Revision: 1.0 $
+ */
+public class ICU4JImpl
+{
+ Bidi bidi;
+
+ /**
+ * Constructor.
+ */
+ public ICU4JImpl()
+ {
+ bidi = new Bidi();
+
+ /* We do not use bidi.setInverse() because that uses
+ * Bidi.REORDER_INVERSE_NUMBERS_AS_L, which caused problems
+ * in some test files. For example, a file had a line of:
+ * 0 1 / ARABIC
+ * and the 0 and 1 were reversed in the end result.
+ * REORDER_INVERSE_LIKE_DIRECT is the inverse Bidi mode
+ * that more closely reflects the Unicode spec.
+ */
+ bidi.setReorderingMode(Bidi.REORDER_INVERSE_LIKE_DIRECT);
+ }
+
+ /**
+ * Takes a line of text in presentation order and converts it to logical order.
+ * @see TextNormalize.makeLineLogicalOrder(String, boolean)
+ *
+ * @param str String to convert
+ * @param isRtlDominant RTL (right-to-left) will be the dominant text direction
+ * @return The converted string
+ */
+ public String makeLineLogicalOrder(String str, boolean isRtlDominant)
+ {
+ bidi.setPara(str, isRtlDominant?Bidi.RTL:Bidi.LTR, null);
+
+ /* Set the mirror flag so that parentheses and other mirror symbols
+ * are properly reversed, when needed. With this removed, lines
+ * such as (CBA) in the PDF file will come out like )ABC( in logical
+ * order.
+ */
+ return bidi.writeReordered(Bidi.DO_MIRRORING);
+ }
+
+ /**
+ * Normalize presentation forms of characters to the separate parts.
+ * @see TextNormalize.normalizePres(String)
+ *
+ * @param str String to normalize
+ * @return Normalized form
+ */
+ public String normalizePres(String str)
+ {
+ StringBuilder builder = null;
+ int p = 0;
+ int q = 0;
+ int strLength = str.length();
+ for (; q < strLength; q++)
+ {
+ // We only normalize if the codepoint is in a given range.
+ // Otherwise, NFKC converts too many things that would cause
+ // confusion. For example, it converts the micro symbol in
+ // extended Latin to the value in the Greek script. We normalize
+ // the Unicode Alphabetic and Arabic A&B Presentation forms.
+ char c = str.charAt(q);
+ if ((0xFB00 <= c && c <= 0xFDFF) || (0xFE70 <= c && c <= 0xFEFF))
+ {
+ if (builder == null) {
+ builder = new StringBuilder(strLength * 2);
+ }
+ builder.append(str.substring(p, q));
+ // Some fonts map U+FDF2 differently than the Unicode spec.
+ // They add an extra U+0627 character to compensate.
+ // This removes the extra character for those fonts.
+ if(c == 0xFDF2 && q > 0 && (str.charAt(q-1) == 0x0627 || str.charAt(q-1) == 0xFE8D))
+ {
+ builder.append("\u0644\u0644\u0647");
+ }
+ else
+ {
+ // Trim because some decompositions have an extra space,
+ // such as U+FC5E
+ builder.append(
+ Normalizer.normalize(c, Normalizer.NFKC).trim());
+ }
+ p = q + 1;
+ }
+ }
+ if (builder == null) {
+ return str;
+ } else {
+ builder.append(str.substring(p, q));
+ return builder.toString();
+ }
+ }
+
+ /**
+ * Decomposes Diacritic characters to their combining forms.
+ *
+ * @param str String to be Normalized
+ * @return A Normalized String
+ */
+ public String normalizeDiac(String str)
+ {
+ StringBuilder retStr = new StringBuilder();
+ int strLength = str.length();
+ for (int i = 0; i < strLength; i++)
+ {
+ char c = str.charAt(i);
+ if(Character.getType(c) == Character.NON_SPACING_MARK
+ || Character.getType(c) == Character.MODIFIER_SYMBOL
+ || Character.getType(c) == Character.MODIFIER_LETTER)
+ {
+ /*
+ * Trim because some decompositions have an extra space, such as
+ * U+00B4
+ */
+ retStr.append(Normalizer.normalize(c, Normalizer.NFKC).trim());
+ }
+ else
+ {
+ retStr.append(str.charAt(i));
+ }
+ }
+ return retStr.toString();
+ }
+}
--- /dev/null
+/*
+ * 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 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.pdmodel.common.COSArrayList;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This contains all of the image parameters for in inlined image.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class ImageParameters
+{
+ private COSDictionary dictionary;
+
+ /**
+ * Constructor.
+ */
+ public ImageParameters()
+ {
+ dictionary = new COSDictionary();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param params The image parameters.
+ */
+ public ImageParameters( COSDictionary params )
+ {
+ dictionary = params;
+ }
+
+ /**
+ * This will get the dictionary that stores the image parameters.
+ *
+ * @return The COS dictionary that stores the image parameters.
+ */
+ public COSDictionary getDictionary()
+ {
+ return dictionary;
+ }
+
+ private COSBase getCOSObject( COSName abbreviatedName, COSName name )
+ {
+ COSBase retval = dictionary.getDictionaryObject( abbreviatedName );
+ if( retval == null )
+ {
+ retval = dictionary.getDictionaryObject( name );
+ }
+ return retval;
+ }
+
+ private int getNumberOrNegativeOne( COSName abbreviatedName, COSName name )
+ {
+ int retval = -1;
+ COSNumber number = (COSNumber)getCOSObject( abbreviatedName, name );
+ if( number != null )
+ {
+ retval = number.intValue();
+ }
+ return retval;
+ }
+
+ /**
+ * The bits per component of this image. This will return -1 if one has not
+ * been set.
+ *
+ * @return The number of bits per component.
+ */
+ public int getBitsPerComponent()
+ {
+ return getNumberOrNegativeOne( COSName.BPC, COSName.BITS_PER_COMPONENT );
+ }
+
+ /**
+ * Set the number of bits per component.
+ *
+ * @param bpc The number of bits per component.
+ */
+ public void setBitsPerComponent( int bpc )
+ {
+ dictionary.setInt( COSName.BPC, bpc );
+ }
+
+
+ /**
+ * This will get the color space or null if none exists.
+ *
+ * @return The color space for this image.
+ *
+ * @throws IOException If there is an error getting the colorspace.
+ */
+ public PDColorSpace getColorSpace() throws IOException
+ {
+ return getColorSpace( null );
+ }
+
+ /**
+ * This will get the color space or null if none exists.
+ *
+ * @param colorSpaces The ColorSpace dictionary from the current resources, if any.
+ *
+ * @return The color space for this image.
+ *
+ * @throws IOException If there is an error getting the colorspace.
+ */
+ public PDColorSpace getColorSpace( Map colorSpaces ) throws IOException
+ {
+ COSBase cs = getCOSObject( COSName.CS, COSName.COLORSPACE );
+ PDColorSpace retval = null;
+ if( cs != null )
+ {
+ retval = PDColorSpaceFactory.createColorSpace( cs, colorSpaces );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the color space for this image.
+ *
+ * @param cs The color space for this image.
+ */
+ public void setColorSpace( PDColorSpace cs )
+ {
+ COSBase base = null;
+ if( cs != null )
+ {
+ base = cs.getCOSObject();
+ }
+ dictionary.setItem( COSName.CS, base );
+ }
+
+ /**
+ * The height of this image. This will return -1 if one has not
+ * been set.
+ *
+ * @return The height.
+ */
+ public int getHeight()
+ {
+ return getNumberOrNegativeOne( COSName.H, COSName.HEIGHT );
+ }
+
+ /**
+ * Set the height of the image.
+ *
+ * @param h The height of the image.
+ */
+ public void setHeight( int h )
+ {
+ dictionary.setInt( COSName.H, h );
+ }
+
+ /**
+ * The width of this image. This will return -1 if one has not
+ * been set.
+ *
+ * @return The width.
+ */
+ public int getWidth()
+ {
+ return getNumberOrNegativeOne( COSName.W, COSName.WIDTH );
+ }
+
+ /**
+ * Set the width of the image.
+ *
+ * @param w The width of the image.
+ */
+ public void setWidth( int w )
+ {
+ dictionary.setInt( COSName.W, w );
+ }
+
+ /**
+ * This will get the list of filters that are associated with this stream. Or
+ * null if there are none.
+ * @return A list of all encoding filters to apply to this stream.
+ */
+ public List getFilters()
+ {
+ List retval = null;
+ COSBase filters = dictionary.getDictionaryObject( new String[] {"Filter", "F"} );
+ if( filters instanceof COSName )
+ {
+ COSName name = (COSName)filters;
+ retval = new COSArrayList( name.getName(), name, dictionary, COSName.FILTER );
+ }
+ else if( filters instanceof COSArray )
+ {
+ retval = COSArrayList.convertCOSNameCOSArrayToList( (COSArray)filters );
+ }
+ return retval;
+ }
+
+ /**
+ * This will set the filters that are part of this stream.
+ *
+ * @param filters The filters that are part of this stream.
+ */
+ public void setFilters( List filters )
+ {
+ COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray( filters );
+ dictionary.setItem( "Filter", obj );
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.io.OutputStream;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fontbox.util.BoundingBox;
+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.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;
+ }
+
+ /**
+ * Some applications may not wrap their page content in a save/restore (q/Q) pair which can
+ * lead to problems with coordinate system transformations when content is appended. This
+ * method lets you add a q/Q pair around the existing page's content.
+ * @param page the page
+ * @throws IOException if an I/O error occurs
+ */
+ public void wrapInSaveRestore(PDPage page) throws IOException
+ {
+ COSDictionary saveGraphicsStateDic = new COSDictionary();
+ COSStream saveGraphicsStateStream = new COSStream(saveGraphicsStateDic,
+ getDocument().getDocument().getScratchFile());
+ OutputStream saveStream = saveGraphicsStateStream.createUnfilteredStream();
+ saveStream.write("q\n".getBytes("ISO-8859-1"));
+ saveStream.flush();
+
+ COSStream restoreGraphicsStateStream = new COSStream(saveGraphicsStateDic,
+ getDocument().getDocument().getScratchFile());
+ OutputStream restoreStream = restoreGraphicsStateStream.createUnfilteredStream();
+ restoreStream.write("Q\n".getBytes("ISO-8859-1"));
+ restoreStream.flush();
+
+ //Wrap the existing page's content in a save/restore pair (q/Q) to have a controlled
+ //environment to add additional content.
+ COSDictionary pageDictionary = page.getCOSDictionary();
+ COSBase contents = pageDictionary.getDictionaryObject(COSName.CONTENTS);
+ if (contents instanceof COSStream)
+ {
+ COSStream contentsStream = (COSStream)contents;
+
+ COSArray array = new COSArray();
+ array.add(saveGraphicsStateStream);
+ array.add(contentsStream);
+ array.add(restoreGraphicsStateStream);
+
+ pageDictionary.setItem(COSName.CONTENTS, array);
+ }
+ else if( contents instanceof COSArray )
+ {
+ COSArray contentsArray = (COSArray)contents;
+
+ contentsArray.add(0, saveGraphicsStateStream);
+ contentsArray.add(restoreGraphicsStateStream);
+ }
+ else
+ {
+ throw new IOException("Contents are unknown type: " + contents.getClass().getName());
+ }
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+
+}
--- /dev/null
+/*
+ * 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.util.Map;
+
+
+/**
+ * This class with handle some simple Map operations.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class MapUtil
+{
+ private MapUtil()
+ {
+ //utility class
+ }
+
+ /**
+ * Generate a unique key for the map based on a prefix.
+ *
+ * @param map The map to look for existing keys.
+ * @param prefix The prefix to use when generating the key.
+ * @return The new unique key that does not currently exist in the map.
+ */
+ public static final String getNextUniqueKey( Map map, String prefix )
+ {
+ int counter = 0;
+ while( map.get( prefix+counter ) != null )
+ {
+ counter++;
+ }
+ return prefix+counter;
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+/**
+ * This class will be used for matrix manipulation.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class Matrix implements Cloneable
+{
+ static final float[] default_single =
+ {
+ 1,0,0,
+ 0,1,0,
+ 0,0,1
+ };
+
+ private float[] single;
+
+ /**
+ * Constructor.
+ */
+ public Matrix()
+ {
+ single = new float[default_single.length];
+ reset();
+ }
+
+ /**
+ * This method resets the numbers in this Matrix to the original values, which are
+ * the values that a newly constructed Matrix would have.
+ */
+ public void reset()
+ {
+ System.arraycopy(default_single, 0, single, 0, default_single.length);
+ }
+
+ /**
+ * Create an affine transform from this matrix's values.
+ *
+ * @return An affine transform with this matrix's values.
+ */
+ public AffineTransform createAffineTransform()
+ {
+ AffineTransform retval = new AffineTransform(
+ single[0], single[1],
+ single[3], single[4],
+ single[6], single[7] );
+ return retval;
+ }
+
+ /**
+ * Set the values of the matrix from the AffineTransform.
+ *
+ * @param af The transform to get the values from.
+ */
+ public void setFromAffineTransform( AffineTransform af )
+ {
+ single[0] = (float)af.getScaleX();
+ single[1] = (float)af.getShearY();
+ single[3] = (float)af.getShearX();
+ single[4] = (float)af.getScaleY();
+ single[6] = (float)af.getTranslateX();
+ single[7] = (float)af.getTranslateY();
+ }
+
+ /**
+ * This will get a matrix value at some point.
+ *
+ * @param row The row to get the value from.
+ * @param column The column to get the value from.
+ *
+ * @return The value at the row/column position.
+ */
+ public float getValue( int row, int column )
+ {
+ return single[row*3+column];
+ }
+
+ /**
+ * This will set a value at a position.
+ *
+ * @param row The row to set the value at.
+ * @param column the column to set the value at.
+ * @param value The value to set at the position.
+ */
+ public void setValue( int row, int column, float value )
+ {
+ single[row*3+column] = value;
+ }
+
+ /**
+ * Return a single dimension array of all values in the matrix.
+ *
+ * @return The values ot this matrix.
+ */
+ public float[][] getValues()
+ {
+ float[][] retval = new float[3][3];
+ retval[0][0] = single[0];
+ retval[0][1] = single[1];
+ retval[0][2] = single[2];
+ retval[1][0] = single[3];
+ retval[1][1] = single[4];
+ retval[1][2] = single[5];
+ retval[2][0] = single[6];
+ retval[2][1] = single[7];
+ retval[2][2] = single[8];
+ return retval;
+ }
+
+ /**
+ * Return a single dimension array of all values in the matrix.
+ *
+ * @return The values ot this matrix.
+ */
+ public double[][] getValuesAsDouble()
+ {
+ double[][] retval = new double[3][3];
+ retval[0][0] = single[0];
+ retval[0][1] = single[1];
+ retval[0][2] = single[2];
+ retval[1][0] = single[3];
+ retval[1][1] = single[4];
+ retval[1][2] = single[5];
+ retval[2][0] = single[6];
+ retval[2][1] = single[7];
+ retval[2][2] = single[8];
+ return retval;
+ }
+
+ /**
+ * This will take the current matrix and multipy it with a matrix that is passed in.
+ *
+ * @param b The matrix to multiply by.
+ *
+ * @return The result of the two multiplied matrices.
+ */
+ public Matrix multiply( Matrix b )
+ {
+ return this.multiply(b, new Matrix());
+ }
+
+ /**
+ * This method multiplies this Matrix with the specified other Matrix, storing the product in the specified
+ * result Matrix. By reusing Matrix instances like this, multiplication chains can be executed without having
+ * to create many temporary Matrix objects.
+ * <p/>
+ * It is allowed to have (other == this) or (result == this) or indeed (other == result) but if this is done,
+ * the backing float[] matrix values may be copied in order to ensure a correct product.
+ *
+ * @param other the second operand Matrix in the multiplication
+ * @param result the Matrix instance into which the result should be stored. If result is null, a new Matrix
+ * instance is created.
+ * @return the product of the two matrices.
+ */
+ public Matrix multiply( Matrix other, Matrix result )
+ {
+ if (result == null)
+ {
+ result = new Matrix();
+ }
+
+ if (other != null && other.single != null)
+ {
+ // the operands
+ float[] thisOperand = this.single;
+ float[] otherOperand = other.single;
+
+ // We're multiplying 2 sets of floats together to produce a third, but we allow
+ // any of these float[] instances to be the same objects.
+ // There is the possibility then to overwrite one of the operands with result values
+ // and therefore corrupt the result.
+
+ // If either of these operands are the same float[] instance as the result, then
+ // they need to be copied.
+
+ if (this == result)
+ {
+ final float[] thisOrigVals = new float[this.single.length];
+ System.arraycopy(this.single, 0, thisOrigVals, 0, this.single.length);
+
+ thisOperand = thisOrigVals;
+ }
+ if (other == result)
+ {
+ final float[] otherOrigVals = new float[other.single.length];
+ System.arraycopy(other.single, 0, otherOrigVals, 0, other.single.length);
+
+ otherOperand = otherOrigVals;
+ }
+
+ result.single[0] = thisOperand[0] * otherOperand[0] + thisOperand[1] * otherOperand[3] + thisOperand[2] * otherOperand[6];
+ result.single[1] = thisOperand[0] * otherOperand[1] + thisOperand[1] * otherOperand[4] + thisOperand[2] * otherOperand[7];
+ result.single[2] = thisOperand[0] * otherOperand[2] + thisOperand[1] * otherOperand[5] + thisOperand[2] * otherOperand[8];
+ result.single[3] = thisOperand[3] * otherOperand[0] + thisOperand[4] * otherOperand[3] + thisOperand[5] * otherOperand[6];
+ result.single[4] = thisOperand[3] * otherOperand[1] + thisOperand[4] * otherOperand[4] + thisOperand[5] * otherOperand[7];
+ result.single[5] = thisOperand[3] * otherOperand[2] + thisOperand[4] * otherOperand[5] + thisOperand[5] * otherOperand[8];
+ result.single[6] = thisOperand[6] * otherOperand[0] + thisOperand[7] * otherOperand[3] + thisOperand[8] * otherOperand[6];
+ result.single[7] = thisOperand[6] * otherOperand[1] + thisOperand[7] * otherOperand[4] + thisOperand[8] * otherOperand[7];
+ result.single[8] = thisOperand[6] * otherOperand[2] + thisOperand[7] * otherOperand[5] + thisOperand[8] * otherOperand[8];
+ }
+
+ return result;
+ }
+
+ /**
+ * Create a new matrix with just the scaling operators.
+ *
+ * @return A new matrix with just the scaling operators.
+ */
+ public Matrix extractScaling()
+ {
+ Matrix retval = new Matrix();
+
+ retval.single[0] = this.single[0];
+ retval.single[4] = this.single[4];
+
+ return retval;
+ }
+
+ /**
+ * Convenience method to create a scaled instance.
+ *
+ * @param x The xscale operator.
+ * @param y The yscale operator.
+ * @return A new matrix with just the x/y scaling
+ */
+ public static Matrix getScaleInstance( float x, float y)
+ {
+ Matrix retval = new Matrix();
+
+ retval.single[0] = x;
+ retval.single[4] = y;
+
+ return retval;
+ }
+
+ /**
+ * Create a new matrix with just the translating operators.
+ *
+ * @return A new matrix with just the translating operators.
+ */
+ public Matrix extractTranslating()
+ {
+ Matrix retval = new Matrix();
+
+ retval.single[6] = this.single[6];
+ retval.single[7] = this.single[7];
+
+ return retval;
+ }
+
+ /**
+ * Convenience method to create a translating instance.
+ *
+ * @param x The x translating operator.
+ * @param y The y translating operator.
+ * @return A new matrix with just the x/y translating.
+ */
+ public static Matrix getTranslatingInstance( float x, float y)
+ {
+ Matrix retval = new Matrix();
+
+ retval.single[6] = x;
+ retval.single[7] = y;
+
+ return retval;
+ }
+
+ /**
+ * Clones this object.
+ * @return cloned matrix as an object.
+ */
+ public Object clone()
+ {
+ Matrix clone = new Matrix();
+ System.arraycopy( single, 0, clone.single, 0, 9 );
+ return clone;
+ }
+
+ /**
+ * This will copy the text matrix data.
+ *
+ * @return a matrix that matches this one.
+ */
+ public Matrix copy()
+ {
+ return (Matrix) clone();
+ }
+
+ /**
+ * This will return a string representation of the matrix.
+ *
+ * @return The matrix as a string.
+ */
+ public String toString()
+ {
+ StringBuffer result = new StringBuffer( "" );
+ result.append( "[[" );
+ result.append( single[0] + "," );
+ result.append( single[1] + "," );
+ result.append( single[2] + "][");
+ result.append( single[3] + "," );
+ result.append( single[4] + "," );
+ result.append( single[5] + "][");
+ result.append( single[6] + "," );
+ result.append( single[7] + "," );
+ result.append( single[8] + "]]");
+
+ return result.toString();
+ }
+
+ /**
+ * Get the xscaling factor of this matrix.
+ * @return The x-scale.
+ */
+ public float getXScale()
+ {
+ float xScale = single[0];
+
+ /**
+ * BM: if the trm is rotated, the calculation is a little more complicated
+ *
+ * The rotation matrix multiplied with the scaling matrix is:
+ * ( x 0 0) ( cos sin 0) ( x*cos x*sin 0)
+ * ( 0 y 0) * (-sin cos 0) = (-y*sin y*cos 0)
+ * ( 0 0 1) ( 0 0 1) ( 0 0 1)
+ *
+ * So, if you want to deduce x from the matrix you take
+ * M(0,0) = x*cos and M(0,1) = x*sin and use the theorem of Pythagoras
+ *
+ * sqrt(M(0,0)^2+M(0,1)^2) =
+ * sqrt(x2*cos2+x2*sin2) =
+ * sqrt(x2*(cos2+sin2)) = <- here is the trick cos2+sin2 is one
+ * sqrt(x2) =
+ * abs(x)
+ */
+ if( !(single[1]==0.0f && single[3]==0.0f) )
+ {
+ xScale = (float)Math.sqrt(Math.pow(single[0], 2)+
+ Math.pow(single[1], 2));
+ }
+ return xScale;
+ }
+
+ /**
+ * Get the y scaling factor of this matrix.
+ * @return The y-scale factor.
+ */
+ public float getYScale()
+ {
+ float yScale = single[4];
+ if( !(single[1]==0.0f && single[3]==0.0f) )
+ {
+ yScale = (float)Math.sqrt(Math.pow(single[3], 2)+
+ Math.pow(single[4], 2));
+ }
+ return yScale;
+ }
+
+ /**
+ * Get the x position in the matrix.
+ * @return The x-position.
+ */
+ public float getXPosition()
+ {
+ return single[6];
+ }
+
+ /**
+ * Get the y position.
+ * @return The y position.
+ */
+ public float getYPosition()
+ {
+ return single[7];
+ }
+}
--- /dev/null
+/*
+ * 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 );
+
+ }
+
+}
--- /dev/null
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+
+/**
+ * Highlighting of words in a PDF document with an XML file.
+ *
+ * @author slagraulet (slagraulet@cardiweb.com)
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ *
+ * @see <a href="http://partners.adobe.com/public/developer/en/pdf/HighlightFileFormat.pdf">
+ * Adobe Highlight File Format</a>
+ */
+public class PDFHighlighter extends PDFTextStripper
+{
+ private Writer highlighterOutput = null;
+ //private Color highlightColor = Color.YELLOW;
+
+ private String[] searchedWords;
+ private ByteArrayOutputStream textOS = null;
+ private Writer textWriter = null;
+ private static final String ENCODING = "UTF-16";
+
+ /**
+ * Default constructor.
+ *
+ * @throws IOException If there is an error constructing this class.
+ */
+ public PDFHighlighter() throws IOException
+ {
+ super(ENCODING);
+ super.setLineSeparator( "" );
+ super.setPageSeparator( "" );
+ super.setWordSeparator( "" );
+ super.setShouldSeparateByBeads( false );
+ super.setSuppressDuplicateOverlappingText( false );
+ }
+
+ /**
+ * Generate an XML highlight string based on the PDF.
+ *
+ * @param pdDocument The PDF to find words in.
+ * @param highlightWord The word to search for.
+ * @param xmlOutput The resulting output xml file.
+ *
+ * @throws IOException If there is an error reading from the PDF, or writing to the XML.
+ */
+ public void generateXMLHighlight(PDDocument pdDocument, String highlightWord, Writer xmlOutput ) throws IOException
+ {
+ generateXMLHighlight( pdDocument, new String[] { highlightWord }, xmlOutput );
+ }
+
+ /**
+ * Generate an XML highlight string based on the PDF.
+ *
+ * @param pdDocument The PDF to find words in.
+ * @param sWords The words to search for.
+ * @param xmlOutput The resulting output xml file.
+ *
+ * @throws IOException If there is an error reading from the PDF, or writing to the XML.
+ */
+ public void generateXMLHighlight(PDDocument pdDocument, String[] sWords, Writer xmlOutput ) throws IOException
+ {
+ highlighterOutput = xmlOutput;
+ searchedWords = sWords;
+ highlighterOutput.write("<XML>\n<Body units=characters " +
+ //color and mode are not implemented by the highlight spec
+ //so don't include them for now
+ //" color=#" + getHighlightColorAsString() +
+ //" mode=active " + */
+ " version=2>\n<Highlight>\n");
+ textOS = new ByteArrayOutputStream();
+ textWriter = new OutputStreamWriter( textOS, ENCODING);
+ writeText(pdDocument, textWriter);
+ highlighterOutput.write("</Highlight>\n</Body>\n</XML>");
+ highlighterOutput.flush();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void endPage( PDPage pdPage ) throws IOException
+ {
+ textWriter.flush();
+
+ String page = new String( textOS.toByteArray(), ENCODING );
+ textOS.reset();
+ //page = page.replaceAll( "\n", "" );
+ //page = page.replaceAll( "\r", "" );
+ //page = CCRStringUtil.stripChar(page, '\n');
+ //page = CCRStringUtil.stripChar(page, '\r');
+
+ // Traitement des listes � puces (caract�res sp�ciaux)
+ if (page.indexOf("a") != -1)
+ {
+ page = page.replaceAll("a[0-9]{1,3}", ".");
+ }
+
+ for (int i = 0; i < searchedWords.length; i++)
+ {
+ Pattern pattern = Pattern.compile(searchedWords[i], Pattern.CASE_INSENSITIVE);
+ Matcher matcher = pattern.matcher(page);
+ while( matcher.find() )
+ {
+ int begin = matcher.start();
+ int end = matcher.end();
+ highlighterOutput.write(" <loc " +
+ "pg=" + (getCurrentPageNo()-1)
+ + " pos=" + begin
+ + " len="+ (end - begin)
+ + ">\n");
+ }
+ }
+ }
+
+ /**
+ * Command line application.
+ *
+ * @param args The command line arguments to the application.
+ *
+ * @throws IOException If there is an error generating the highlight file.
+ */
+ public static void main(String[] args) throws IOException
+ {
+ PDFHighlighter xmlExtractor = new PDFHighlighter();
+ PDDocument doc = null;
+ try
+ {
+ if( args.length < 2 )
+ {
+ usage();
+ }
+ String[] highlightStrings = new String[ args.length - 1];
+ System.arraycopy( args, 1, highlightStrings, 0, highlightStrings.length );
+ doc = PDDocument.load( args[0] );
+
+ xmlExtractor.generateXMLHighlight(
+ doc,
+ highlightStrings,
+ new OutputStreamWriter( System.out ) );
+ }
+ finally
+ {
+ if( doc != null )
+ {
+ doc.close();
+ }
+ }
+ }
+
+ private static void usage()
+ {
+ System.err.println( "usage: java " + PDFHighlighter.class.getName() + " <pdf file> word1 word2 word3 ..." );
+ System.exit( 1 );
+ }
+
+
+ /**
+ * Get the color to highlight the strings with. Default is Color.YELLOW.
+ *
+ * @return The color to highlight strings with.
+ */
+ /*public Color getHighlightColor()
+ {
+ return highlightColor;
+ }**/
+
+ /**
+ * Get the color to highlight the strings with. Default is Color.YELLOW.
+ *
+ * @param color The color to highlight strings with.
+ */
+ /*public void setHighlightColor(Color color)
+ {
+ this.highlightColor = color;
+ }**/
+
+ /**
+ * Set the highlight color using HTML like rgb string. The string must be 6 characters long.
+ *
+ * @param color The color to use for highlighting. Should be in the format of "FF0000".
+ */
+ /*public void setHighlightColor( String color )
+ {
+ highlightColor = Color.decode( color );
+ }**/
+
+ /**
+ * Get the highlight color as an HTML like string. This will return a string of six characters.
+ *
+ * @return The current highlight color. For example FF0000
+ */
+ /*public String getHighlightColorAsString()
+ {
+ //BJL: kudos to anyone that has a cleaner way of doing this!
+ String red = Integer.toHexString( highlightColor.getRed() );
+ String green = Integer.toHexString( highlightColor.getGreen() );
+ String blue = Integer.toHexString( highlightColor.getBlue() );
+
+ return (red.length() < 2 ? "0" + red : red) +
+ (green.length() < 2 ? "0" + green : green) +
+ (blue.length() < 2 ? "0" + blue : blue);
+ }**/
+}
--- /dev/null
+/*
+ * 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.HeadlessException;
+import java.awt.Toolkit;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import javax.imageio.IIOException;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.stream.ImageOutputStream;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * This class will take a PDF document and strip out all of the text and ignore the
+ * formatting and such. Please note; it is up to clients of this class to verify that
+ * a specific user has the correct permissions to extract text from the
+ * PDF document.
+ * <p>
+ * Patterned after PDFTextStripper.
+ *
+ * @author <a href="mailto:DanielWilson@Users.SourceForge.net">Daniel Wilson</a>
+ * @version $Revision: 1.1 $
+ */
+public class PDFImageWriter extends PDFStreamEngine
+{
+
+ /**
+ * Instantiate a new PDFImageWriter object.
+ */
+ public PDFImageWriter()
+ {
+ }
+
+ /**
+ * Instantiate a new PDFImageWriter object. Loading all of the operator mappings
+ * from the properties object that is passed in.
+ *
+ * @param props The properties containing the mapping of operators to PDFOperator
+ * classes.
+ *
+ * @throws IOException If there is an error reading the properties.
+ */
+ public PDFImageWriter( Properties props ) throws IOException
+ {
+ super( props );
+ }
+
+ /**
+ * Converts a given page range of a PDF document to bitmap images.
+ * @param document the PDF document
+ * @param imageType the target format (ex. "png")
+ * @param password the password (needed if the PDF is encrypted)
+ * @param startPage the start page (1 is the first page)
+ * @param endPage the end page (set to Integer.MAX_VALUE for all pages)
+ * @param outputPrefix used to construct the filename for the individual images
+ * @return true if the images were produced, false if there was an error
+ * @throws IOException if an I/O error occurs
+ */
+ public boolean writeImage(PDDocument document, String imageType, String password,
+ int startPage, int endPage, String outputPrefix)
+ throws IOException
+ {
+ int resolution;
+ try
+ {
+ resolution = Toolkit.getDefaultToolkit().getScreenResolution();
+ }
+ catch( HeadlessException e )
+ {
+ resolution = 96;
+ }
+ return writeImage(document, imageType, password, startPage, endPage, outputPrefix,
+ 8, resolution);
+ }
+
+ /**
+ * Converts a given page range of a PDF document to bitmap images.
+ * @param document the PDF document
+ * @param imageFormat the target format (ex. "png")
+ * @param password the password (needed if the PDF is encrypted)
+ * @param startPage the start page (1 is the first page)
+ * @param endPage the end page (set to Integer.MAX_VALUE for all pages)
+ * @param outputPrefix used to construct the filename for the individual images
+ * @param imageType the image type (see {@link BufferedImage}.TYPE_*)
+ * @param resolution the resolution in dpi (dots per inch)
+ * @return true if the images were produced, false if there was an error
+ * @throws IOException if an I/O error occurs
+ */
+ public boolean writeImage(PDDocument document, String imageFormat, String password,
+ int startPage, int endPage, String outputPrefix, int imageType, int resolution)
+ throws IOException
+ {
+ boolean bSuccess = true;
+ List pages = document.getDocumentCatalog().getAllPages();
+ for( int i = startPage - 1; i < endPage && i < pages.size(); i++ )
+ {
+ ImageOutputStream output = null;
+ ImageWriter imageWriter = null;
+ try
+ {
+ PDPage page = (PDPage)pages.get( i );
+ BufferedImage image = page.convertToImage(imageType, resolution);
+ String fileName = outputPrefix + (i + 1) + "." + imageFormat;
+ System.out.println( "Writing: " + fileName );
+ output = ImageIO.createImageOutputStream( new File( fileName ) );
+
+ boolean foundWriter = false;
+ Iterator writerIter = ImageIO.getImageWritersByFormatName( imageFormat );
+ while( writerIter.hasNext() && !foundWriter )
+ {
+ try
+ {
+ imageWriter = (ImageWriter)writerIter.next();
+ ImageWriteParam writerParams = imageWriter.getDefaultWriteParam();
+ if( writerParams.canWriteCompressed() )
+ {
+ writerParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+ writerParams.setCompressionQuality(1.0f);
+ }
+ IIOMetadata meta = createMetadata( image, imageWriter, writerParams, resolution);
+ imageWriter.setOutput( output );
+ imageWriter.write( null, new IIOImage( image, null, meta ), writerParams );
+ foundWriter = true;
+ }
+ catch( IIOException io )
+ {
+ throw new IOException( io.getMessage() );
+ }
+ finally
+ {
+ if( imageWriter != null )
+ {
+ imageWriter.dispose();
+ }
+ }
+ }
+ if( !foundWriter )
+ {
+ bSuccess = false;
+ }
+ }
+ finally
+ {
+ if( output != null )
+ {
+ output.flush();
+ output.close();
+ }
+ }
+ }
+ return bSuccess;
+ }
+
+ private IIOMetadata createMetadata(RenderedImage image, ImageWriter imageWriter,
+ ImageWriteParam writerParams, int resolution)
+ {
+ ImageTypeSpecifier type;
+ if (writerParams.getDestinationType() != null)
+ {
+ type = writerParams.getDestinationType();
+ }
+ else
+ {
+ type = ImageTypeSpecifier.createFromRenderedImage( image );
+ }
+ IIOMetadata meta = imageWriter.getDefaultImageMetadata( type, writerParams );
+ return (addResolution(meta, resolution) ? meta : null);
+ }
+
+ private static final String STANDARD_METADATA_FORMAT = "javax_imageio_1.0";
+
+ private boolean addResolution(IIOMetadata meta, int resolution)
+ {
+ if (meta.isStandardMetadataFormatSupported())
+ {
+ IIOMetadataNode root = (IIOMetadataNode)meta.getAsTree(STANDARD_METADATA_FORMAT);
+ IIOMetadataNode dim = getChildNode(root, "Dimension");
+ IIOMetadataNode child;
+ child = getChildNode(dim, "HorizontalPixelSize");
+ if (child == null)
+ {
+ child = new IIOMetadataNode("HorizontalPixelSize");
+ dim.appendChild(child);
+ }
+ child.setAttribute("value",
+ Double.toString(resolution / 25.4));
+ child = getChildNode(dim, "VerticalPixelSize");
+ if (child == null)
+ {
+ child = new IIOMetadataNode("VerticalPixelSize");
+ dim.appendChild(child);
+ }
+ child.setAttribute("value",
+ Double.toString(resolution / 25.4));
+ try
+ {
+ meta.mergeTree(STANDARD_METADATA_FORMAT, root);
+ }
+ catch (IIOInvalidTreeException e)
+ {
+ throw new RuntimeException("Cannot update image metadata: "
+ + e.getMessage());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private static IIOMetadataNode getChildNode(Node n, String name)
+ {
+ NodeList nodes = n.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++)
+ {
+ Node child = nodes.item(i);
+ if (name.equals(child.getNodeName()))
+ {
+ return (IIOMetadataNode)child;
+ }
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Stack;
+
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDMarkedContent;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+
+/**
+ * This is an stream engine to extract the marked content of a pdf.
+ * @author koch
+ * @version $Revision$
+ */
+public class PDFMarkedContentExtractor extends PDFStreamEngine
+{
+ private boolean suppressDuplicateOverlappingText = true;
+ private List<PDMarkedContent> markedContents = new ArrayList<PDMarkedContent>();
+ private Stack<PDMarkedContent> currentMarkedContents = new Stack<PDMarkedContent>();
+
+ private Map<String, List<TextPosition>> characterListMapping =
+ new HashMap<String, List<TextPosition>>();
+
+ /**
+ * encoding that text will be written in (or null).
+ */
+ protected String outputEncoding;
+
+ /**
+ * The normalizer is used to remove text ligatures/presentation forms
+ * and to correct the direction of right to left text, such as Arabic and Hebrew.
+ */
+ private TextNormalize normalize = null;
+
+ /**
+ * Instantiate a new PDFTextStripper object. This object will load
+ * properties from PDFMarkedContentExtractor.properties and will not
+ * do anything special to convert the text to a more encoding-specific
+ * output.
+ *
+ * @throws IOException If there is an error loading the properties.
+ */
+ public PDFMarkedContentExtractor() throws IOException
+ {
+ super( ResourceLoader.loadProperties(
+ "org/apache/pdfbox/resources/PDFMarkedContentExtractor.properties", true ) );
+ this.outputEncoding = null;
+ this.normalize = new TextNormalize(this.outputEncoding);
+ }
+
+
+ /**
+ * Instantiate a new PDFTextStripper object. Loading all of the operator mappings
+ * from the properties object that is passed in. Does not convert the text
+ * to more encoding-specific output.
+ *
+ * @param props The properties containing the mapping of operators to PDFOperator
+ * classes.
+ *
+ * @throws IOException If there is an error reading the properties.
+ */
+ public PDFMarkedContentExtractor( Properties props ) throws IOException
+ {
+ super( props );
+ this.outputEncoding = null;
+ this.normalize = new TextNormalize(this.outputEncoding);
+ }
+
+ /**
+ * Instantiate a new PDFTextStripper object. This object will load
+ * properties from PDFMarkedContentExtractor.properties and will apply
+ * encoding-specific conversions to the output text.
+ *
+ * @param encoding The encoding that the output will be written in.
+ * @throws IOException If there is an error reading the properties.
+ */
+ public PDFMarkedContentExtractor( String encoding ) throws IOException
+ {
+ super( ResourceLoader.loadProperties(
+ "org/apache/pdfbox/resources/PDFMarkedContentExtractor.properties", true ));
+ this.outputEncoding = encoding;
+ this.normalize = new TextNormalize(this.outputEncoding);
+ }
+
+
+ /**
+ * This will determine of two floating point numbers are within a specified variance.
+ *
+ * @param first The first number to compare to.
+ * @param second The second number to compare to.
+ * @param variance The allowed variance.
+ */
+ private boolean within( float first, float second, float variance )
+ {
+ return second > first - variance && second < first + variance;
+ }
+
+
+ public void beginMarkedContentSequence(COSName tag, COSDictionary properties)
+ {
+ PDMarkedContent markedContent = PDMarkedContent.create(tag, properties);
+ if (this.currentMarkedContents.isEmpty())
+ {
+ this.markedContents.add(markedContent);
+ }
+ else
+ {
+ PDMarkedContent currentMarkedContent =
+ this.currentMarkedContents.peek();
+ if (currentMarkedContent != null)
+ {
+ currentMarkedContent.addMarkedContent(markedContent);
+ }
+ }
+ this.currentMarkedContents.push(markedContent);
+ }
+
+ public void endMarkedContentSequence()
+ {
+ if (!this.currentMarkedContents.isEmpty())
+ {
+ this.currentMarkedContents.pop();
+ }
+ }
+
+ public void xobject(PDXObject xobject)
+ {
+ if (!this.currentMarkedContents.isEmpty())
+ {
+ this.currentMarkedContents.peek().addXObject(xobject);
+ }
+ }
+
+
+ /**
+ * This will process a TextPosition object and add the
+ * text to the list of characters on a page. It takes care of
+ * overlapping text.
+ *
+ * @param text The text to process.
+ */
+ protected void processTextPosition( TextPosition text )
+ {
+ boolean showCharacter = true;
+ if( this.suppressDuplicateOverlappingText )
+ {
+ showCharacter = false;
+ String textCharacter = text.getCharacter();
+ float textX = text.getX();
+ float textY = text.getY();
+ List<TextPosition> sameTextCharacters = this.characterListMapping.get( textCharacter );
+ if( sameTextCharacters == null )
+ {
+ sameTextCharacters = new ArrayList<TextPosition>();
+ this.characterListMapping.put( textCharacter, sameTextCharacters );
+ }
+
+ // RDD - Here we compute the value that represents the end of the rendered
+ // text. This value is used to determine whether subsequent text rendered
+ // on the same line overwrites the current text.
+ //
+ // We subtract any positive padding to handle cases where extreme amounts
+ // of padding are applied, then backed off (not sure why this is done, but there
+ // are cases where the padding is on the order of 10x the character width, and
+ // the TJ just backs up to compensate after each character). Also, we subtract
+ // an amount to allow for kerning (a percentage of the width of the last
+ // character).
+ //
+ boolean suppressCharacter = false;
+ float tolerance = (text.getWidth()/textCharacter.length())/3.0f;
+ for( int i=0; i<sameTextCharacters.size() && textCharacter != null; i++ )
+ {
+ TextPosition character = (TextPosition)sameTextCharacters.get( i );
+ String charCharacter = character.getCharacter();
+ float charX = character.getX();
+ float charY = character.getY();
+ //only want to suppress
+
+ if( charCharacter != null &&
+ //charCharacter.equals( textCharacter ) &&
+ within( charX, textX, tolerance ) &&
+ within( charY,
+ textY,
+ tolerance ) )
+ {
+ suppressCharacter = true;
+ }
+ }
+ if( !suppressCharacter )
+ {
+ sameTextCharacters.add( text );
+ showCharacter = true;
+ }
+ }
+
+ if( showCharacter )
+ {
+ List<TextPosition> textList = new ArrayList<TextPosition>();
+
+ /* In the wild, some PDF encoded documents put diacritics (accents on
+ * top of characters) into a separate Tj element. When displaying them
+ * graphically, the two chunks get overlayed. With text output though,
+ * we need to do the overlay. This code recombines the diacritic with
+ * its associated character if the two are consecutive.
+ */
+ if(textList.isEmpty())
+ {
+ textList.add(text);
+ }
+ else
+ {
+ /* test if we overlap the previous entry.
+ * Note that we are making an assumption that we need to only look back
+ * one TextPosition to find what we are overlapping.
+ * This may not always be true. */
+ TextPosition previousTextPosition = (TextPosition)textList.get(textList.size()-1);
+ if(text.isDiacritic() && previousTextPosition.contains(text))
+ {
+ previousTextPosition.mergeDiacritic(text, this.normalize);
+ }
+ /* If the previous TextPosition was the diacritic, merge it into this
+ * one and remove it from the list. */
+ else if(previousTextPosition.isDiacritic() && text.contains(previousTextPosition))
+ {
+ text.mergeDiacritic(previousTextPosition, this.normalize);
+ textList.remove(textList.size()-1);
+ textList.add(text);
+ }
+ else
+ {
+ textList.add(text);
+ }
+ }
+ if (!this.currentMarkedContents.isEmpty())
+ {
+ this.currentMarkedContents.peek().addText(text);
+ }
+ }
+ }
+
+
+ public List<PDMarkedContent> getMarkedContents()
+ {
+ return this.markedContents;
+ }
+
+}
--- /dev/null
+/*
+ * 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.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+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.PDStream;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
+import org.apache.pdfbox.pdmodel.interactive.form.PDField;
+import org.apache.pdfbox.pdmodel.interactive.form.PDFieldFactory;
+
+/**
+ * This class will take a list of pdf documents and merge them, saving the result
+ * in a new document.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class PDFMergerUtility
+{
+
+ private List<InputStream> sources;
+ private String destinationFileName;
+ private OutputStream destinationStream;
+ private boolean ignoreAcroFormErrors = false;
+
+ /**
+ * Instantiate a new PDFMergerUtility.
+ */
+ public PDFMergerUtility()
+ {
+ sources = new ArrayList<InputStream>();
+ }
+
+ /**
+ * Get the name of the destination file.
+ * @return Returns the destination.
+ */
+ public String getDestinationFileName()
+ {
+ return destinationFileName;
+ }
+
+ /**
+ * Set the name of the destination file.
+ * @param destination
+ * The destination to set.
+ */
+ public void setDestinationFileName(String destination)
+ {
+ this.destinationFileName = destination;
+ }
+
+ /**
+ * Get the destination OutputStream.
+ * @return Returns the destination OutputStream.
+ */
+ public OutputStream getDestinationStream()
+ {
+ return destinationStream;
+ }
+
+ /**
+ * Set the destination OutputStream.
+ * @param destination
+ * The destination to set.
+ */
+ public void setDestinationStream(OutputStream destinationStream)
+ {
+ this.destinationStream = destinationStream;
+ }
+
+ /**
+ * Add a source file to the list of files to merge.
+ *
+ * @param source Full path and file name of source document.
+ */
+ public void addSource(String source)
+ {
+ try
+ {
+ sources.add(new FileInputStream(new File(source)));
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Add a source file to the list of files to merge.
+ *
+ * @param source File representing source document
+ */
+ public void addSource(File source)
+ {
+ try
+ {
+ sources.add(new FileInputStream(source));
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Add a source to the list of documents to merge.
+ *
+ * @param source InputStream representing source document
+ */
+ public void addSource(InputStream source)
+ {
+ sources.add(source);
+ }
+
+ /**
+ * Add a list of sources to the list of documents to merge.
+ *
+ * @param source List of InputStream objects representing source documents
+ */
+ public void addSources(List<InputStream> sources)
+ {
+ this.sources.addAll(sources);
+ }
+
+ /**
+ * Merge the list of source documents, saving the result in the destination file.
+ *
+ * @throws IOException If there is an error saving the document.
+ * @throws COSVisitorException If an error occurs while saving the destination file.
+ */
+ public void mergeDocuments() throws IOException, COSVisitorException
+ {
+ PDDocument destination = null;
+ InputStream sourceFile;
+ PDDocument source;
+ if (sources != null && sources.size() > 0)
+ {
+ java.util.Vector<PDDocument> tobeclosed = new java.util.Vector<PDDocument>();
+
+ try
+ {
+ Iterator<InputStream> sit = sources.iterator();
+ sourceFile = sit.next();
+ destination = PDDocument.load(sourceFile);
+
+ while (sit.hasNext())
+ {
+ sourceFile = sit.next();
+ source = PDDocument.load(sourceFile);
+ tobeclosed.add(source);
+ appendDocument(destination, source);
+ }
+ if(destinationStream == null)
+ {
+ destination.save(destinationFileName);
+ }
+ else
+ {
+ destination.save(destinationStream);
+ }
+ }
+ finally
+ {
+ if (destination != null)
+ {
+ destination.close();
+ }
+ for (PDDocument doc : tobeclosed)
+ {
+ doc.close();
+ }
+ }
+ }
+ }
+
+
+ /**
+ * append all pages from source to destination.
+ *
+ * @param destination the document to receive the pages
+ * @param source the document originating the new pages
+ *
+ * @throws IOException If there is an error accessing data from either document.
+ */
+ public void appendDocument(PDDocument destination, PDDocument source) throws IOException
+ {
+ if( destination.isEncrypted() )
+ {
+ throw new IOException( "Error: destination PDF is encrypted, can't append encrypted PDF documents." );
+ }
+ if( source.isEncrypted() )
+ {
+ throw new IOException( "Error: source PDF is encrypted, can't append encrypted PDF documents." );
+ }
+ PDDocumentInformation destInfo = destination.getDocumentInformation();
+ PDDocumentInformation srcInfo = source.getDocumentInformation();
+ destInfo.getDictionary().mergeInto( srcInfo.getDictionary() );
+
+ PDDocumentCatalog destCatalog = destination.getDocumentCatalog();
+ PDDocumentCatalog srcCatalog = source.getDocumentCatalog();
+
+ if( destCatalog.getOpenAction() == null )
+ {
+ destCatalog.setOpenAction( srcCatalog.getOpenAction() );
+ }
+
+ PDFCloneUtility cloner = new PDFCloneUtility(destination);
+
+ try
+ {
+ PDAcroForm destAcroForm = destCatalog.getAcroForm();
+ PDAcroForm srcAcroForm = srcCatalog.getAcroForm();
+ if( destAcroForm == null )
+ {
+ cloner.cloneForNewDocument( srcAcroForm );
+ destCatalog.setAcroForm( srcAcroForm );
+ }
+ else
+ {
+ if( srcAcroForm != null )
+ {
+ mergeAcroForm(cloner, destAcroForm, srcAcroForm);
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ // if we are not ignoring exceptions, we'll re-throw this
+ if(!ignoreAcroFormErrors)
+ {
+ throw (IOException)e;
+ }
+ }
+
+ COSArray destThreads = (COSArray)destCatalog.getCOSDictionary().getDictionaryObject(
+ COSName.THREADS);
+ COSArray srcThreads = (COSArray)cloner.cloneForNewDocument(
+ destCatalog.getCOSDictionary().getDictionaryObject( COSName.THREADS ));
+ if( destThreads == null )
+ {
+ destCatalog.getCOSDictionary().setItem( COSName.THREADS, srcThreads );
+ }
+ else
+ {
+ destThreads.addAll( srcThreads );
+ }
+
+ PDDocumentNameDictionary destNames = destCatalog.getNames();
+ PDDocumentNameDictionary srcNames = srcCatalog.getNames();
+ if( srcNames != null )
+ {
+ if( destNames == null )
+ {
+ destCatalog.getCOSDictionary().setItem( COSName.NAMES,
+ cloner.cloneForNewDocument( srcNames ) );
+ }
+ else
+ {
+ cloner.cloneMerge(srcNames, destNames);
+ }
+
+ }
+
+ PDDocumentOutline destOutline = destCatalog.getDocumentOutline();
+ PDDocumentOutline srcOutline = srcCatalog.getDocumentOutline();
+ if( srcOutline != null )
+ {
+ if( destOutline == null )
+ {
+ PDDocumentOutline cloned =
+ new PDDocumentOutline( (COSDictionary)cloner.cloneForNewDocument( srcOutline ) );
+ destCatalog.setDocumentOutline( cloned );
+ }
+ else
+ {
+ PDOutlineItem first = srcOutline.getFirstChild();
+ if(first != null)
+ {
+ PDOutlineItem clonedFirst = new PDOutlineItem(
+ (COSDictionary)cloner.cloneForNewDocument( first ));
+ destOutline.appendChild( clonedFirst );
+ }
+ }
+ }
+
+ String destPageMode = destCatalog.getPageMode();
+ String srcPageMode = srcCatalog.getPageMode();
+ if( destPageMode == null )
+ {
+ destCatalog.setPageMode( srcPageMode );
+ }
+
+ 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();
+ COSArray destNums = null;
+ if( destLabels == null )
+ {
+ destLabels = new COSDictionary();
+ destNums = new COSArray();
+ destLabels.setItem( COSName.NUMS, destNums );
+ destCatalog.getCOSDictionary().setItem( COSName.PAGE_LABELS, destLabels );
+ }
+ else
+ {
+ destNums = (COSArray)destLabels.getDictionaryObject( COSName.NUMS );
+ }
+ COSArray srcNums = (COSArray)srcLabels.getDictionaryObject( COSName.NUMS );
+ if (srcNums != null)
+ {
+ for( int i=0; i<srcNums.size(); i+=2 )
+ {
+ COSNumber labelIndex = (COSNumber)srcNums.getObject( i );
+ long labelIndexValue = labelIndex.intValue();
+ destNums.add( COSInteger.get( labelIndexValue + destPageCount ) );
+ destNums.add( cloner.cloneForNewDocument( srcNums.getObject( i+1 ) ) );
+ }
+ }
+ }
+
+ COSStream destMetadata = (COSStream)destCatalog.getCOSDictionary().getDictionaryObject( COSName.METADATA );
+ COSStream srcMetadata = (COSStream)srcCatalog.getCOSDictionary().getDictionaryObject( COSName.METADATA );
+ if( destMetadata == null && srcMetadata != null )
+ {
+ PDStream newStream = new PDStream( destination, srcMetadata.getUnfilteredStream(), false );
+ newStream.getStream().mergeInto( srcMetadata );
+ newStream.addCompression();
+ destCatalog.getCOSDictionary().setItem( COSName.METADATA, newStream );
+ }
+
+ //finally append the pages
+ List<PDPage> pages = source.getDocumentCatalog().getAllPages();
+ Iterator<PDPage> pageIter = pages.iterator();
+ while( pageIter.hasNext() )
+ {
+ PDPage page = pageIter.next();
+ PDPage newPage =
+ new PDPage( (COSDictionary)cloner.cloneForNewDocument( page.getCOSDictionary() ) );
+ newPage.setCropBox( page.findCropBox() );
+ newPage.setMediaBox( page.findMediaBox() );
+ newPage.setRotation( page.findRotation() );
+ destination.addPage( newPage );
+ }
+ }
+
+
+ private int nextFieldNum = 1;
+
+ /**
+ * Merge the contents of the source form into the destination form
+ * for the destination file.
+ *
+ * @param cloner the object cloner for the destination document
+ * @param destAcroForm the destination form
+ * @param srcAcroForm the source form
+ * @throws IOException If an error occurs while adding the field.
+ */
+ private void mergeAcroForm(PDFCloneUtility cloner, PDAcroForm destAcroForm, PDAcroForm srcAcroForm)
+ throws IOException
+ {
+ List destFields = destAcroForm.getFields();
+ List srcFields = srcAcroForm.getFields();
+ if( srcFields != null )
+ {
+ if( destFields == null )
+ {
+ destFields = new COSArrayList();
+ destAcroForm.setFields( destFields );
+ }
+ Iterator srcFieldsIterator = srcFields.iterator();
+ while (srcFieldsIterator.hasNext())
+ {
+ PDField srcField = (PDField)srcFieldsIterator.next();
+ PDField destField =
+ PDFieldFactory.createField(
+ destAcroForm,
+ (COSDictionary)cloner.cloneForNewDocument(srcField.getDictionary() ));
+ // 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 )
+ {
+ destField.setPartialName("dummyFieldName"+(nextFieldNum++));
+ }
+ destFields.add(destField);
+ }
+ }
+ }
+
+ public boolean isIgnoreAcroFormErrors()
+ {
+ return ignoreAcroFormErrors;
+ }
+
+ public void setIgnoreAcroFormErrors(boolean ignoreAcroFormErrors)
+ {
+ this.ignoreAcroFormErrors = ignoreAcroFormErrors;
+ }
+
+
+}
--- /dev/null
+/*
+ * 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.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class represents an Operator in the content stream.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.14 $
+ */
+public class PDFOperator
+{
+ private String theOperator;
+ private byte[] imageData;
+ private ImageParameters imageParameters;
+
+ private static Map operators = Collections.synchronizedMap( new HashMap() );
+
+ /**
+ * Constructor.
+ *
+ * @param aOperator The operator that this object will represent.
+ */
+ private PDFOperator( String aOperator )
+ {
+ theOperator = aOperator;
+ if( aOperator.startsWith( "/" ) )
+ {
+ throw new RuntimeException( "Operators are not allowed to start with / '" + aOperator + "'" );
+ }
+ }
+
+ /**
+ * This is used to create/cache operators in the system.
+ *
+ * @param operator The operator for the system.
+ *
+ * @return The operator that matches the operator keyword.
+ */
+ public static PDFOperator getOperator( String operator )
+ {
+ PDFOperator operation = null;
+ if( operator.equals( "ID" ) || operator.equals( "BI" ) )
+ {
+ //we can't cache the ID operators.
+ operation = new PDFOperator( operator );
+ }
+ else
+ {
+ operation = (PDFOperator)operators.get( operator );
+ if( operation == null )
+ {
+ operation = new PDFOperator( operator );
+ operators.put( operator, operation );
+ }
+ }
+
+ return operation;
+ }
+
+ /**
+ * This will get the operation that this operator represents.
+ *
+ * @return The string representation of the operation.
+ */
+ public String getOperation()
+ {
+ return theOperator;
+ }
+
+ /**
+ * This will print a string rep of this class.
+ *
+ * @return A string rep of this class.
+ */
+ public String toString()
+ {
+ return "PDFOperator{" + theOperator + "}";
+ }
+
+ /**
+ * This is the special case for the ID operator where there are just random
+ * bytes inlined the stream.
+ *
+ * @return Value of property imageData.
+ */
+ public byte[] getImageData()
+ {
+ return this.imageData;
+ }
+
+ /**
+ * This will set the image data, this is only used for the ID operator.
+ *
+ * @param imageDataArray New value of property imageData.
+ */
+ public void setImageData(byte[] imageDataArray)
+ {
+ imageData = imageDataArray;
+ }
+
+ /**
+ * This will get the image parameters, this is only valid for BI operators.
+ *
+ * @return The image parameters.
+ */
+ public ImageParameters getImageParameters()
+ {
+ return imageParameters;
+ }
+
+ /**
+ * This will set the image parameters, this is only valid for BI operators.
+ *
+ * @param params The image parameters.
+ */
+ public void setImageParameters( ImageParameters params)
+ {
+ imageParameters = params;
+ }
+}
--- /dev/null
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.Stack;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+
+import org.apache.pdfbox.pdmodel.common.PDMatrix;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType3Font;
+
+import org.apache.pdfbox.pdmodel.graphics.PDExtendedGraphicsState;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * This class will run through a PDF content stream and execute certain operations
+ * and provide a callback interface for clients that want to do things with the stream.
+ * See the PDFTextStripper class for an example of how to use this class.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.38 $
+ */
+public class PDFStreamEngine
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(PDFStreamEngine.class);
+
+ /**
+ * The PDF operators that are ignored by this engine.
+ */
+ private final Set<String> unsupportedOperators = new HashSet<String>();
+
+ private static final byte[] SPACE_BYTES = { (byte)32 };
+
+ private PDGraphicsState graphicsState = null;
+
+ private Matrix textMatrix = null;
+ private Matrix textLineMatrix = null;
+ private Stack<PDGraphicsState> graphicsStack = new Stack<PDGraphicsState>();
+
+ private Map<String,OperatorProcessor> operators = new HashMap<String,OperatorProcessor>();
+
+ private Stack<StreamResources> streamResourcesStack = new Stack<StreamResources>();
+
+ private PDPage page;
+
+ private Map<String,PDFont> documentFontCache = new HashMap<String,PDFont>();
+
+ private int validCharCnt;
+ private int totalCharCnt;
+
+ /**
+ * Flag to skip malformed or otherwise unparseable input where possible.
+ */
+ private boolean forceParsing = false;
+
+ /**
+ * This is a simple internal class used by the Stream engine to handle the
+ * resources stack.
+ */
+ private static class StreamResources
+ {
+ private Map<String,PDFont> fonts;
+ private Map<String,PDColorSpace> colorSpaces;
+ private Map<String,PDXObject> xobjects;
+ private Map<String,PDExtendedGraphicsState> graphicsStates;
+ private PDResources resources;
+
+ private StreamResources()
+ {};
+ }
+
+ /**
+ * Constructor.
+ */
+ public PDFStreamEngine()
+ {
+ //default constructor
+ validCharCnt = 0;
+ totalCharCnt = 0;
+
+ }
+
+ /**
+ * Constructor with engine properties. The property keys are all
+ * PDF operators, the values are class names used to execute those
+ * operators. An empty value means that the operator will be silently
+ * ignored.
+ *
+ * @param properties The engine properties.
+ *
+ * @throws IOException If there is an error setting the engine properties.
+ */
+ public PDFStreamEngine( Properties properties ) throws IOException
+ {
+ if( properties == null )
+ {
+ throw new NullPointerException( "properties cannot be null" );
+ }
+ Enumeration<?> names = properties.propertyNames();
+ for ( Object name : Collections.list( names ) )
+ {
+ String operator = name.toString();
+ String processorClassName = properties.getProperty( operator );
+ if( "".equals( processorClassName ) )
+ {
+ unsupportedOperators.add( operator );
+ }
+ else
+ {
+ try
+ {
+ Class<?> klass = Class.forName( processorClassName );
+ OperatorProcessor processor =
+ (OperatorProcessor) klass.newInstance();
+ registerOperatorProcessor( operator, processor );
+ }
+ catch( Exception e )
+ {
+ throw new WrappedIOException(
+ "OperatorProcessor class " + processorClassName
+ + " could not be instantiated", e );
+ }
+ }
+ }
+ validCharCnt = 0;
+ totalCharCnt = 0;
+ }
+
+ public boolean isForceParsing() {
+ return forceParsing;
+ }
+
+ public void setForceParsing(boolean forceParsing) {
+ this.forceParsing = forceParsing;
+ }
+
+ /**
+ * Register a custom operator processor with the engine.
+ *
+ * @param operator The operator as a string.
+ * @param op Processor instance.
+ */
+ public void registerOperatorProcessor( String operator, OperatorProcessor op )
+ {
+ op.setContext( this );
+ operators.put( operator, op );
+ }
+
+ /**
+ * This method must be called between processing documents. The
+ * PDFStreamEngine caches information for the document between pages
+ * and this will release the cached information. This only needs
+ * to be called if processing a new document.
+ *
+ */
+ public void resetEngine()
+ {
+ documentFontCache.clear();
+ validCharCnt = 0;
+ totalCharCnt = 0;
+ }
+
+ /**
+ * This will process the contents of the stream.
+ *
+ * @param aPage The page.
+ * @param resources The location to retrieve resources.
+ * @param cosStream the Stream to execute.
+ *
+ *
+ * @throws IOException if there is an error accessing the stream.
+ */
+ public void processStream( PDPage aPage, PDResources resources, COSStream cosStream ) throws IOException
+ {
+ graphicsState = new PDGraphicsState(aPage.findCropBox());
+ textMatrix = null;
+ textLineMatrix = null;
+ graphicsStack.clear();
+ streamResourcesStack.clear();
+
+ processSubStream( aPage, resources, cosStream );
+ }
+
+ /**
+ * Process a sub stream of the current stream.
+ *
+ * @param aPage The page used for drawing.
+ * @param resources The resources used when processing the stream.
+ * @param cosStream The stream to process.
+ *
+ * @throws IOException If there is an exception while processing the stream.
+ */
+ public void processSubStream(
+ PDPage aPage, PDResources resources, COSStream cosStream)
+ throws IOException {
+ page = aPage;
+ if (resources != null) {
+ StreamResources sr = new StreamResources();
+ sr.fonts = resources.getFonts( documentFontCache );
+ sr.colorSpaces = resources.getColorSpaces();
+ sr.xobjects = resources.getXObjects();
+ sr.graphicsStates = resources.getGraphicsStates();
+ sr.resources = resources;
+
+ streamResourcesStack.push(sr);
+ try {
+ processSubStream(cosStream);
+ } finally {
+ streamResourcesStack.pop();
+ }
+ } else {
+ processSubStream(cosStream);
+ }
+ }
+
+ private void processSubStream(COSStream cosStream) throws IOException {
+ List<COSBase> arguments = new ArrayList<COSBase>();
+ PDFStreamParser parser = new PDFStreamParser(cosStream, forceParsing);
+ try {
+ Iterator<Object> iter = parser.getTokenIterator();
+
+ while (iter.hasNext()) {
+ Object next = iter.next();
+ if (log.isDebugEnabled()) {
+ log.debug("processing substream token: " + next);
+ }
+ if (next instanceof COSObject) {
+ arguments.add(((COSObject) next).getObject());
+ } else if (next instanceof PDFOperator) {
+ processOperator((PDFOperator) next, arguments);
+ arguments = new ArrayList<COSBase>();
+ } else {
+ arguments.add((COSBase) next);
+ }
+ }
+ } finally {
+ parser.close();
+ }
+ }
+
+
+ /**
+ * A method provided as an event interface to allow a subclass to perform
+ * some specific functionality when text needs to be processed.
+ *
+ * @param text The text to be processed.
+ */
+ protected void processTextPosition( TextPosition text )
+ {
+ //subclasses can override to provide specific functionality.
+ }
+
+ /**
+ * A method provided as an event interface to allow a subclass to perform
+ * some specific functionality on the string encoded by a glyph.
+ *
+ * @param str The string to be processed.
+ */
+ protected String inspectFontEncoding( String str )
+ {
+ return str;
+ }
+
+ /**
+ * Process encoded text from the PDF Stream.
+ * You should override this method if you want to perform an action when
+ * encoded text is being processed.
+ *
+ * @param string The encoded text
+ *
+ * @throws IOException If there is an error processing the string
+ */
+ public void processEncodedText( byte[] string ) throws IOException
+ {
+ /* Note on variable names. There are three different units being used
+ * in this code. Character sizes are given in glyph units, text locations
+ * are initially given in text units, and we want to save the data in
+ * display units. The variable names should end with Text or Disp to
+ * represent if the values are in text or disp units (no glyph units are saved).
+ */
+ final float fontSizeText = graphicsState.getTextState().getFontSize();
+ final float horizontalScalingText = graphicsState.getTextState().getHorizontalScalingPercent()/100f;
+ //float verticalScalingText = horizontalScaling;//not sure if this is right but what else to do???
+ final float riseText = graphicsState.getTextState().getRise();
+ final float wordSpacingText = graphicsState.getTextState().getWordSpacing();
+ final float characterSpacingText = graphicsState.getTextState().getCharacterSpacing();
+
+ //We won't know the actual number of characters until
+ //we process the byte data(could be two bytes each) but
+ //it won't ever be more than string.length*2(there are some cases
+ //were a single byte will result in two output characters "fi"
+
+ final PDFont font = graphicsState.getTextState().getFont();
+ // all fonts are providing the width/height of a character in thousandths of a unit of text space
+ float fontMatrixXScaling = 1/1000f;
+ float fontMatrixYScaling = 1/1000f;
+ float glyphSpaceToTextSpaceFactor = 1/1000f;
+ // expect Type3 fonts, those are providing the width of a character in glyph space units
+ if (font instanceof PDType3Font)
+ {
+ PDMatrix fontMatrix = font.getFontMatrix();
+ fontMatrixXScaling = fontMatrix.getValue(0, 0);
+ fontMatrixYScaling = fontMatrix.getValue(1, 1);
+ //This will typically be 1000 but in the case of a type3 font
+ //this might be a different number
+ glyphSpaceToTextSpaceFactor = 1f/fontMatrix.getValue( 0, 0 );
+ }
+ float spaceWidthText=0;
+ try
+ {
+ // to avoid crash as described in PDFBOX-614
+ // lets see what the space displacement should be
+ spaceWidthText = (font.getFontWidth( SPACE_BYTES, 0, 1 )*glyphSpaceToTextSpaceFactor);
+ }
+ catch (Throwable exception)
+ {
+ log.warn( exception, exception);
+ }
+
+ if( spaceWidthText == 0 )
+ {
+ spaceWidthText = (font.getAverageFontWidth()*glyphSpaceToTextSpaceFactor);
+ //The average space width appears to be higher than necessary
+ //so lets make it a little bit smaller.
+ spaceWidthText *= .80f;
+ }
+
+ float maxVerticalDisplacementText = 0;
+
+ Matrix textStateParameters = new Matrix();
+ textStateParameters.setValue(0,0, fontSizeText*horizontalScalingText);
+ textStateParameters.setValue(1,1, fontSizeText);
+ textStateParameters.setValue(2,1, riseText);
+
+ int pageRotation = page.findRotation();
+ float pageHeight = page.findMediaBox().getHeight();
+ float pageWidth = page.findMediaBox().getWidth();
+
+ Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
+ Matrix textXctm = new Matrix();
+ Matrix textMatrixEnd = new Matrix();
+ Matrix td = new Matrix();
+ Matrix tempMatrix = new Matrix();
+
+ int codeLength = 1;
+ for( int i=0; i<string.length; i+=codeLength)
+ {
+ // Decode the value to a Unicode character
+ codeLength = 1;
+ String c = font.encode( string, i, codeLength );
+ if( c == null && i+1<string.length)
+ {
+ //maybe a multibyte encoding
+ codeLength++;
+ c = font.encode( string, i, codeLength );
+ }
+
+ // the space width has to be transformed into display units
+ float spaceWidthDisp = spaceWidthText * fontSizeText * horizontalScalingText * textMatrix.getValue(0, 0) * ctm.getValue(0, 0);
+
+ //todo, handle horizontal displacement
+ // get the width and height of this character in text units
+ float characterHorizontalDisplacementText = font.getFontWidth( string, i, codeLength );
+ float characterVerticalDisplacementText = font.getFontHeight( string, i, codeLength );
+
+ // multiply the width/height with the scaling factor
+ characterHorizontalDisplacementText = characterHorizontalDisplacementText * fontMatrixXScaling;
+ characterVerticalDisplacementText = characterVerticalDisplacementText * fontMatrixYScaling;
+
+ maxVerticalDisplacementText =
+ Math.max(
+ maxVerticalDisplacementText,
+ characterVerticalDisplacementText);
+
+ // PDF Spec - 5.5.2 Word Spacing
+ //
+ // Word spacing works the same was as character spacing, but applies
+ // only to the space character, code 32.
+ //
+ // Note: Word spacing is applied to every occurrence of the single-byte
+ // character code 32 in a string. This can occur when using a simple
+ // font or a composite font that defines code 32 as a single-byte code.
+ // It does not apply to occurrences of the byte value 32 in multiple-byte
+ // codes.
+ //
+ // RDD - My interpretation of this is that only character code 32's that
+ // encode to spaces should have word spacing applied. Cases have been
+ // observed where a font has a space character with a character code
+ // other than 32, and where word spacing (Tw) was used. In these cases,
+ // applying word spacing to either the non-32 space or to the character
+ // code 32 non-space resulted in errors consistent with this interpretation.
+ //
+ float spacingText = 0;
+ if( (string[i] == 0x20) && codeLength == 1 )
+ {
+ spacingText += wordSpacingText;
+ }
+ textXctm = textMatrix.multiply(ctm, textXctm);
+ // Convert textMatrix to display units
+ // We need to instantiate a new Matrix instance here as it is passed to the TextPosition constructor below.
+ Matrix textMatrixStart = textStateParameters.multiply(textXctm);
+
+ // TODO : tx should be set for horizontal text and ty for vertical text
+ // which seems to be specified in the font (not the direction in the matrix).
+ float tx = ((characterHorizontalDisplacementText)*fontSizeText)*horizontalScalingText;
+ float ty = 0;
+ // reset the matrix instead of creating a new one
+ td.reset();
+ td.setValue( 2, 0, tx );
+ td.setValue( 2, 1, ty );
+
+ // The text matrix gets updated after each glyph is placed. The updated
+ // version will have the X and Y coordinates for the next glyph.
+ // textMatrixEnd contains the coordinates of the end of the last glyph without
+ // taking characterSpacingText and spacintText into account, otherwise it'll be
+ // impossible to detect new words within text extraction
+ tempMatrix = textStateParameters.multiply(td, tempMatrix);
+ textMatrixEnd = tempMatrix.multiply(textXctm, textMatrixEnd);
+ final float endXPosition = textMatrixEnd.getXPosition();
+ final float endYPosition = textMatrixEnd.getYPosition();
+
+ // add some spacing to the text matrix (see comment above)
+ tx = ((characterHorizontalDisplacementText)*fontSizeText+characterSpacingText+spacingText)*horizontalScalingText;
+ td.setValue( 2, 0, tx );
+ textMatrix = td.multiply(textMatrix, textMatrix );
+
+ // determine the width of this character
+ // XXX: Note that if we handled vertical text, we should be using Y here
+ float startXPosition = textMatrixStart.getXPosition();
+ float widthText = endXPosition - startXPosition;
+
+ //there are several cases where one character code will
+ //output multiple characters. For example "fi" or a
+ //glyphname that has no mapping like "visiblespace"
+ if( c != null )
+ {
+ validCharCnt++;
+ }
+ else
+ {
+ // PDFBOX-373: Replace a null entry with "?" so it is
+ // not printed as "(null)"
+ c = "?";
+ }
+ totalCharCnt++;
+
+ float totalVerticalDisplacementDisp = maxVerticalDisplacementText * fontSizeText * textMatrix.getYScale();
+
+ // process the decoded text
+ processTextPosition(
+ new TextPosition(
+ pageRotation,
+ pageWidth,
+ pageHeight,
+ textMatrixStart,
+ endXPosition,
+ endYPosition,
+ totalVerticalDisplacementDisp,
+ widthText,
+ spaceWidthDisp,
+ c,
+ font,
+ fontSizeText,
+ (int)(fontSizeText * textMatrix.getXScale())
+ ));
+ }
+ }
+
+ /**
+ * This is used to handle an operation.
+ *
+ * @param operation The operation to perform.
+ * @param arguments The list of arguments.
+ *
+ * @throws IOException If there is an error processing the operation.
+ */
+ public void processOperator( String operation, List<COSBase> arguments ) throws IOException
+ {
+ try
+ {
+ PDFOperator oper = PDFOperator.getOperator( operation );
+ processOperator( oper, arguments );
+ }
+ catch (IOException e)
+ {
+ log.warn(e, e);
+ }
+ }
+
+ /**
+ * This is used to handle an operation.
+ *
+ * @param operator The operation to perform.
+ * @param arguments The list of arguments.
+ *
+ * @throws IOException If there is an error processing the operation.
+ */
+ protected void processOperator( PDFOperator operator, List<COSBase> arguments ) throws IOException
+ {
+ try
+ {
+ String operation = operator.getOperation();
+ OperatorProcessor processor = (OperatorProcessor)operators.get( operation );
+ if( processor != null )
+ {
+ processor.setContext(this);
+ processor.process( operator, arguments );
+ }
+ else
+ {
+ if (!unsupportedOperators.contains(operation))
+ {
+ log.info("unsupported/disabled operation: " + operation);
+ unsupportedOperators.add(operation);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ log.warn(e, e);
+ }
+ }
+
+ /**
+ * @return Returns the colorSpaces.
+ */
+ public Map<String,PDColorSpace> getColorSpaces()
+ {
+ return streamResourcesStack.peek().colorSpaces;
+ }
+
+ /**
+ * @return Returns the colorSpaces.
+ */
+ public Map<String,PDXObject> getXObjects()
+ {
+ return streamResourcesStack.peek().xobjects;
+ }
+
+ /**
+ * @param value The colorSpaces to set.
+ */
+ public void setColorSpaces(Map<String,PDColorSpace> value)
+ {
+ streamResourcesStack.peek().colorSpaces = value;
+ }
+ /**
+ * @return Returns the fonts.
+ */
+ public Map<String,PDFont> getFonts()
+ {
+ return streamResourcesStack.peek().fonts;
+ }
+ /**
+ * @param value The fonts to set.
+ */
+ public void setFonts(Map<String,PDFont> value)
+ {
+ streamResourcesStack.peek().fonts = value;
+ }
+ /**
+ * @return Returns the graphicsStack.
+ */
+ public Stack<PDGraphicsState> getGraphicsStack()
+ {
+ return graphicsStack;
+ }
+ /**
+ * @param value The graphicsStack to set.
+ */
+ public void setGraphicsStack(Stack<PDGraphicsState> value)
+ {
+ graphicsStack = value;
+ }
+ /**
+ * @return Returns the graphicsState.
+ */
+ public PDGraphicsState getGraphicsState()
+ {
+ return graphicsState;
+ }
+ /**
+ * @param value The graphicsState to set.
+ */
+ public void setGraphicsState(PDGraphicsState value)
+ {
+ graphicsState = value;
+ }
+ /**
+ * @return Returns the graphicsStates.
+ */
+ public Map<String,PDExtendedGraphicsState> getGraphicsStates()
+ {
+ return streamResourcesStack.peek().graphicsStates;
+ }
+ /**
+ * @param value The graphicsStates to set.
+ */
+ public void setGraphicsStates(Map<String,PDExtendedGraphicsState> value)
+ {
+ ((StreamResources) streamResourcesStack.peek()).graphicsStates = value;
+ }
+ /**
+ * @return Returns the textLineMatrix.
+ */
+ public Matrix getTextLineMatrix()
+ {
+ return textLineMatrix;
+ }
+ /**
+ * @param value The textLineMatrix to set.
+ */
+ public void setTextLineMatrix(Matrix value)
+ {
+ textLineMatrix = value;
+ }
+ /**
+ * @return Returns the textMatrix.
+ */
+ public Matrix getTextMatrix()
+ {
+ return textMatrix;
+ }
+ /**
+ * @param value The textMatrix to set.
+ */
+ public void setTextMatrix(Matrix value)
+ {
+ textMatrix = value;
+ }
+ /**
+ * @return Returns the resources.
+ */
+ public PDResources getResources()
+ {
+ return streamResourcesStack.peek().resources;
+ }
+
+ /**
+ * Get the current page that is being processed.
+ *
+ * @return The page being processed.
+ */
+ public PDPage getCurrentPage()
+ {
+ return page;
+ }
+
+ /**
+ * Get the total number of valid characters in the doc
+ * that could be decoded in processEncodedText().
+ * @return The number of valid characters.
+ */
+ public int getValidCharCnt()
+ {
+ return validCharCnt;
+ }
+
+ /**
+ * Get the total number of characters in the doc
+ * (including ones that could not be mapped).
+ * @return The number of characters.
+ */
+ public int getTotalCharCnt()
+ {
+ return totalCharCnt;
+ }
+
+}
--- /dev/null
+/*
+ * 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.Iterator;
+import java.util.List;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+
+/**
+ * Wrap stripped text in simple HTML, trying to form HTML paragraphs. Paragraphs
+ * broken by pages, columns, or figures are not mended.
+ *
+ *
+ * @author jjb - http://www.johnjbarton.com
+ * @version $Revision: 1.3 $
+ */
+public class PDFText2HTML extends PDFTextStripper
+{
+ private static final int INITIAL_PDF_TO_HTML_BYTES = 8192;
+
+ private boolean onFirstPage = true;
+
+ /**
+ * Constructor.
+ * @param encoding The encoding to be used
+ * @throws IOException If there is an error during initialization.
+ */
+ public PDFText2HTML(String encoding) throws IOException
+ {
+ super(encoding);
+ setLineSeparator(systemLineSeparator);
+ setParagraphStart("<p>");
+ setParagraphEnd("</p>"+systemLineSeparator);
+ setPageStart("<div style=\"page-break-before:always; page-break-after:always\">");
+ setPageEnd("</div>"+systemLineSeparator);
+ setArticleStart(systemLineSeparator);
+ setArticleEnd(systemLineSeparator);
+ }
+
+ /**
+ * Write the header to the output document. Now also writes the tag defining
+ * the character encoding.
+ *
+ * @throws IOException
+ * If there is a problem writing out the header to the document.
+ */
+ protected void writeHeader() throws IOException
+ {
+ StringBuffer buf = new StringBuffer(INITIAL_PDF_TO_HTML_BYTES);
+ buf.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"" + "\n"
+ + "\"http://www.w3.org/TR/html4/loose.dtd\">\n");
+ buf.append("<html><head>");
+ buf.append("<title>" + escape(getTitle()) + "</title>\n");
+ if(outputEncoding != null)
+ {
+ buf.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset="
+ + this.outputEncoding + "\">\n");
+ }
+ buf.append("</head>\n");
+ buf.append("<body>\n");
+ super.writeString(buf.toString());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void writePage() throws IOException
+ {
+ if (onFirstPage)
+ {
+ writeHeader();
+ onFirstPage = false;
+ }
+ super.writePage();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void endDocument(PDDocument pdf) throws IOException
+ {
+ super.writeString("</body></html>");
+ }
+
+ /**
+ * This method will attempt to guess the title of the document using
+ * either the document properties or the first lines of text.
+ *
+ * @return returns the title.
+ */
+ protected String getTitle()
+ {
+ String titleGuess = document.getDocumentInformation().getTitle();
+ if(titleGuess != null && titleGuess.length() > 0)
+ {
+ return titleGuess;
+ }
+ else
+ {
+ Iterator<List<TextPosition>> textIter = getCharactersByArticle().iterator();
+ float lastFontSize = -1.0f;
+
+ StringBuffer titleText = new StringBuffer();
+ while (textIter.hasNext())
+ {
+ Iterator<TextPosition> textByArticle = textIter.next().iterator();
+ while (textByArticle.hasNext())
+ {
+ TextPosition position = textByArticle.next();
+
+ float currentFontSize = position.getFontSize();
+ //If we're past 64 chars we will assume that we're past the title
+ //64 is arbitrary
+ if (currentFontSize != lastFontSize || titleText.length() > 64)
+ {
+ if (titleText.length() > 0)
+ {
+ return titleText.toString();
+ }
+ lastFontSize = currentFontSize;
+ }
+ if (currentFontSize > 13.0f)
+ { // most body text is 12pt
+ titleText.append(position.getCharacter());
+ }
+ }
+ }
+ }
+ return "";
+ }
+
+
+ /**
+ * Write out the article separator (div tag) with proper text direction
+ * information.
+ *
+ * @param isltr true if direction of text is left to right
+ * @throws IOException
+ * If there is an error writing to the stream.
+ */
+ protected void startArticle(boolean isltr) throws IOException
+ {
+ if (isltr)
+ {
+ super.writeString("<div>");
+ }
+ else
+ {
+ super.writeString("<div dir=\"RTL\">");
+ }
+ }
+
+ /**
+ * Write out the article separator.
+ *
+ * @throws IOException
+ * If there is an error writing to the stream.
+ */
+ protected void endArticle() throws IOException
+ {
+ super.endArticle();
+ super.writeString("</div>");
+ }
+
+ /**
+ * Write a string to the output stream and escape some HTML characters.
+ *
+ * @param chars String to be written to the stream
+ * @throws IOException
+ * If there is an error writing to the stream.
+ */
+ protected void writeString(String chars) throws IOException
+ {
+ super.writeString(escape(chars));
+ }
+
+ /**
+ * Escape some HTML characters.
+ *
+ * @param chars String to be escaped
+ * @return returns escaped String.
+ */
+ private String escape(String chars)
+ {
+ StringBuilder builder = new StringBuilder(chars.length());
+ for (int i = 0; i < chars.length(); i++)
+ {
+ char c = chars.charAt(i);
+ // write non-ASCII as named entities
+ if ((c < 32) || (c > 126))
+ {
+ int charAsInt = c;
+ builder.append("&#").append(charAsInt).append(";");
+ }
+ else
+ {
+ switch (c)
+ {
+ case 34:
+ builder.append(""");
+ break;
+ case 38:
+ builder.append("&");
+ break;
+ case 60:
+ builder.append("<");
+ break;
+ case 62:
+ builder.append(">");
+ break;
+ default:
+ builder.append(String.valueOf(c));
+ }
+ }
+ }
+ return builder.toString();
+ }
+}
--- /dev/null
+/*
+ * 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.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import org.apache.pdfbox.cos.COSDocument;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.exceptions.CryptographyException;
+import org.apache.pdfbox.exceptions.InvalidPasswordException;
+import org.apache.pdfbox.exceptions.WrappedIOException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
+import org.apache.pdfbox.pdmodel.interactive.pagenavigation.PDThreadBead;
+import org.apache.pdfbox.util.TextPosition;
+
+
+/**
+ * This class will take a pdf document and strip out all of the text and ignore the
+ * formatting and such. Please note; it is up to clients of this class to verify that
+ * a specific user has the correct permissions to extract text from the
+ * PDF document.
+ *
+ * The basic flow of this process is that we get a document and use a series of
+ * processXXX() functions that work on smaller and smaller chunks of the page.
+ * Eventually, we fully process each page and then print it.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.70 $
+ */
+public class PDFTextStripper extends PDFStreamEngine
+{
+
+ private static final String thisClassName = PDFTextStripper.class.getSimpleName().toLowerCase();
+
+ private static float DEFAULT_INDENT_THRESHOLD = 2.0f;
+ private static float DEFAULT_DROP_THRESHOLD = 2.5f;
+
+ //enable the ability to set the default indent/drop thresholds
+ //with -D system properties:
+ // pdftextstripper.indent
+ // pdftextstripper.drop
+ static
+ {
+ String prop = thisClassName+".indent";
+ String s = System.getProperty(prop);
+ if(s!=null && s.length()>0)
+ {
+ try
+ {
+ float f = Float.parseFloat(s);
+ DEFAULT_INDENT_THRESHOLD = f;
+ }
+ catch(NumberFormatException nfe)
+ {
+ //ignore and use default
+ }
+ }
+ prop = thisClassName+".drop";
+ s = System.getProperty(prop);
+ if(s!=null && s.length()>0)
+ {
+ try
+ {
+ float f = Float.parseFloat(s);
+ DEFAULT_DROP_THRESHOLD = f;
+ }
+ catch(NumberFormatException nfe){
+ //ignore and use default
+ }
+ }
+ }
+
+ /**
+ * The platforms line separator.
+ */
+ protected final String systemLineSeparator = System.getProperty("line.separator");
+
+ private String lineSeparator = systemLineSeparator;
+ private String pageSeparator = systemLineSeparator;
+ private String wordSeparator = " ";
+ private String paragraphStart = "";
+ private String paragraphEnd = "";
+ private String pageStart = "";
+ private String pageEnd = pageSeparator;
+ private String articleStart = "";
+ private String articleEnd = "";
+
+
+ private int currentPageNo = 0;
+ private int startPage = 1;
+ private int endPage = Integer.MAX_VALUE;
+ private PDOutlineItem startBookmark = null;
+ private int startBookmarkPageNumber = -1;
+ private PDOutlineItem endBookmark = null;
+ private int endBookmarkPageNumber = -1;
+ private boolean suppressDuplicateOverlappingText = true;
+ private boolean shouldSeparateByBeads = true;
+ private boolean sortByPosition = false;
+ private boolean addMoreFormatting = false;
+
+ private float indentThreshold = DEFAULT_INDENT_THRESHOLD;
+ private float dropThreshold = DEFAULT_DROP_THRESHOLD;
+
+ // We will need to estimate where to add spaces.
+ // These are used to help guess.
+ private float spacingTolerance = .5f;
+ private float averageCharTolerance = .3f;
+
+ private List<PDThreadBead> pageArticles = null;
+ /**
+ * The charactersByArticle is used to extract text by article divisions. For example
+ * a PDF that has two columns like a newspaper, we want to extract the first column and
+ * then the second column. In this example the PDF would have 2 beads(or articles), one for
+ * each column. The size of the charactersByArticle would be 5, because not all text on the
+ * screen will fall into one of the articles. The five divisions are shown below
+ *
+ * Text before first article
+ * first article text
+ * text between first article and second article
+ * second article text
+ * text after second article
+ *
+ * Most PDFs won't have any beads, so charactersByArticle will contain a single entry.
+ */
+ protected Vector<List<TextPosition>> charactersByArticle = new Vector<List<TextPosition>>();
+
+ private Map<String, List<TextPosition>> characterListMapping = new HashMap<String, List<TextPosition>>();
+
+ /**
+ * encoding that text will be written in (or null).
+ */
+ protected String outputEncoding;
+
+ /**
+ * The document to read.
+ */
+ protected PDDocument document;
+ /**
+ * The stream to write the output to.
+ */
+ protected Writer output;
+
+ /**
+ * The normalizer is used to remove text ligatures/presentation forms
+ * and to correct the direction of right to left text, such as Arabic and Hebrew.
+ */
+ private TextNormalize normalize = null;
+
+ /**
+ * Instantiate a new PDFTextStripper object. This object will load
+ * properties from PDFTextStripper.properties and will not do
+ * anything special to convert the text to a more encoding-specific
+ * output.
+ *
+ * @throws IOException If there is an error loading the properties.
+ */
+ public PDFTextStripper() throws IOException
+ {
+ super( ResourceLoader.loadProperties(
+ "org/apache/pdfbox/resources/PDFTextStripper.properties", true ) );
+ this.outputEncoding = null;
+ normalize = new TextNormalize(this.outputEncoding);
+ }
+
+
+ /**
+ * Instantiate a new PDFTextStripper object. Loading all of the operator mappings
+ * from the properties object that is passed in. Does not convert the text
+ * to more encoding-specific output.
+ *
+ * @param props The properties containing the mapping of operators to PDFOperator
+ * classes.
+ *
+ * @throws IOException If there is an error reading the properties.
+ */
+ public PDFTextStripper( Properties props ) throws IOException
+ {
+ super( props );
+ this.outputEncoding = null;
+ normalize = new TextNormalize(this.outputEncoding);
+ }
+ /**
+ * Instantiate a new PDFTextStripper object. This object will load
+ * properties from PDFTextStripper.properties and will apply
+ * encoding-specific conversions to the output text.
+ *
+ * @param encoding The encoding that the output will be written in.
+ * @throws IOException If there is an error reading the properties.
+ */
+ public PDFTextStripper( String encoding ) throws IOException
+ {
+ super( ResourceLoader.loadProperties(
+ "org/apache/pdfbox/resources/PDFTextStripper.properties", true ));
+ this.outputEncoding = encoding;
+ normalize = new TextNormalize(this.outputEncoding);
+ }
+
+ /**
+ * This will return the text of a document. See writeText. <br />
+ * NOTE: The document must not be encrypted when coming into this method.
+ *
+ * @param doc The document to get the text from.
+ *
+ * @return The text of the PDF document.
+ *
+ * @throws IOException if the doc state is invalid or it is encrypted.
+ */
+ public String getText( PDDocument doc ) throws IOException
+ {
+ StringWriter outputStream = new StringWriter();
+ writeText( doc, outputStream );
+ return outputStream.toString();
+ }
+
+ /**
+ * @deprecated
+ * @see PDFTextStripper#getText( PDDocument )
+ * @param doc The document to extract the text from.
+ * @return The document text.
+ * @throws IOException If there is an error extracting the text.
+ */
+ public String getText( COSDocument doc ) throws IOException
+ {
+ return getText( new PDDocument( doc ) );
+ }
+
+ /**
+ * @deprecated
+ * @see PDFTextStripper#writeText( PDDocument, Writer )
+ * @param doc The document to extract the text.
+ * @param outputStream The stream to write the text to.
+ * @throws IOException If there is an error extracting the text.
+ */
+ public void writeText( COSDocument doc, Writer outputStream ) throws IOException
+ {
+ writeText( new PDDocument( doc ), outputStream );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void resetEngine()
+ {
+ super.resetEngine();
+ currentPageNo = 0;
+ }
+
+ /**
+ * This will take a PDDocument and write the text of that document to the print writer.
+ *
+ * @param doc The document to get the data from.
+ * @param outputStream The location to put the text.
+ *
+ * @throws IOException If the doc is in an invalid state.
+ */
+ public void writeText( PDDocument doc, Writer outputStream ) throws IOException
+ {
+ resetEngine();
+ document = doc;
+ output = outputStream;
+ if (getAddMoreFormatting()) {
+ paragraphEnd = lineSeparator;
+ pageStart = lineSeparator;
+ articleStart = lineSeparator;
+ articleEnd = lineSeparator;
+ }
+ startDocument(document);
+
+ if( document.isEncrypted() )
+ {
+ // We are expecting non-encrypted documents here, but it is common
+ // for users to pass in a document that is encrypted with an empty
+ // password (such a document appears to not be encrypted by
+ // someone viewing the document, thus the confusion). We will
+ // attempt to decrypt with the empty password to handle this case.
+ //
+ try
+ {
+ document.decrypt("");
+ }
+ catch (CryptographyException e)
+ {
+ throw new WrappedIOException("Error decrypting document, details: ", e);
+ }
+ catch (InvalidPasswordException e)
+ {
+ throw new WrappedIOException("Error: document is encrypted", e);
+ }
+ }
+
+ processPages( document.getDocumentCatalog().getAllPages() );
+ endDocument(document);
+ }
+
+ /**
+ * This will process all of the pages and the text that is in them.
+ *
+ * @param pages The pages object in the document.
+ *
+ * @throws IOException If there is an error parsing the text.
+ */
+ protected void processPages( List<COSObjectable> pages ) throws IOException
+ {
+ if( startBookmark != null )
+ {
+ startBookmarkPageNumber = getPageNumber( startBookmark, pages );
+ }
+
+ if( endBookmark != null )
+ {
+ endBookmarkPageNumber = getPageNumber( endBookmark, pages );
+ }
+
+ if( startBookmarkPageNumber == -1 && startBookmark != null &&
+ endBookmarkPageNumber == -1 && endBookmark != null &&
+ startBookmark.getCOSObject() == endBookmark.getCOSObject() )
+ {
+ //this is a special case where both the start and end bookmark
+ //are the same but point to nothing. In this case
+ //we will not extract any text.
+ startBookmarkPageNumber = 0;
+ endBookmarkPageNumber = 0;
+ }
+
+
+ Iterator<COSObjectable> pageIter = pages.iterator();
+ while( pageIter.hasNext() )
+ {
+ PDPage nextPage = (PDPage)pageIter.next();
+ PDStream contentStream = nextPage.getContents();
+ currentPageNo++;
+ if( contentStream != null )
+ {
+ COSStream contents = contentStream.getStream();
+ processPage( nextPage, contents );
+ }
+ }
+ }
+
+ private int getPageNumber( PDOutlineItem bookmark, List<COSObjectable> allPages ) throws IOException
+ {
+ int pageNumber = -1;
+ PDPage page = bookmark.findDestinationPage( document );
+ if( page != null )
+ {
+ pageNumber = allPages.indexOf( page )+1;//use one based indexing
+ }
+ return pageNumber;
+ }
+
+ /**
+ * This method is available for subclasses of this class. It will be called before processing
+ * of the document start.
+ *
+ * @param pdf The PDF document that is being processed.
+ * @throws IOException If an IO error occurs.
+ */
+ protected void startDocument(PDDocument pdf) throws IOException
+ {
+ // no default implementation, but available for subclasses
+ }
+
+ /**
+ * This method is available for subclasses of this class. It will be called after processing
+ * of the document finishes.
+ *
+ * @param pdf The PDF document that is being processed.
+ * @throws IOException If an IO error occurs.
+ */
+ protected void endDocument(PDDocument pdf ) throws IOException
+ {
+ // no default implementation, but available for subclasses
+ }
+
+ /**
+ * This will process the contents of a page.
+ *
+ * @param page The page to process.
+ * @param content The contents of the page.
+ *
+ * @throws IOException If there is an error processing the page.
+ */
+ protected void processPage( PDPage page, COSStream content ) throws IOException
+ {
+ if( currentPageNo >= startPage && currentPageNo <= endPage &&
+ (startBookmarkPageNumber == -1 || currentPageNo >= startBookmarkPageNumber ) &&
+ (endBookmarkPageNumber == -1 || currentPageNo <= endBookmarkPageNumber ))
+ {
+ startPage( page );
+ pageArticles = page.getThreadBeads();
+ int numberOfArticleSections = 1 + pageArticles.size() * 2;
+ if( !shouldSeparateByBeads )
+ {
+ numberOfArticleSections = 1;
+ }
+ int originalSize = charactersByArticle.size();
+ charactersByArticle.setSize( numberOfArticleSections );
+ for( int i=0; i<numberOfArticleSections; i++ )
+ {
+ if( numberOfArticleSections < originalSize )
+ {
+ ((List<TextPosition>)charactersByArticle.get( i )).clear();
+ }
+ else
+ {
+ charactersByArticle.set( i, new ArrayList<TextPosition>() );
+ }
+ }
+
+ characterListMapping.clear();
+ processStream( page, page.findResources(), content );
+ writePage();
+ endPage( page );
+ }
+
+ }
+
+ /**
+ * Start a new article, which is typically defined as a column
+ * on a single page (also referred to as a bead). This assumes
+ * that the primary direction of text is left to right.
+ * Default implementation is to do nothing. Subclasses
+ * may provide additional information.
+ *
+ * @throws IOException If there is any error writing to the stream.
+ */
+ protected void startArticle() throws IOException
+ {
+ startArticle(true);
+ }
+
+ /**
+ * Start a new article, which is typically defined as a column
+ * on a single page (also referred to as a bead).
+ * Default implementation is to do nothing. Subclasses
+ * may provide additional information.
+ *
+ * @param isltr true if primary direction of text is left to right.
+ * @throws IOException If there is any error writing to the stream.
+ */
+ protected void startArticle(boolean isltr) throws IOException
+ {
+ output.write(getArticleStart());
+ }
+
+ /**
+ * End an article. Default implementation is to do nothing. Subclasses
+ * may provide additional information.
+ *
+ * @throws IOException If there is any error writing to the stream.
+ */
+ protected void endArticle() throws IOException
+ {
+ output.write(getArticleEnd());
+ }
+
+ /**
+ * Start a new page. Default implementation is to do nothing. Subclasses
+ * may provide additional information.
+ *
+ * @param page The page we are about to process.
+ *
+ * @throws IOException If there is any error writing to the stream.
+ */
+ protected void startPage( PDPage page ) throws IOException
+ {
+ //default is to do nothing.
+ }
+
+ /**
+ * End a page. Default implementation is to do nothing. Subclasses
+ * may provide additional information.
+ *
+ * @param page The page we are about to process.
+ *
+ * @throws IOException If there is any error writing to the stream.
+ */
+ protected void endPage( PDPage page ) throws IOException
+ {
+ //default is to do nothing
+ }
+
+ private static final float ENDOFLASTTEXTX_RESET_VALUE = -1;
+ private static final float MAXYFORLINE_RESET_VALUE = -Float.MAX_VALUE;
+ private static final float EXPECTEDSTARTOFNEXTWORDX_RESET_VALUE = -Float.MAX_VALUE;
+ private static final float MAXHEIGHTFORLINE_RESET_VALUE = -1;
+ private static final float MINYTOPFORLINE_RESET_VALUE = Float.MAX_VALUE;
+ private static final float LASTWORDSPACING_RESET_VALUE = -1;
+
+ /**
+ * This will print the text of the processed page to "output".
+ * It will estimate, based on the coordinates of the text, where
+ * newlines and word spacings should be placed. The text will be
+ * sorted only if that feature was enabled.
+ *
+ * @throws IOException If there is an error writing the text.
+ */
+ protected void writePage() throws IOException
+ {
+ float maxYForLine = MAXYFORLINE_RESET_VALUE;
+ float minYTopForLine = MINYTOPFORLINE_RESET_VALUE;
+ float endOfLastTextX = ENDOFLASTTEXTX_RESET_VALUE;
+ float lastWordSpacing = LASTWORDSPACING_RESET_VALUE;
+ float maxHeightForLine = MAXHEIGHTFORLINE_RESET_VALUE;
+ PositionWrapper lastPosition = null;
+ PositionWrapper lastLineStartPosition = null;
+
+ boolean startOfPage = true;//flag to indicate start of page
+ boolean startOfArticle = true;
+ if(charactersByArticle.size() > 0) {
+ writePageStart();
+ }
+
+ for( int i = 0; i < charactersByArticle.size(); i++)
+ {
+ List<TextPosition> textList = charactersByArticle.get( i );
+ if( getSortByPosition() )
+ {
+ TextPositionComparator comparator = new TextPositionComparator();
+ Collections.sort( textList, comparator );
+ }
+
+ Iterator<TextPosition> textIter = textList.iterator();
+
+ /* Before we can display the text, we need to do some normalizing.
+ * Arabic and Hebrew text is right to left and is typically stored
+ * in its logical format, which means that the rightmost character is
+ * stored first, followed by the second character from the right etc.
+ * However, PDF stores the text in presentation form, which is left to
+ * right. We need to do some normalization to convert the PDF data to
+ * the proper logical output format.
+ *
+ * Note that if we did not sort the text, then the output of reversing the
+ * text is undefined and can sometimes produce worse output then not trying
+ * to reverse the order. Sorting should be done for these languages.
+ * */
+
+ /* First step is to determine if we have any right to left text, and
+ * if so, is it dominant. */
+ int ltrCnt = 0;
+ int rtlCnt = 0;
+
+ while( textIter.hasNext() )
+ {
+ TextPosition position = (TextPosition)textIter.next();
+ String stringValue = position.getCharacter();
+ for (int a = 0; a < stringValue.length(); a++)
+ {
+ byte dir = Character.getDirectionality(stringValue.charAt(a));
+ if ((dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT ) ||
+ (dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING) ||
+ (dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE ))
+ {
+ ltrCnt++;
+ }
+ else if ((dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT ) ||
+ (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) ||
+ (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING) ||
+ (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE ))
+ {
+ rtlCnt++;
+ }
+ }
+ }
+
+ // choose the dominant direction
+ boolean isRtlDominant = rtlCnt > ltrCnt;
+
+ startArticle(!isRtlDominant);
+ startOfArticle = true;
+ // we will later use this to skip reordering
+ boolean hasRtl = rtlCnt > 0;
+
+ /* Now cycle through to print the text.
+ * We queue up a line at a time before we print so that we can convert
+ * the line from presentation form to logical form (if needed).
+ */
+ List<TextPosition> line = new ArrayList<TextPosition>();
+
+ textIter = textList.iterator(); // start from the beginning again
+ /* PDF files don't always store spaces. We will need to guess where we should add
+ * spaces based on the distances between TextPositions. Historically, this was done
+ * based on the size of the space character provided by the font. In general, this worked
+ * but there were cases where it did not work. Calculating the average character width
+ * and using that as a metric works better in some cases but fails in some cases where the
+ * spacing worked. So we use both. NOTE: Adobe reader also fails on some of these examples.
+ */
+ //Keeps track of the previous average character width
+ float previousAveCharWidth = -1;
+ while( textIter.hasNext() )
+ {
+ TextPosition position = (TextPosition)textIter.next();
+ PositionWrapper current = new PositionWrapper(position);
+ String characterValue = position.getCharacter();
+
+ //Resets the average character width when we see a change in font
+ // or a change in the font size
+ if(lastPosition != null && ((position.getFont() != lastPosition.getTextPosition().getFont())
+ || (position.getFontSize() != lastPosition.getTextPosition().getFontSize())))
+ {
+ previousAveCharWidth = -1;
+ }
+
+ float positionX;
+ float positionY;
+ float positionWidth;
+ float positionHeight;
+
+ /* If we are sorting, then we need to use the text direction
+ * adjusted coordinates, because they were used in the sorting. */
+ if (getSortByPosition())
+ {
+ positionX = position.getXDirAdj();
+ positionY = position.getYDirAdj();
+ positionWidth = position.getWidthDirAdj();
+ positionHeight = position.getHeightDir();
+ }
+ else
+ {
+ positionX = position.getX();
+ positionY = position.getY();
+ positionWidth = position.getWidth();
+ positionHeight = position.getHeight();
+ }
+
+ //The current amount of characters in a word
+ int wordCharCount = position.getIndividualWidths().length;
+
+ /* Estimate the expected width of the space based on the
+ * space character with some margin. */
+ float wordSpacing = position.getWidthOfSpace();
+ float deltaSpace = 0;
+ if ((wordSpacing == 0) || (wordSpacing == Float.NaN))
+ {
+ deltaSpace = Float.MAX_VALUE;
+ }
+ else
+ {
+ if( lastWordSpacing < 0 )
+ {
+ deltaSpace = (wordSpacing * getSpacingTolerance());
+ }
+ else
+ {
+ deltaSpace = (((wordSpacing+lastWordSpacing)/2f)* getSpacingTolerance());
+ }
+ }
+
+ /* Estimate the expected width of the space based on the
+ * average character width with some margin. This calculation does not
+ * make a true average (average of averages) but we found that it gave the
+ * best results after numerous experiments. Based on experiments we also found that
+ * .3 worked well. */
+ float averageCharWidth = -1;
+ if(previousAveCharWidth < 0)
+ {
+ averageCharWidth = (positionWidth/wordCharCount);
+ }
+ else
+ {
+ averageCharWidth = (previousAveCharWidth + (positionWidth/wordCharCount))/2f;
+ }
+ float deltaCharWidth = (averageCharWidth * getAverageCharTolerance());
+
+ //Compares the values obtained by the average method and the wordSpacing method and picks
+ //the smaller number.
+ float expectedStartOfNextWordX = EXPECTEDSTARTOFNEXTWORDX_RESET_VALUE;
+ if(endOfLastTextX != ENDOFLASTTEXTX_RESET_VALUE)
+ {
+ if(deltaCharWidth > deltaSpace)
+ {
+ expectedStartOfNextWordX = endOfLastTextX + deltaSpace;
+ }
+ else
+ {
+ expectedStartOfNextWordX = endOfLastTextX + deltaCharWidth;
+ }
+ }
+
+ if( lastPosition != null )
+ {
+ if(startOfArticle){
+ lastPosition.setArticleStart();
+ startOfArticle = false;
+ }
+ // RDD - Here we determine whether this text object is on the current
+ // line. We use the lastBaselineFontSize to handle the superscript
+ // case, and the size of the current font to handle the subscript case.
+ // Text must overlap with the last rendered baseline text by at least
+ // a small amount in order to be considered as being on the same line.
+
+ /* XXX BC: In theory, this check should really check if the next char is in full range
+ * seen in this line. This is what I tried to do with minYTopForLine, but this caused a lot
+ * of regression test failures. So, I'm leaving it be for now. */
+ if(!overlap(positionY, positionHeight, maxYForLine, maxHeightForLine))
+ {
+ writeLine(normalize(line,isRtlDominant,hasRtl),isRtlDominant);
+ line.clear();
+
+ lastLineStartPosition = handleLineSeparation(current, lastPosition, lastLineStartPosition, maxHeightForLine);
+
+ endOfLastTextX = ENDOFLASTTEXTX_RESET_VALUE;
+ expectedStartOfNextWordX = EXPECTEDSTARTOFNEXTWORDX_RESET_VALUE;
+ maxYForLine = MAXYFORLINE_RESET_VALUE;
+ maxHeightForLine = MAXHEIGHTFORLINE_RESET_VALUE;
+ minYTopForLine = MINYTOPFORLINE_RESET_VALUE;
+ }
+
+ //Test if our TextPosition starts after a new word would be expected to start.
+ if (expectedStartOfNextWordX != EXPECTEDSTARTOFNEXTWORDX_RESET_VALUE && expectedStartOfNextWordX < positionX &&
+ //only bother adding a space if the last character was not a space
+ lastPosition.getTextPosition().getCharacter() != null &&
+ !lastPosition.getTextPosition().getCharacter().endsWith( " " ) )
+ {
+ line.add(WordSeparator.getSeparator());
+ }
+ }
+
+ if (positionY >= maxYForLine)
+ {
+ maxYForLine = positionY;
+ }
+
+ // RDD - endX is what PDF considers to be the x coordinate of the
+ // end position of the text. We use it in computing our metrics below.
+ endOfLastTextX = positionX + positionWidth;
+
+ // add it to the list
+ if (characterValue != null)
+ {
+ if(startOfPage && lastPosition==null){
+ writeParagraphStart();//not sure this is correct for RTL?
+ }
+ line.add(position);
+ }
+ maxHeightForLine = Math.max( maxHeightForLine, positionHeight );
+ minYTopForLine = Math.min(minYTopForLine,positionY - positionHeight);
+ lastPosition = current;
+ if(startOfPage){
+ lastPosition.setParagraphStart();
+ lastPosition.setLineStart();
+ lastLineStartPosition = lastPosition;
+ startOfPage=false;
+ }
+ lastWordSpacing = wordSpacing;
+ previousAveCharWidth = averageCharWidth;
+ }
+
+ // print the final line
+ if (line.size() > 0)
+ {
+ writeLine(normalize(line,isRtlDominant,hasRtl),isRtlDominant);
+ writeParagraphEnd();
+ }
+
+ endArticle();
+ }
+ writePageEnd();
+ }
+
+ private boolean overlap( float y1, float height1, float y2, float height2 )
+ {
+ return within( y1, y2, .1f) || (y2 <= y1 && y2 >= y1-height1) ||
+ (y1 <= y2 && y1 >= y2-height2);
+ }
+
+ /**
+ * Write the page separator value to the output stream.
+ * @throws IOException
+ * If there is a problem writing out the pageseparator to the document.
+ */
+ protected void writePageSeperator() throws IOException
+ {
+ // RDD - newline at end of flush - required for end of page (so that the top
+ // of the next page starts on its own line.
+ //
+ output.write(getPageSeparator());
+ output.flush();
+ }
+
+ /**
+ * Write the line separator value to the output stream.
+ * @throws IOException
+ * If there is a problem writing out the lineseparator to the document.
+ */
+ protected void writeLineSeparator( ) throws IOException
+ {
+ output.write(getLineSeparator());
+ }
+
+
+ /**
+ * Write the word separator value to the output stream.
+ * @throws IOException
+ * If there is a problem writing out the wordseparator to the document.
+ */
+ protected void writeWordSeparator() throws IOException
+ {
+ output.write(getWordSeparator());
+ }
+
+ /**
+ * Write the string in TextPosition to the output stream.
+ *
+ * @param text The text to write to the stream.
+ * @throws IOException If there is an error when writing the text.
+ */
+ protected void writeCharacters( TextPosition text ) throws IOException
+ {
+ output.write( text.getCharacter() );
+ }
+
+ /**
+ * Write a Java string to the output stream.
+ *
+ * @param text The text to write to the stream.
+ * @throws IOException If there is an error when writing the text.
+ */
+ protected void writeString( String text ) throws IOException
+ {
+ output.write( text );
+ }
+
+ /**
+ * This will determine of two floating point numbers are within a specified variance.
+ *
+ * @param first The first number to compare to.
+ * @param second The second number to compare to.
+ * @param variance The allowed variance.
+ */
+ private boolean within( float first, float second, float variance )
+ {
+ return second < first + variance && second > first - variance;
+ }
+
+ /**
+ * This will process a TextPosition object and add the
+ * text to the list of characters on a page. It takes care of
+ * overlapping text.
+ *
+ * @param text The text to process.
+ */
+ protected void processTextPosition( TextPosition text )
+ {
+ boolean showCharacter = true;
+ if( suppressDuplicateOverlappingText )
+ {
+ showCharacter = false;
+ String textCharacter = text.getCharacter();
+ float textX = text.getX();
+ float textY = text.getY();
+ List<TextPosition> sameTextCharacters = (List<TextPosition>)characterListMapping.get( textCharacter );
+ if( sameTextCharacters == null )
+ {
+ sameTextCharacters = new ArrayList<TextPosition>();
+ characterListMapping.put( textCharacter, sameTextCharacters );
+ }
+
+ // RDD - Here we compute the value that represents the end of the rendered
+ // text. This value is used to determine whether subsequent text rendered
+ // on the same line overwrites the current text.
+ //
+ // We subtract any positive padding to handle cases where extreme amounts
+ // of padding are applied, then backed off (not sure why this is done, but there
+ // are cases where the padding is on the order of 10x the character width, and
+ // the TJ just backs up to compensate after each character). Also, we subtract
+ // an amount to allow for kerning (a percentage of the width of the last
+ // character).
+ //
+ boolean suppressCharacter = false;
+ float tolerance = (text.getWidth()/textCharacter.length())/3.0f;
+ for( int i=0; i<sameTextCharacters.size() && textCharacter != null; i++ )
+ {
+ TextPosition character = sameTextCharacters.get( i );
+ String charCharacter = character.getCharacter();
+ float charX = character.getX();
+ float charY = character.getY();
+ //only want to suppress
+
+ if( charCharacter != null &&
+ //charCharacter.equals( textCharacter ) &&
+ within( charX, textX, tolerance ) &&
+ within( charY,
+ textY,
+ tolerance ) )
+ {
+ suppressCharacter = true;
+ }
+ }
+ if( !suppressCharacter )
+ {
+ sameTextCharacters.add( text );
+ showCharacter = true;
+ }
+ }
+
+ if( showCharacter )
+ {
+ //if we are showing the character then we need to determine which
+ //article it belongs to.
+ int foundArticleDivisionIndex = -1;
+ int notFoundButFirstLeftAndAboveArticleDivisionIndex = -1;
+ int notFoundButFirstLeftArticleDivisionIndex = -1;
+ int notFoundButFirstAboveArticleDivisionIndex = -1;
+ float x = text.getX();
+ float y = text.getY();
+ if( shouldSeparateByBeads )
+ {
+ for( int i=0; i<pageArticles.size() && foundArticleDivisionIndex == -1; i++ )
+ {
+ PDThreadBead bead = (PDThreadBead)pageArticles.get( i );
+ if( bead != null )
+ {
+ PDRectangle rect = bead.getRectangle();
+ if( rect.contains( x, y ) )
+ {
+ foundArticleDivisionIndex = i*2+1;
+ }
+ else if( (x < rect.getLowerLeftX() ||
+ y < rect.getUpperRightY()) &&
+ notFoundButFirstLeftAndAboveArticleDivisionIndex == -1)
+ {
+ notFoundButFirstLeftAndAboveArticleDivisionIndex = i*2;
+ }
+ else if( x < rect.getLowerLeftX() &&
+ notFoundButFirstLeftArticleDivisionIndex == -1)
+ {
+ notFoundButFirstLeftArticleDivisionIndex = i*2;
+ }
+ else if( y < rect.getUpperRightY() &&
+ notFoundButFirstAboveArticleDivisionIndex == -1)
+ {
+ notFoundButFirstAboveArticleDivisionIndex = i*2;
+ }
+ }
+ else
+ {
+ foundArticleDivisionIndex = 0;
+ }
+ }
+ }
+ else
+ {
+ foundArticleDivisionIndex = 0;
+ }
+ int articleDivisionIndex = -1;
+ if( foundArticleDivisionIndex != -1 )
+ {
+ articleDivisionIndex = foundArticleDivisionIndex;
+ }
+ else if( notFoundButFirstLeftAndAboveArticleDivisionIndex != -1 )
+ {
+ articleDivisionIndex = notFoundButFirstLeftAndAboveArticleDivisionIndex;
+ }
+ else if( notFoundButFirstLeftArticleDivisionIndex != -1 )
+ {
+ articleDivisionIndex = notFoundButFirstLeftArticleDivisionIndex;
+ }
+ else if( notFoundButFirstAboveArticleDivisionIndex != -1 )
+ {
+ articleDivisionIndex = notFoundButFirstAboveArticleDivisionIndex;
+ }
+ else
+ {
+ articleDivisionIndex = charactersByArticle.size()-1;
+ }
+
+ List<TextPosition> textList = (List<TextPosition>) charactersByArticle.get( articleDivisionIndex );
+
+ /* In the wild, some PDF encoded documents put diacritics (accents on
+ * top of characters) into a separate Tj element. When displaying them
+ * graphically, the two chunks get overlayed. With text output though,
+ * we need to do the overlay. This code recombines the diacritic with
+ * its associated character if the two are consecutive.
+ */
+ if(textList.isEmpty())
+ {
+ textList.add(text);
+ }
+ else
+ {
+ /* test if we overlap the previous entry.
+ * Note that we are making an assumption that we need to only look back
+ * one TextPosition to find what we are overlapping.
+ * This may not always be true. */
+ TextPosition previousTextPosition = (TextPosition)textList.get(textList.size()-1);
+ if(text.isDiacritic() && previousTextPosition.contains(text))
+ {
+ previousTextPosition.mergeDiacritic(text, normalize);
+ }
+ /* If the previous TextPosition was the diacritic, merge it into this
+ * one and remove it from the list. */
+ else if(previousTextPosition.isDiacritic() && text.contains(previousTextPosition))
+ {
+ text.mergeDiacritic(previousTextPosition, normalize);
+ textList.remove(textList.size()-1);
+ textList.add(text);
+ }
+ else
+ {
+ textList.add(text);
+ }
+ }
+ }
+ }
+
+ /**
+ * This is the page that the text extraction will start on. The pages start
+ * at page 1. For example in a 5 page PDF document, if the start page is 1
+ * then all pages will be extracted. If the start page is 4 then pages 4 and 5
+ * will be extracted. The default value is 1.
+ *
+ * @return Value of property startPage.
+ */
+ public int getStartPage()
+ {
+ return startPage;
+ }
+
+ /**
+ * This will set the first page to be extracted by this class.
+ *
+ * @param startPageValue New value of property startPage.
+ */
+ public void setStartPage(int startPageValue)
+ {
+ startPage = startPageValue;
+ }
+
+ /**
+ * This will get the last page that will be extracted. This is inclusive,
+ * for example if a 5 page PDF an endPage value of 5 would extract the
+ * entire document, an end page of 2 would extract pages 1 and 2. This defaults
+ * to Integer.MAX_VALUE such that all pages of the pdf will be extracted.
+ *
+ * @return Value of property endPage.
+ */
+ public int getEndPage()
+ {
+ return endPage;
+ }
+
+ /**
+ * This will set the last page to be extracted by this class.
+ *
+ * @param endPageValue New value of property endPage.
+ */
+ public void setEndPage(int endPageValue)
+ {
+ endPage = endPageValue;
+ }
+
+ /**
+ * Set the desired line separator for output text. The line.separator
+ * system property is used if the line separator preference is not set
+ * explicitly using this method.
+ *
+ * @param separator The desired line separator string.
+ */
+ public void setLineSeparator(String separator)
+ {
+ lineSeparator = separator;
+ }
+
+ /**
+ * This will get the line separator.
+ *
+ * @return The desired line separator string.
+ */
+ public String getLineSeparator()
+ {
+ return lineSeparator;
+ }
+
+ /**
+ * Set the desired page separator for output text. The line.separator
+ * system property is used if the page separator preference is not set
+ * explicitly using this method.
+ *
+ * @param separator The desired page separator string.
+ */
+ public void setPageSeparator(String separator)
+ {
+ pageSeparator = separator;
+ }
+
+ /**
+ * This will get the word separator.
+ *
+ * @return The desired word separator string.
+ */
+ public String getWordSeparator()
+ {
+ return wordSeparator;
+ }
+
+ /**
+ * Set the desired word separator for output text. The PDFBox text extraction
+ * algorithm will output a space character if there is enough space between
+ * two words. By default a space character is used. If you need and accurate
+ * count of characters that are found in a PDF document then you might want to
+ * set the word separator to the empty string.
+ *
+ * @param separator The desired page separator string.
+ */
+ public void setWordSeparator(String separator)
+ {
+ wordSeparator = separator;
+ }
+
+ /**
+ * This will get the page separator.
+ *
+ * @return The page separator string.
+ */
+ public String getPageSeparator()
+ {
+ return pageSeparator;
+ }
+ /**
+ * @return Returns the suppressDuplicateOverlappingText.
+ */
+ public boolean getSuppressDuplicateOverlappingText()
+ {
+ return suppressDuplicateOverlappingText;
+ }
+
+ /**
+ * Get the current page number that is being processed.
+ *
+ * @return A 1 based number representing the current page.
+ */
+ protected int getCurrentPageNo()
+ {
+ return currentPageNo;
+ }
+
+ /**
+ * The output stream that is being written to.
+ *
+ * @return The stream that output is being written to.
+ */
+ protected Writer getOutput()
+ {
+ return output;
+ }
+
+ /**
+ * Character strings are grouped by articles. It is quite common that there
+ * will only be a single article. This returns a List that contains List objects,
+ * the inner lists will contain TextPosition objects.
+ *
+ * @return A double List of TextPositions for all text strings on the page.
+ */
+ protected Vector<List<TextPosition>> getCharactersByArticle()
+ {
+ return charactersByArticle;
+ }
+
+ /**
+ * By default the text stripper will attempt to remove text that overlapps each other.
+ * Word paints the same character several times in order to make it look bold. By setting
+ * this to false all text will be extracted, which means that certain sections will be
+ * duplicated, but better performance will be noticed.
+ *
+ * @param suppressDuplicateOverlappingTextValue The suppressDuplicateOverlappingText to set.
+ */
+ public void setSuppressDuplicateOverlappingText(
+ boolean suppressDuplicateOverlappingTextValue)
+ {
+ this.suppressDuplicateOverlappingText = suppressDuplicateOverlappingTextValue;
+ }
+
+ /**
+ * This will tell if the text stripper should separate by beads.
+ *
+ * @return If the text will be grouped by beads.
+ */
+ public boolean getSeparateByBeads()
+ {
+ return shouldSeparateByBeads;
+ }
+
+ /**
+ * Set if the text stripper should group the text output by a list of beads. The default value is true!
+ *
+ * @param aShouldSeparateByBeads The new grouping of beads.
+ */
+ public void setShouldSeparateByBeads(boolean aShouldSeparateByBeads)
+ {
+ this.shouldSeparateByBeads = aShouldSeparateByBeads;
+ }
+
+ /**
+ * Get the bookmark where text extraction should end, inclusive. Default is null.
+ *
+ * @return The ending bookmark.
+ */
+ public PDOutlineItem getEndBookmark()
+ {
+ return endBookmark;
+ }
+
+ /**
+ * Set the bookmark where the text extraction should stop.
+ *
+ * @param aEndBookmark The ending bookmark.
+ */
+ public void setEndBookmark(PDOutlineItem aEndBookmark)
+ {
+ endBookmark = aEndBookmark;
+ }
+
+ /**
+ * Get the bookmark where text extraction should start, inclusive. Default is null.
+ *
+ * @return The starting bookmark.
+ */
+ public PDOutlineItem getStartBookmark()
+ {
+ return startBookmark;
+ }
+
+ /**
+ * Set the bookmark where text extraction should start, inclusive.
+ *
+ * @param aStartBookmark The starting bookmark.
+ */
+ public void setStartBookmark(PDOutlineItem aStartBookmark)
+ {
+ startBookmark = aStartBookmark;
+ }
+
+ /**
+ * This will tell if the text stripper should add some more text formatting.
+ * @return true if some more text formatting will be added
+ */
+ public boolean getAddMoreFormatting()
+ {
+ return addMoreFormatting;
+ }
+
+ /**
+ * There will some additional text formatting be added if addMoreFormatting
+ * is set to true. Default is false.
+ * @param newAddMoreFormatting Tell PDFBox to add some more text formatting
+ */
+ public void setAddMoreFormatting(boolean newAddMoreFormatting)
+ {
+ addMoreFormatting = newAddMoreFormatting;
+ }
+
+ /**
+ * This will tell if the text stripper should sort the text tokens
+ * before writing to the stream.
+ *
+ * @return true If the text tokens will be sorted before being written.
+ */
+ public boolean getSortByPosition()
+ {
+ return sortByPosition;
+ }
+
+ /**
+ * The order of the text tokens in a PDF file may not be in the same
+ * as they appear visually on the screen. For example, a PDF writer may
+ * write out all text by font, so all bold or larger text, then make a second
+ * pass and write out the normal text.<br/>
+ * The default is to <b>not</b> sort by position.<br/>
+ * <br/>
+ * A PDF writer could choose to write each character in a different order. By
+ * default PDFBox does <b>not</b> sort the text tokens before processing them due to
+ * performance reasons.
+ *
+ * @param newSortByPosition Tell PDFBox to sort the text positions.
+ */
+ public void setSortByPosition(boolean newSortByPosition)
+ {
+ sortByPosition = newSortByPosition;
+ }
+
+ /**
+ * Get the current space width-based tolerance value that is being used
+ * to estimate where spaces in text should be added. Note that the
+ * default value for this has been determined from trial and error.
+ *
+ * @return The current tolerance / scaling factor
+ */
+ public float getSpacingTolerance()
+ {
+ return spacingTolerance;
+ }
+
+ /**
+ * Set the space width-based tolerance value that is used
+ * to estimate where spaces in text should be added. Note that the
+ * default value for this has been determined from trial and error.
+ * Setting this value larger will reduce the number of spaces added.
+ *
+ * @param spacingToleranceValue tolerance / scaling factor to use
+ */
+ public void setSpacingTolerance(float spacingToleranceValue)
+ {
+ this.spacingTolerance = spacingToleranceValue;
+ }
+
+ /**
+ * Get the current character width-based tolerance value that is being used
+ * to estimate where spaces in text should be added. Note that the
+ * default value for this has been determined from trial and error.
+ *
+ * @return The current tolerance / scaling factor
+ */
+ public float getAverageCharTolerance()
+ {
+ return averageCharTolerance;
+ }
+
+ /**
+ * Set the character width-based tolerance value that is used
+ * to estimate where spaces in text should be added. Note that the
+ * default value for this has been determined from trial and error.
+ * Setting this value larger will reduce the number of spaces added.
+ *
+ * @param averageCharToleranceValue average tolerance / scaling factor to use
+ */
+ public void setAverageCharTolerance(float averageCharToleranceValue)
+ {
+ this.averageCharTolerance = averageCharToleranceValue;
+ }
+
+
+ /**
+ * returns the multiple of whitespace character widths
+ * for the current text which the current
+ * line start can be indented from the previous line start
+ * beyond which the current line start is considered
+ * to be a paragraph start.
+ * @return the number of whitespace character widths to use
+ * when detecting paragraph indents.
+ */
+ public float getIndentThreshold()
+ {
+ return indentThreshold;
+ }
+
+ /**
+ * sets the multiple of whitespace character widths
+ * for the current text which the current
+ * line start can be indented from the previous line start
+ * beyond which the current line start is considered
+ * to be a paragraph start. The default value is 2.0.
+ *
+ * @param indentThreshold the number of whitespace character widths to use
+ * when detecting paragraph indents.
+ */
+ public void setIndentThreshold(float indentThreshold)
+ {
+ this.indentThreshold = indentThreshold;
+ }
+
+ /**
+ * the minimum whitespace, as a multiple
+ * of the max height of the current characters
+ * beyond which the current line start is considered
+ * to be a paragraph start.
+ * @return the character height multiple for
+ * max allowed whitespace between lines in
+ * the same paragraph.
+ */
+ public float getDropThreshold()
+ {
+ return dropThreshold;
+ }
+
+ /**
+ * sets the minimum whitespace, as a multiple
+ * of the max height of the current characters
+ * beyond which the current line start is considered
+ * to be a paragraph start. The default value is 2.5.
+ *
+ * @param dropThreshold the character height multiple for
+ * max allowed whitespace between lines in
+ * the same paragraph.
+ */
+ public void setDropThreshold(float dropThreshold)
+ {
+ this.dropThreshold = dropThreshold;
+ }
+
+ /**
+ * Returns the string which will be used at the beginning of a paragraph.
+ * @return the paragraph start string
+ */
+ public String getParagraphStart()
+ {
+ return paragraphStart;
+ }
+
+ /**
+ * Sets the string which will be used at the beginning of a paragraph.
+ * @param s the paragraph start string
+ */
+ public void setParagraphStart(String s)
+ {
+ this.paragraphStart = s;
+ }
+
+ /**
+ * Returns the string which will be used at the end of a paragraph.
+ * @return the paragraph end string
+ */
+ public String getParagraphEnd()
+ {
+ return paragraphEnd;
+ }
+
+ /**
+ * Sets the string which will be used at the end of a paragraph.
+ * @param s the paragraph end string
+ */
+ public void setParagraphEnd(String s)
+ {
+ this.paragraphEnd = s;
+ }
+
+
+ /**
+ * Returns the string which will be used at the beginning of a page.
+ * @return the page start string
+ */
+ public String getPageStart()
+ {
+ return pageStart;
+ }
+
+ /**
+ * Sets the string which will be used at the beginning of a page.
+ * @param s the page start string
+ */
+ public void setPageStart(String pageStart)
+ {
+ this.pageStart = pageStart;
+ }
+
+ /**
+ * Returns the string which will be used at the end of a page.
+ * @return the page end string
+ */
+ public String getPageEnd()
+ {
+ return pageEnd;
+ }
+
+ /**
+ * Sets the string which will be used at the end of a page.
+ * @param s the page end string
+ */
+ public void setPageEnd(String pageEnd)
+ {
+ this.pageEnd = pageEnd;
+ }
+
+ /**
+ * Returns the string which will be used at the beginning of an article.
+ * @return the article start string
+ */
+ public String getArticleStart() {
+ return articleStart;
+ }
+
+ /**
+ * Sets the string which will be used at the beginning of an article.
+ * @param s the article start string
+ */
+ public void setArticleStart(String articleStart) {
+ this.articleStart = articleStart;
+ }
+
+ /**
+ * Returns the string which will be used at the end of an article.
+ * @return the article end string
+ */
+ public String getArticleEnd(){
+ return articleEnd;
+ }
+
+ /**
+ * Sets the string which will be used at the end of an article.
+ * @param s the article end string
+ */
+ public void setArticleEnd(String articleEnd){
+ this.articleEnd = articleEnd;
+ }
+
+
+ /**
+ * Reverse characters of a compound Arabic glyph.
+ * When getSortByPosition() is true, inspect the sequence encoded
+ * by one glyph. If the glyph encodes two or more Arabic characters,
+ * reverse these characters from a logical order to a visual order.
+ * This ensures that the bidirectional algorithm that runs later will
+ * convert them back to a logical order.
+ *
+ * @param str a string obtained from font.encoding()
+ */
+ public String inspectFontEncoding(String str)
+ {
+ if (!sortByPosition || str == null || str.length() < 2)
+ return str;
+
+ for (int i = 0; i < str.length(); ++i)
+ {
+ if (Character.getDirectionality(str.charAt(i))
+ != Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+ return str;
+ }
+
+ StringBuilder reversed = new StringBuilder(str.length());
+ for (int i = str.length() - 1; i >= 0; --i)
+ {
+ reversed.append(str.charAt(i));
+ }
+ return reversed.toString();
+ }
+
+ /**
+ * handles the line separator for a new line given
+ * the specified current and previous TextPositions.
+ * @param position the current text position
+ * @param lastPosition the previous text position
+ * @param lastLineStartPosition the last text position that followed a line
+ * separator.
+ * @param maxHeightForLine max height for positions since lastLineStartPosition
+ * @throws IOException
+ */
+ protected PositionWrapper handleLineSeparation(PositionWrapper current,
+ PositionWrapper lastPosition, PositionWrapper lastLineStartPosition, float maxHeightForLine)
+ throws IOException {
+ current.setLineStart();
+ isParagraphSeparation(current, lastPosition, lastLineStartPosition, maxHeightForLine);
+ lastLineStartPosition = current;
+ if (current.isParagraphStart()) {
+ if(lastPosition.isArticleStart()) {
+ writeParagraphStart();
+ } else {
+ writeLineSeparator();
+ writeParagraphSeparator();
+ }
+ } else {
+ writeLineSeparator();
+ }
+ return lastLineStartPosition;
+ }
+
+ /**
+ * tests the relationship between the last text position, the current text
+ * position and the last text position that followed a line separator to
+ * decide if the gap represents a paragraph separation. This should
+ * <i>only</i> be called for consecutive text positions that first pass the
+ * line separation test.
+ * <p>
+ * This base implementation tests to see if the lastLineStartPosition is
+ * null OR if the current vertical position has dropped below the last text
+ * vertical position by at least 2.5 times the current text height OR if the
+ * current horizontal position is indented by at least 2 times the current
+ * width of a space character.</p>
+ * <p>
+ * This also attempts to identify text that is indented under a hanging indent.</p>
+ * <p>
+ * This method sets the isParagraphStart and isHangingIndent flags on the current
+ * position object.</p>
+ *
+ * @param position the current text position. This may have its isParagraphStart
+ * or isHangingIndent flags set upon return.
+ * @param lastPosition the previous text position (should not be null).
+ * @param lastLineStartPosition the last text position that followed a line
+ * separator. May be null.
+ * @param maxHeightForLine max height for text positions since lasLineStartPosition.
+ */
+ protected void isParagraphSeparation(PositionWrapper position,
+ PositionWrapper lastPosition, PositionWrapper lastLineStartPosition, float maxHeightForLine){
+ boolean result = false;
+ if(lastLineStartPosition == null) {
+ result = true;
+ }else{
+ float yGap = Math.abs(position.getTextPosition().getYDirAdj()-
+ lastPosition.getTextPosition().getYDirAdj());
+ float xGap = (position.getTextPosition().getXDirAdj()-
+ lastLineStartPosition.getTextPosition().getXDirAdj());//do we need to flip this for rtl?
+ if(yGap > (getDropThreshold()*maxHeightForLine)){
+ result = true;
+ }else if(xGap > (getIndentThreshold()*position.getTextPosition().getWidthOfSpace())){
+ //text is indented, but try to screen for hanging indent
+ if(!lastLineStartPosition.isParagraphStart()){
+ result = true;
+ }else{
+ position.setHangingIndent();
+ }
+ }else if(xGap < -position.getTextPosition().getWidthOfSpace()){
+ //text is left of previous line. Was it a hanging indent?
+ if(!lastLineStartPosition.isParagraphStart()){
+ result = true;
+ }
+ }else if(Math.abs(xGap) < (0.25 * position.getTextPosition().getWidth())){
+ //current horizontal position is within 1/4 a char of the last
+ //linestart. We'll treat them as lined up.
+ if(lastLineStartPosition.isHangingIndent()){
+ position.setHangingIndent();
+ }else if(lastLineStartPosition.isParagraphStart()){
+ //check to see if the previous line looks like
+ //any of a number of standard list item formats
+ Pattern liPattern = matchListItemPattern(lastLineStartPosition);
+ if(liPattern!=null){
+ Pattern currentPattern = matchListItemPattern(position);
+ if(liPattern == currentPattern){
+ result = true;
+ }
+ }
+ }
+ }
+ }
+ if(result){
+ position.setParagraphStart();
+ }
+ }
+
+ /**
+ * writes the paragraph separator string to the output.
+ * @throws IOException
+ */
+ protected void writeParagraphSeparator()throws IOException{
+ writeParagraphEnd();
+ writeParagraphStart();
+ }
+
+ /**
+ * Write something (if defined) at the start of a paragraph.
+ * @throws IOException if something went wrong
+ */
+ protected void writeParagraphStart() throws IOException{
+ output.write(getParagraphStart());
+ }
+
+ /**
+ * Write something (if defined) at the end of a paragraph.
+ * @throws IOException if something went wrong
+ */
+ protected void writeParagraphEnd() throws IOException{
+ output.write(getParagraphEnd());
+ }
+
+ /**
+ * Write something (if defined) at the start of a page.
+ * @throws IOException if something went wrong
+ */
+ protected void writePageStart()throws IOException{
+ output.write(getPageStart());
+ }
+
+ /**
+ * Write something (if defined) at the end of a page.
+ * @throws IOException if something went wrong
+ */
+ protected void writePageEnd()throws IOException{
+ output.write(getPageEnd());
+ }
+
+ /**
+ * returns the list item Pattern object that matches
+ * the text at the specified PositionWrapper or null
+ * if the text does not match such a pattern. The list
+ * of Patterns tested against is given by the
+ * {@link #getListItemPatterns()} method. To add to
+ * the list, simply override that method (if sub-classing)
+ * or explicitly supply your own list using
+ * {@link #setListItemPatterns(List)}.
+ * @param pw
+ * @return
+ */
+ protected Pattern matchListItemPattern(PositionWrapper pw) {
+ TextPosition tp = pw.getTextPosition();
+ String txt = tp.getCharacter();
+ Pattern p = matchPattern(txt,getListItemPatterns());
+ return p;
+ }
+
+ /**
+ * a list of regular expressions that match commonly used
+ * list item formats, i.e. bullets, numbers, letters,
+ * Roman numerals, etc. Not meant to be
+ * comprehensive.
+ */
+ private static final String[] LIST_ITEM_EXPRESSIONS = {
+ "\\.",
+ "\\d+\\.",
+ "\\[\\d+\\]",
+ "\\d+\\)",
+ "[A-Z]\\.",
+ "[a-z]\\.",
+ "[A-Z]\\)",
+ "[a-z]\\)",
+ "[IVXL]+\\.",
+ "[ivxl]+\\.",
+
+ };
+
+ private List<Pattern> liPatterns = null;
+ /**
+ * use to supply a different set of regular expression
+ * patterns for matching list item starts.
+ *
+ * @param patterns
+ */
+ protected void setListItemPatterns(List<Pattern> patterns){
+ liPatterns = patterns;
+ }
+
+
+ /**
+ * returns a list of regular expression Patterns representing
+ * different common list item formats. For example
+ * numbered items of form:
+ * <ol>
+ * <li>some text</li>
+ * <li>more text</li>
+ * </ol>
+ * or
+ * <ul>
+ * <li>some text</li>
+ * <li>more text</li>
+ * </ul>
+ * etc., all begin with some character pattern. The pattern "\\d+\." (matches "1.", "2.", ...)
+ * or "\[\\d+\]" (matches "[1]", "[2]", ...).
+ * <p>
+ * This method returns a list of such regular expression Patterns.
+ * @return a list of Pattern objects.
+ */
+ protected List<Pattern> getListItemPatterns(){
+ if(liPatterns == null){
+ liPatterns = new ArrayList<Pattern>();
+ for(String expression : LIST_ITEM_EXPRESSIONS){
+ Pattern p = Pattern.compile(expression);
+ liPatterns.add(p);
+ }
+ }
+ return liPatterns;
+ }
+
+ /**
+ * iterates over the specified list of Patterns until
+ * it finds one that matches the specified string. Then
+ * returns the Pattern.
+ * <p>
+ * Order of the supplied list of patterns is important as
+ * most common patterns should come first. Patterns
+ * should be strict in general, and all will be
+ * used with case sensitivity on.
+ * </p>
+ * @param s
+ * @param patterns
+ * @return
+ */
+ protected static final Pattern matchPattern(String s, List<Pattern> patterns){
+ Pattern matchedPattern = null;
+ for(Pattern p : patterns){
+ if(p.matcher(s).matches()){
+ return p;
+ }
+ }
+ return matchedPattern;
+ }
+
+ /**
+ * Write a list of string containing a whole line of a document.
+ * @param line a list with the words of the given line
+ * @param isRtlDominant determines if rtl or ltl is dominant
+ * @throws IOException if something went wrong
+ */
+ private void writeLine(List<String> line, boolean isRtlDominant)throws IOException{
+ int numberOfStrings = line.size();
+ if (isRtlDominant) {
+ for(int i=numberOfStrings-1; i>=0; i--){
+ if (i < numberOfStrings-1)
+ writeWordSeparator();
+ writeString(line.get(i));
+ }
+ }
+ else {
+ for(int i=0; i<numberOfStrings; i++){
+ writeString(line.get(i));
+ if (!isRtlDominant && i < numberOfStrings-1)
+ writeWordSeparator();
+ }
+ }
+ }
+
+ /**
+ * Normalize the given list of TextPositions.
+ * @param line list of TextPositions
+ * @param isRtlDominant determines if rtl or ltl is dominant
+ * @param hasRtl determines if lines contains rtl formatted text(parts)
+ * @return a list of strings, one string for every word
+ */
+ private List<String> normalize(List<TextPosition> line, boolean isRtlDominant, boolean hasRtl){
+ LinkedList<String> normalized = new LinkedList<String>();
+ StringBuilder lineBuilder = new StringBuilder();
+ for(TextPosition text : line){
+ if (text instanceof WordSeparator) {
+ String lineStr = lineBuilder.toString();
+ if (hasRtl) {
+ lineStr = normalize.makeLineLogicalOrder(lineStr,isRtlDominant);
+ }
+ lineStr = normalize.normalizePres(lineStr);
+ normalized.add(lineStr);
+ lineBuilder = new StringBuilder();
+ }
+ else {
+ lineBuilder.append(text.getCharacter());
+ }
+ }
+ if (lineBuilder.length() > 0) {
+ String lineStr = lineBuilder.toString();
+ if (hasRtl) {
+ lineStr = normalize.makeLineLogicalOrder(lineStr,isRtlDominant);
+ }
+ lineStr = normalize.normalizePres(lineStr);
+ normalized.add(lineStr);
+ }
+ return normalized;
+ }
+
+ /**
+ * internal marker class. Used as a place holder in
+ * a line of TextPositions.
+ * @author ME21969
+ *
+ */
+ private static final class WordSeparator extends TextPosition{
+ private static final WordSeparator separator = new WordSeparator();
+
+ private WordSeparator(){
+ }
+
+ public static final WordSeparator getSeparator(){
+ return separator;
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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.Rectangle2D;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.common.PDStream;
+
+/**
+ * This will extract text from a specified region in the PDF.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class PDFTextStripperByArea extends PDFTextStripper
+{
+ private List<String> regions = new ArrayList<String>();
+ private Map<String,Rectangle2D> regionArea = new HashMap<String,Rectangle2D>();
+ private Map<String,Vector<ArrayList<TextPosition>>> regionCharacterList =
+ new HashMap<String,Vector<ArrayList<TextPosition>>>();
+ private Map<String,StringWriter> regionText = new HashMap<String,StringWriter>();
+
+ /**
+ * Constructor.
+ * @throws IOException If there is an error loading properties.
+ */
+ public PDFTextStripperByArea() throws IOException
+ {
+ super();
+ setPageSeparator( "" );
+ }
+
+
+ /**
+ * Instantiate a new PDFTextStripperArea object. Loading all of the operator
+ * mappings from the properties object that is passed in. Does not convert
+ * the text to more encoding-specific output.
+ *
+ * @param props
+ * The properties containing the mapping of operators to
+ * PDFOperator classes.
+ *
+ * @throws IOException
+ * If there is an error reading the properties.
+ */
+ public PDFTextStripperByArea(Properties props) throws IOException
+ {
+ super(props);
+ setPageSeparator("");
+ }
+
+ /**
+ * Instantiate a new PDFTextStripperArea object. This object will load
+ * properties from PDFTextStripper.properties and will apply
+ * encoding-specific conversions to the output text.
+ *
+ * @param encoding
+ * The encoding that the output will be written in.
+ * @throws IOException
+ * If there is an error reading the properties.
+ */
+ public PDFTextStripperByArea(String encoding) throws IOException
+ {
+ super(encoding);
+ setPageSeparator("");
+ }
+
+ /**
+ * Add a new region to group text by.
+ *
+ * @param regionName The name of the region.
+ * @param rect The rectangle area to retrieve the text from.
+ */
+ public void addRegion( String regionName, Rectangle2D rect )
+ {
+ regions.add( regionName );
+ regionArea.put( regionName, rect );
+ }
+
+ /**
+ * Get the list of regions that have been setup.
+ *
+ * @return A list of java.lang.String objects to identify the region names.
+ */
+ public List<String> getRegions()
+ {
+ return regions;
+ }
+
+ /**
+ * Get the text for the region, this should be called after extractRegions().
+ *
+ * @param regionName The name of the region to get the text from.
+ * @return The text that was identified in that region.
+ */
+ public String getTextForRegion( String regionName )
+ {
+ StringWriter text = regionText.get( regionName );
+ return text.toString();
+ }
+
+ /**
+ * Process the page to extract the region text.
+ *
+ * @param page The page to extract the regions from.
+ * @throws IOException If there is an error while extracting text.
+ */
+ public void extractRegions( PDPage page ) throws IOException
+ {
+ Iterator<String> regionIter = regions.iterator();
+ while( regionIter.hasNext() )
+ {
+ setStartPage(getCurrentPageNo());
+ setEndPage(getCurrentPageNo());
+ //reset the stored text for the region so this class
+ //can be reused.
+ String regionName = regionIter.next();
+ Vector<ArrayList<TextPosition>> regionCharactersByArticle = new Vector<ArrayList<TextPosition>>();
+ regionCharactersByArticle.add( new ArrayList<TextPosition>() );
+ regionCharacterList.put( regionName, regionCharactersByArticle );
+ regionText.put( regionName, new StringWriter() );
+ }
+
+ PDStream contentStream = page.getContents();
+ if( contentStream != null )
+ {
+ COSStream contents = contentStream.getStream();
+ processPage( page, contents );
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ protected void processTextPosition( TextPosition text )
+ {
+ Iterator<String> regionIter = regionArea.keySet().iterator();
+ while( regionIter.hasNext() )
+ {
+ String region = regionIter.next();
+ Rectangle2D rect = regionArea.get( region );
+ if( rect.contains( text.getX(), text.getY() ) )
+ {
+ charactersByArticle = (Vector)regionCharacterList.get( region );
+ super.processTextPosition( text );
+ }
+ }
+ }
+
+
+ /**
+ * This will print the processed page text to the output stream.
+ *
+ * @throws IOException If there is an error writing the text.
+ */
+ protected void writePage() throws IOException
+ {
+ Iterator<String> regionIter = regionArea.keySet().iterator();
+ while( regionIter.hasNext() )
+ {
+ String region = regionIter.next();
+ charactersByArticle = (Vector)regionCharacterList.get( region );
+ output = regionText.get( region );
+ super.writePage();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.List;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+/**
+ * This class will extract one or more sequential pages and create a new document.
+ * @author Adam Nichols (adam@apache.org)
+ */
+public class PageExtractor {
+ protected PDDocument sourceDocument;
+ protected int startPage = 1; // first page to extract is page 1 (by default)
+ protected int endPage = 0;
+
+ /**
+ * Creates a new instance of PageExtractor
+ * @param document The document to split.
+ */
+ public PageExtractor(PDDocument sourceDocument) {
+ this.sourceDocument = sourceDocument;
+ endPage = sourceDocument.getNumberOfPages();
+ }
+
+ /**
+ * Creates a new instance of PageExtractor
+ * @param document The document to split.
+ * @param startPage The first page you want extracted (inclusive)
+ * @param endPage The last page you want extracted (inclusive)
+ */
+ public PageExtractor(PDDocument sourceDocument, int startPage, int endPage) {
+ this(sourceDocument);
+ this.startPage = startPage;
+ this.endPage = endPage;
+ }
+
+ /**
+ * This will take a document and extract the desired pages into a new
+ * document. Both startPage and endPage are included in the extracted
+ * document. If the endPage is greater than the number of pages in the
+ * source document, it will go to the end of the document. If startPage is
+ * less than 1, it'll start with page 1. If startPage is greater than
+ * endPage or greater than the number of pages in the source document, a
+ * blank document will be returned.
+ *
+ * @return The extracted document
+ * @throws IOException If there is an IOError
+ */
+ public PDDocument extract() throws IOException {
+ PDDocument extractedDocument = new PDDocument();
+ extractedDocument.setDocumentInformation(sourceDocument.getDocumentInformation());
+ extractedDocument.getDocumentCatalog().setViewerPreferences(
+ sourceDocument.getDocumentCatalog().getViewerPreferences());
+
+ List<PDPage> pages = (List<PDPage>)sourceDocument.getDocumentCatalog().getAllPages();
+ int pageCounter = 1;
+ for(PDPage page : pages) {
+ if(pageCounter >= startPage && pageCounter <= endPage) {
+ PDPage imported = extractedDocument.importPage(page);
+ imported.setCropBox(page.findCropBox());
+ imported.setMediaBox(page.findMediaBox());
+ imported.setResources(page.findResources());
+ imported.setRotation(page.findRotation());
+ }
+ pageCounter++;
+ }
+
+ return extractedDocument;
+ }
+
+ /**
+ * Gets the first page number to be extracted.
+ * @return the first page number which should be extracted
+ */
+ public int getStartPage() {
+ return startPage;
+ }
+
+ /**
+ * Sets the first page number to be extracted.
+ * @param startPage the first page number which should be extracted
+ */
+ public void setStartPage(int startPage) {
+ this.startPage = startPage;
+ }
+
+ /**
+ * Gets the last page number (inclusive) to be extracted.
+ * @return the last page number which should be extracted
+ */
+ public int getEndPage() {
+ return endPage;
+ }
+
+ /**
+ * Sets the last page number to be extracted.
+ * @param endPage the last page number which should be extracted
+ */
+ public void setEndPage(int endPage) {
+ this.endPage = endPage;
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+
+/**
+ * wrapper of TextPosition that adds flags to track
+ * status as linestart and paragraph start positions.
+ * <p>
+ * This is implemented as a wrapper since the TextPosition
+ * class doesn't provide complete access to its
+ * state fields to subclasses. Also, conceptually TextPosition is
+ * immutable while these flags need to be set post-creation so
+ * it makes sense to put these flags in this separate class.
+ * </p>
+ * @author m.martinez@ll.mit.edu
+ *
+ */
+public class PositionWrapper{
+
+ private boolean isLineStart = false;
+ private boolean isParagraphStart = false;
+ private boolean isPageBreak = false;
+ private boolean isHangingIndent = false;
+ private boolean isArticleStart = false;
+
+ private TextPosition position = null;
+
+ /**
+ * returns the underlying TextPosition object
+ * @return
+ */
+ protected TextPosition getTextPosition(){
+ return position;
+ }
+
+
+ public boolean isLineStart() {
+ return isLineStart;
+ }
+
+
+ /**
+ * sets the isLineStart() flag to true
+ */
+ public void setLineStart() {
+ this.isLineStart = true;
+ }
+
+
+ public boolean isParagraphStart() {
+ return isParagraphStart;
+ }
+
+
+ /**
+ * sets the isParagraphStart() flag to true.
+ */
+ public void setParagraphStart() {
+ this.isParagraphStart = true;
+ }
+
+
+ public boolean isArticleStart() {
+ return isArticleStart;
+ }
+
+
+ /**
+ * sets the isArticleStart() flag to true.
+ */
+ public void setArticleStart() {
+ this.isArticleStart = true;
+ }
+
+
+ public boolean isPageBreak() {
+ return isPageBreak;
+ }
+
+
+ /**
+ * sets the isPageBreak() flag to true
+ */
+ public void setPageBreak() {
+ this.isPageBreak = true;
+ }
+
+
+ public boolean isHangingIndent() {
+ return isHangingIndent;
+ }
+
+
+ /**
+ * sets the isHangingIndent() flag to true
+ */
+ public void setHangingIndent() {
+ this.isHangingIndent = true;
+ }
+
+
+ /**
+ * constructs a PositionWrapper around the specified TextPosition object.
+ * @param position
+ */
+ public PositionWrapper(TextPosition position){
+ this.position = position;
+ }
+
+}
--- /dev/null
+/*
+ * 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.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+import java.util.Properties;
+
+/**
+ * This class will handle loading resource files(AFM/CMAP).
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.9 $
+ */
+public class ResourceLoader
+{
+
+ /**
+ * private constructor for utility class.
+ */
+ private ResourceLoader()
+ {
+ //private utility class
+ }
+
+ /**
+ * This will attempt to load the resource given the resource name.
+ *
+ * @param resourceName The resource to try and load.
+ *
+ * @return The resource as a stream or null if it could not be found.
+ *
+ * @throws IOException If there is an error while attempting to load the resource.
+ */
+ public static InputStream loadResource( String resourceName ) throws IOException
+ {
+ ClassLoader loader = ResourceLoader.class.getClassLoader();
+
+ InputStream is = null;
+
+ if( loader != null )
+ {
+ is = loader.getResourceAsStream( resourceName );
+ }
+
+ //see sourceforge bug 863053, this is a fix for a user that
+ //needed to have PDFBox loaded by the bootstrap classloader
+ if( is == null )
+ {
+ loader = ClassLoader.getSystemClassLoader();
+ if( loader != null )
+ {
+ is = loader.getResourceAsStream( resourceName );
+ }
+ }
+
+ if( is == null )
+ {
+ File f = new File( resourceName );
+ if( f.exists() )
+ {
+ is = new FileInputStream( f );
+ }
+ }
+
+ return is;
+ }
+
+ /**
+ * This will attempt to load the resource given the resource name.
+ *
+ * @param resourceName The resource to try and load.
+ * @param failIfNotFound Throw an error message if the properties were not found.
+ *
+ * @return The resource as a stream or null if it could not be found.
+ *
+ * @throws IOException If there is an error loading the properties.
+ */
+ public static Properties loadProperties( String resourceName, boolean failIfNotFound ) throws IOException
+ {
+ Properties properties = null;
+ InputStream is = null;
+ try
+ {
+ is = loadResource( resourceName );
+ if( is != null )
+ {
+ properties = new Properties();
+ properties.load( is );
+ }
+ else
+ {
+ if( failIfNotFound )
+ {
+ throw new IOException( "Error: could not find resource '" + resourceName + "' on classpath." );
+ }
+ }
+ }
+ finally
+ {
+ if( is != null )
+ {
+ is.close();
+ }
+ }
+ return properties;
+ }
+
+ /**
+ * This will attempt to load the resource given the resource name.
+ *
+ * @param resourceName The resource to try and load.
+ * @param defaults A stream of default properties.
+ *
+ * @return The resource as a stream or null if it could not be found.
+ *
+ * @throws IOException If there is an error loading the properties.
+ */
+ public static Properties loadProperties( String resourceName, Properties defaults ) throws IOException
+ {
+ InputStream is = null;
+ try
+ {
+ is = loadResource( resourceName );
+ if( is != null )
+ {
+ defaults.load( is );
+ }
+ }
+ finally
+ {
+ if( is != null )
+ {
+ is.close();
+ }
+ }
+ return defaults;
+ }
+}
--- /dev/null
+/*
+ * 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 org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Split a document into several other documents.
+ *
+ * @author Mario Ivankovits (mario@ops.co.at)
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class Splitter
+{
+
+ /**
+ * The source PDF document.
+ */
+ protected PDDocument pdfDocument;
+
+ /**
+ * The current PDF document that contains the splitted page.
+ */
+ protected PDDocument currentDocument = null;
+
+ private int splitAtPage=1;
+ private List<PDDocument> newDocuments = null;
+
+ /**
+ * The current page number that we are processing, zero based.
+ */
+ protected int pageNumber = 0;
+
+ /**
+ * This will take a document and split into several other documents.
+ *
+ * @param document The document to split.
+ *
+ * @return A list of all the split documents.
+ *
+ * @throws IOException If there is an IOError
+ */
+ public List<PDDocument> split( PDDocument document ) throws IOException
+ {
+ newDocuments = new ArrayList<PDDocument>();
+ pdfDocument = document;
+
+ List pages = pdfDocument.getDocumentCatalog().getAllPages();
+ processPages(pages);
+ return newDocuments;
+ }
+
+ /**
+ * This will tell the splitting algorithm where to split the pages. The default
+ * is 1, so every page will become a new document. If it was to then each document would
+ * contain 2 pages. So it the source document had 5 pages it would split into
+ * 3 new documents, 2 documents containing 2 pages and 1 document containing one
+ * page.
+ *
+ * @param split The number of pages each split document should contain.
+ */
+ public void setSplitAtPage( int split )
+ {
+ if( split <= 0 )
+ {
+ throw new RuntimeException( "Error split must be at least one page." );
+ }
+ splitAtPage = split;
+ }
+
+ /**
+ * This will return how many pages each split document will contain.
+ *
+ * @return The split parameter.
+ */
+ public int getSplitAtPage()
+ {
+ return splitAtPage;
+ }
+
+ /**
+ * Interface method to handle the start of the page processing.
+ *
+ * @param pages The list of pages from the source document.
+ *
+ * @throws IOException If an IO error occurs.
+ */
+ protected void processPages(List pages) throws IOException
+ {
+ Iterator iter = pages.iterator();
+ while( iter.hasNext() )
+ {
+ PDPage page = (PDPage)iter.next();
+ processNextPage( page );
+ }
+ }
+
+ /**
+ * Interface method, you can control where a document gets split by implementing
+ * this method. By default a split occurs at every page. If you wanted to split
+ * based on some complex logic then you could override this method. For example.
+ * <code>
+ * protected void createNewDocumentIfNecessary()
+ * {
+ * if( isPrime( pageNumber ) )
+ * {
+ * super.createNewDocumentIfNecessary();
+ * }
+ * }
+ * </code>
+ *
+ * @throws IOException If there is an error creating the new document.
+ */
+ protected void createNewDocumentIfNecessary() throws IOException
+ {
+ if (isNewDocNecessary())
+ {
+ createNewDocument();
+ }
+ }
+
+ /**
+ * Check if it is necessary to create a new document.
+ *
+ * @return true If a new document should be created.
+ */
+ protected boolean isNewDocNecessary()
+ {
+ return pageNumber % splitAtPage == 0 || currentDocument == null;
+ }
+
+ /**
+ * Create a new document to write the splitted contents to.
+ *
+ * @throws IOException If there is an problem creating the new document.
+ */
+ protected void createNewDocument() throws IOException
+ {
+ currentDocument = new PDDocument();
+ currentDocument.setDocumentInformation(pdfDocument.getDocumentInformation());
+ currentDocument.getDocumentCatalog().setViewerPreferences(
+ pdfDocument.getDocumentCatalog().getViewerPreferences());
+ newDocuments.add(currentDocument);
+ }
+
+
+
+ /**
+ * Interface to start processing a new page.
+ *
+ * @param page The page that is about to get processed.
+ *
+ * @throws IOException If there is an error creating the new document.
+ */
+ protected void processNextPage( PDPage page ) throws IOException
+ {
+ createNewDocumentIfNecessary();
+ PDPage imported = currentDocument.importPage( page );
+ imported.setCropBox( page.findCropBox() );
+ imported.setMediaBox( page.findMediaBox() );
+ // only the resources of the page will be copied
+ imported.setResources( page.getResources() );
+ imported.setRotation( page.findRotation() );
+ pageNumber++;
+ }
+}
--- /dev/null
+/*
+ * 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.UnsupportedEncodingException;
+
+public class StringUtil
+{
+ /**
+ * Converts a string to it ISO-8859-1 byte sequence
+ *
+ * It is an workaround for variable initialisations outside of functions.
+ */
+ public static byte[] getBytes(String s)
+ {
+ try
+ {
+ return s.getBytes("ISO-8859-1");
+ }
+ catch(UnsupportedEncodingException e)
+ {
+ throw new RuntimeException("Unsupported Encoding", e);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.util.HashMap;
+
+/**
+ * This class allows a caller to normalize text in various ways.
+ * It will load the ICU4J jar file if it is defined on the classpath.
+ *
+ * @author <a href="mailto:carrier@digital-evidence.org">Brian Carrier</a>
+ * @version $Revision: 1.0 $
+ */
+public class TextNormalize
+{
+ private ICU4JImpl icu4j = null;
+ private static final HashMap DIACHASH = new HashMap();
+ private String outputEncoding;
+
+ /**
+ *
+ * @param encoding The Encoding that the text will eventually be written as (or null)
+ */
+ public TextNormalize(String encoding)
+ {
+ findICU4J();
+ populateDiacHash();
+ this.outputEncoding = encoding;
+ }
+
+ private void findICU4J()
+ {
+ // see if we can load the icu4j classes from the classpath
+ try
+ {
+ this.getClass().getClassLoader().loadClass("com.ibm.icu.text.Bidi");
+ this.getClass().getClassLoader().loadClass("com.ibm.icu.text.Normalizer");
+ icu4j = new ICU4JImpl();
+ }
+ catch (ClassNotFoundException e)
+ {
+ icu4j = null;
+ }
+ }
+ /*
+ * Adds non-decomposing diacritics to the hash with their related
+ * combining character. These are values that the unicode spec claims
+ * are equivalent but are not mapped in the form NFKC normalization method.
+ * Determined by going through the Combining Diacritical Marks section of
+ * the Unicode spec and identifying which characters are not mapped to by
+ * the normalization.
+ */
+ private void populateDiacHash()
+ {
+ DIACHASH.put(new Integer(0x0060), "\u0300");
+ DIACHASH.put(new Integer(0x02CB), "\u0300");
+ DIACHASH.put(new Integer(0x0027), "\u0301");
+ DIACHASH.put(new Integer(0x02B9), "\u0301");
+ DIACHASH.put(new Integer(0x02CA), "\u0301");
+ DIACHASH.put(new Integer(0x005e), "\u0302");
+ DIACHASH.put(new Integer(0x02C6), "\u0302");
+ DIACHASH.put(new Integer(0x007E), "\u0303");
+ DIACHASH.put(new Integer(0x02C9), "\u0304");
+ DIACHASH.put(new Integer(0x00B0), "\u030A");
+ DIACHASH.put(new Integer(0x02BA), "\u030B");
+ DIACHASH.put(new Integer(0x02C7), "\u030C");
+ DIACHASH.put(new Integer(0x02C8), "\u030D");
+ DIACHASH.put(new Integer(0x0022), "\u030E");
+ DIACHASH.put(new Integer(0x02BB), "\u0312");
+ DIACHASH.put(new Integer(0x02BC), "\u0313");
+ DIACHASH.put(new Integer(0x0486), "\u0313");
+ DIACHASH.put(new Integer(0x055A), "\u0313");
+ DIACHASH.put(new Integer(0x02BD), "\u0314");
+ DIACHASH.put(new Integer(0x0485), "\u0314");
+ DIACHASH.put(new Integer(0x0559), "\u0314");
+ DIACHASH.put(new Integer(0x02D4), "\u031D");
+ DIACHASH.put(new Integer(0x02D5), "\u031E");
+ DIACHASH.put(new Integer(0x02D6), "\u031F");
+ DIACHASH.put(new Integer(0x02D7), "\u0320");
+ DIACHASH.put(new Integer(0x02B2), "\u0321");
+ DIACHASH.put(new Integer(0x02CC), "\u0329");
+ DIACHASH.put(new Integer(0x02B7), "\u032B");
+ DIACHASH.put(new Integer(0x02CD), "\u0331");
+ DIACHASH.put(new Integer(0x005F), "\u0332");
+ DIACHASH.put(new Integer(0x204E), "\u0359");
+ }
+
+ /**
+ * Takes a line of text in presentation order and converts it to logical order.
+ * For most text other than Arabic and Hebrew, the presentation and logical
+ * orders are the same. However, for Arabic and Hebrew, they are different and
+ * if the text involves both RTL and LTR text then the Unicode BIDI algorithm
+ * must be used to determine how to map between them.
+ *
+ * @param str Presentation form of line to convert (i.e. left most char is first char)
+ * @param isRtlDominant true if the PAGE has a dominant right to left ordering
+ * @return Logical form of string (or original string if ICU4J library is not on classpath)
+ */
+ public String makeLineLogicalOrder(String str, boolean isRtlDominant)
+ {
+ if (icu4j != null)
+ {
+ return icu4j.makeLineLogicalOrder(str, isRtlDominant);
+ }
+ else
+ {
+ return str;
+ }
+ }
+
+ /**
+ * Normalize the presentation forms of characters in the string.
+ * For example, convert the single "fi" ligature to "f" and "i".
+ *
+ * @param str String to normalize
+ * @return Normalized string (or original string if ICU4J library is not on classpath)
+ */
+ public String normalizePres(String str)
+ {
+ if (icu4j != null)
+ {
+ return icu4j.normalizePres(str);
+ }
+ else
+ {
+ return str;
+ }
+ }
+
+ /**
+ * Normalize the diacritic, for example,
+ * convert non-combining diacritic characters to their combining
+ * counterparts.
+ *
+ * @param str String to normalize
+ * @return Normalized string (or original string if ICU4J library is not on classpath)
+ */
+ public String normalizeDiac(String str)
+ {
+ /*
+ * Unicode contains special combining forms of the diacritic characters
+ * and we want to use these.
+ */
+ if(outputEncoding != null && outputEncoding.toUpperCase().startsWith("UTF"))
+ {
+ Integer c = new Integer(str.charAt(0));
+ // convert the characters not defined in the Unicode spec
+ if(DIACHASH.containsKey(c))
+ {
+ return (String)DIACHASH.get(c);
+ }
+ else if (icu4j != null)
+ {
+ return icu4j.normalizeDiac(str);
+ }
+ else
+ {
+ return str;
+ }
+ }
+ else
+ {
+ return str;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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 org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+
+/**
+ * This represents a string and a position on the screen of those characters.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.12 $
+ */
+public class TextPosition
+{
+ /* TextMatrix for the start of the text object. Coordinates
+ * are in display units and have not been adjusted. */
+ private Matrix textPos;
+
+ // ending X and Y coordinates in display units
+ private float endX;
+ private float endY;
+
+ private float maxTextHeight; // maximum height of text, in display units
+ private int rot; // 0, 90, 180, 270 degrees of page rotation
+ private float x = Float.NEGATIVE_INFINITY;
+ private float y = Float.NEGATIVE_INFINITY;
+ private float pageHeight;
+ private float pageWidth;
+ private float[] widths;
+ private float widthOfSpace; // width of a space, in display units
+ private String str;
+ private PDFont font;
+ private float fontSize;
+ private int fontSizePt;
+ // TODO remove unused value
+ private float wordSpacing; // word spacing value, in display units
+
+ /**
+ * Constructor.
+ */
+ protected TextPosition()
+ {
+
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param page Page that the text is located in
+ * @param textPositionSt TextMatrix for start of text (in display units)
+ * @param textPositionEnd TextMatrix for end of text (in display units)
+ * @param maxFontH Maximum height of text (in display units)
+ * @param individualWidths The width of each individual character. (in ? units)
+ * @param spaceWidth The width of the space character. (in display units)
+ * @param string The character to be displayed.
+ * @param currentFont The current for for this text position.
+ * @param fontSizeValue The new font size.
+ * @param fontSizeInPt The font size in pt units.
+ * @param ws The word spacing parameter (in display units)
+ */
+ public TextPosition(
+ PDPage page,
+ Matrix textPositionSt,
+ Matrix textPositionEnd,
+ float maxFontH,
+ float[] individualWidths,
+ float spaceWidth,
+ String string,
+ PDFont currentFont,
+ float fontSizeValue,
+ int fontSizeInPt,
+ float ws
+ )
+ {
+ this.textPos = textPositionSt;
+
+ this.endX = textPositionEnd.getXPosition();
+ this.endY = textPositionEnd.getYPosition();
+
+ this.rot = page.findRotation();
+ // make sure it is 0 to 270 and no negative numbers
+ if(this.rot < 0)
+ {
+ rot += 360;
+ }
+
+ this.maxTextHeight = maxFontH;
+ this.pageHeight = page.findMediaBox().getHeight();
+ this.pageWidth = page.findMediaBox().getWidth();
+
+ this.widths = individualWidths;
+ this.widthOfSpace = spaceWidth;
+ this.str = string;
+ this.font = currentFont;
+ this.fontSize = fontSizeValue;
+ this.fontSizePt = fontSizeInPt;
+ this.wordSpacing = ws;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param pageRotation rotation of the page that the text is located in
+ * @param pageWidth rotation of the page that the text is located in
+ * @param pageHeight rotation of the page that the text is located in
+ * @param textPositionSt TextMatrix for start of text (in display units)
+ * @param textPositionEnd TextMatrix for end of text (in display units)
+ * @param maxFontH Maximum height of text (in display units)
+ * @param individualWidth The width of the given character/string. (in ? units)
+ * @param spaceWidth The width of the space character. (in display units)
+ * @param string The character to be displayed.
+ * @param currentFont The current for for this text position.
+ * @param fontSizeValue The new font size.
+ * @param fontSizeInPt The font size in pt units.
+ *
+ * @deprecated Use {@link TextPosition(int, float, float, Matrix, float, float, float, float, float, String, PDFont, float, int)} instead.
+ */
+ public TextPosition(
+ int pageRotation,
+ float pageWidth,
+ float pageHeight,
+ Matrix textPositionSt,
+ Matrix textPositionEnd,
+ float maxFontH,
+ float individualWidth,
+ float spaceWidth,
+ String string,
+ PDFont currentFont,
+ float fontSizeValue,
+ int fontSizeInPt
+ )
+ {
+ this(pageRotation, pageWidth, pageHeight, textPositionSt,
+ textPositionEnd.getXPosition(), textPositionEnd.getYPosition(),
+ maxFontH, individualWidth, spaceWidth, string, currentFont, fontSizeValue, fontSizeInPt);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param pageRotation rotation of the page that the text is located in
+ * @param pageWidth rotation of the page that the text is located in
+ * @param pageHeight rotation of the page that the text is located in
+ * @param textPositionSt TextMatrix for start of text (in display units)
+ * @param endX
+ * @param endY
+ * @param maxFontH Maximum height of text (in display units)
+ * @param individualWidth The width of the given character/string. (in ? units)
+ * @param spaceWidth The width of the space character. (in display units)
+ * @param string The character to be displayed.
+ * @param currentFont The current for for this text position.
+ * @param fontSizeValue The new font size.
+ * @param fontSizeInPt The font size in pt units.
+ */
+ public TextPosition(
+ int pageRotation,
+ float pageWidth,
+ float pageHeight,
+ Matrix textPositionSt,
+ float endX,
+ float endY,
+ float maxFontH,
+ float individualWidth,
+ float spaceWidth,
+ String string,
+ PDFont currentFont,
+ float fontSizeValue,
+ int fontSizeInPt
+ )
+ {
+ this.textPos = textPositionSt;
+
+ this.endX = endX;
+ this.endY = endY;
+
+ this.rot = pageRotation;
+ // make sure it is 0 to 270 and no negative numbers
+ if(this.rot < 0)
+ {
+ rot += 360;
+ }
+
+ this.maxTextHeight = maxFontH;
+ this.pageHeight = pageHeight;
+ this.pageWidth = pageWidth;
+
+ this.widths = new float[]{individualWidth};
+ this.widthOfSpace = spaceWidth;
+ this.str = string;
+ this.font = currentFont;
+ this.fontSize = fontSizeValue;
+ this.fontSizePt = fontSizeInPt;
+ }
+
+ /**
+ * Return the string of characters stored in this object.
+ *
+ * @return The string on the screen.
+ */
+ public String getCharacter()
+ {
+ return str;
+ }
+
+ /**
+ * Return the Matrix textPos stored in this object.
+ *
+ * @return The Matrix containing all infos of the starting textposition
+ */
+ public Matrix getTextPos()
+ {
+ return textPos;
+ }
+
+ /**
+ * Return the direction/orientation of the string in this object
+ * based on its text matrix.
+ * @return The direction of the text (0, 90, 180, or 270)
+ */
+ public float getDir()
+ {
+ float a = textPos.getValue(0,0);
+ float b = textPos.getValue(0,1);
+ float c = textPos.getValue(1,0);
+ float d = textPos.getValue(1,1);
+
+ // 12 0 left to right
+ // 0 12
+ if ((a > 0) && (Math.abs(b) < d) && (Math.abs(c) < a) && (d > 0))
+ {
+ return 0;
+ }
+ // -12 0 right to left (upside down)
+ // 0 -12
+ else if ((a < 0) && (Math.abs(b) < Math.abs(d)) && (Math.abs(c) < Math.abs(a)) && (d < 0))
+ {
+ return 180;
+ }
+ // 0 12 up
+ // -12 0
+ else if ((Math.abs(a) < Math.abs(c)) && (b > 0) && (c < 0) && (Math.abs(d) < b))
+ {
+ return 90;
+ }
+ // 0 -12 down
+ // 12 0
+ else if ((Math.abs(a) < c) && (b < 0) && (c > 0) && (Math.abs(d) < Math.abs(b)))
+ {
+ return 270;
+ }
+ return 0;
+ }
+
+ /**
+ * Return the X starting coordinate of the text, adjusted by
+ * the given rotation amount. The rotation adjusts where the 0,0
+ * location is relative to the text.
+ *
+ * @param rotation Rotation to apply (0, 90, 180, or 270). 0 will perform no adjustments.
+ * @return X coordinate
+ */
+ private float getXRot(float rotation)
+ {
+ if (rotation == 0)
+ {
+ return textPos.getValue(2,0);
+ }
+ else if (rotation == 90)
+ {
+ return textPos.getValue(2,1);
+ }
+ else if (rotation == 180)
+ {
+ return pageWidth - textPos.getValue(2,0);
+ }
+ else if (rotation == 270)
+ {
+ return pageHeight - textPos.getValue(2,1);
+ }
+ return 0;
+ }
+
+ /**
+ * This will get the page rotation adjusted x position of the character.
+ * This is adjusted based on page rotation so that the upper
+ * left is 0,0.
+ *
+ * @return The x coordinate of the character.
+ */
+ public float getX()
+ {
+ if(x==Float.NEGATIVE_INFINITY){
+ x = getXRot(rot);
+ }
+ return x;
+ }
+
+ /**
+ * This will get the text direction adjusted x position of the character.
+ * This is adjusted based on text direction so that the first character
+ * in that direction is in the upper left at 0,0.
+ *
+ * @return The x coordinate of the text.
+ */
+ public float getXDirAdj()
+ {
+ return getXRot(getDir());
+ }
+
+ /**
+ * This will get the y position of the character with 0,0 in lower left.
+ * This will be adjusted by the given rotation.
+ * @param rotation Rotation to apply to text to adjust the 0,0 location (0,90,180,270)
+ *
+ * @return The y coordinate of the text
+ */
+ private float getYLowerLeftRot(float rotation)
+ {
+ if (rotation == 0)
+ {
+ return textPos.getValue(2,1);
+ }
+ else if (rotation == 90)
+ {
+ return pageWidth - textPos.getValue(2,0);
+ }
+ else if (rotation == 180)
+ {
+ return pageHeight - textPos.getValue(2,1);
+ }
+ else if (rotation == 270)
+ {
+ return textPos.getValue(2,0);
+ }
+ return 0;
+ }
+
+ /**
+ * This will get the y position of the text, adjusted so that 0,0 is upper left and
+ * it is adjusted based on the page rotation.
+ *
+ * @return The adjusted y coordinate of the character.
+ */
+ public float getY()
+ {
+ if(y==Float.NEGATIVE_INFINITY){
+ if ((rot == 0) || (rot == 180))
+ {
+ y = pageHeight - getYLowerLeftRot(rot);
+ }
+ else
+ {
+ y = pageWidth - getYLowerLeftRot(rot);
+ }
+ }
+ return y;
+ }
+
+ /**
+ * This will get the y position of the text, adjusted so that 0,0 is upper left and
+ * it is adjusted based on the text direction.
+ *
+ * @return The adjusted y coordinate of the character.
+ */
+ public float getYDirAdj()
+ {
+ float dir = getDir();
+ // some PDFBox code assumes that the 0,0 point is in upper left, not lower left
+ if ((dir == 0) || (dir == 180))
+ {
+ return pageHeight - getYLowerLeftRot(dir);
+ }
+ else
+ {
+ return pageWidth - getYLowerLeftRot(dir);
+ }
+ }
+
+
+
+ /**
+ * Get the length or width of the text, based on a given rotation.
+ *
+ * @param rotation Rotation that was used to determine coordinates (0,90,180,270)
+ * @return Width of text in display units
+ */
+ private float getWidthRot(float rotation)
+ {
+ if ((rotation == 90) || (rotation == 270))
+ {
+ return Math.abs(endY - textPos.getYPosition());
+ }
+ else
+ {
+ return Math.abs(endX - textPos.getXPosition());
+ }
+ }
+
+ /**
+ * This will get the width of the string when page rotation adjusted coordinates are used.
+ *
+ * @return The width of the text in display units.
+ */
+ public float getWidth()
+ {
+ return getWidthRot(rot);
+ }
+
+ /**
+ * This will get the width of the string when text direction adjusted coordinates are used.
+ *
+ * @return The width of the text in display units.
+ */
+ public float getWidthDirAdj()
+ {
+ return getWidthRot(getDir());
+ }
+
+ /**
+ * This will get the maximum height of all characters in this string.
+ *
+ * @return The maximum height of all characters in this string.
+ */
+ public float getHeight()
+ {
+ return maxTextHeight;
+ }
+
+ /**
+ * This will get the maximum height of all characters in this string.
+ *
+ * @return The maximum height of all characters in this string.
+ */
+ public float getHeightDir()
+ {
+ // this is not really a rotation-dependent calculation, but this is defined for symmetry.
+ return maxTextHeight;
+ }
+
+ /**
+ * This will get the font size that this object is
+ * suppose to be drawn at.
+ *
+ * @return The font size.
+ */
+ public float getFontSize()
+ {
+ return fontSize;
+ }
+
+ /**
+ * This will get the font size in pt.
+ * To get this size we have to multiply the pdf-fontsize and the scaling from the textmatrix
+ *
+ * @return The font size in pt.
+ */
+ public float getFontSizeInPt()
+ {
+ return fontSizePt;
+ }
+
+ /**
+ * This will get the font for the text being drawn.
+ *
+ * @return The font size.
+ */
+ public PDFont getFont()
+ {
+ return font;
+ }
+
+ /**
+ * This will get the current word spacing.
+ *
+ * @return The current word spacing.
+ */
+ @Deprecated
+ public float getWordSpacing()
+ {
+ return wordSpacing;
+ }
+
+ /**
+ * This will get the width of a space character. This is useful for some
+ * algorithms such as the text stripper, that need to know the width of a
+ * space character.
+ *
+ * @return The width of a space character.
+ */
+ public float getWidthOfSpace()
+ {
+ return widthOfSpace;
+ }
+ /**
+ * @return Returns the xScale.
+ */
+ public float getXScale()
+ {
+ return textPos.getXScale();
+ }
+
+ /**
+ * @return Returns the yScale.
+ */
+ public float getYScale()
+ {
+ return textPos.getYScale();
+ }
+
+ /**
+ * Get the widths of each individual character.
+ *
+ * @return An array that is the same length as the length of the string.
+ */
+ public float[] getIndividualWidths()
+ {
+ return widths;
+ }
+
+ /**
+ * Show the string data for this text position.
+ *
+ * @return A human readable form of this object.
+ */
+ public String toString()
+ {
+ return getCharacter();
+ }
+
+
+ /**
+ * Determine if this TextPosition logically contains
+ * another (i.e. they overlap and should be rendered on top
+ * of each other).
+ * @param tp2 The other TestPosition to compare against
+ *
+ * @return True if tp2 is contained in the bounding box of this text.
+ */
+ public boolean contains( TextPosition tp2)
+ {
+ double thisXstart = getXDirAdj();
+ double thisXend = getXDirAdj() + getWidthDirAdj();
+
+ double tp2Xstart = tp2.getXDirAdj();
+ double tp2Xend = tp2.getXDirAdj() + tp2.getWidthDirAdj();
+
+ /*
+ * No X overlap at all so return as soon as possible.
+ */
+ if(tp2Xend <= thisXstart || tp2Xstart >= thisXend)
+ {
+ return false;
+ }
+ /*
+ * No Y overlap at all so return as soon as possible.
+ * Note: 0.0 is in the upper left and y-coordinate is
+ * top of TextPosition
+ */
+ if((tp2.getYDirAdj() + tp2.getHeightDir() < getYDirAdj()) ||
+ (tp2.getYDirAdj() > getYDirAdj() + getHeightDir()))
+ {
+ return false;
+ }
+ /* We're going to calculate the percentage of overlap. If its less
+ * than a 15% x-coordinate overlap then we'll return false because its negligible.
+ * .15 was determined by trial and error in the regression test files.
+ */
+ else if((tp2Xstart > thisXstart) && (tp2Xend > thisXend))
+ {
+ double overlap = thisXend - tp2Xstart;
+ double overlapPercent = overlap/getWidthDirAdj();
+ return (overlapPercent > .15);
+ }
+ else if((tp2Xstart < thisXstart) && (tp2Xend < thisXend))
+ {
+ double overlap = tp2Xend - thisXstart;
+ double overlapPercent = overlap/getWidthDirAdj();
+ return (overlapPercent > .15);
+ }
+ return true;
+ }
+
+ /**
+ * Merge a single character TextPosition into the current object.
+ * This is to be used only for cases where we have a diacritic that
+ * overlaps an existing TextPosition. In a graphical display, we could
+ * overlay them, but for text extraction we need to merge them. Use the
+ * contains() method to test if two objects overlap.
+ *
+ * @param diacritic TextPosition to merge into the current TextPosition.
+ * @param normalize Instance of TextNormalize class to be used to normalize diacritic
+ */
+ public void mergeDiacritic(TextPosition diacritic, TextNormalize normalize)
+ {
+ if (diacritic.getCharacter().length() > 1)
+ {
+ return;
+ }
+
+ float diacXStart = diacritic.getXDirAdj();
+ float diacXEnd = diacXStart + diacritic.widths[0];
+
+ float currCharXStart = getXDirAdj();
+
+ int strLen = str.length();
+ boolean wasAdded = false;
+
+ for (int i = 0; i < strLen && !wasAdded; i++)
+ {
+ float currCharXEnd = currCharXStart + widths[i];
+
+ /*
+ * This is the case where there is an overlap of the diacritic character with
+ * the current character and the previous character. If no previous character,
+ * just append the diacritic after the current one.
+ */
+ if(diacXStart < currCharXStart && diacXEnd <= currCharXEnd)
+ {
+ if(i == 0)
+ {
+ insertDiacritic(i, diacritic, normalize);
+ }
+ else
+ {
+ float distanceOverlapping1 = diacXEnd - currCharXStart;
+ float percentage1 = distanceOverlapping1/widths[i];
+
+ float distanceOverlapping2 = currCharXStart - diacXStart;
+ float percentage2 = distanceOverlapping2/widths[i-1];
+
+ if(percentage1 >= percentage2)
+ {
+ insertDiacritic(i, diacritic, normalize);
+ }
+ else
+ {
+ insertDiacritic(i-1, diacritic, normalize);
+ }
+ }
+ wasAdded = true;
+ }
+ //diacritic completely covers this character and therefore we assume that
+ //this is the character the diacritic belongs to
+ else if(diacXStart < currCharXStart && diacXEnd > currCharXEnd)
+ {
+ insertDiacritic(i, diacritic, normalize);
+ wasAdded = true;
+ }
+ //Otherwise, The diacritic modifies this character because its completely
+ //contained by the character width
+ else if(diacXStart >= currCharXStart && diacXEnd <= currCharXEnd)
+ {
+ insertDiacritic(i, diacritic, normalize);
+ wasAdded = true;
+ }
+ /*
+ * Last character in the TextPosition so we add diacritic to the end
+ */
+ else if(diacXStart >= currCharXStart && diacXEnd > currCharXEnd && i == (strLen - 1))
+ {
+ insertDiacritic(i, diacritic, normalize);
+ wasAdded = true;
+ }
+ /*
+ * Couldn't find anything useful so we go to the next character in the
+ * TextPosition
+ */
+ currCharXStart += widths[i];
+ }
+ }
+ /**
+ * Inserts the diacritic TextPosition to the str of this TextPosition
+ * and updates the widths array to include the extra character width.
+ * @param i current character
+ * @param diacritic The diacritic TextPosition
+ * @param normalize Instance of TextNormalize class to be used to normalize diacritic
+ */
+ private void insertDiacritic(int i, TextPosition diacritic, TextNormalize normalize)
+ {
+ /* we add the diacritic to the right or left of the character
+ * depending on the direction of the character. Note that this
+ * is only required because the text is currently stored in
+ * presentation order and not in logical order.
+ */
+ int dir = Character.getDirectionality(str.charAt(i));
+ StringBuffer buf = new StringBuffer();
+
+ buf.append(str.substring(0,i));
+
+ float[] widths2 = new float[widths.length+1];
+ System.arraycopy(widths, 0, widths2, 0, i);
+
+ if ((dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
+ || (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+ || (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING)
+ || (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE))
+ {
+ buf.append(normalize.normalizeDiac(diacritic.getCharacter()));
+ widths2[i] = 0;
+ buf.append(str.charAt(i));
+ widths2[i+1] = widths[i];
+ }
+ else
+ {
+ buf.append(str.charAt(i));
+ widths2[i] = widths[i];
+ buf.append(normalize.normalizeDiac(diacritic.getCharacter()));
+ widths2[i+1] = 0;
+ }
+
+ // Get the rest of the string
+ buf.append(str.substring(i+1, str.length()));
+ System.arraycopy(widths, i+1, widths2, i+2, widths.length-i-1);
+
+ str = buf.toString();
+ widths = widths2;
+ }
+
+ /**
+ *
+ * @return True if the current character is a diacritic char.
+ */
+ public boolean isDiacritic()
+ {
+ String cText = this.getCharacter();
+ return (cText.length() == 1 && (Character.getType(cText.charAt(0)) == Character.NON_SPACING_MARK
+ || Character.getType(cText.charAt(0)) == Character.MODIFIER_SYMBOL
+ || Character.getType(cText.charAt(0)) == Character.MODIFIER_LETTER));
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.util.Comparator;
+
+/**
+ * This class is a comparator for TextPosition operators. It handles
+ * pages with text in different directions by grouping the text based
+ * on direction and sorting in that direction. This allows continuous text
+ * in a given direction to be more easily grouped together.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.7 $
+ */
+public class TextPositionComparator implements Comparator
+{
+ /**
+ * {@inheritDoc}
+ */
+ public int compare(Object o1, Object o2)
+ {
+ int retval = 0;
+ TextPosition pos1 = (TextPosition)o1;
+ TextPosition pos2 = (TextPosition)o2;
+
+ /* Only compare text that is in the same direction. */
+ if (pos1.getDir() < pos2.getDir())
+ {
+ return -1;
+ }
+ else if (pos1.getDir() > pos2.getDir())
+ {
+ return 1;
+ }
+
+ // Get the text direction adjusted coordinates
+ float x1 = pos1.getXDirAdj();
+ float x2 = pos2.getXDirAdj();
+
+ float pos1YBottom = pos1.getYDirAdj();
+ float pos2YBottom = pos2.getYDirAdj();
+ // note that the coordinates have been adjusted so 0,0 is in upper left
+ float pos1YTop = pos1YBottom - pos1.getHeightDir();
+ float pos2YTop = pos2YBottom - pos2.getHeightDir();
+
+ float yDifference = Math.abs( pos1YBottom-pos2YBottom);
+ //we will do a simple tolerance comparison.
+ if( yDifference < .1 ||
+ (pos2YBottom >= pos1YTop && pos2YBottom <= pos1YBottom) ||
+ (pos1YBottom >= pos2YTop && pos1YBottom <= pos2YBottom))
+ {
+ if( x1 < x2 )
+ {
+ retval = -1;
+ }
+ else if( x1 > x2 )
+ {
+ retval = 1;
+ }
+ else
+ {
+ retval = 0;
+ }
+ }
+ else if( pos1YBottom < pos2YBottom )
+ {
+ retval = -1;
+ }
+ else
+ {
+ return 1;
+ }
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.InputStream;
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+/**
+ * This class with handle some simple XML operations.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class XMLUtil
+{
+ /**
+ * Utility class, should not be instantiated.
+ *
+ */
+ private XMLUtil()
+ {
+ }
+
+ /**
+ * This will parse an XML stream and create a DOM document.
+ *
+ * @param is The stream to get the XML from.
+ * @return The DOM document.
+ * @throws IOException It there is an error creating the dom.
+ */
+ public static Document parse( InputStream is ) throws IOException
+ {
+ try
+ {
+ DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = builderFactory.newDocumentBuilder();
+ return builder.parse( is );
+ }
+ catch( Exception e )
+ {
+ IOException thrown = new IOException( e.getMessage() );
+ throw thrown;
+ }
+ }
+
+ /**
+ * This will get the text value of an element.
+ *
+ * @param node The node to get the text value for.
+ * @return The text of the node.
+ */
+ public static String getNodeValue( Element node )
+ {
+ String retval = "";
+ NodeList children = node.getChildNodes();
+ for( int i=0; i<children.getLength(); i++ )
+ {
+ Node next = children.item( i );
+ if( next instanceof Text )
+ {
+ retval = next.getNodeValue();
+ }
+ }
+ return retval;
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.util.PDFMarkedContentExtractor;
+import org.apache.pdfbox.util.PDFOperator;
+/**
+ * BMC : Begins a marked-content sequence.
+ * @author koch
+ * @version $Revision$
+ *
+ */
+public class BeginMarkedContentSequence extends OperatorProcessor
+{
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ throws IOException
+ {
+ COSName tag = null;
+ for (COSBase argument : arguments)
+ {
+ if (argument instanceof COSName)
+ {
+ tag = (COSName) argument;
+ }
+ }
+ if (this.context instanceof PDFMarkedContentExtractor)
+ {
+ ((PDFMarkedContentExtractor) this.context).beginMarkedContentSequence(tag, null);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.util.PDFMarkedContentExtractor;
+import org.apache.pdfbox.util.PDFOperator;
+/**
+ * BDC : Begins a marked-content sequence with property list.
+ *
+ * @author koch
+ * @version $Revision$
+ */
+public class BeginMarkedContentSequenceWithProperties extends OperatorProcessor
+{
+
+ /**
+ * {@inheritDoc}
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ throws IOException
+ {
+ COSName tag = null;
+ COSDictionary properties = null;
+ for (COSBase argument : arguments)
+ {
+ if (argument instanceof COSName)
+ {
+ tag = (COSName) argument;
+ }
+ else if (argument instanceof COSDictionary)
+ {
+ properties = (COSDictionary) argument;
+ }
+ }
+ if (this.context instanceof PDFMarkedContentExtractor)
+ {
+ ((PDFMarkedContentExtractor) this.context).beginMarkedContentSequence(tag, properties);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+public class BeginText extends OperatorProcessor
+{
+
+ /**
+ * process : BT : Begin text object.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ context.setTextMatrix( new Matrix());
+ context.setTextLineMatrix( new Matrix() );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class CloseAndStrokePath extends OperatorProcessor
+{
+
+ /**
+ * s close and stroke the path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ context.processOperator( "h", arguments );
+ context.processOperator( "S", arguments );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+import java.io.IOException;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+
+public class Concatenate extends OperatorProcessor
+{
+
+ /**
+ * process : cm : Concatenate matrix to current transformation matrix.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If there is an error processing the operator.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+
+ //concatenate matrix to current transformation matrix
+ COSNumber a = (COSNumber) arguments.get(0);
+ COSNumber b = (COSNumber) arguments.get(1);
+ COSNumber c = (COSNumber) arguments.get(2);
+ COSNumber d = (COSNumber) arguments.get(3);
+ COSNumber e = (COSNumber) arguments.get(4);
+ COSNumber f = (COSNumber) arguments.get(5);
+
+ Matrix newMatrix = new Matrix();
+ newMatrix.setValue(0, 0, a.floatValue());
+ newMatrix.setValue(0, 1, b.floatValue());
+ newMatrix.setValue(1, 0, c.floatValue());
+ newMatrix.setValue(1, 1, d.floatValue());
+ newMatrix.setValue(2, 0, e.floatValue());
+ newMatrix.setValue(2, 1, f.floatValue());
+
+ //this line has changed
+ context.getGraphicsState().setCurrentTransformationMatrix(
+ newMatrix.multiply( context.getGraphicsState().getCurrentTransformationMatrix() ) );
+
+
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFMarkedContentExtractor;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * EMC : Ends a marked-content sequence begun by BMC or BDC.
+ * @author koch
+ * @version $Revision: $
+ */
+public class EndMarkedContentSequence extends OperatorProcessor
+{
+
+ /**
+ * {@inheritDoc}
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ throws IOException
+ {
+ if (this.context instanceof PDFMarkedContentExtractor)
+ {
+ ((PDFMarkedContentExtractor) this.context).endMarkedContentSequence();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+public class EndText extends OperatorProcessor
+{
+
+ /**
+ * process : ET : End text object.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ context.setTextMatrix( null);
+ context.setTextLineMatrix( null);
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+public class GRestore extends OperatorProcessor
+{
+ /**
+ * process : Q : Restore graphics state.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ context.setGraphicsState( (PDGraphicsState)context.getGraphicsStack().pop() );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+
+public class GSave extends OperatorProcessor
+{
+ /**
+ * process : q : Save graphics state.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ context.getGraphicsStack().push( (PDGraphicsState)context.getGraphicsState().clone() );
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+import org.apache.pdfbox.util.PDFMarkedContentExtractor;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Invoke named XObject.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @author Mario Ivankovits
+ *
+ * @version $Revision: 1.9 $
+ */
+public class Invoke extends OperatorProcessor
+{
+ //private Set inProcess = new TreeSet();
+
+ /**
+ * process : Do - Invoke a named xobject.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If there is an error processing this operator.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ COSName name = (COSName) arguments.get( 0 );
+
+ Map xobjects = context.getXObjects();
+ PDXObject xobject = (PDXObject) xobjects.get(name.getName());
+ if (this.context instanceof PDFMarkedContentExtractor)
+ {
+ ((PDFMarkedContentExtractor) this.context).xobject(xobject);
+ }
+
+ if(xobject instanceof PDXObjectForm)
+ {
+ PDXObjectForm form = (PDXObjectForm)xobject;
+ COSStream invoke = (COSStream)form.getCOSObject();
+ PDResources pdResources = form.getResources();
+ PDPage page = context.getCurrentPage();
+ if(pdResources == null)
+ {
+ pdResources = page.findResources();
+ }
+
+ getContext().processSubStream( page, pdResources, invoke );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+public class MoveAndShow extends OperatorProcessor
+{
+ /**
+ * ' Move to next line and show text.
+ * @param arguments List
+ * @param operator The operator that is being executed.
+ * @throws IOException If there is an error processing the operator.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ // Move to start of next text line, and show text
+ //
+
+ context.processOperator("T*", null);
+ context.processOperator("Tj", arguments);
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+public class MoveText extends OperatorProcessor
+{
+
+ /**
+ * process : Td : Move text position.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ COSNumber x = (COSNumber)arguments.get( 0 );
+ COSNumber y = (COSNumber)arguments.get( 1 );
+ Matrix td = new Matrix();
+ td.setValue( 2, 0, x.floatValue() );
+ td.setValue( 2, 1, y.floatValue() );
+ context.setTextLineMatrix( td.multiply( context.getTextLineMatrix() ) );
+ context.setTextMatrix( context.getTextLineMatrix().copy() );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+public class MoveTextSetLeading extends OperatorProcessor
+{
+
+ /**
+ * process : TD Move text position and set leading.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If there is an error during processing.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ //move text position and set leading
+ COSNumber y = (COSNumber)arguments.get( 1 );
+
+ ArrayList<COSBase> args = new ArrayList<COSBase>();
+ args.add(new COSFloat(-1*y.floatValue()));
+ context.processOperator("TL", args);
+ context.processOperator("Td", arguments);
+
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+public class NextLine extends OperatorProcessor
+{
+ /**
+ * process : T* Move to start of next text line.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If there is an error during processing.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ //move to start of next text line
+ ArrayList<COSBase> args = new ArrayList<COSBase>();
+ args.add(new COSFloat(0.0f));
+ // this must be -leading instead of just leading as written in the
+ // specification (p.369) the acrobat reader seems to implement it the same way
+ args.add(new COSFloat(-1*context.getGraphicsState().getTextState().getLeading()));
+ // use Td instead of repeating code
+ context.processOperator("Td", args);
+
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.PDFStreamEngine;
+import java.util.List;
+import java.io.IOException;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.3 $
+ */
+public abstract class OperatorProcessor
+{
+
+ /**
+ * The stream engine processing context.
+ */
+ protected PDFStreamEngine context = null;
+
+ /**
+ * Constructor.
+ *
+ */
+ protected OperatorProcessor()
+ {
+ }
+
+ /**
+ * Get the context for processing.
+ *
+ * @return The processing context.
+ */
+ protected PDFStreamEngine getContext()
+ {
+ return context;
+ }
+
+ /**
+ * Set the processing context.
+ *
+ * @param ctx The context for processing.
+ */
+ public void setContext(PDFStreamEngine ctx)
+ {
+ context = ctx;
+ }
+
+ /**
+ * process the operator.
+ * @param operator The operator that is being processed.
+ * @param arguments arguments needed by this operator.
+ *
+ * @throws IOException If there is an error processing the operator.
+ */
+ public abstract void process(PDFOperator operator, List<COSBase> arguments) throws IOException;
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+public class SetCharSpacing extends OperatorProcessor
+{
+ /**
+ * process : Tc Set character spacing.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ //set character spacing
+ if( arguments.size() > 0 )
+ {
+ //There are some documents which are incorrectly structured, and have
+ //a wrong number of arguments to this, so we will assume the last argument
+ //in the list
+ Object charSpacing = arguments.get( arguments.size()-1 );
+ if( charSpacing instanceof COSNumber )
+ {
+ COSNumber characterSpacing = (COSNumber)charSpacing;
+ context.getGraphicsState().getTextState().setCharacterSpacing( characterSpacing.floatValue() );
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.graphics.PDExtendedGraphicsState;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetGraphicsStateParameters extends OperatorProcessor
+{
+ /**
+ * gs Set parameters from graphics state parameter dictionary.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ //set parameters from graphics state parameter dictionary
+ COSName graphicsName = (COSName)arguments.get( 0 );
+ PDExtendedGraphicsState gs = (PDExtendedGraphicsState)context.getGraphicsStates().get( graphicsName.getName() );
+ gs.copyIntoGraphicsState( context.getGraphicsState() );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetHorizontalTextScaling extends OperatorProcessor
+{
+ /**
+ * Tz Set horizontal text scaling.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ COSNumber scaling = (COSNumber)arguments.get(0);
+ context.getGraphicsState().getTextState().setHorizontalScalingPercent( scaling.floatValue() );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de>Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineCapStyle extends OperatorProcessor
+{
+
+ /**
+ * Set the line cap style.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ int lineCapStyle = ((COSNumber)arguments.get( 0 )).intValue();
+ context.getGraphicsState().setLineCap( lineCapStyle );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class SetLineDashPattern extends OperatorProcessor
+{
+
+ /**
+ * Set the line dash pattern.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ COSArray dashArray = (COSArray)arguments.get( 0 );
+ int dashPhase = ((COSNumber)arguments.get( 1 )).intValue();
+ PDLineDashPattern lineDash = new PDLineDashPattern( dashArray, dashPhase );
+ context.getGraphicsState().setLineDashPattern( lineDash );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de>Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineJoinStyle extends OperatorProcessor
+{
+
+ /**
+ * Set the line cap style.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ int lineJoinStyle = ((COSNumber)arguments.get( 0 )).intValue();
+ context.getGraphicsState().setLineJoin( lineJoinStyle );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+
+public class SetLineMiterLimit extends OperatorProcessor
+{
+ /**
+ * w Set miter limit.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ COSNumber miterLimit = (COSNumber)arguments.get( 0 );
+ context.getGraphicsState().setMiterLimit( miterLimit.doubleValue() );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetLineWidth extends OperatorProcessor
+{
+ /**
+ * w Set line width.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ COSNumber width = (COSNumber)arguments.get( 0 );
+ context.getGraphicsState().setLineWidth( width.doubleValue() );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+
+public class SetMatrix extends OperatorProcessor
+{
+
+ /**
+ * Tm Set text matrix and text line matrix.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ //Set text matrix and text line matrix
+ COSNumber a = (COSNumber)arguments.get( 0 );
+ COSNumber b = (COSNumber)arguments.get( 1 );
+ COSNumber c = (COSNumber)arguments.get( 2 );
+ COSNumber d = (COSNumber)arguments.get( 3 );
+ COSNumber e = (COSNumber)arguments.get( 4 );
+ COSNumber f = (COSNumber)arguments.get( 5 );
+
+ Matrix textMatrix = new Matrix();
+ textMatrix.setValue( 0, 0, a.floatValue() );
+ textMatrix.setValue( 0, 1, b.floatValue() );
+ textMatrix.setValue( 1, 0, c.floatValue() );
+ textMatrix.setValue( 1, 1, d.floatValue() );
+ textMatrix.setValue( 2, 0, e.floatValue() );
+ textMatrix.setValue( 2, 1, f.floatValue() );
+ context.setTextMatrix( textMatrix );
+ context.setTextLineMatrix( textMatrix.copy() );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.6 $
+ */
+
+public class SetMoveAndShow extends OperatorProcessor
+{
+ /**
+ * " Set word and character spacing, move to next line, and show text.
+ * @param operator The operator that is being executed.
+ * @param arguments List.
+ * @throws IOException If there is an error processing the operator.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ //Set word and character spacing, move to next line, and show text
+ //
+ context.processOperator("Tw", arguments.subList(0,1));
+ context.processOperator("Tc", arguments.subList(1,2));
+ context.processOperator("'", arguments.subList(2,3));
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class SetNonStrokingCMYKColor extends OperatorProcessor
+{
+ /**
+ * k Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorSpace cs = PDDeviceCMYK.INSTANCE;
+ PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+ colorInstance.setColorSpace( cs );
+ float[] values = new float[4];
+ for( int i=0; i<arguments.size(); i++ )
+ {
+ values[i] = ((COSNumber)arguments.get( i )).floatValue();
+ }
+ colorInstance.setColorSpaceValue( values );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class SetNonStrokingCalRGBColor extends OperatorProcessor
+{
+ /**
+ * rg Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+ float[] values = new float[3];
+ for( int i=0; i<arguments.size(); i++ )
+ {
+ values[i] = ((COSNumber)arguments.get( i )).floatValue();
+ }
+ colorInstance.setColorSpaceValue( values );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDCalRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.util.PDFOperator;
+import java.io.IOException;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetNonStrokingColor extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(SetNonStrokingColor.class);
+
+ /**
+ * sc,scn Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorSpace colorSpace = context.getGraphicsState().getNonStrokingColor().getColorSpace();
+ if (colorSpace != null)
+ {
+ OperatorProcessor newOperator = null;
+ if (colorSpace instanceof PDDeviceGray)
+ {
+ newOperator = new SetNonStrokingGrayColor();
+ }
+ else if (colorSpace instanceof PDDeviceRGB)
+ {
+ newOperator = new SetNonStrokingRGBColor();
+ }
+ else if (colorSpace instanceof PDDeviceCMYK)
+ {
+ newOperator = new SetNonStrokingCMYKColor();
+ }
+ else if (colorSpace instanceof PDICCBased)
+ {
+ newOperator = new SetNonStrokingICCBasedColor();
+ }
+ else if (colorSpace instanceof PDCalRGB)
+ {
+ newOperator = new SetNonStrokingCalRGBColor();
+ }
+ else if (colorSpace instanceof PDSeparation)
+ {
+ newOperator = new SetNonStrokingSeparation();
+ }
+ else if (colorSpace instanceof PDDeviceN)
+ {
+ newOperator = new SetNonStrokingDeviceN();
+ }
+
+ if (newOperator != null)
+ {
+ newOperator.setContext(getContext());
+ newOperator.process(operator, arguments);
+ }
+ else
+ {
+ log.warn("Not supported colorspace "+colorSpace.getName()
+ + " within operator "+operator.getOperation());
+ }
+ }
+ else
+ {
+ log.warn("Colorspace not found in "+getClass().getName()+".process!!");
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class SetNonStrokingColorSpace extends OperatorProcessor
+{
+ private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
+
+ /**
+ * cs Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+// (PDF 1.1) Set color space for stroking operations
+ COSName name = (COSName)arguments.get( 0 );
+ PDColorSpace cs = PDColorSpaceFactory.createColorSpace( name, context.getColorSpaces() );
+ PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+ colorInstance.setColorSpace( cs );
+ int numComponents = cs.getNumberOfComponents();
+ float[] values = EMPTY_FLOAT_ARRAY;
+ if( numComponents >= 0 )
+ {
+ values = new float[numComponents];
+ for( int i=0; i<numComponents; i++ )
+ {
+ values[i] = 0f;
+ }
+ if( cs instanceof PDDeviceCMYK )
+ {
+ values[3] = 1f;
+ }
+ }
+ colorInstance.setColorSpaceValue( values );
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDCalRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @version $Revision: 1.0 $
+ */
+public class SetNonStrokingDeviceN extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log =
+ LogFactory.getLog(SetNonStrokingDeviceN.class);
+
+ /**
+ * scn Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+ PDColorSpace colorSpace = colorInstance.getColorSpace();
+
+ if (colorSpace != null)
+ {
+ List<COSBase> argList = arguments;
+ OperatorProcessor newOperator = null;
+
+ if (colorSpace instanceof PDDeviceN) {
+ PDDeviceN sep = (PDDeviceN) colorSpace;
+ colorSpace = sep.getAlternateColorSpace();
+ argList = sep.calculateColorValues(arguments).toList();
+ }
+
+ if (colorSpace instanceof PDDeviceGray)
+ {
+ newOperator = new SetNonStrokingGrayColor();
+ }
+ else if (colorSpace instanceof PDDeviceRGB)
+ {
+ newOperator = new SetNonStrokingRGBColor();
+ }
+ else if (colorSpace instanceof PDDeviceCMYK)
+ {
+ newOperator = new SetNonStrokingCMYKColor();
+ }
+ else if (colorSpace instanceof PDICCBased)
+ {
+ newOperator = new SetNonStrokingICCBasedColor();
+ }
+ else if (colorSpace instanceof PDCalRGB)
+ {
+ newOperator = new SetNonStrokingCalRGBColor();
+ }
+
+ if (newOperator != null)
+ {
+ colorInstance.setColorSpace(colorSpace);
+ newOperator.setContext(getContext());
+ newOperator.process(operator, argList);
+ }
+ else
+ {
+ log.warn("Not supported colorspace "+colorSpace.getName()
+ + " within operator "+operator.getOperation());
+ }
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class SetNonStrokingGrayColor extends OperatorProcessor
+{
+ /**
+ * rg Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorSpace cs = new PDDeviceGray();
+ PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+ colorInstance.setColorSpace( cs );
+ float[] values = new float[1];
+ if( arguments.size() >= 1 )
+ {
+ values[0] = ((COSNumber)arguments.get( 0 )).floatValue();
+ }
+ else
+ {
+ throw new IOException( "Error: Expected at least one argument when setting non stroking gray color");
+ }
+ colorInstance.setColorSpaceValue( values );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetNonStrokingICCBasedColor extends OperatorProcessor
+{
+ /**
+ * scn Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+ PDColorSpace cs = colorInstance.getColorSpace();
+ int numberOfComponents = cs.getNumberOfComponents();
+ float[] values = new float[numberOfComponents];
+ for( int i=0; i<numberOfComponents; i++ )
+ {
+ values[i] = ((COSNumber)arguments.get( i )).floatValue();
+ }
+ colorInstance.setColorSpaceValue( values );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Set the non stroking color space.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class SetNonStrokingRGBColor extends OperatorProcessor
+{
+ /**
+ * rg Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorSpace cs = PDDeviceRGB.INSTANCE;
+ PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+ colorInstance.setColorSpace( cs );
+ float[] values = new float[3];
+ for( int i=0; i<arguments.size(); i++ )
+ {
+ values[i] = ((COSNumber)arguments.get( i )).floatValue();
+ }
+ colorInstance.setColorSpaceValue( values );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author <a href="mailto:WilliamstonConsulting@GMail.com">Daniel Wilson</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetNonStrokingSeparation extends OperatorProcessor
+{
+
+ /**
+ * scn Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState colorInstance = context.getGraphicsState().getNonStrokingColor();
+ PDColorSpace colorSpace = colorInstance.getColorSpace();
+
+ if (colorSpace != null)
+ {
+ PDSeparation sep = (PDSeparation) colorSpace;
+ colorSpace = sep.getAlternateColorSpace();
+ COSArray values = sep.calculateColorValues(arguments.get(0));
+ colorInstance.setColorSpaceValue(values.toFloatArray());
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class SetStrokingCMYKColor extends OperatorProcessor
+{
+ /**
+ * K Set color space for stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState color = context.getGraphicsState().getStrokingColor();
+ color.setColorSpace( PDDeviceCMYK.INSTANCE );
+ float[] values = new float[4];
+ for( int i=0; i<arguments.size(); i++ )
+ {
+ values[i] = ((COSNumber)arguments.get( i )).floatValue();
+ }
+ color.setColorSpaceValue( values );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Structural modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is replaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class SetStrokingCalRGBColor extends OperatorProcessor
+{
+ /**
+ * RG Set color space for stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState color = context.getGraphicsState().getStrokingColor();
+ float[] values = new float[3];
+ for( int i=0; i<arguments.size(); i++ )
+ {
+ values[i] = ((COSNumber)arguments.get( i )).floatValue();
+ }
+ color.setColorSpaceValue( values );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.pdmodel.graphics.color.PDCalRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.util.PDFOperator;
+import java.io.IOException;
+
+/**
+ * <p>Set the stroking color space.</p>
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetStrokingColor extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(SetStrokingColor.class);
+
+ /**
+ * SC,SCN Set color space for stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorSpace colorSpace = context.getGraphicsState().getStrokingColor().getColorSpace();
+ if (colorSpace != null)
+ {
+ OperatorProcessor newOperator = null;
+ if (colorSpace instanceof PDDeviceGray)
+ {
+ newOperator = new SetStrokingGrayColor();
+ }
+ else if (colorSpace instanceof PDDeviceRGB)
+ {
+ newOperator = new SetStrokingRGBColor();
+ }
+ else if (colorSpace instanceof PDDeviceCMYK)
+ {
+ newOperator = new SetStrokingCMYKColor();
+ }
+ else if (colorSpace instanceof PDICCBased)
+ {
+ newOperator = new SetStrokingICCBasedColor();
+ }
+ else if (colorSpace instanceof PDCalRGB)
+ {
+ newOperator = new SetStrokingCalRGBColor();
+ }
+ else if (colorSpace instanceof PDSeparation)
+ {
+ newOperator = new SetStrokingSeparation();
+ }
+ else if (colorSpace instanceof PDDeviceN)
+ {
+ newOperator = new SetStrokingDeviceN();
+ }
+
+ if (newOperator != null)
+ {
+ newOperator.setContext(getContext());
+ newOperator.process(operator, arguments);
+ }
+ else
+ {
+ log.info("Not supported colorspace "+colorSpace.getName()
+ + " within operator "+operator.getOperation());
+ }
+ }
+ else
+ {
+ log.warn("Colorspace not found in "+getClass().getName()+".process!!");
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetStrokingColorSpace extends OperatorProcessor
+{
+ private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
+
+ /**
+ * CS Set color space for stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ //(PDF 1.1) Set color space for stroking operations
+ COSName name = (COSName)arguments.get( 0 );
+ PDColorSpace cs = PDColorSpaceFactory.createColorSpace( name, context.getColorSpaces() );
+ PDColorState color = context.getGraphicsState().getStrokingColor();
+ color.setColorSpace( cs );
+ int numComponents = cs.getNumberOfComponents();
+ float[] values = EMPTY_FLOAT_ARRAY;
+ if( numComponents >= 0 )
+ {
+ values = new float[numComponents];
+ for( int i=0; i<numComponents; i++ )
+ {
+ values[i] = 0f;
+ }
+ if( cs instanceof PDDeviceCMYK )
+ {
+ values[3] = 1f;
+ }
+ }
+ color.setColorSpaceValue( values );
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDCalRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceN;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @version $Revision: 1.0 $
+ */
+public class SetStrokingDeviceN extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log =
+ LogFactory.getLog(SetStrokingDeviceN.class);
+
+ /**
+ * scn Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState color = context.getGraphicsState().getStrokingColor();
+ PDColorSpace colorSpace = color.getColorSpace();
+
+ if (colorSpace != null)
+ {
+ OperatorProcessor newOperator = null;
+ List<COSBase> argList = arguments;
+
+ if (colorSpace instanceof PDDeviceN)
+ {
+ PDDeviceN sep = (PDDeviceN) colorSpace;
+ colorSpace = sep.getAlternateColorSpace();
+ argList = sep.calculateColorValues(arguments).toList();
+ }
+
+ if (colorSpace instanceof PDDeviceGray)
+ {
+ newOperator = new SetStrokingGrayColor();
+ }
+ else if (colorSpace instanceof PDDeviceRGB)
+ {
+ newOperator = new SetStrokingRGBColor();
+ }
+ else if (colorSpace instanceof PDDeviceCMYK)
+ {
+ newOperator = new SetStrokingCMYKColor();
+ }
+ else if (colorSpace instanceof PDICCBased)
+ {
+ newOperator = new SetStrokingICCBasedColor();
+ }
+ else if (colorSpace instanceof PDCalRGB)
+ {
+ newOperator = new SetStrokingCalRGBColor();
+ }
+
+ if (newOperator != null)
+ {
+ color.setColorSpace(colorSpace);
+ newOperator.setContext(getContext());
+ newOperator.process(operator, argList);
+ }
+ else
+ {
+ log.warn("Not supported colorspace "+colorSpace.getName()
+ + " within operator "+operator.getOperation());
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class SetStrokingGrayColor extends OperatorProcessor
+{
+ /**
+ * RG Set color space for stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState color = context.getGraphicsState().getStrokingColor();
+ color.setColorSpace( new PDDeviceGray() );
+ float[] values = new float[1];
+ if( arguments.size() >= 1 )
+ {
+ values[0] = ((COSNumber)arguments.get( 0 )).floatValue();
+ }
+ else
+ {
+ throw new IOException( "Error: Expected at least one argument when setting non stroking gray color");
+ }
+ color.setColorSpaceValue( values );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetStrokingICCBasedColor extends OperatorProcessor
+{
+ /**
+ * scn Set color space for stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState color = context.getGraphicsState().getStrokingColor();
+ PDColorSpace cs = color.getColorSpace();
+ int numberOfComponents = cs.getNumberOfComponents();
+ float[] values = new float[numberOfComponents];
+ for( int i=0; i<numberOfComponents; i++ )
+ {
+ values[i] = ((COSNumber)arguments.get( i )).floatValue();
+ }
+ color.setColorSpaceValue( values );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class SetStrokingRGBColor extends OperatorProcessor
+{
+ /**
+ * RG Set color space for stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState color = context.getGraphicsState().getStrokingColor();
+ color.setColorSpace( PDDeviceRGB.INSTANCE );
+ float[] values = new float[3];
+ for( int i=0; i<arguments.size(); i++ )
+ {
+ values[i] = ((COSNumber)arguments.get( i )).floatValue();
+ }
+ color.setColorSpaceValue( values );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorState;
+import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ *
+ * @author <a href="mailto:WilliamstonConsulting@GMail.com">Daniel Wilson</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetStrokingSeparation extends OperatorProcessor
+{
+
+ /**
+ * scn Set color space for non stroking operations.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PDColorState colorInstance = context.getGraphicsState().getStrokingColor();
+ PDColorSpace colorSpace = colorInstance.getColorSpace();
+
+ if (colorSpace != null)
+ {
+ PDSeparation sep = (PDSeparation) colorSpace;
+ colorSpace = sep.getAlternateColorSpace();
+ COSArray values = sep.calculateColorValues(arguments.get(0));
+ colorInstance.setColorSpaceValue(values.toFloatArray());
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.5 $
+ */
+
+public class SetTextFont extends OperatorProcessor
+{
+ /**
+ * Tf selectfont Set text font and size.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ //there are some documents that are incorrectly structured and
+ //arguments are in the wrong spot, so we will silently ignore them
+ //if there are no arguments
+ if( arguments.size() >= 2 )
+ {
+ //set font and size
+ COSName fontName = (COSName)arguments.get( 0 );
+ float fontSize = ((COSNumber)arguments.get( 1 ) ).floatValue();
+ context.getGraphicsState().getTextState().setFontSize( fontSize );
+
+ context.getGraphicsState().getTextState().setFont( (PDFont)context.getFonts().get( fontName.getName() ) );
+ if( context.getGraphicsState().getTextState().getFont() == null )
+ {
+ throw new IOException( "Error: Could not find font(" + fontName + ") in map=" + context.getFonts() );
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+
+public class SetTextLeading extends OperatorProcessor
+{
+ /**
+ * TL Set text leading.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ COSNumber leading = (COSNumber)arguments.get( 0 );
+ context.getGraphicsState().getTextState().setLeading( leading.floatValue() );
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetTextRenderingMode extends OperatorProcessor
+{
+ /**
+ * Tr Set text rendering mode.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ COSNumber mode = (COSNumber)arguments.get( 0 );
+ context.getGraphicsState().getTextState().setRenderingMode( mode.intValue() );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * <p>Structal modification of the PDFEngine class :
+ * the long sequence of conditions in processOperator is remplaced by
+ * this strategy pattern.</p>
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+
+public class SetTextRise extends OperatorProcessor
+{
+ /**
+ * Ts Set text rise.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ COSNumber rise = (COSNumber)arguments.get(0);
+ context.getGraphicsState().getTextState().setRise( rise.floatValue() );
+ }
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.util.PDFOperator;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+
+public class SetWordSpacing extends OperatorProcessor
+{
+ /**
+ * Tw Set word spacing.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ //set word spacing
+ COSNumber wordSpacing = (COSNumber)arguments.get( 0 );
+ context.getGraphicsState().getTextState().setWordSpacing( wordSpacing.floatValue() );
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.io.IOException;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.4 $
+ */
+
+public class ShowText extends OperatorProcessor
+{
+
+ /**
+ * Tj show Show text.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If there is an error processing this operator.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ COSString string = (COSString)arguments.get( 0 );
+ context.processEncodedText( string.getBytes() );
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator;
+
+import java.util.List;
+
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import java.io.IOException;
+import org.apache.pdfbox.cos.COSString;
+
+/**
+ * @author Huault : huault@free.fr
+ * @version $Revision: 1.6 $
+ */
+
+public class ShowTextGlyph extends OperatorProcessor
+{
+ /**
+ * TJ Show text, allowing individual glyph positioning.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If there is an error processing this operator.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ COSArray array = (COSArray)arguments.get( 0 );
+ int arraySize = array.size();
+ float fontsize = context.getGraphicsState().getTextState().getFontSize();
+ float horizontalScaling = context.getGraphicsState().getTextState().getHorizontalScalingPercent()/100;
+ for( int i=0; i<arraySize; i++ )
+ {
+ COSBase next = array.get( i );
+ if( next instanceof COSNumber )
+ {
+ float adjustment = ((COSNumber)next).floatValue();
+ Matrix adjMatrix = new Matrix();
+ adjustment=-(adjustment/1000)*horizontalScaling*fontsize;
+ // TODO vertical writing mode
+ adjMatrix.setValue( 2, 0, adjustment );
+ context.setTextMatrix( adjMatrix.multiply(context.getTextMatrix(), adjMatrix) );
+ }
+ else if( next instanceof COSString )
+ {
+ context.processEncodedText( ((COSString)next).getBytes() );
+ }
+ else
+ {
+ throw new IOException( "Unknown type in array for TJ operation:" + next );
+ }
+ }
+ }
+
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains implementations of all of the PDF operators.
+</body>
+</html>
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class AppendRectangleToPath extends OperatorProcessor
+{
+
+
+ /**
+ * process : re : append rectangle to path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ PageDrawer drawer = (PageDrawer)context;
+
+ COSNumber x = (COSNumber)arguments.get( 0 );
+ COSNumber y = (COSNumber)arguments.get( 1 );
+ COSNumber w = (COSNumber)arguments.get( 2 );
+ COSNumber h = (COSNumber)arguments.get( 3 );
+
+ double x1 = x.doubleValue();
+ double y1 = y.doubleValue();
+ // create a pair of coordinates for the transformation
+ double x2 = w.doubleValue()+x1;
+ double y2 = h.doubleValue()+y1;
+
+ Point2D startCoords = drawer.transformedPoint(x1,y1);
+ Point2D endCoords = drawer.transformedPoint(x2,y2);
+
+ float width = (float)(endCoords.getX()-startCoords.getX());
+ float height = (float)(endCoords.getY()-startCoords.getY());
+ float xStart = (float)startCoords.getX();
+ float yStart = (float)startCoords.getY();
+
+ // To ensure that the path is created in the right direction,
+ // we have to create it by combining single lines instead of
+ // creating a simple rectangle
+ GeneralPath path = drawer.getLinePath();
+ path.moveTo(xStart, yStart);
+ path.lineTo(xStart+width, yStart);
+ path.lineTo(xStart+width, yStart+height);
+ path.lineTo(xStart, yStart+height);
+ path.lineTo(xStart, yStart);
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDInlinedImage;
+import org.apache.pdfbox.util.ImageParameters;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class BeginInlineImage extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(BeginInlineImage.class);
+
+ /**
+ * process : BI : begin inline image.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If there is an error displaying the inline image.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PageDrawer drawer = (PageDrawer)context;
+ PDPage page = drawer.getPage();
+ //begin inline image object
+ ImageParameters params = operator.getImageParameters();
+ PDInlinedImage image = new PDInlinedImage();
+ image.setImageParameters( params );
+ image.setImageData( operator.getImageData() );
+ BufferedImage awtImage = image.createImage( context.getColorSpaces() );
+
+ if (awtImage == null)
+ {
+ log.warn("BeginInlineImage.process(): createImage returned NULL");
+ return;
+ }
+ int imageWidth = awtImage.getWidth();
+ int imageHeight = awtImage.getHeight();
+ double pageHeight = drawer.getPageSize().getHeight();
+
+ Matrix ctm = drawer.getGraphicsState().getCurrentTransformationMatrix();
+ int pageRotation = page.findRotation();
+
+ AffineTransform ctmAT = ctm.createAffineTransform();
+ ctmAT.scale(1f/imageWidth, 1f/imageHeight);
+ Matrix rotationMatrix = new Matrix();
+ rotationMatrix.setFromAffineTransform( ctmAT );
+ // calculate the inverse rotation angle
+ // scaleX = m00 = cos
+ // shearX = m01 = -sin
+ // tan = sin/cos
+ double angle = Math.atan(ctmAT.getShearX()/ctmAT.getScaleX());
+ Matrix translationMatrix = null;
+ if (pageRotation == 0 || pageRotation == 180)
+ {
+ translationMatrix = Matrix.getTranslatingInstance((float)(Math.sin(angle)*ctm.getXScale()), (float)(pageHeight-2*ctm.getYPosition()-Math.cos(angle)*ctm.getYScale()));
+ }
+ else if (pageRotation == 90 || pageRotation == 270)
+ {
+ translationMatrix = Matrix.getTranslatingInstance((float)(Math.sin(angle)*ctm.getYScale()), (float)(pageHeight-2*ctm.getYPosition()));
+ }
+ rotationMatrix = rotationMatrix.multiply(translationMatrix);
+ rotationMatrix.setValue(0, 1, (-1)*rotationMatrix.getValue(0, 1));
+ rotationMatrix.setValue(1, 0, (-1)*rotationMatrix.getValue(1, 0));
+ AffineTransform at = new AffineTransform(
+ rotationMatrix.getValue(0,0),rotationMatrix.getValue(0,1),
+ rotationMatrix.getValue(1,0), rotationMatrix.getValue( 1, 1),
+ rotationMatrix.getValue(2,0),rotationMatrix.getValue(2,1)
+ );
+ drawer.drawImage(awtImage, at);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:Daniel.Wilson@BlackLocustSoftware.com">Daniel Wilson</a>
+ * @version $Revision: 1.1 $
+ */
+public class ClipEvenOddRule extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(ClipEvenOddRule.class);
+
+ /**
+ * process : W* : set clipping path using even odd rule.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException if there is an error during execution.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+
+ try
+ {
+ PageDrawer drawer = (PageDrawer)context;
+ drawer.setClippingPath(GeneralPath.WIND_EVEN_ODD);
+ }
+ catch (Exception e)
+ {
+ log.warn(e, e);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:Daniel.Wilson@BlackLocustSoftware.com">Daniel Wilson</a>
+ * @version $Revision: 1.1 $
+ */
+public class ClipNonZeroRule extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(ClipNonZeroRule.class);
+
+ /**
+ * process : W : Set the clipping path using non zero winding rule.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If there is an error during the processing.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+
+ try
+ {
+ PageDrawer drawer = (PageDrawer)context;
+ drawer.setClippingPath(GeneralPath.WIND_NON_ZERO);
+ }
+ catch (Exception e)
+ {
+ log.warn(e, e);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class CloseFillEvenOddAndStrokePath extends org.apache.pdfbox.util.operator.OperatorProcessor
+{
+
+ /**
+ * fill and stroke the path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ context.processOperator( "h", arguments );
+
+ context.processOperator( "f*", arguments );
+
+ context.processOperator( "S", arguments );
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class CloseFillNonZeroAndStrokePath extends OperatorProcessor
+{
+
+ /**
+ * fill and stroke the path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ context.processOperator( "h", arguments );
+
+ context.processOperator( "f", arguments );
+
+ context.processOperator( "S", arguments );
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class ClosePath extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(ClosePath.class);
+
+ /**
+ * process : h : Close path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException if something went wrong during logging
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PageDrawer drawer = (PageDrawer)context;
+ try
+ {
+ drawer.getLinePath().closePath();
+ }
+ catch( Throwable t )
+ {
+ log.warn(t, t);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+import java.awt.geom.Point2D;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class CurveTo extends OperatorProcessor
+{
+
+
+ /**
+ * process : c : Append curved segment to path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ PageDrawer drawer = (PageDrawer)context;
+
+ COSNumber x1 = (COSNumber)arguments.get( 0 );
+ COSNumber y1 = (COSNumber)arguments.get( 1 );
+ COSNumber x2 = (COSNumber)arguments.get( 2 );
+ COSNumber y2 = (COSNumber)arguments.get( 3 );
+ COSNumber x3 = (COSNumber)arguments.get( 4 );
+ COSNumber y3 = (COSNumber)arguments.get( 5 );
+
+ Point2D point1 = drawer.transformedPoint(x1.doubleValue(), y1.doubleValue());
+ Point2D point2 = drawer.transformedPoint(x2.doubleValue(), y2.doubleValue());
+ Point2D point3 = drawer.transformedPoint(x3.doubleValue(), y3.doubleValue());
+
+ drawer.getLinePath().curveTo((float)point1.getX(), (float)point1.getY(),
+ (float)point2.getX(), (float)point2.getY(), (float)point3.getX(), (float)point3.getY());
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+import java.awt.geom.Point2D;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class CurveToReplicateFinalPoint extends OperatorProcessor
+{
+
+
+ /**
+ * process : y : Append curved segment to path (final point replicated).
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ PageDrawer drawer = (PageDrawer)context;
+
+ COSNumber x1 = (COSNumber)arguments.get( 0 );
+ COSNumber y1 = (COSNumber)arguments.get( 1 );
+ COSNumber x3 = (COSNumber)arguments.get( 2 );
+ COSNumber y3 = (COSNumber)arguments.get( 3 );
+
+ Point2D point1 = drawer.transformedPoint(x1.doubleValue(), y1.doubleValue());
+ Point2D point3 = drawer.transformedPoint(x3.doubleValue(), y3.doubleValue());
+
+ drawer.getLinePath().curveTo((float)point1.getX(), (float)point1.getY(),
+ (float)point3.getX(), (float)point3.getY(), (float)point3.getX(), (float)point3.getY());
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class CurveToReplicateInitialPoint extends OperatorProcessor
+{
+
+
+ /**
+ * process : v : Append curved segment to path (initial point replicated).
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ PageDrawer drawer = (PageDrawer)context;
+
+ COSNumber x2 = (COSNumber)arguments.get( 0 );
+ COSNumber y2 = (COSNumber)arguments.get( 1 );
+ COSNumber x3 = (COSNumber)arguments.get( 2 );
+ COSNumber y3 = (COSNumber)arguments.get( 3 );
+ GeneralPath path = drawer.getLinePath();
+ Point2D currentPoint = path.getCurrentPoint();
+
+ Point2D point2 = drawer.transformedPoint(x2.doubleValue(), y2.doubleValue());
+ Point2D point3 = drawer.transformedPoint(x3.doubleValue(), y3.doubleValue());
+
+ drawer.getLinePath().curveTo((float)currentPoint.getX(), (float)currentPoint.getY(),
+ (float)point2.getX(), (float)point2.getY(), (float)point3.getX(), (float)point3.getY());
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class EndPath extends OperatorProcessor
+{
+
+
+ /**
+ * process : n : End path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ PageDrawer drawer = (PageDrawer)context;
+ drawer.getLinePath().reset();
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 101 $
+ */
+public class FillEvenOddAndStrokePath extends org.apache.pdfbox.util.operator.OperatorProcessor
+{
+
+ /**
+ * fill and stroke the path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PageDrawer drawer = (PageDrawer)context;
+ GeneralPath currentPath = (GeneralPath)drawer.getLinePath().clone();
+
+ context.processOperator( "f*", arguments );
+ drawer.setLinePath( currentPath );
+
+ context.processOperator( "S", arguments );
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class FillEvenOddRule extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(FillEvenOddRule.class);
+
+ /**
+ * process : f* : fill path using even odd rule.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException if there is an error during execution.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ try
+ {
+ ///dwilson refactoring
+ PageDrawer drawer = (PageDrawer)context;
+ drawer.fillPath(GeneralPath.WIND_EVEN_ODD);
+ }
+ catch (Exception e)
+ {
+ log.warn(e, e);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.1 $
+ */
+public class FillNonZeroAndStrokePath extends org.apache.pdfbox.util.operator.OperatorProcessor
+{
+
+ /**
+ * fill and stroke the path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PageDrawer drawer = (PageDrawer)context;
+ GeneralPath currentPath = (GeneralPath)drawer.getLinePath().clone();
+
+ context.processOperator( "f", arguments );
+ drawer.setLinePath( currentPath );
+
+ context.processOperator( "S", arguments );
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.5 $
+ */
+public class FillNonZeroRule extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(FillNonZeroRule.class);
+
+ /**
+ * process : F/f : fill path using non zero winding rule.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If there is an error during the processing.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+
+ try
+ {
+ ///dwilson refactoring
+ PageDrawer drawer = (PageDrawer)context;
+ drawer.fillPath(GeneralPath.WIND_NON_ZERO);
+ }
+ catch (Exception e)
+ {
+ log.warn(e, e);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.graphics.PDGraphicsState;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm;
+import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.4 $
+ */
+public class Invoke extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(Invoke.class);
+
+ /**
+ * process : Do : Paint the specified XObject (section 4.7).
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If there is an error invoking the sub object.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ PageDrawer drawer = (PageDrawer)context;
+ PDPage page = drawer.getPage();
+ COSName objectName = (COSName)arguments.get( 0 );
+ Map xobjects = drawer.getResources().getXObjects();
+ PDXObject xobject = (PDXObject)xobjects.get( objectName.getName() );
+ if ( xobject == null )
+ {
+ log.warn("Can't find the XObject for '"+objectName.getName()+"'");
+ }
+ else if( xobject instanceof PDXObjectImage )
+ {
+ PDXObjectImage image = (PDXObjectImage)xobject;
+ try
+ {
+ image.setGraphicsState(drawer.getGraphicsState());
+ BufferedImage awtImage = image.getRGBImage();
+ if (awtImage == null)
+ {
+ log.warn("getRGBImage returned NULL");
+ return;//TODO PKOCH
+ }
+ int imageWidth = awtImage.getWidth();
+ int imageHeight = awtImage.getHeight();
+ double pageHeight = drawer.getPageSize().getHeight();
+
+ log.debug("imageWidth: " + imageWidth + "\t\timageHeight: " + imageHeight);
+
+ Matrix ctm = drawer.getGraphicsState().getCurrentTransformationMatrix();
+ float yScaling = ctm.getYScale();
+ float angle = (float)Math.acos(ctm.getValue(0, 0)/ctm.getXScale());
+ if (ctm.getValue(0, 1) < 0 && ctm.getValue(1, 0) > 0)
+ angle = (-1)*angle;
+ ctm.setValue(2, 1, (float)(pageHeight - ctm.getYPosition() - Math.cos(angle)*yScaling));
+ ctm.setValue(2, 0, (float)(ctm.getXPosition() - Math.sin(angle)*yScaling));
+ // because of the moved 0,0-reference, we have to shear in the opposite direction
+ ctm.setValue(0, 1, (-1)*ctm.getValue(0, 1));
+ ctm.setValue(1, 0, (-1)*ctm.getValue(1, 0));
+ AffineTransform ctmAT = ctm.createAffineTransform();
+ ctmAT.scale(1f/imageWidth, 1f/imageHeight);
+ drawer.drawImage( awtImage, ctmAT );
+ }
+ catch( Exception e )
+ {
+ e.printStackTrace();
+ log.error(e, e);
+ }
+ }
+ else if(xobject instanceof PDXObjectForm)
+ {
+ // save the graphics state
+ context.getGraphicsStack().push( (PDGraphicsState)context.getGraphicsState().clone() );
+
+ PDXObjectForm form = (PDXObjectForm)xobject;
+ COSStream invoke = (COSStream)form.getCOSObject();
+ PDResources pdResources = form.getResources();
+ if(pdResources == null)
+ {
+ pdResources = page.findResources();
+ }
+ // if there is an optional form matrix, we have to
+ // map the form space to the user space
+ Matrix matrix = form.getMatrix();
+ if (matrix != null)
+ {
+ Matrix xobjectCTM = matrix.multiply( context.getGraphicsState().getCurrentTransformationMatrix());
+ context.getGraphicsState().setCurrentTransformationMatrix(xobjectCTM);
+ }
+ getContext().processSubStream( page, pdResources, invoke );
+
+ // restore the graphics state
+ context.setGraphicsState( (PDGraphicsState)context.getGraphicsStack().pop() );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+import java.awt.geom.Point2D;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class LineTo extends OperatorProcessor
+{
+
+
+ /**
+ * process : l : Append straight line segment to path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments)
+ {
+ PageDrawer drawer = (PageDrawer)context;
+
+ //append straight line segment from the current point to the point.
+ COSNumber x = (COSNumber)arguments.get( 0 );
+ COSNumber y = (COSNumber)arguments.get( 1 );
+
+ Point2D pos = drawer.transformedPoint(x.doubleValue(), y.doubleValue());
+ drawer.getLinePath().lineTo((float)pos.getX(), (float)pos.getY());
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.awt.geom.Point2D;
+import java.util.List;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class MoveTo extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(MoveTo.class);
+
+ /**
+ * process : m : Begin new subpath.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If there is an error processing the operator.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ try
+ {
+ PageDrawer drawer = (PageDrawer)context;
+ COSNumber x = (COSNumber)arguments.get( 0 );
+ COSNumber y = (COSNumber)arguments.get( 1 );
+ Point2D pos = drawer.transformedPoint(x.doubleValue(), y.doubleValue());
+ drawer.getLinePath().moveTo((float)pos.getX(), (float)pos.getY());
+ }
+ catch (Exception exception)
+ {
+ log.warn( exception, exception);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.awt.geom.GeneralPath;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+/**
+ * Implementation of sh operator for page drawer.
+ * See section 4.6.3 of the PDF 1.7 specification.
+ *
+ * @author <a href="mailto:Daniel.Wilson@BlackLocustSoftware.com">Daniel Wilson</a>
+ * @version $Revision: 1.0 $
+ */
+public class SHFill extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(SHFill.class);
+
+ /**
+ * process : sh : shade fill the path or clipping area.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException if there is an error during execution.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ try
+ {
+ PageDrawer drawer = (PageDrawer)context;
+ drawer.SHFill((COSName)(arguments.get(0)));
+
+ }
+ catch (Exception e)
+ {
+ log.warn(e, e);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de>Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineCapStyle extends org.apache.pdfbox.util.operator.SetLineCapStyle
+{
+
+ /**
+ * Set the line cap style.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ super.process( operator, arguments );
+ int lineCapStyle = context.getGraphicsState().getLineCap();
+ PageDrawer drawer = (PageDrawer)context;
+ BasicStroke stroke = (BasicStroke)drawer.getStroke();
+ if (stroke == null)
+ {
+ drawer.setStroke( new BasicStroke(1,lineCapStyle,BasicStroke.JOIN_MITER) );
+ }
+ else
+ {
+ drawer.setStroke( new BasicStroke(stroke.getLineWidth(), lineCapStyle, stroke.getLineJoin(),
+ stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()));
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.pdmodel.graphics.PDLineDashPattern;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineDashPattern extends org.apache.pdfbox.util.operator.SetLineDashPattern
+{
+
+ /**
+ * Set the line dash pattern.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ super.process( operator, arguments );
+ PDLineDashPattern lineDashPattern = context.getGraphicsState().getLineDashPattern();
+ PageDrawer drawer = (PageDrawer)context;
+ BasicStroke stroke = (BasicStroke)drawer.getStroke();
+ if (stroke == null)
+ {
+ if (lineDashPattern.isDashPatternEmpty())
+ {
+ drawer.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f) );
+ }
+ else
+ {
+ drawer.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f,
+ lineDashPattern.getCOSDashPattern().toFloatArray(), lineDashPattern.getPhaseStart()) );
+ }
+ }
+ else
+ {
+ if (lineDashPattern.isDashPatternEmpty())
+ {
+ drawer.setStroke( new BasicStroke(stroke.getLineWidth(), stroke.getEndCap(),
+ stroke.getLineJoin(), stroke.getMiterLimit()) );
+ }
+ else
+ {
+ drawer.setStroke( new BasicStroke(stroke.getLineWidth(), stroke.getEndCap(), stroke.getLineJoin(),
+ stroke.getMiterLimit(), lineDashPattern.getCOSDashPattern().toFloatArray(),
+ lineDashPattern.getPhaseStart()) );
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de>Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineJoinStyle extends org.apache.pdfbox.util.operator.SetLineJoinStyle
+{
+
+ /**
+ * Set the line cap style.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ super.process( operator, arguments );
+ int lineJoinStyle = context.getGraphicsState().getLineJoin();
+ PageDrawer drawer = (PageDrawer)context;
+ BasicStroke stroke = (BasicStroke)drawer.getStroke();
+ if (stroke == null)
+ {
+ drawer.setStroke( new BasicStroke(1,BasicStroke.CAP_SQUARE,lineJoinStyle) );
+ }
+ else
+ {
+ drawer.setStroke( new BasicStroke(stroke.getLineWidth(), stroke.getEndCap(), lineJoinStyle,
+ stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()) );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:andreas@lehmi.de">Andreas Lehmkühler</a>
+ * @version $Revision: 1.0 $
+ */
+public class SetLineMiterLimit extends org.apache.pdfbox.util.operator.SetLineMiterLimit
+{
+
+ /**
+ * Set the line dash pattern.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ super.process(operator, arguments);
+ float miterLimit = (float)context.getGraphicsState().getMiterLimit();
+ PageDrawer drawer = (PageDrawer)context;
+ BasicStroke stroke = (BasicStroke)drawer.getStroke();
+ if (stroke == null)
+ {
+ drawer.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER,
+ miterLimit, null, 0.0f));
+ }
+ else
+ {
+ drawer.setStroke( new BasicStroke(stroke.getLineWidth(), stroke.getEndCap(), stroke.getLineJoin(),
+ miterLimit, null, 0.0f));
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.PDFOperator;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.2 $
+ */
+public class SetLineWidth extends org.apache.pdfbox.util.operator.SetLineWidth
+{
+
+ /**
+ * w Set line width.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ super.process( operator, arguments );
+ float lineWidth = (float)context.getGraphicsState().getLineWidth();
+ if (lineWidth == 0)
+ {
+ lineWidth = 1;
+ }
+ PageDrawer drawer = (PageDrawer)context;
+ BasicStroke stroke = (BasicStroke)drawer.getStroke();
+ if (stroke == null)
+ {
+ drawer.setStroke( new BasicStroke( lineWidth ) );
+ }
+ else
+ {
+ drawer.setStroke( new BasicStroke(lineWidth, stroke.getEndCap(), stroke.getLineJoin(),
+ stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()) );
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.operator.pagedrawer;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.pdfviewer.PageDrawer;
+import org.apache.pdfbox.util.Matrix;
+import org.apache.pdfbox.util.PDFOperator;
+import org.apache.pdfbox.util.operator.OperatorProcessor;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+/**
+ * Implementation of content stream operator for page drawer.
+ *
+ * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
+ * @version $Revision: 1.3 $
+ */
+public class StrokePath extends OperatorProcessor
+{
+
+ /**
+ * Log instance.
+ */
+ private static final Log log = LogFactory.getLog(StrokePath.class);
+
+ /**
+ * S stroke the path.
+ * @param operator The operator that is being executed.
+ * @param arguments List
+ *
+ * @throws IOException If an error occurs while processing the font.
+ */
+ public void process(PDFOperator operator, List<COSBase> arguments) throws IOException
+ {
+ ///dwilson 3/19/07 refactor
+ try
+ {
+ PageDrawer drawer = (PageDrawer)context;
+
+ float lineWidth = (float)context.getGraphicsState().getLineWidth();
+ Matrix ctm = context.getGraphicsState().getCurrentTransformationMatrix();
+ if ( ctm != null && ctm.getXScale() > 0)
+ {
+ lineWidth = lineWidth * ctm.getXScale();
+ }
+
+ BasicStroke stroke = (BasicStroke)drawer.getStroke();
+ if (stroke == null)
+ {
+ drawer.setStroke( new BasicStroke( lineWidth ) );
+ }
+ else
+ {
+ drawer.setStroke( new BasicStroke(lineWidth, stroke.getEndCap(), stroke.getLineJoin(),
+ stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()) );
+ }
+ drawer.strokePath();
+ }
+ catch (Exception exception)
+ {
+ log.warn(exception, exception);
+ }
+ }
+}
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains implementations of all of the PDF operators.
+</body>
+</html>
--- /dev/null
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains utility classes that are used by the PDFBox project.
+</body>
+</html>