From a08f142e35fe7c4511e00187f35033250021b34c Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Tue, 19 Mar 2013 17:37:03 +0000 Subject: [PATCH 0001/1017] [maven-release-plugin] copy for branch 1.8 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1458423 13f79535-47bb-0310-9956-ffa450edef68 From caca777a6850e96d77148b6d78fa5e7653812631 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 7 Apr 2013 13:29:12 +0000 Subject: [PATCH 0002/1017] merged the following changes from trunk PDFBOX-1515 rev. 1465217 PDFBOX-1538 rev. 1460370 PDFBOX-1547 rev. 1461796 PDFBOX-1549 rev. 1465359 PDFBOX-1551 rev. 1465163 PDFBOX-1557 rev. 1465366 PDFBOX-1558 rev. 1465360 PDFBOX-1559 rev. 1465266 DOAP file rev. 1460372 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1465396 13f79535-47bb-0310-9956-ffa450edef68 --- doap_PDFBox.rdf | 5 + .../org/apache/fontbox/ttf/TTFSubFont.java | 158 +- .../pdfparser/NonSequentialPDFParser.java | 1740 +++++++++-------- .../apache/pdfbox/pdfviewer/PageDrawer.java | 16 +- .../apache/pdfbox/pdfwriter/COSWriter.java | 2 +- .../org/apache/pdfbox/pdmodel/PDPage.java | 12 + .../pdfbox/pdmodel/font/PDTrueTypeFont.java | 12 +- .../annotation/PDAppearanceStream.java | 55 +- .../pdmodel/interactive/form/PDSignature.java | 4 + .../apache/pdfbox/util/PDFStreamEngine.java | 4 +- 10 files changed, 1176 insertions(+), 832 deletions(-) diff --git a/doap_PDFBox.rdf b/doap_PDFBox.rdf index a935a69a63e..33bbbc58195 100644 --- a/doap_PDFBox.rdf +++ b/doap_PDFBox.rdf @@ -35,6 +35,11 @@ + + Apache PDFBox + 2013-03-23 + 1.8.0 + Apache PDFBox 2012-07-25 diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java b/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java index 83f2af7c430..daddf5b5a67 100755 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubFont.java @@ -153,10 +153,10 @@ private static long buildUint32(int high, int low) private static long buildUint32(byte[] bytes) { - return ((((long)bytes[3])&0xffL) << 24) | - ((((long)bytes[2])&0xffL) << 16) | - ((((long)bytes[1])&0xffL) << 8) | - (((long)bytes[0])&0xffL); + return ((((long)bytes[0])&0xffL) << 24) | + ((((long)bytes[1])&0xffL) << 16) | + ((((long)bytes[2])&0xffL) << 8) | + (((long)bytes[3])&0xffL); } /** @@ -200,11 +200,6 @@ private static long writeTableHeader(DataOutputStream dos, String tag, long offs checksum &= 0xffffffffL; - if ((n%4)!=0) - { - nup += 4-(n%4); - } - LOG.debug(String.format("Writing table header [%s,%08x,%08x,%08x]",tag,checksum,offset,bytes.length)); byte[] tagbytes = tag.getBytes("US-ASCII"); @@ -214,7 +209,8 @@ private static long writeTableHeader(DataOutputStream dos, String tag, long offs dos.writeInt((int)offset); dos.writeInt(bytes.length); - return buildUint32(tagbytes) + checksum + offset + bytes.length; + // account for the checksum twice, one time for the header field, on time for the content itself. + return buildUint32(tagbytes) + checksum + checksum + offset + bytes.length; } private static void writeTableBody(OutputStream os, byte[] bytes) throws IOException @@ -472,9 +468,9 @@ private byte[] buildMaxpTable() throws IOException writeUint16(dos,p.getMaxTwilightPoints()); writeUint16(dos,p.getMaxStorage()); writeUint16(dos,p.getMaxFunctionDefs()); - writeUint16(dos,p.getMaxInstructionDefs()); + writeUint16(dos,0); writeUint16(dos,p.getMaxStackElements()); - writeUint16(dos,p.getMaxSizeOfInstructions()); + writeUint16(dos,0); writeUint16(dos,p.getMaxComponentElements()); writeUint16(dos,p.getMaxComponentDepth()); @@ -497,7 +493,7 @@ private byte[] buildOS2Table() throws IOException LOG.debug("Building table [OS/2]..."); - writeUint16(dos,1); + writeUint16(dos,0); writeSint16(dos,os2.getAverageCharWidth()); writeUint16(dos,os2.getWeightClass()); writeUint16(dos,os2.getWidthClass()); @@ -520,10 +516,10 @@ private byte[] buildOS2Table() throws IOException writeUint8(dos,os2.getFamilySubClass()); dos.write(os2.getPanose()); - writeUint32(dos,os2.getUnicodeRange1()); - writeUint32(dos,os2.getUnicodeRange2()); - writeUint32(dos,os2.getUnicodeRange3()); - writeUint32(dos,os2.getUnicodeRange4()); + writeUint32(dos,0); + writeUint32(dos,0); + writeUint32(dos,0); + writeUint32(dos,0); dos.write(os2.getAchVendId().getBytes("ISO-8859-1")); @@ -534,8 +530,18 @@ private byte[] buildOS2Table() throws IOException writeUint16(dos,os2.getFsSelection()); writeUint16(dos,first.getKey()); writeUint16(dos,characters.lastKey()); + /* + * The mysterious Microsoft additions. + * + * SHORT sTypoAscender + * SHORT sTypoDescender + * SHORT sTypoLineGap + * USHORT usWinAscent + * USHORT usWinDescent + */ writeUint16(dos,os2.getTypoAscender()); writeUint16(dos,os2.getTypoDescender()); + writeUint16(dos,os2.getTypeLineGap()); writeUint16(dos,os2.getWinAscent()); writeUint16(dos,os2.getWinDescent()); @@ -544,22 +550,17 @@ private byte[] buildOS2Table() throws IOException return bos.toByteArray(); } - private byte[] buildLocaTable() throws IOException + private byte[] buildLocaTable(long[] newOffsets) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); LOG.debug("Building table [loca]..."); - long[] offsets = this.baseTTF.getIndexToLocation().getOffsets(); - long offset = 0L; - for (Integer glyphId : this.glyphIds) - { - LOG.debug(String.format("glyphId={%d},offset={%08x}",glyphId,offset)); - writeUint32(dos,offset); - offset += offsets[glyphId.intValue()+1] - offsets[glyphId.intValue()]; + for (long newOff : newOffsets) + { + writeUint32(dos,newOff); } - writeUint32(dos,offset); dos.flush(); LOG.debug("Finished table [loca]."); return bos.toByteArray(); @@ -646,7 +647,7 @@ else if ((flags & (1 << 3)) != 0) return glyphIdsToAdd == null; } - private byte[] buildGlyfTable() throws IOException + private byte[] buildGlyfTable(long[] newOffsets) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); LOG.debug("Building table [glyf]..."); @@ -657,10 +658,13 @@ private byte[] buildGlyfTable() throws IOException { is.skip(g.getOffset()); long lastOff = 0L; + long newOff = 0L; + int ioff = 0; for (Integer glyphId : this.glyphIds) { long offset = offsets[glyphId.intValue()]; long len = offsets[glyphId.intValue()+1] - offset; + newOffsets[ioff++] = newOff; is.skip(offset-lastOff); byte[] buf= new byte[(int)len]; is.read(buf); @@ -672,9 +676,11 @@ private byte[] buildGlyfTable() throws IOException int flags = 0; do { - flags = ((((int)buf[off]) & 0xff) << 8) | (buf[off+1] & 0xff); + // clear the WE_HAVE_INSTRUCTIONS bit. (bit 8 is lsb of the first byte) + buf[off] &= 0xfe; + flags = ((((int)buf[off]) & 0xff) << 8) | ((int)buf[off+1] & 0xff); off +=2; - int ogid = ((((int)buf[off]) & 0xff) << 8) | (buf[off+1] & 0xff); + int ogid = ((((int)buf[off]) & 0xff) << 8) | ((int)buf[off+1] & 0xff); if (!this.glyphIds.contains(ogid)) { this.glyphIds.add(ogid); @@ -715,10 +721,70 @@ else if ((flags & (1 << 3)) != 0) // MORE_COMPONENTS } while ((flags & (1 << 5)) != 0); + // write the compound glyph + bos.write(buf,0,off); + newOff += off; } - bos.write(buf); + else if (buf.length > 0) + { + /* + * bail out instructions for simple glyphs, an excerpt from the specs is given below: + * + * int16 numberOfContours If the number of contours is positive or zero, it is a single glyph; + * If the number of contours is -1, the glyph is compound + * FWord xMin Minimum x for coordinate data + * FWord yMin Minimum y for coordinate data + * FWord xMax Maximum x for coordinate data + * FWord yMax Maximum y for coordinate data + * (here follow the data for the simple or compound glyph) + * + * Table 15: Simple glyph definition + * Type Name Description + * uint16 endPtsOfContours[n] Array of last points of each contour; n is the number of contours; + * array entries are point indices + * uint16 instructionLength Total number of bytes needed for instructions + * uint8 instructions[instructionLength] Array of instructions for this glyph + * uint8 flags[variable] Array of flags + * uint8 or int16 xCoordinates[] Array of x-coordinates; the first is relative to (0,0), + * others are relative to previous point + * uint8 or int16 yCoordinates[] Array of y-coordinates; the first is relative to (0,0), + * others are relative to previous point + */ + + int numberOfContours = (((int)buf[0]& 0xff) << 8) | ((int)buf[1] & 0xff); + + // offset of instructionLength + int off = 2*5 + numberOfContours * 2; + + // write numberOfContours, xMin, yMin, xMax, yMax, endPtsOfContours[n] + bos.write(buf,0,off); + newOff += off; + + int instructionLength = ((((int)buf[off]) & 0xff) << 8) | ((int)buf[off+1] & 0xff); + + // zarro instructions. + bos.write(0); + bos.write(0); + newOff += 2; + + off += 2 + instructionLength; + + // flags and coordinates + bos.write(buf,off,buf.length - off); + newOff += buf.length - off; + } + + + if ((newOff % 4L) != 0L) + { + int np = (int)(4-newOff%4L); + bos.write(PAD_BUF,0,np); + newOff += np; + } + lastOff = offsets[glyphId.intValue()+1]; } + newOffsets[ioff++] = newOff; } finally { @@ -821,7 +887,7 @@ else if (!lastChar.getKey().equals(prevChar.getKey())) */ writeUint16(dos,4); - writeUint16(dos, 8*2 + nseg * (8*2)); + writeUint16(dos, 8*2 + nseg * (4*2)); writeUint16(dos,0); writeUint16(dos,nseg*2); int nsegHigh = Integer.highestOneBit(nseg); @@ -1050,19 +1116,19 @@ public void writeToStream(OutputStream os) throws IOException 'name' naming 'post' PostScript */ - String[] tableNames = {"head","hhea","maxp","name","OS/2","loca","cmap","hmtx","glyf","post"}; + String[] tableNames = {"OS/2","cmap","glyf","head","hhea","hmtx","loca","maxp","name","post"}; byte [][] tables = new byte[tableNames.length][]; - tables[0] = this.buildHeadTable(); - tables[1] = this.buildHheaTable(); - tables[2] = this.buildMaxpTable(); - tables[3] = this.buildNameTable(); - tables[4] = this.buildOS2Table(); - tables[5] = this.buildLocaTable(); - tables[6] = this.buildCmapTable(); - tables[7] = this.buildHmtxTable(); - tables[8] = this.buildGlyfTable(); + long[] newOffsets = new long[this.glyphIds.size()+1]; + tables[3] = this.buildHeadTable(); + tables[4] = this.buildHheaTable(); + tables[7] = this.buildMaxpTable(); + tables[8] = this.buildNameTable(); + tables[0] = this.buildOS2Table(); + tables[2] = this.buildGlyfTable(newOffsets); + tables[6] = this.buildLocaTable(newOffsets); + tables[1] = this.buildCmapTable(); + tables[5] = this.buildHmtxTable(); tables[9] = this.buildPostTable(); - long checksum = writeFileHeader(dos,tableNames.length); long offset = 12L + 16L * tableNames.length; for (int i=0;i>> 24); - tables[0][9] = (byte)(checksum >>> 16); - tables[0][10] = (byte)(checksum >>> 8); - tables[0][11] = (byte)(checksum); + tables[3][8] = (byte)(checksum >>> 24); + tables[3][9] = (byte)(checksum >>> 16); + tables[3][10] = (byte)(checksum >>> 8); + tables[3][11] = (byte)(checksum); for (int i=0;iQuickParser presented in - * PDFBOX-1104 - * by Jeremy Villalobos. + * This class is a much enhanced version of QuickParser presented + * in PDFBOX-1104 by + * Jeremy Villalobos. */ public class NonSequentialPDFParser extends PDFParser { - - public static final String SYSPROP_PARSEMINIMAL = - "org.apache.pdfbox.pdfparser.nonSequentialPDFParser.parseMinimal"; - public static final String SYSPROP_EOFLOOKUPRANGE = - "org.apache.pdfbox.pdfparser.nonSequentialPDFParser.eofLookupRange"; - - private static final InputStream EMPTY_INPUT_STREAM = new ByteArrayInputStream( new byte[0] ); - - protected static final int DEFAULT_TRAIL_BYTECOUNT = 2048; - protected static final char[] EOF_MARKER = new char[] { '%','%','E','O','F' }; - protected static final char[] STARTXREF_MARKER = new char[] { 's','t','a','r','t','x','r','e','f' }; - protected static final char[] OBJ_MARKER = new char[] { 'o','b','j' }; + + private static final int E = 'e'; + private static final int N = 'n'; + + public static final String SYSPROP_PARSEMINIMAL = "org.apache.pdfbox.pdfparser.nonSequentialPDFParser.parseMinimal"; + public static final String SYSPROP_EOFLOOKUPRANGE = "org.apache.pdfbox.pdfparser.nonSequentialPDFParser.eofLookupRange"; + + private static final InputStream EMPTY_INPUT_STREAM = new ByteArrayInputStream(new byte[0]); + + protected static final int DEFAULT_TRAIL_BYTECOUNT = 2048; + protected static final char[] EOF_MARKER = new char[] + { '%', '%', 'E', 'O', 'F' }; + protected static final char[] STARTXREF_MARKER = new char[] + { 's', 't', 'a', 'r', 't', 'x', 'r', 'e', 'f' }; + protected static final char[] OBJ_MARKER = new char[] + { 'o', 'b', 'j' }; private final File pdfFile; private final RandomAccessBufferedFileInputStream raStream; - + protected SecurityHandler securityHandler = null; - + private String keyStoreFilename = null; - private String alias = null; - private String password = ""; - private int readTrailBytes = DEFAULT_TRAIL_BYTECOUNT; // how many trailing bytes to read for EOF marker - - /** If true object references in catalog are not followed; - * pro: page objects will be only parsed when needed; cons: some information of catalog - * might not be available (e.g. outline). - * Catalog parsing without pages is not an option since a number of entries will - * also refer to page objects (like OpenAction). + private String alias = null; + private String password = ""; + private int readTrailBytes = DEFAULT_TRAIL_BYTECOUNT; // how many trailing + // bytes to read for + // EOF marker + + /** + * If true object references in catalog are not followed; pro: + * page objects will be only parsed when needed; cons: some information of + * catalog might not be available (e.g. outline). Catalog parsing without + * pages is not an option since a number of entries will also refer to page + * objects (like OpenAction). */ - private boolean parseMinimalCatalog = "true".equals( System.getProperty( SYSPROP_PARSEMINIMAL ) ); - + private boolean parseMinimalCatalog = "true".equals(System.getProperty(SYSPROP_PARSEMINIMAL)); + private boolean initialParseDone = false; - private boolean allPagesParsed = false; - - private static final Log LOG = LogFactory.getLog( NonSequentialPDFParser.class ); - - /** - * true if the NonSequentialPDFParser is initialized by a InputStream, in this case - * a temporary file is created. At the end of the {@linkplain #parse()} method,the temporary file will - * be deleted. - */ - private boolean isTmpPDFFile = false; - - public static final String TMP_FILE_PREFIX = "tmpPDF"; - - // ------------------------------------------------------------------------ - /** - * Constructs parser for given file using memory buffer. - * - * @param filename the filename of the pdf to be parsed - * - * @throws IOException If something went wrong. - */ - public NonSequentialPDFParser( String filename ) throws IOException - { - this( new File( filename ), null ); - } - - /** - * Constructs parser for given file using given buffer for temporary storage. - * - * @param file the pdf to be parsed - * @param raBuf the buffer to be used for parsing - * - * @throws IOException If something went wrong. - */ - /** - * Constructs parser for given file using given buffer for temporary storage. - * - * @param file the pdf to be parsed - * @param raBuf the buffer to be used for parsing - * - * @throws IOException If something went wrong. - */ - public NonSequentialPDFParser( File file, RandomAccess raBuf ) throws IOException - { - this(file, raBuf, ""); - } - - /** - * Constructs parser for given file using given buffer for temporary storage. - * - * @param file the pdf to be parsed - * @param raBuf the buffer to be used for parsing - * - * @throws IOException If something went wrong. - */ - /** - * Constructs parser for given file using given buffer for temporary storage. - * - * @param file the pdf to be parsed - * @param raBuf the buffer to be used for parsing - * @param decryptionPassword password to be used for decryption - * - * @throws IOException If something went wrong. - */ - public NonSequentialPDFParser( File file, RandomAccess raBuf, String decryptionPassword ) throws IOException - { - super( EMPTY_INPUT_STREAM, null, false ); - pdfFile = file; - raStream = new RandomAccessBufferedFileInputStream( pdfFile ); - init(file, raBuf, decryptionPassword); - } - - private void init(File file, RandomAccess raBuf, String decryptionPassword) throws IOException { - String eofLookupRangeStr = System.getProperty( SYSPROP_EOFLOOKUPRANGE ); - if ( eofLookupRangeStr != null ) - { - try - { - setEOFLookupRange( Integer.parseInt( eofLookupRangeStr ) ); - } - catch ( NumberFormatException nfe ) - { - LOG.warn( "System property " + SYSPROP_EOFLOOKUPRANGE + - " does not contain an integer value, but: '" + eofLookupRangeStr + "'" ); - } - } - - setDocument( ( raBuf == null ) ? new COSDocument( new RandomAccessBuffer(), false ) : new COSDocument( raBuf, false ) ); - - pdfSource = new PushBackInputStream( raStream, 4096 ); - - password = decryptionPassword; - } - - public NonSequentialPDFParser(InputStream input) throws IOException - { - super( EMPTY_INPUT_STREAM, null, false ); - pdfFile = createTmpFile(input); - raStream = new RandomAccessBufferedFileInputStream( pdfFile ); - init(pdfFile, null, ""); - } - - /** - * Create a temporary file with the input stream. - * If the creation succeed, the {@linkplain #isTmpPDFFile} is set to true. - * This Temporary file will be deleted at end of the parse method - * @param input - * @return - * @throws IOException - */ - private File createTmpFile(InputStream input) throws IOException { - File tmpFile = null; - FileOutputStream fos = null; - try { - tmpFile = File.createTempFile(TMP_FILE_PREFIX, ".pdf"); - fos = new FileOutputStream(tmpFile); - IOUtils.copy(input, fos); - isTmpPDFFile = true; - return tmpFile; - } finally { - IOUtils.closeQuietly(input); - IOUtils.closeQuietly(fos); - } - } + private boolean allPagesParsed = false; + + private static final Log LOG = LogFactory.getLog(NonSequentialPDFParser.class); + + /** + * true if the NonSequentialPDFParser is initialized by a + * InputStream, in this case a temporary file is created. At the end of the + * {@linkplain #parse()} method,the temporary file will be deleted. + */ + private boolean isTmpPDFFile = false; + + public static final String TMP_FILE_PREFIX = "tmpPDF"; // ------------------------------------------------------------------------ - /** - * Sets how many trailing bytes of PDF file are searched for - * EOF marker and 'startxref' marker. - * If not set we use default value {@link #DEFAULT_TRAIL_BYTECOUNT}. - * - * - * - *

In case system property {@link #SYSPROP_EOFLOOKUPRANGE} is defined - * this value will be set on initialization but can be overwritten later.

- * - * @param byteCount number of trailing bytes + /** + * Constructs parser for given file using memory buffer. + * + * @param filename the filename of the pdf to be parsed + * + * @throws IOException If something went wrong. + */ + public NonSequentialPDFParser(String filename) throws IOException + { + this(new File(filename), null); + } + + /** + * Constructs parser for given file using given buffer for temporary + * storage. + * + * @param file the pdf to be parsed + * @param raBuf the buffer to be used for parsing + * + * @throws IOException If something went wrong. + */ + /** + * Constructs parser for given file using given buffer for temporary + * storage. + * + * @param file the pdf to be parsed + * @param raBuf the buffer to be used for parsing + * + * @throws IOException If something went wrong. + */ + public NonSequentialPDFParser(File file, RandomAccess raBuf) throws IOException + { + this(file, raBuf, ""); + } + + /** + * Constructs parser for given file using given buffer for temporary + * storage. + * + * @param file the pdf to be parsed + * @param raBuf the buffer to be used for parsing + * + * @throws IOException If something went wrong. + */ + /** + * Constructs parser for given file using given buffer for temporary + * storage. + * + * @param file the pdf to be parsed + * @param raBuf the buffer to be used for parsing + * @param decryptionPassword password to be used for decryption + * + * @throws IOException If something went wrong. */ - public void setEOFLookupRange( int byteCount ) + public NonSequentialPDFParser(File file, RandomAccess raBuf, String decryptionPassword) throws IOException { - if ( byteCount > 15 ) + super(EMPTY_INPUT_STREAM, null, false); + pdfFile = file; + raStream = new RandomAccessBufferedFileInputStream(pdfFile); + init(file, raBuf, decryptionPassword); + } + + private void init(File file, RandomAccess raBuf, String decryptionPassword) throws IOException + { + String eofLookupRangeStr = System.getProperty(SYSPROP_EOFLOOKUPRANGE); + if (eofLookupRangeStr != null) + { + try { - readTrailBytes = byteCount; + setEOFLookupRange(Integer.parseInt(eofLookupRangeStr)); } + catch (NumberFormatException nfe) + { + LOG.warn("System property " + SYSPROP_EOFLOOKUPRANGE + " does not contain an integer value, but: '" + + eofLookupRangeStr + "'"); + } + } + + setDocument((raBuf == null) ? new COSDocument(new RandomAccessBuffer(), false) : new COSDocument(raBuf, false)); + + pdfSource = new PushBackInputStream(raStream, 4096); + + password = decryptionPassword; + } + + public NonSequentialPDFParser(InputStream input) throws IOException + { + super(EMPTY_INPUT_STREAM, null, false); + pdfFile = createTmpFile(input); + raStream = new RandomAccessBufferedFileInputStream(pdfFile); + init(pdfFile, null, ""); + } + + /** + * Create a temporary file with the input stream. If the creation succeed, + * the {@linkplain #isTmpPDFFile} is set to true. This Temporary file will + * be deleted at end of the parse method + * + * @param input + * @return + * @throws IOException + */ + private File createTmpFile(InputStream input) throws IOException + { + File tmpFile = null; + FileOutputStream fos = null; + try + { + tmpFile = File.createTempFile(TMP_FILE_PREFIX, ".pdf"); + fos = new FileOutputStream(tmpFile); + IOUtils.copy(input, fos); + isTmpPDFFile = true; + return tmpFile; + } + finally + { + IOUtils.closeQuietly(input); + IOUtils.closeQuietly(fos); + } + } + + // ------------------------------------------------------------------------ + /** + * Sets how many trailing bytes of PDF file are searched for EOF marker and + * 'startxref' marker. If not set we use default value + * {@link #DEFAULT_TRAIL_BYTECOUNT}. + * + * + * + *

In case system property {@link #SYSPROP_EOFLOOKUPRANGE} is defined + * this value will be set on initialization but can be overwritten + * later.

+ * + * @param byteCount number of trailing bytes + */ + public void setEOFLookupRange(int byteCount) + { + if (byteCount > 15) + { + readTrailBytes = byteCount; + } } - + // ------------------------------------------------------------------------ /** - * The initial parse will first parse only the trailer, the xrefstart and - * all xref tables to have a pointer (offset) to all the pdf's objects. - * It can handle linearized pdfs, which will have an xref at the - * end pointing to an xref at the beginning of the file. - * Last the root object is parsed. + * The initial parse will first parse only the trailer, the xrefstart and + * all xref tables to have a pointer (offset) to all the pdf's objects. It + * can handle linearized pdfs, which will have an xref at the end pointing + * to an xref at the beginning of the file. Last the root object is parsed. * * @throws IOException */ protected void initialParse() throws IOException { final long startxrefOff = getStartxrefOffset(); - + // ---- parse startxref - setPdfSource( startxrefOff ); + setPdfSource(startxrefOff); parseStartXref(); - + final long xrefOffset = document.getStartXref(); - long prev = xrefOffset; - - // ---- parse whole chain of xref tables/object streams using PREV reference - while( prev > -1 ) + long prev = xrefOffset; + + // ---- parse whole chain of xref tables/object streams using PREV + // reference + while (prev > -1) { // seek to xref table - setPdfSource( prev ); - + setPdfSource(prev); + // -- parse xref - if ( pdfSource.peek() == 'x' ) + if (pdfSource.peek() == 'x') { // xref table and trailer // use existing parser to parse xref table - parseXrefTable( prev ); - + parseXrefTable(prev); + // parse the last trailer. - if ( ! parseTrailer() ) + if (!parseTrailer()) { - throw new IOException( "Expected trailer object at position: " + pdfSource.getOffset() ); + throw new IOException("Expected trailer object at position: " + pdfSource.getOffset()); } COSDictionary trailer = xrefTrailerResolver.getCurrentTrailer(); - prev = trailer.getInt( COSName.PREV ); + prev = trailer.getInt(COSName.PREV); } else { // xref stream - prev = parseXrefObjStream( prev ); + prev = parseXrefObjStream(prev); } } - + // ---- build valid xrefs out of the xref chain - xrefTrailerResolver.setStartxref( xrefOffset ); - document.setTrailer( xrefTrailerResolver.getTrailer() ); - + xrefTrailerResolver.setStartxref(xrefOffset); + COSDictionary trailer = xrefTrailerResolver.getTrailer(); + document.setTrailer(trailer); + + // JIRA-1557 - ensure that all COSObject are loaded in the trailer + for (COSBase trailerEntry : trailer.getValues()) + { + if (trailerEntry instanceof COSObject) + { + COSObject tmpObj = (COSObject) trailerEntry; + parseObjectDynamically(tmpObj, true); + } + } // ---- prepare encryption if necessary - COSBase trailerEncryptItem = document.getTrailer().getItem( COSName.ENCRYPT ); - if ( trailerEncryptItem != null ) + COSBase trailerEncryptItem = document.getTrailer().getItem(COSName.ENCRYPT); + if (trailerEncryptItem != null) { - if ( trailerEncryptItem instanceof COSObject ) - { - COSObject trailerEncryptObj = (COSObject) trailerEncryptItem; - parseObjectDynamically( trailerEncryptObj, true ); - } - try - { - PDEncryptionDictionary encParameters = new PDEncryptionDictionary( document.getEncryptionDictionary() ); - - DecryptionMaterial decryptionMaterial = null; - if( keyStoreFilename != null ) - { - KeyStore ks = KeyStore.getInstance( "PKCS12" ); - ks.load( new FileInputStream( keyStoreFilename ), password.toCharArray() ); - - decryptionMaterial = new PublicKeyDecryptionMaterial( ks, alias, password ); - } - else - { - decryptionMaterial = new StandardDecryptionMaterial( password ); - } - - securityHandler = SecurityHandlersManager.getInstance().getSecurityHandler( encParameters.getFilter() ); - securityHandler.prepareForDecryption( encParameters, document.getDocumentID(), decryptionMaterial ); - - AccessPermission permission = securityHandler.getCurrentAccessPermission(); - if ( ! permission.canExtractContent() ) - { - LOG.warn( "PDF file '" + pdfFile.getPath() + "' does not allow extracting content." ); - } - - } - catch ( Exception e ) - { - throw new IOException( "Error (" + e.getClass().getSimpleName() + - ") while creating security handler for decryption: " + - e.getMessage() /*, e // TODO: remove remark with Java 1.6 */); - } + if (trailerEncryptItem instanceof COSObject) + { + COSObject trailerEncryptObj = (COSObject) trailerEncryptItem; + parseObjectDynamically(trailerEncryptObj, true); + } + try + { + PDEncryptionDictionary encParameters = new PDEncryptionDictionary(document.getEncryptionDictionary()); + + DecryptionMaterial decryptionMaterial = null; + if (keyStoreFilename != null) + { + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(new FileInputStream(keyStoreFilename), password.toCharArray()); + + decryptionMaterial = new PublicKeyDecryptionMaterial(ks, alias, password); + } + else + { + decryptionMaterial = new StandardDecryptionMaterial(password); + } + + securityHandler = SecurityHandlersManager.getInstance().getSecurityHandler(encParameters.getFilter()); + securityHandler.prepareForDecryption(encParameters, document.getDocumentID(), decryptionMaterial); + + AccessPermission permission = securityHandler.getCurrentAccessPermission(); + if (!permission.canExtractContent()) + { + LOG.warn("PDF file '" + pdfFile.getPath() + "' does not allow extracting content."); + } + + } + catch (Exception e) + { + throw new IOException("Error (" + e.getClass().getSimpleName() + + ") while creating security handler for decryption: " + + e.getMessage() /*, e TODO: remove remark with Java 1.6 */); + } } - + // ---- parse catalog or root object - COSObject root = (COSObject) xrefTrailerResolver.getTrailer().getItem( COSName.ROOT ); - - if ( root == null ) + COSObject root = (COSObject) xrefTrailerResolver.getTrailer().getItem(COSName.ROOT); + + if (root == null) { - throw new IOException( "Missing root object specification in trailer." ); + throw new IOException("Missing root object specification in trailer."); } - - parseObjectDynamically( root, false ); - + + parseObjectDynamically(root, false); + // ---- resolve all objects (including pages) - if ( ! parseMinimalCatalog ) + if (!parseMinimalCatalog) { COSObject catalogObj = document.getCatalog(); - if ( catalogObj != null ) + if (catalogObj != null) { - if ( catalogObj.getObject() instanceof COSDictionary ) + if (catalogObj.getObject() instanceof COSDictionary) { - parseDictObjects( (COSDictionary) catalogObj.getObject(), (COSName[]) null ); + parseDictObjects((COSDictionary) catalogObj.getObject(), (COSName[]) null); allPagesParsed = true; document.setDecrypted(); } } } initialParseDone = true; - } - + // ------------------------------------------------------------------------ - /** Parses an xref object stream starting with indirect object id. - * - * @return value of PREV item in dictionary or -1 if no such item exists + /** + * Parses an xref object stream starting with indirect object id. + * + * @return value of PREV item in dictionary or -1 if no such + * item exists */ - private long parseXrefObjStream( long objByteOffset ) throws IOException + private long parseXrefObjStream(long objByteOffset) throws IOException { // ---- parse indirect object head readInt(); readInt(); - readPattern( OBJ_MARKER ); - - COSDictionary dict = parseCOSDictionary(); - COSStream xrefStream = parseCOSStream(dict, getDocument().getScratchFile() ); - parseXrefStream( xrefStream, (int) objByteOffset ); - - return dict.getLong( COSName.PREV ); + readPattern(OBJ_MARKER); + + COSDictionary dict = parseCOSDictionary(); + COSStream xrefStream = parseCOSStream(dict, getDocument().getScratchFile()); + parseXrefStream(xrefStream, (int) objByteOffset); + + return dict.getLong(COSName.PREV); } // ------------------------------------------------------------------------ @@ -413,190 +443,200 @@ private final long getPdfSourceOffset() } /** Sets {@link #pdfSource} to start next parsing at given file offset. */ - protected final void setPdfSource( long fileOffset ) throws IOException + protected final void setPdfSource(long fileOffset) throws IOException { - - pdfSource.seek( fileOffset ); + + pdfSource.seek(fileOffset); // alternative using 'old fashioned' input stream - // if ( pdfSource != null ) - // pdfSource.close(); - // - // pdfSource = new PushBackInputStream( - // new BufferedInputStream( - // new FileInputStream( file ), 16384), 4096); - // pdfSource.skip( _fileOffset ); + // if ( pdfSource != null ) + // pdfSource.close(); + // + // pdfSource = new PushBackInputStream( + // new BufferedInputStream( + // new FileInputStream( file ), 16384), 4096); + // pdfSource.skip( _fileOffset ); } /** Enable handling of alternative pdfSource implementation. */ protected final void releasePdfSourceInputStream() throws IOException { - // if ( pdfSource != null ) - // pdfSource.close(); + // if ( pdfSource != null ) + // pdfSource.close(); } - private final void closeFileStream() throws IOException + private final void closeFileStream() throws IOException { - if ( pdfSource != null ) + if (pdfSource != null) { pdfSource.close(); } } // ------------------------------------------------------------------------ - /** Looks for and parses startxref. We first look for last '%%EOF' marker - * (within last {@link #DEFAULT_TRAIL_BYTECOUNT} bytes (or range set via - * {@link #setEOFLookupRange(int)}) and go back to find startxref. */ + /** + * Looks for and parses startxref. We first look for last '%%EOF' marker + * (within last {@link #DEFAULT_TRAIL_BYTECOUNT} bytes (or range set via + * {@link #setEOFLookupRange(int)}) and go back to find + * startxref. + */ protected final long getStartxrefOffset() throws IOException { - byte[] buf; - long skipBytes; - + byte[] buf; + long skipBytes; + // ---- read trailing bytes into buffer final long fileLen = pdfFile.length(); - + FileInputStream fIn = null; - try + try { - fIn = new FileInputStream( pdfFile ); - - final int trailByteCount = ( fileLen < readTrailBytes ) ? (int) fileLen : readTrailBytes; - buf = new byte[ trailByteCount ]; - fIn.skip( skipBytes = fileLen - trailByteCount ); - + fIn = new FileInputStream(pdfFile); + + final int trailByteCount = (fileLen < readTrailBytes) ? (int) fileLen : readTrailBytes; + buf = new byte[trailByteCount]; + fIn.skip(skipBytes = fileLen - trailByteCount); + int off = 0; int readBytes; - while ( off < trailByteCount ) + while (off < trailByteCount) { - readBytes = fIn.read( buf, off, trailByteCount - off ); - // in order to not get stuck in a loop we check readBytes (this should never happen) - if ( readBytes < 1 ) + readBytes = fIn.read(buf, off, trailByteCount - off); + // in order to not get stuck in a loop we check readBytes (this + // should never happen) + if (readBytes < 1) { - throw new IOException( "No more bytes to read for trailing buffer, but expected: " + - ( trailByteCount - off ) ); + throw new IOException("No more bytes to read for trailing buffer, but expected: " + + (trailByteCount - off)); } off += readBytes; } } finally { - if ( fIn != null ) + if (fIn != null) { - try - { - fIn.close(); - } - catch ( IOException ioe ) - {} + try + { + fIn.close(); + } + catch (IOException ioe) + { + } } } - + // ---- find last '%%EOF' - int bufOff = lastIndexOf( EOF_MARKER, buf, buf.length ); - - if ( bufOff < 0 ) + int bufOff = lastIndexOf(EOF_MARKER, buf, buf.length); + + if (bufOff < 0) { - throw new IOException( "Missing end of file marker '" + ( new String( EOF_MARKER ) ) + "'" ); - } + throw new IOException("Missing end of file marker '" + (new String(EOF_MARKER)) + "'"); + } // ---- find last startxref preceding EOF marker - bufOff = lastIndexOf( STARTXREF_MARKER, buf, bufOff ); - - if ( bufOff < 0 ) + bufOff = lastIndexOf(STARTXREF_MARKER, buf, bufOff); + + if (bufOff < 0) { - throw new IOException( "Missing 'startxref' marker." ); + throw new IOException("Missing 'startxref' marker."); } return skipBytes + bufOff; } // ------------------------------------------------------------------------ - /** Searches last appearance of pattern within buffer. Lookup before _lastOff - * and goes back until 0. - * - * @param pattern pattern to search for - * @param buf buffer to search pattern in - * @param endOff offset (exclusive) where lookup starts at - * - * @return start offset of pattern within buffer or -1 if pattern could not be found + /** + * Searches last appearance of pattern within buffer. Lookup before _lastOff + * and goes back until 0. + * + * @param pattern pattern to search for + * @param buf buffer to search pattern in + * @param endOff offset (exclusive) where lookup starts at + * + * @return start offset of pattern within buffer or -1 if + * pattern could not be found */ - protected int lastIndexOf( final char[] pattern, final byte[] buf, final int endOff ) + protected int lastIndexOf(final char[] pattern, final byte[] buf, final int endOff) { final int lastPatternChOff = pattern.length - 1; - - int bufOff = endOff; - int patOff = lastPatternChOff; - char lookupCh = pattern[ patOff ]; - - while ( --bufOff >= 0 ) + + int bufOff = endOff; + int patOff = lastPatternChOff; + char lookupCh = pattern[patOff]; + + while (--bufOff >= 0) { - if ( buf[ bufOff ] == lookupCh ) + if (buf[bufOff] == lookupCh) { - if ( --patOff < 0 ) + if (--patOff < 0) { // whole pattern matched return bufOff; } // matched current char, advance to preceding one - lookupCh = pattern[ patOff ]; + lookupCh = pattern[patOff]; } - else if ( patOff < lastPatternChOff ) + else if (patOff < lastPatternChOff) { - // no char match but already matched some chars; reset - lookupCh = pattern[ patOff = lastPatternChOff ]; + // no char match but already matched some chars; reset + lookupCh = pattern[patOff = lastPatternChOff]; } } - + return -1; } // ------------------------------------------------------------------------ - /** Reads given pattern from {@link #pdfSource}. Skipping whitespace at start and end. + /** + * Reads given pattern from {@link #pdfSource}. Skipping whitespace at start + * and end. * * @throws IOException if pattern could not be read */ - protected final void readPattern( final char[] pattern ) throws IOException + protected final void readPattern(final char[] pattern) throws IOException { skipSpaces(); - - for ( char c : pattern ) + + for (char c : pattern) { - if ( pdfSource.read() != c ) + if (pdfSource.read() != c) { - throw new IOException( "Expected pattern '" + new String( pattern ) + - " but missed at character '" + c + "'" ); + throw new IOException("Expected pattern '" + new String(pattern) + " but missed at character '" + c + + "'"); } } - + skipSpaces(); } // ------------------------------------------------------------------------ private COSDictionary pagesDictionary = null; - - /** Returns PAGES {@link COSDictionary} object or throws {@link IOException} - * if PAGES dictionary does not exist. */ - private COSDictionary getPagesObject() throws IOException + + /** + * Returns PAGES {@link COSDictionary} object or throws {@link IOException} + * if PAGES dictionary does not exist. + */ + private COSDictionary getPagesObject() throws IOException { - if ( pagesDictionary != null ) + if (pagesDictionary != null) { return pagesDictionary; - } - COSObject pages = (COSObject) document.getCatalog().getItem( COSName.PAGES ); - - if ( pages == null ) + } + COSObject pages = (COSObject) document.getCatalog().getItem(COSName.PAGES); + + if (pages == null) { - throw new IOException( "Missing PAGES entry in document catalog." ); + throw new IOException("Missing PAGES entry in document catalog."); } - - COSBase object = parseObjectDynamically( pages, false ); - - if ( ! ( object instanceof COSDictionary ) ) + + COSBase object = parseObjectDynamically(pages, false); + + if (!(object instanceof COSDictionary)) { - throw new IOException( "PAGES not a dictionary object, but: " + - object.getClass().getSimpleName() ); + throw new IOException("PAGES not a dictionary object, but: " + object.getClass().getSimpleName()); } - + pagesDictionary = (COSDictionary) object; - + return pagesDictionary; } @@ -605,101 +645,111 @@ private COSDictionary getPagesObject() throws IOException /** * {@inheritDoc} */ - @Override - public void parse() throws IOException + @Override + public void parse() throws IOException { - boolean exceptionOccurred = true; // set to false if all is processed - + boolean exceptionOccurred = true; // set to false if all is processed + try { - if ( ! initialParseDone ) + if (!initialParseDone) { initialParse(); } - + final int pageCount = getPageNumber(); - - if ( ! allPagesParsed ) + + if (!allPagesParsed) { - for ( int pNr = 0; pNr < pageCount; pNr++ ) + for (int pNr = 0; pNr < pageCount; pNr++) { - getPage( pNr ); + getPage(pNr); } allPagesParsed = true; document.setDecrypted(); } - + exceptionOccurred = false; } finally { - try - { + try + { closeFileStream(); - } - catch ( IOException ioe ) - {} + } + catch (IOException ioe) + { + } - deleteTempFile(); + deleteTempFile(); - if ( exceptionOccurred && ( document != null ) ) + if (exceptionOccurred && (document != null)) { - try + try { - document.close(); - } - catch ( IOException ioe ) - {} + document.close(); + } + catch (IOException ioe) + { + } } } - } - - protected File getPdfFile() { - return this.pdfFile; - } - - /** - * Remove the temporary file. - * A temporary file is created if this class is instantiated with an InputStream - */ - protected void deleteTempFile() { - if (isTmpPDFFile) { - try { - if (!pdfFile.delete()) LOG.warn("Temporary file '" + pdfFile.getName() + "' can't be deleted"); - } catch (SecurityException e) { - LOG.warn("Temporary file '" + pdfFile.getName() + "' can't be deleted", e); - } - } - } + } + + protected File getPdfFile() + { + return this.pdfFile; + } + + /** + * Remove the temporary file. A temporary file is created if this class is + * instantiated with an InputStream + */ + protected void deleteTempFile() + { + if (isTmpPDFFile) + { + try + { + if (!pdfFile.delete()) + LOG.warn("Temporary file '" + pdfFile.getName() + "' can't be deleted"); + } + catch (SecurityException e) + { + LOG.warn("Temporary file '" + pdfFile.getName() + "' can't be deleted", e); + } + } + } + // ------------------------------------------------------------------------ - /** + /** * Returns security handler of the document or null if document - * is not encrypted or {@link #parse()} wasn't called before. - * + * is not encrypted or {@link #parse()} wasn't called before. + * * @return the security handler. */ - public SecurityHandler getSecurityHandler() + public SecurityHandler getSecurityHandler() { return securityHandler; } // ------------------------------------------------------------------------ /** - * This will get the PD document that was parsed. When you are done with + * 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. - * + * * Overwriting super method was necessary in order to set security handler. - * + * * @return The document at the PD layer. - * + * * @throws IOException If there is an error getting the document. */ @Override public PDDocument getPDDocument() throws IOException { PDDocument pdDocument = super.getPDDocument(); - if ( securityHandler != null ) - pdDocument.setSecurityHandler( securityHandler ); + if (securityHandler != null) + pdDocument.setSecurityHandler(securityHandler); return pdDocument; } @@ -709,16 +759,16 @@ public PDDocument getPDDocument() throws IOException * * @return the number of pages. * - * @throws IOException if PAGES or other needed object is missing + * @throws IOException if PAGES or other needed object is missing */ public int getPageNumber() throws IOException { - int pageCount = getPagesObject().getInt( COSName.COUNT ); - - if ( pageCount < 0 ) + int pageCount = getPagesObject().getInt(COSName.COUNT); + + if (pageCount < 0) { - throw new IOException( "No page number specified." ); - } + throw new IOException("No page number specified."); + } return pageCount; } @@ -730,88 +780,88 @@ public int getPageNumber() throws IOException * @return the page with the given pagenumber. * @throws IOException If something went wrong. */ - public PDPage getPage( int pageNr ) throws IOException + public PDPage getPage(int pageNr) throws IOException { getPagesObject(); - + // ---- get list of top level pages - COSArray kids = (COSArray) pagesDictionary.getDictionaryObject( COSName.KIDS ); - - if ( kids == null ) + COSArray kids = (COSArray) pagesDictionary.getDictionaryObject(COSName.KIDS); + + if (kids == null) { - throw new IOException( "Missing 'Kids' entry in pages dictionary." ); + throw new IOException("Missing 'Kids' entry in pages dictionary."); } - - // ---- get page we are looking for (possibly going recursively into subpages) - COSObject pageObj = getPageObject( pageNr, kids, 0 ); - - if ( pageObj == null ) + + // ---- get page we are looking for (possibly going recursively into + // subpages) + COSObject pageObj = getPageObject(pageNr, kids, 0); + + if (pageObj == null) { - throw new IOException( "Page " + pageNr + " not found." ); + throw new IOException("Page " + pageNr + " not found."); } - + // ---- parse all objects necessary to load page. COSDictionary pageDict = (COSDictionary) pageObj.getObject(); - - if ( parseMinimalCatalog && ( ! allPagesParsed ) ) + + if (parseMinimalCatalog && (!allPagesParsed)) { // parse page resources since we did not do this on start - COSDictionary resDict = (COSDictionary) pageDict.getDictionaryObject( COSName.RESOURCES ); - parseDictObjects( resDict ); + COSDictionary resDict = (COSDictionary) pageDict.getDictionaryObject(COSName.RESOURCES); + parseDictObjects(resDict); } - - return new PDPage( pageDict ); + + return new PDPage(pageDict); } /** - * Returns the object for a specific page. - * The page tree is made up of kids. The kids have COSArray with COSObjects - * inside of them. The COSObject can be parsed using the dynamic parsing method - * We want to only parse the minimum COSObjects and still return a complete page. - * ready to be used. + * Returns the object for a specific page. The page tree is made up of kids. + * The kids have COSArray with COSObjects inside of them. The COSObject can + * be parsed using the dynamic parsing method We want to only parse the + * minimum COSObjects and still return a complete page. ready to be used. * - * @param num the requested page number; numbering starts with 0 + * @param num the requested page number; numbering starts with 0 * @param startKids Kids array to start with looking up page number * @param startPageCount * - * @return page object or null if no such page exists + * @return page object or null if no such page exists * * @throws IOException */ - private COSObject getPageObject( int num, COSArray startKids, int startPageCount ) throws IOException + private COSObject getPageObject(int num, COSArray startKids, int startPageCount) throws IOException { - int curPageCount = startPageCount; - Iterator kidsIter = startKids.iterator(); - - while( kidsIter.hasNext() ) + int curPageCount = startPageCount; + Iterator kidsIter = startKids.iterator(); + + while (kidsIter.hasNext()) { - COSObject obj = (COSObject) kidsIter.next(); - COSBase base = obj.getObject(); - if( base == null ) + COSObject obj = (COSObject) kidsIter.next(); + COSBase base = obj.getObject(); + if (base == null) { - base = parseObjectDynamically( obj, false ); - obj.setObject( base ); + base = parseObjectDynamically(obj, false); + obj.setObject(base); } - - COSDictionary dic = (COSDictionary) base; - int count = dic.getInt( COSName.COUNT ); - if ( count >= 0 ) + + COSDictionary dic = (COSDictionary) base; + int count = dic.getInt(COSName.COUNT); + if (count >= 0) { // skip this branch if requested page comes later - if( ( curPageCount + count ) <= num ) + if ((curPageCount + count) <= num) { curPageCount += count; continue; } } - - COSArray kids = (COSArray) dic.getDictionaryObject( COSName.KIDS ); - if( kids != null) + + COSArray kids = (COSArray) dic.getDictionaryObject(COSName.KIDS); + if (kids != null) { // recursively scan subpages - COSObject ans = getPageObject( num, kids, curPageCount ); + COSObject ans = getPageObject(num, kids, curPageCount); // if ans is not null, we got what we were looking for - if( ans != null ) + if (ans != null) { return ans; } @@ -819,290 +869,298 @@ private COSObject getPageObject( int num, COSArray startKids, int startPageCount else { // found page? - if( curPageCount == num ) + if (curPageCount == num) { return obj; } - // page has no kids and it is not the page we are looking for + // page has no kids and it is not the page we are looking for curPageCount++; } } return null; } - /** Creates a unique object id using object number and object generation number. - * (requires object number < 2^31)) */ - private final long getObjectId( final COSObject obj ) + /** + * Creates a unique object id using object number and object generation + * number. (requires object number < 2^31)) + */ + private final long getObjectId(final COSObject obj) { - return ( obj.getObjectNumber().longValue() << 32 ) | obj.getGenerationNumber().longValue(); + return (obj.getObjectNumber().longValue() << 32) | obj.getGenerationNumber().longValue(); } - - /** Adds all from newObjects to toBeParsedList if it is not an COSObject - * or we didn't add this COSObject already (checked via addedObjects). */ - private final void addNewToList( final Queue toBeParsedList, - final Collection newObjects, - final Set addedObjects ) + + /** + * Adds all from newObjects to toBeParsedList if it is not an COSObject or + * we didn't add this COSObject already (checked via addedObjects). + */ + private final void addNewToList(final Queue toBeParsedList, final Collection newObjects, + final Set addedObjects) { - for ( COSBase newObject : newObjects ) + for (COSBase newObject : newObjects) { - if ( newObject instanceof COSObject ) + if (newObject instanceof COSObject) { - final long objId = getObjectId( (COSObject) newObject ); - if ( ! addedObjects.add( objId ) ) + final long objId = getObjectId((COSObject) newObject); + if (!addedObjects.add(objId)) { continue; } } - toBeParsedList.add( newObject ); + toBeParsedList.add(newObject); } } - /** Adds newObject to toBeParsedList if it is not an COSObject - * or we didn't add this COSObject already (checked via addedObjects). */ - private final void addNewToList( final Queue toBeParsedList, - final COSBase newObject, - final Set addedObjects ) + /** + * Adds newObject to toBeParsedList if it is not an COSObject or we didn't + * add this COSObject already (checked via addedObjects). + */ + private final void addNewToList(final Queue toBeParsedList, final COSBase newObject, + final Set addedObjects) { - if ( newObject instanceof COSObject ) + if (newObject instanceof COSObject) { - final long objId = getObjectId( (COSObject) newObject ); - if ( ! addedObjects.add( objId ) ) + final long objId = getObjectId((COSObject) newObject); + if (!addedObjects.add(objId)) { return; } } - toBeParsedList.add( newObject ); + toBeParsedList.add(newObject); } /** - * Will parse every object necessary to load a single page from the pdf document. - * We try our best to order objects according to offset in file before reading - * to minimize seek operations. + * Will parse every object necessary to load a single page from the pdf + * document. We try our best to order objects according to offset in file + * before reading to minimize seek operations. * * @param dict the COSObject from the parent pages. - * @param excludeObjects dictionary object reference entries with these names will not be parsed + * @param excludeObjects dictionary object reference entries with these + * names will not be parsed * * @throws IOException */ - private void parseDictObjects( COSDictionary dict, COSName... excludeObjects ) throws IOException + private void parseDictObjects(COSDictionary dict, COSName... excludeObjects) throws IOException { // ---- create queue for objects waiting for further parsing - final Queue toBeParsedList = new LinkedList(); + final Queue toBeParsedList = new LinkedList(); // offset ordered object map - final TreeMap> objToBeParsed = new TreeMap>(); + final TreeMap> objToBeParsed = new TreeMap>(); // in case of compressed objects offset points to stmObj - final Set parsedObjects = new HashSet(); - final Set addedObjects = new HashSet(); - + final Set parsedObjects = new HashSet(); + final Set addedObjects = new HashSet(); + // ---- add objects not to be parsed to list of already parsed objects - if ( excludeObjects != null ) + if (excludeObjects != null) { - for ( COSName objName : excludeObjects ) + for (COSName objName : excludeObjects) { - COSBase baseObj = dict.getItem( objName ); - if ( baseObj instanceof COSObject ) + COSBase baseObj = dict.getItem(objName); + if (baseObj instanceof COSObject) { - parsedObjects.add( getObjectId( (COSObject) baseObj ) ); + parsedObjects.add(getObjectId((COSObject) baseObj)); } } } - - addNewToList( toBeParsedList, dict.getValues(), addedObjects ); - + + addNewToList(toBeParsedList, dict.getValues(), addedObjects); + // ---- go through objects to be parsed - while( ! ( toBeParsedList.isEmpty() && objToBeParsed.isEmpty() ) ) + while (!(toBeParsedList.isEmpty() && objToBeParsed.isEmpty())) { // -- first get all COSObject from other kind of objects and - // put them in objToBeParsed; afterwards toBeParsedList is empty + // put them in objToBeParsed; afterwards toBeParsedList is empty COSBase baseObj; - while ( ( baseObj = toBeParsedList.poll() ) != null ) + while ((baseObj = toBeParsedList.poll()) != null) { - if ( baseObj instanceof COSStream ) + if (baseObj instanceof COSStream) { - addNewToList( toBeParsedList, ((COSStream) baseObj).getValues(), addedObjects ); + addNewToList(toBeParsedList, ((COSStream) baseObj).getValues(), addedObjects); } - else if ( baseObj instanceof COSDictionary ) + else if (baseObj instanceof COSDictionary) { - addNewToList( toBeParsedList, ((COSDictionary) baseObj).getValues(), addedObjects ); + addNewToList(toBeParsedList, ((COSDictionary) baseObj).getValues(), addedObjects); } - else if ( baseObj instanceof COSArray ) + else if (baseObj instanceof COSArray) { - final Iterator arrIter = ( (COSArray) baseObj ).iterator(); - while ( arrIter.hasNext() ) + final Iterator arrIter = ((COSArray) baseObj).iterator(); + while (arrIter.hasNext()) { - addNewToList( toBeParsedList, arrIter.next(), addedObjects ); + addNewToList(toBeParsedList, arrIter.next(), addedObjects); } } - else if ( baseObj instanceof COSObject ) + else if (baseObj instanceof COSObject) { - COSObject obj = (COSObject) baseObj; - long objId = getObjectId( obj ); - COSObjectKey objKey = new COSObjectKey( obj.getObjectNumber().intValue(), - obj.getGenerationNumber().intValue() ); - - if ( ! ( parsedObjects.contains( objId ) /*|| document.hasObjectInPool( objKey ) */ ) ) + COSObject obj = (COSObject) baseObj; + long objId = getObjectId(obj); + COSObjectKey objKey = new COSObjectKey(obj.getObjectNumber().intValue(), obj.getGenerationNumber() + .intValue()); + + if (!(parsedObjects.contains(objId) /* + * || + * document.hasObjectInPool + * ( objKey ) + */)) { - Long fileOffset = xrefTrailerResolver.getXrefTable().get( objKey ); - // it is allowed that object references point to null, thus we have to test - if ( fileOffset != null ) + Long fileOffset = xrefTrailerResolver.getXrefTable().get(objKey); + // it is allowed that object references point to null, + // thus we have to test + if (fileOffset != null) { - if ( fileOffset > 0 ) + if (fileOffset > 0) { - objToBeParsed.put( fileOffset, Collections.singletonList( obj ) ); + objToBeParsed.put(fileOffset, Collections.singletonList(obj)); } - else + else { - // negative offset means we have a compressed object within object stream; + // negative offset means we have a compressed + // object within object stream; // get offset of object stream - fileOffset = xrefTrailerResolver.getXrefTable().get( new COSObjectKey( -fileOffset, 0 ) ); - if ( ( fileOffset == null ) || ( fileOffset <= 0 ) ) + fileOffset = xrefTrailerResolver.getXrefTable().get(new COSObjectKey(-fileOffset, 0)); + if ((fileOffset == null) || (fileOffset <= 0)) { - throw new IOException( "Invalid object stream xref object reference: " + fileOffset ); + throw new IOException("Invalid object stream xref object reference: " + fileOffset); } - - List stmObjects = objToBeParsed.get( fileOffset ); - if ( stmObjects == null ) + + List stmObjects = objToBeParsed.get(fileOffset); + if (stmObjects == null) { - objToBeParsed.put( fileOffset, stmObjects = new ArrayList() ); + objToBeParsed.put(fileOffset, stmObjects = new ArrayList()); } - stmObjects.add( obj ); + stmObjects.add(obj); } } else { // NULL object - COSObject pdfObject = document.getObjectFromPool( objKey ); - pdfObject.setObject( COSNull.NULL ); + COSObject pdfObject = document.getObjectFromPool(objKey); + pdfObject.setObject(COSNull.NULL); } } } } - + // ---- read first COSObject with smallest offset; - // resulting object will be added to toBeParsedList - if ( objToBeParsed.isEmpty() ) + // resulting object will be added to toBeParsedList + if (objToBeParsed.isEmpty()) { break; } - - for ( COSObject obj : objToBeParsed.remove( objToBeParsed.firstKey() ) ) + + for (COSObject obj : objToBeParsed.remove(objToBeParsed.firstKey())) { - COSBase parsedObj = parseObjectDynamically( obj, false ); - - obj.setObject( parsedObj ); - addNewToList( toBeParsedList, parsedObj, addedObjects ); - - parsedObjects.add( getObjectId( obj ) ); + COSBase parsedObj = parseObjectDynamically(obj, false); + + obj.setObject(parsedObj); + addNewToList(toBeParsedList, parsedObj, addedObjects); + + parsedObjects.add(getObjectId(obj)); } } } - + /** - * This will parse the next object from the stream and add it to - * the local state. - * This is taken from {@link PDFParser} and reduced to parsing - * an indirect object. - * - * @param obj object to be parsed (we only take object number and generation number for lookup start offset) - * @param requireExistingNotCompressedObj if true object to be parsed must - * not be contained within compressed stream - * @return the parsed object (which is also added to document object) + * This will parse the next object from the stream and add it to the local + * state. This is taken from {@link PDFParser} and reduced to parsing an + * indirect object. + * + * @param obj object to be parsed (we only take object number and generation + * number for lookup start offset) + * @param requireExistingNotCompressedObj if true object to be + * parsed must not be contained within compressed stream + * @return the parsed object (which is also added to document object) * * @throws IOException If an IO error occurs. */ - protected final COSBase parseObjectDynamically( COSObject obj, boolean requireExistingNotCompressedObj ) - throws IOException + protected final COSBase parseObjectDynamically(COSObject obj, boolean requireExistingNotCompressedObj) + throws IOException { - return parseObjectDynamically( obj.getObjectNumber().intValue(), - obj.getGenerationNumber().intValue(), - requireExistingNotCompressedObj ); + return parseObjectDynamically(obj.getObjectNumber().intValue(), obj.getGenerationNumber().intValue(), + requireExistingNotCompressedObj); } /** - * This will parse the next object from the stream and add it to - * the local state. - * This is taken from {@link PDFParser} and reduced to parsing - * an indirect object. - * - * @param objNr object number of object to be parsed - * @param objGenNr object generation number of object to be parsed - * @param requireExistingNotCompressedObj if true the object to be parsed must be defined - * in xref (comment: null objects may be missing from xref) and - * it must not be a compressed object within object stream - * (this is used to circumvent being stuck in a loop in a malicious PDF) + * This will parse the next object from the stream and add it to the local + * state. This is taken from {@link PDFParser} and reduced to parsing an + * indirect object. + * + * @param objNr object number of object to be parsed + * @param objGenNr object generation number of object to be parsed + * @param requireExistingNotCompressedObj if true the object to + * be parsed must be defined in xref (comment: null objects may + * be missing from xref) and it must not be a compressed object + * within object stream (this is used to circumvent being stuck + * in a loop in a malicious PDF) * - * @return the parsed object (which is also added to document object) + * @return the parsed object (which is also added to document object) * * @throws IOException If an IO error occurs. */ - protected COSBase parseObjectDynamically( int objNr, int objGenNr, boolean requireExistingNotCompressedObj ) - throws IOException + protected COSBase parseObjectDynamically(int objNr, int objGenNr, boolean requireExistingNotCompressedObj) + throws IOException { // ---- create object key and get object (container) from pool - final COSObjectKey objKey = new COSObjectKey( objNr, objGenNr ); - final COSObject pdfObject = document.getObjectFromPool( objKey ); - - if ( pdfObject.getObject() == null ) + final COSObjectKey objKey = new COSObjectKey(objNr, objGenNr); + final COSObject pdfObject = document.getObjectFromPool(objKey); + + if (pdfObject.getObject() == null) { // not previously parsed // ---- read offset or object stream object number from xref table - Long offsetOrObjstmObNr = xrefTrailerResolver.getXrefTable().get( objKey ); + Long offsetOrObjstmObNr = xrefTrailerResolver.getXrefTable().get(objKey); // sanity test to circumvent loops with broken documents - if ( requireExistingNotCompressedObj && - ( ( offsetOrObjstmObNr == null ) || ( offsetOrObjstmObNr <= 0 ) ) ) - { - throw new IOException( "Object must be defined and must not be compressed object: " + - objKey.getNumber() + ":" + objKey.getGeneration() ); + if (requireExistingNotCompressedObj && ((offsetOrObjstmObNr == null) || (offsetOrObjstmObNr <= 0))) + { + throw new IOException("Object must be defined and must not be compressed object: " + objKey.getNumber() + + ":" + objKey.getGeneration()); } - - if ( offsetOrObjstmObNr == null ) + + if (offsetOrObjstmObNr == null) { // not defined object -> NULL object (Spec. 1.7, chap. 3.2.9) - pdfObject.setObject( COSNull.NULL ); + pdfObject.setObject(COSNull.NULL); } - else if ( offsetOrObjstmObNr > 0 ) + else if (offsetOrObjstmObNr > 0) { // offset of indirect object in file // ---- go to object start - setPdfSource( offsetOrObjstmObNr ); - + setPdfSource(offsetOrObjstmObNr); + // ---- we must have an indirect object - final int readObjNr = readInt(); + final int readObjNr = readInt(); final int readObjGen = readInt(); - readPattern( OBJ_MARKER ); - + readPattern(OBJ_MARKER); + // ---- consistency check - if ( ( readObjNr != objKey.getNumber() ) || - ( readObjGen != objKey.getGeneration() ) ) + if ((readObjNr != objKey.getNumber()) || (readObjGen != objKey.getGeneration())) { - throw new IOException( "XREF for " + objKey.getNumber() + ":" + objKey.getGeneration() + - " points to wrong object: " + readObjNr + ":" + readObjGen ); + throw new IOException("XREF for " + objKey.getNumber() + ":" + objKey.getGeneration() + + " points to wrong object: " + readObjNr + ":" + readObjGen); } - + skipSpaces(); - COSBase pb = parseDirObject(); - String endObjectKey = readString(); - - if ( endObjectKey.equals( "stream" ) ) + COSBase pb = parseDirObject(); + String endObjectKey = readString(); + + if (endObjectKey.equals("stream")) { - pdfSource.unread( endObjectKey.getBytes("ISO-8859-1") ); - pdfSource.unread( ' ' ); - if( pb instanceof COSDictionary ) + pdfSource.unread(endObjectKey.getBytes("ISO-8859-1")); + pdfSource.unread(' '); + if (pb instanceof COSDictionary) { - COSStream stream = parseCOSStream( (COSDictionary)pb, - getDocument().getScratchFile() ); - - if ( securityHandler != null ) + COSStream stream = parseCOSStream((COSDictionary) pb, getDocument().getScratchFile()); + + if (securityHandler != null) { - try + try { - securityHandler.decryptStream(stream, objNr, objGenNr ); - } - catch ( CryptographyException ce ) + securityHandler.decryptStream(stream, objNr, objGenNr); + } + catch (CryptographyException ce) { - throw new IOException( "Error decrypting stream object " + objNr + ": " + ce.getMessage() - /*, ce // TODO: remove remark with Java 1.6 */ ); + throw new IOException("Error decrypting stream object " + objNr + ": " + + ce.getMessage() + /* , ce // TODO: remove remark with Java 1.6 */); } } pb = stream; @@ -1110,179 +1168,185 @@ else if ( offsetOrObjstmObNr > 0 ) 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 (offset: " + offsetOrObjstmObNr + ")." ); + // the combination of a dict and the stream/endstream + // forms a complete stream object + throw new IOException("Stream not preceded by dictionary (offset: " + offsetOrObjstmObNr + ")."); } skipSpaces(); endObjectKey = readLine(); - + // we have case with a second 'endstream' before endobj - if ( ! endObjectKey.startsWith( "endobj" ) ) + if (!endObjectKey.startsWith("endobj")) { - if ( endObjectKey.startsWith( "endstream" ) ) + if (endObjectKey.startsWith("endstream")) { - endObjectKey = endObjectKey.substring( 9 ).trim(); - if ( endObjectKey.length() == 0 ) + endObjectKey = endObjectKey.substring(9).trim(); + if (endObjectKey.length() == 0) { // no other characters in extra endstream line - endObjectKey = readLine(); // read next line + endObjectKey = readLine(); // read next line } } } } - else if ( securityHandler != null ) + else if (securityHandler != null) { // decrypt - if ( pb instanceof COSString ) + if (pb instanceof COSString) { - decrypt( (COSString) pb, objNr, objGenNr ); + decrypt((COSString) pb, objNr, objGenNr); } - else if ( pb instanceof COSDictionary ) + else if (pb instanceof COSDictionary) { - for( Entry entry : ((COSDictionary) pb).entrySet() ) + for (Entry entry : ((COSDictionary) pb).entrySet()) { - // TODO: specially handle 'Contents' entry of signature dictionary like in SecurityHandler#decryptDictionary - if ( entry.getValue() instanceof COSString ) + // TODO: specially handle 'Contents' entry of + // signature dictionary like in + // SecurityHandler#decryptDictionary + if (entry.getValue() instanceof COSString) { - decrypt( (COSString) entry.getValue(), objNr, objGenNr ); + decrypt((COSString) entry.getValue(), objNr, objGenNr); } } } - else if ( pb instanceof COSArray ) + else if (pb instanceof COSArray) { final COSArray array = (COSArray) pb; - for( int aIdx = 0, len = array.size(); aIdx < len; aIdx++ ) + for (int aIdx = 0, len = array.size(); aIdx < len; aIdx++) { - if ( array.get( aIdx ) instanceof COSString ) + if (array.get(aIdx) instanceof COSString) { - decrypt( (COSString) array.get( aIdx ), objNr, objGenNr ); + decrypt((COSString) array.get(aIdx), objNr, objGenNr); } } } } - - pdfObject.setObject( pb ); - - if ( ! endObjectKey.startsWith( "endobj" ) ) + + pdfObject.setObject(pb); + + if (!endObjectKey.startsWith("endobj")) { - throw new IOException( "Object (" + readObjNr + ":" + readObjGen + - ") at offset " + offsetOrObjstmObNr + " does not end with 'endobj'." ); + throw new IOException("Object (" + readObjNr + ":" + readObjGen + ") at offset " + + offsetOrObjstmObNr + " does not end with 'endobj'."); } - + releasePdfSourceInputStream(); - + } else { - // xref value is object nr of object stream containing object to be parsed; - // since our object was not found it means object stream was not parsed so far - final int objstmObjNr = (int) ( - offsetOrObjstmObNr ); - final COSBase objstmBaseObj = parseObjectDynamically( objstmObjNr, 0, true ); - if ( objstmBaseObj instanceof COSStream ) + // xref value is object nr of object stream containing object to + // be parsed; + // since our object was not found it means object stream was not + // parsed so far + final int objstmObjNr = (int) (-offsetOrObjstmObNr); + final COSBase objstmBaseObj = parseObjectDynamically(objstmObjNr, 0, true); + if (objstmBaseObj instanceof COSStream) { // parse object stream - PDFObjectStreamParser parser = - new PDFObjectStreamParser( (COSStream) objstmBaseObj, document, forceParsing ); + PDFObjectStreamParser parser = new PDFObjectStreamParser((COSStream) objstmBaseObj, document, + forceParsing); parser.parse(); - - // get set of object numbers referenced for this object stream - final Set refObjNrs = xrefTrailerResolver.getContainedObjectNumbers( objstmObjNr ); - - // register all objects which are referenced to be contained in object stream - for( COSObject next : parser.getObjects() ) + + // get set of object numbers referenced for this object + // stream + final Set refObjNrs = xrefTrailerResolver.getContainedObjectNumbers(objstmObjNr); + + // register all objects which are referenced to be contained + // in object stream + for (COSObject next : parser.getObjects()) { - COSObjectKey stmObjKey = new COSObjectKey( next ); - if ( refObjNrs.contains( stmObjKey.getNumber() ) ) + COSObjectKey stmObjKey = new COSObjectKey(next); + if (refObjNrs.contains(stmObjKey.getNumber())) { - COSObject stmObj = document.getObjectFromPool( stmObjKey ); - stmObj.setObject( next.getObject() ); + COSObject stmObj = document.getObjectFromPool(stmObjKey); + stmObj.setObject(next.getObject()); } } } } - } + } return pdfObject.getObject(); } - + // ------------------------------------------------------------------------ /** Decrypts given COSString. */ - protected final void decrypt( COSString str, long objNr, long objGenNr ) - throws IOException + protected final void decrypt(COSString str, long objNr, long objGenNr) throws IOException { - try + try { - securityHandler.decryptString( str, objNr, objGenNr ); + securityHandler.decryptString(str, objNr, objGenNr); } - catch ( CryptographyException ce ) + catch (CryptographyException ce) { - throw new IOException( "Error decrypting string: " + ce.getMessage() - /*, ce // TODO: remove remark with Java 1.6 */ ); - } + throw new IOException("Error decrypting string: " + ce.getMessage() + /* , ce // TODO: remove remark with Java 1.6 */); + } } - + // ------------------------------------------------------------------------ private boolean inGetLength = false; - + /** Returns length value referred to or defined in given object. */ - private COSNumber getLength( final COSBase lengthBaseObj ) throws IOException + private COSNumber getLength(final COSBase lengthBaseObj) throws IOException { - if ( lengthBaseObj == null ) + if (lengthBaseObj == null) { return null; } - - if ( inGetLength ) + + if (inGetLength) { - throw new IOException( "Loop while reading length from " + lengthBaseObj ); + throw new IOException("Loop while reading length from " + lengthBaseObj); } - + COSNumber retVal = null; - + try { inGetLength = true; - + // ---- maybe length was given directly - if ( lengthBaseObj instanceof COSNumber ) + if (lengthBaseObj instanceof COSNumber) { retVal = (COSNumber) lengthBaseObj; } // ---- length in referenced object - else if ( lengthBaseObj instanceof COSObject ) + else if (lengthBaseObj instanceof COSObject) { COSObject lengthObj = (COSObject) lengthBaseObj; - - if ( lengthObj.getObject() == null ) + + if (lengthObj.getObject() == null) { // not read so far - + // keep current stream position final long curFileOffset = getPdfSourceOffset(); releasePdfSourceInputStream(); - - parseObjectDynamically( lengthObj, true ); - + + parseObjectDynamically(lengthObj, true); + // reset current stream position - setPdfSource( curFileOffset ); - - if ( lengthObj.getObject() == null ) + setPdfSource(curFileOffset); + + if (lengthObj.getObject() == null) { - throw new IOException( "Length object content was not read." ); + throw new IOException("Length object content was not read."); } } - - if ( ! ( lengthObj.getObject() instanceof COSNumber ) ) + + if (!(lengthObj.getObject() instanceof COSNumber)) { - throw new IOException( "Wrong type of referenced length object " + lengthObj + ": " + - lengthObj.getObject().getClass().getSimpleName() ); + throw new IOException("Wrong type of referenced length object " + lengthObj + ": " + + lengthObj.getObject().getClass().getSimpleName()); } - + retVal = (COSNumber) lengthObj.getObject(); - + } else { - throw new IOException( "Wrong type of length object: " + lengthBaseObj.getClass().getSimpleName() ); + throw new IOException("Wrong type of length object: " + lengthBaseObj.getClass().getSimpleName()); } } finally @@ -1291,112 +1355,236 @@ else if ( lengthBaseObj instanceof COSObject ) } return retVal; } - + // ------------------------------------------------------------------------ - private final int streamCopyBufLen = 8192; - private final byte[] streamCopyBuf = new byte[ streamCopyBufLen ]; - + private final int streamCopyBufLen = 8192; + private final byte[] streamCopyBuf = new byte[streamCopyBufLen]; + /** * This will read a COSStream from the input stream using length attribute - * within dictionary. - * If length attribute is a indirect reference it is first resolved to get - * the stream length. This means we copy stream data without testing for - * 'endstream' or 'endobj' and thus it is no problem if these keywords - * occur within stream. - * We require 'endstream' to be found after stream data is read. - * - * @param dic dictionary that goes with this stream. - * @param file file to write the stream to when reading. - * + * within dictionary. If length attribute is a indirect reference it is + * first resolved to get the stream length. This means we copy stream data + * without testing for 'endstream' or 'endobj' and thus it is no problem if + * these keywords occur within stream. We require 'endstream' to be found + * after stream data is read. + * + * @param dic dictionary that goes with this stream. + * @param file file to write the stream to when reading. + * * @return parsed pdf stream. - * - * @throws IOException if an error occurred reading the stream, like problems - * with reading length attribute, stream does not end with 'endstream' - * after data read, stream too short etc. + * + * @throws IOException if an error occurred reading the stream, like + * problems with reading length attribute, stream does not end + * with 'endstream' after data read, stream too short etc. */ @Override - protected COSStream parseCOSStream( COSDictionary dic, RandomAccess file ) throws IOException + protected COSStream parseCOSStream(COSDictionary dic, RandomAccess file) throws IOException { - final COSStream stream = new COSStream( dic, file ); + final COSStream stream = new COSStream(dic, file); OutputStream out = null; try { - readString(); // read 'stream'; this was already tested in parseObjectsDynamically() - + readString(); // read 'stream'; this was already tested in + // parseObjectsDynamically() + // ---- skip whitespaces before start of data - // PDF Ref 1.7, chap. 3.2.7: - // 'stream' should be followed by either a CRLF (0x0d 0x0a) or LF but nothing else. + // PDF Ref 1.7, chap. 3.2.7: + // 'stream' should be followed by either a CRLF (0x0d 0x0a) 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 + + // 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 ) + + if (whitespace == 0x0D) { whitespace = pdfSource.read(); - if( whitespace != 0x0A ) + if (whitespace != 0x0A) { - // the spec says this is invalid but it happens in the real + // the spec says this is invalid but it happens in the + // real // world so we must support it - pdfSource.unread( whitespace ); + pdfSource.unread(whitespace); } } else if (whitespace != 0x0A) { - // no whitespace after 'stream'; PDF ref. says 'should' so that is ok - pdfSource.unread( whitespace ); + // no whitespace after 'stream'; PDF ref. says 'should' so + // that is ok + pdfSource.unread(whitespace); } - } - - /*This needs to be dic.getItem because when we are parsing, the underlying object - * might still be null. + } + + /* + * This needs to be dic.getItem because when we are parsing, the + * underlying object might still be null. */ - COSNumber streamLengthObj = getLength( dic.getItem( COSName.LENGTH ) ); - if ( streamLengthObj == null ) + COSNumber streamLengthObj = getLength(dic.getItem(COSName.LENGTH)); + if (streamLengthObj == null) { - throw new IOException( "Missing length for stream." ); + throw new IOException("Missing length for stream."); } - + // ---- get output stream to copy data to - out = stream.createFilteredStream( streamLengthObj ); - + out = stream.createFilteredStream(streamLengthObj); + long remainBytes = streamLengthObj.longValue(); - - while ( remainBytes > 0 ) + int bytesRead = 0; + boolean unexpectedEndOfStream = false; + if (remainBytes == 35090) + System.out.println(); + while (remainBytes > 0) { - final int readBytes = pdfSource.read( streamCopyBuf, 0, - ( remainBytes > streamCopyBufLen ) ? streamCopyBufLen : (int) remainBytes ); - if ( readBytes <= 0 ) + final int readBytes = pdfSource.read(streamCopyBuf, 0, + (remainBytes > streamCopyBufLen) ? streamCopyBufLen : (int) remainBytes); + if (readBytes <= 0) { - throw new IOException( "No more bytes from stream but expected: " + remainBytes ); - } - out.write( streamCopyBuf, 0, readBytes ); - + // throw new IOException( + // "No more bytes from stream but expected: " + remainBytes + // ); + unexpectedEndOfStream = true; + break; + } + out.write(streamCopyBuf, 0, readBytes); remainBytes -= readBytes; + bytesRead += readBytes; + } + if (unexpectedEndOfStream) + { + pdfSource.unread(bytesRead); + out = stream.createFilteredStream(streamLengthObj); + readUntilEndStream(out); } - String endStream = readString(); - - if ( ! endStream.equals( "endstream" ) ) + if (!endStream.equals("endstream")) { - throw new IOException( "Error reading stream using length value. Expected='endstream' actual='" - + endStream + "' " ); - } - - } - finally + throw new IOException("Error reading stream using length value. Expected='endstream' actual='" + + endStream + "' "); + } + } + finally { - if ( out != null ) + if (out != null) { out.close(); } } return stream; } + + private void readUntilEndStream(final OutputStream out) throws IOException + { + + int bufSize; + int charMatchCount = 0; + byte[] keyw = ENDSTREAM; + + final int quickTestOffset = 5; // last character position of shortest + // keyword ('endobj') + + // read next chunk into buffer; already matched chars are added to + // beginning of buffer + while ((bufSize = pdfSource.read(streamCopyBuf, charMatchCount, streamCopyBufLen - charMatchCount)) > 0) + { + bufSize += charMatchCount; + + int bIdx = charMatchCount; + int quickTestIdx; + + // iterate over buffer, trying to find keyword match + for (int maxQuicktestIdx = bufSize - quickTestOffset; bIdx < bufSize; bIdx++) + { + // reduce compare operations by first test last character we + // would have to + // match if current one matches; if it is not a character from + // keywords + // we can move behind the test character; + // this shortcut is inspired by Boyer–Moore string search + // algorithm + // and can reduce parsing time by approx. 20% + if ((charMatchCount == 0) && ((quickTestIdx = bIdx + quickTestOffset) < maxQuicktestIdx)) + { + + final byte ch = streamCopyBuf[quickTestIdx]; + if ((ch > 't') || (ch < 'a')) + { + // last character we would have to match if current + // character would match + // is not a character from keywords -> jump behind and + // start over + bIdx = quickTestIdx; + continue; + } + } + + final byte ch = streamCopyBuf[bIdx]; // could be negative - but + // we only compare to ASCII + + if (ch == keyw[charMatchCount]) + { + if (++charMatchCount == keyw.length) + { + // match found + bIdx++; + break; + } + } + else + { + if ((charMatchCount == 3) && (ch == ENDOBJ[charMatchCount])) + { + // maybe ENDSTREAM is missing but we could have ENDOBJ + keyw = ENDOBJ; + charMatchCount++; + + } + else + { + // no match; incrementing match start by 1 would be dumb + // since we already know matched chars + // depending on current char read we may already have + // beginning of a new match: + // 'e': first char matched; + // 'n': if we are at match position idx 7 we already + // read 'e' thus 2 chars matched + // for each other char we have to start matching first + // keyword char beginning with next + // read position + charMatchCount = (ch == E) ? 1 : ((ch == N) && (charMatchCount == 7)) ? 2 : 0; + // search again for 'endstream' + keyw = ENDSTREAM; + } + } + } // for + + int contentBytes = Math.max(0, bIdx - charMatchCount); + + // write buffer content until first matched char to output stream + if (contentBytes > 0) + { + out.write(streamCopyBuf, 0, contentBytes); + } + if (charMatchCount == keyw.length) + { + // keyword matched; unread matched keyword (endstream/endobj) + // and following buffered content + pdfSource.unread(streamCopyBuf, contentBytes, bufSize - contentBytes); + break; + + } + else + { + // copy matched chars at start of buffer + System.arraycopy(keyw, 0, streamCopyBuf, 0, charMatchCount); + } + + } // while + } + } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java index d4e0fad6bbd..3b79134959e 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java @@ -124,7 +124,7 @@ public void drawPage( Graphics g, PDPage p, Dimension pageDimension ) throws IOE PDResources resources = page.findResources(); processStream( page, resources, page.getContents().getStream() ); } - List annotations = page.getAnnotations(); + List annotations = page.getAnnotations(); for( int i=0; i appearanceMap = appearDictionary.getNormalAppearance(); if (appearanceMap != null) { PDAppearanceStream appearance = (PDAppearanceStream)appearanceMap.get( appearanceName ); if( appearance != null ) { - g.translate( (int)rect.getLowerLeftX(), (int)-rect.getLowerLeftY() ); + Point2D point = new Point2D.Float(rect.getLowerLeftX(), rect.getLowerLeftY()); + Matrix matrix = appearance.getMatrix(); + if (matrix != null) + { + // transform the rectangle using the given matrix + AffineTransform at = matrix.createAffineTransform(); + at.transform(point, point); + } + g.translate( (int)point.getX(), -(int)point.getY() ); processSubStream( page, appearance.getResources(), appearance.getStream() ); - g.translate( (int)-rect.getLowerLeftX(), (int)+rect.getLowerLeftY() ); + g.translate( -(int)point.getX(), (int)point.getY() ); } } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java index 6bcaab2cab4..83e03d2bc82 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java @@ -1139,7 +1139,7 @@ public Object visitFromDocument(COSDocument doc) throws COSVisitorException } // the trailer section should only be used for xref tables not for xref streams - if (!doc.isXRefStream() || hybridPrev != -1) + if (!incrementalUpdate || !doc.isXRefStream() || hybridPrev != -1) { doWriteTrailer(doc); } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java index 94c4ef4dd20..8a8b13876de 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java @@ -16,6 +16,8 @@ */ 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; @@ -62,6 +64,11 @@ 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; @@ -311,6 +318,11 @@ public PDRectangle findMediaBox() { retval = getParent().findMediaBox(); } + if (retval == null) + { + LOG.debug("Can't find MediaBox, using LETTER as default pagesize!"); + retval = PDPage.PAGE_SIZE_LETTER; + } return retval; } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java index 2fbb2dc5c36..d0226cf8de0 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java @@ -380,6 +380,9 @@ else if( nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME ) HorizontalMetricsTable hMet = ttf.getHorizontalMetrics(); int[] widthValues = hMet.getAdvanceWidth(); + // some monospaced fonts provide only one value for the width + // instead of an array containing the same value for every glyphid + boolean isMonospaced = widthValues.length == 1; int nWidths=lastChar-firstChar+1; List widths = new ArrayList(nWidths); // width of the .notdef character. @@ -406,7 +409,14 @@ else if( nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME ) int gid = uniMap.getGlyphId(charCode); if (gid != 0) { - widths.set( e.getKey().intValue()-firstChar,widthValues[gid]*scaling ); + if (isMonospaced) + { + widths.set( e.getKey().intValue()-firstChar,widthValues[0]*scaling ); + } + else + { + widths.set( e.getKey().intValue()-firstChar,widthValues[gid]*scaling ); + } } } setWidths( widths ); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java index b114cbad7eb..a6818bf1179 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java @@ -16,16 +16,21 @@ */ package org.apache.pdfbox.pdmodel.interactive.annotation; +import java.awt.geom.AffineTransform; + 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.COSStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.common.COSObjectable; import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.util.Matrix; /** @@ -76,7 +81,7 @@ public COSBase getCOSObject() public PDRectangle getBoundingBox() { PDRectangle box = null; - COSArray bbox = (COSArray)stream.getDictionaryObject( COSName.getPDFName( "BBox" ) ); + COSArray bbox = (COSArray)stream.getDictionaryObject( COSName.BBOX ); if( bbox != null ) { box = new PDRectangle( bbox ); @@ -96,7 +101,7 @@ public void setBoundingBox( PDRectangle rectangle ) { array = rectangle.getCOSArray(); } - stream.setItem( COSName.getPDFName( "BBox" ), array ); + stream.setItem( COSName.BBOX, array ); } /** @@ -129,4 +134,50 @@ public void setResources( PDResources resources ) } stream.setItem( COSName.RESOURCES, dict ); } + + /** + * Gets the optional matrix for this appearance. This may return null. + * + * @return The matrix of this appearance. + */ + public Matrix getMatrix() + { + Matrix retval = null; + COSArray array = (COSArray)stream.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 this appearance. + * @param transform the transformation matrix + */ + public void setMatrix(AffineTransform transform) + { + if (transform != null) + { + COSArray matrix = new COSArray(); + double[] values = new double[6]; + transform.getMatrix(values); + for (double v : values) + { + matrix.add(new COSFloat((float)v)); + } + stream.setItem(COSName.MATRIX, matrix); + } + else + { + stream.removeItem(COSName.MATRIX); + } + } + } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDSignature.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDSignature.java index 9549c009664..6712107c3f1 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDSignature.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDSignature.java @@ -25,6 +25,8 @@ * * @author Ben Litchfield * @version $Revision: 1.5 $ + * + * @deprecated Use {@link PDSignatureField} instead (see PDFBOX-1513). */ public class PDSignature extends PDField { @@ -38,6 +40,8 @@ public class PDSignature extends PDField public PDSignature( PDAcroForm theAcroForm, COSDictionary field) { super(theAcroForm,field); + throw new RuntimeException( "The usage of " + getClass().getName() + + " is deprecated. Please use " + PDSignatureField.class.getName() + " instead (see PDFBOX-1513)" ); } /** diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFStreamEngine.java b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFStreamEngine.java index 8a1a51b2e50..9e9cb0f0ef6 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFStreamEngine.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFStreamEngine.java @@ -375,8 +375,8 @@ public void processEncodedText( byte[] string ) throws IOException textStateParameters.setValue(2,1, riseText); int pageRotation = page.findRotation(); - float pageHeight = page.findMediaBox().getHeight(); - float pageWidth = page.findMediaBox().getWidth(); + float pageHeight = page.findCropBox().getHeight(); + float pageWidth = page.findCropBox().getWidth(); Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); Matrix textXctm = new Matrix(); From 93253337ee930c391a2182760273b227dd99641a Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 7 Apr 2013 13:30:25 +0000 Subject: [PATCH 0003/1017] updated ant build script git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1465397 13f79535-47bb-0310-9956-ffa450edef68 --- pdfbox/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdfbox/build.xml b/pdfbox/build.xml index a76defcd7e8..6349b4d5846 100644 --- a/pdfbox/build.xml +++ b/pdfbox/build.xml @@ -28,7 +28,7 @@ - + From 07c6b7798661149067d8e4cefa482424d074b27e Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 7 Apr 2013 13:40:58 +0000 Subject: [PATCH 0004/1017] updated RELEASE-NOTES git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1465400 13f79535-47bb-0310-9956-ffa450edef68 --- RELEASE-NOTES.txt | 145 +++++----------------------------------------- 1 file changed, 15 insertions(+), 130 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 2bb6eaac402..2c92d8353e6 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,143 +1,28 @@ -Release Notes -- Apache PDFBox -- Version 1.8.0 +Release Notes -- Apache PDFBox -- Version 1.8.1 Introduction ------------ The Apache PDFBox library is an open source Java tool for working with PDF documents. -This is an incremental feature release based on the earlier 1.x releases. -This release contains many improvements and fixes especially related to -performance, resource usage and rendering. The most significant -new features are the first official release of the new PDF/A preflight module as -part of Apache PDFBox and the improved signature abilities. +This is an incremental bugfix release based on the earlier 1.8.0 release. It +contains a couple of fixes, most notable a fix for an issue which leads to +corrupted files. -For more details on these changes and all the other fixes and improvements -included in this release, please refer to the following issues on the -PDFBox issue tracker at https://issues.apache.org/jira/browse/PDFBOX. - -New features - -[PDFBOX-46] - Support XFA form submitting -[PDFBOX-81] - Excetion while extracting images -[PDFBOX-84] - Read PDF XFA Form Contents -[PDFBOX-127] - Accessing XML-Forms (patch provided) -[PDFBOX-1067] - PDF Scan from Xerox WorkCentre 5030 renders as all black -[PDFBOX-1514] - Improved overlay cammand line tool - -Improvements - -[PDFBOX-1246] - Allow resolution to be defined when calling ImageIOUtil.writeImage -[PDFBOX-1312] - Refactor the PdfA parser -[PDFBOX-1352] - xmpbox refactoring -[PDFBOX-1367] - Do not generate preflight jar with dependencies at each build -[PDFBOX-1369] - support getting file pointer from RandomAccessRead interface -[PDFBOX-1377] - Simplify PDF/A schema parsing -[PDFBOX-1387] - Create NonSequentialParser with InputStream -[PDFBOX-1388] - Create a branch to refactor xmpbox -[PDFBOX-1392] - Enable usage of compressionQuality when creating a PDJpeg -[PDFBOX-1399] - Add an example on how to extract embedded files -[PDFBOX-1418] - Improved font mapping -[PDFBOX-1423] - An error exists on this page. Acrobat may not display the page correctly -[PDFBOX-1425] - Make PositionWrapper.getTextPosition public -[PDFBOX-1439] - Problems with Image Extraction from PDF -[PDFBOX-1468] - Decrypting unencrypted strings -[PDFBOX-1488] - Add generics to the COSArrayList class -[PDFBOX-1492] - Add basic XFA extraction -[PDFBOX-1513] - PDF signature improvements -[PDFBOX-1536] - Improve the ExtractEmbeddedFiles example to deal with different kind of - trees representing the embedded files +For more details on all fixes included in this release, please refer to the following +issues on the PDFBox issue tracker at https://issues.apache.org/jira/browse/PDFBOX. Bug Fixes -[PDFBOX-137] - Does not detect paper format -[PDFBOX-811] - EmbeddedFiles example does not work -[PDFBOX-819] - PDFBox prints landscape documents as portrait -[PDFBOX-927] - Problem on writing some kind of images to a File in filesystem -[PDFBOX-969] - IndexOutOfBound whle creating a Type1C font -[PDFBOX-985] - PDF Printing Orientation -[PDFBOX-992] - IndexOutOfBoundsException: while parsing few pdf's -[PDFBOX-1072] - PDFImageWriter extracts black images from arabic PDFs -[PDFBOX-1084] - java.lang.NumberFormatException when getting PDF text of some PDF file if - dup line does not contains font index -[PDFBOX-1130] - ExtractText -html doesn't always close the

tags it opens -[PDFBOX-1138] - Printing fails for pages in landscape format -[PDFBOX-1169] - Images extracted from PDF are loosing color (are shown in blackcolor) -[PDFBOX-1191] - Lost information while extracting images from pdf scanned by XEROX -[PDFBOX-1298] - java.lang.IllegalArgumentException: fromIndex(0) > toIndex(-2) -[PDFBOX-1344] - xml namespace problem in ResourceRef -[PDFBOX-1346] - Can't assign an arbitrary string value to an editable acroform combobox -[PDFBOX-1359] - stack overflow~~ ExtractText (PDF2TXT) -[PDFBOX-1362] - Slovakian characters -[PDFBOX-1364] - Error On MetaData -[PDFBOX-1365] - Error On MetaData: The Metadata entry doesn't reference a stream object -[PDFBOX-1368] - Xmp validation KO if there are complex type in a seq element -[PDFBOX-1371] - MetaData : Trapped property -[PDFBOX-1373] - Body Syntax Error : Possible Encoding problem -[PDFBOX-1374] - Error On MetaData: Title -[PDFBOX-1376] - xmpbox cannot parse structured types containing structured types -[PDFBOX-1378] - [PATCH] COSArray: Avoid NullPointerException in setString -[PDFBOX-1379] - [PATCH] COSDocument: setVersion -[PDFBOX-1380] - [PATCH] PDNameTreeNode -[PDFBOX-1381] - [PATCH] PDNumberTreeNode -[PDFBOX-1382] - [PATCH] PDObjectReference -[PDFBOX-1394] - Image streams are lost when adding new images to page -[PDFBOX-1395] - Transparency isn't checked in Page dictionary -[PDFBOX-1398] - Runtime exception when trying to check PDF/A compliance on non PDF/A document -[PDFBOX-1408] - Width of space character is calculated wrong -[PDFBOX-1411] - [Patch] PDPixelMap.createImageStream can attempt to close output stream it - didn't open, hiding errors. -[PDFBOX-1412] - NullPointerException when getting fields from a PDF file -[PDFBOX-1421] - TextPosition.getX()returen 0 in case of rotation ==360 -[PDFBOX-1424] - Wrong glyph (Persian) is used in extacted text instead of the original glyph - (Persian) in PDF file -[PDFBOX-1427] - PDF page rotation is not working -[PDFBOX-1431] - Some pdfss created by ABBY trigger a NPE -[PDFBOX-1432] - PDF rotation problem -[PDFBOX-1434] - Font being changed after form field is set -[PDFBOX-1440] - Garbled image from PDFToImage -[PDFBOX-1443] - Images are rendered blank -[PDFBOX-1445] - /ImageMask true does not work. Patch included. -[PDFBOX-1447] - wasted work in PDFMarkedContentExtractor.processTextPosition() -[PDFBOX-1449] - Preflight doesn't report on non-embedded font -[PDFBOX-1456] - wasted work in PublicKeySecurityHandler.prepareForDecryption() -[PDFBOX-1458] - wasted work in PDOptionalContentProperties.setGroupEnabled() -[PDFBOX-1464] - unnecessary linear searches in "CFFParser.Format0FDSelect.getFd" -[PDFBOX-1465] - Preflight crashes on PDF -[PDFBOX-1469] - [PATCH] PDPageContentStream incorrectly sets colors in CMYK color space -[PDFBOX-1470] - about attribute is serialized more than one time in XmpSerializer -[PDFBOX-1471] - Parsing of xmp properties set in xml attributes is not done -[PDFBOX-1473] - Incorrect handling of OpenType fonts -[PDFBOX-1475] - Exception thrown during rendering page if /DecodeParms specified indirectly - (like [9 0 R]) in XObject/Image -[PDFBOX-1476] - Isartor tests fails due to bad rdf:about handling -[PDFBOX-1477] - PDF/A file is declared invalid on windows and valid with linux -[PDFBOX-1481] - Ignore postscript code when parsing a type1 font -[PDFBOX-1482] - Java color spaces returned by PDDeviceN do not take tint transformation into account - and type mismatch -[PDFBOX-1489] - Maven Dependency not resolveable agains central -[PDFBOX-1490] - pdf page => inline image not converted -[PDFBOX-1491] - Image with colour key masking triggers NPE -[PDFBOX-1496] - Can't add multiple form XObjects to a PDF - they become duplicated -[PDFBOX-1497] - Preflight throws an exception on DeviceN validation -[PDFBOX-1499] - The blank white page is converted with method pdPage.convertToImage(); -[PDFBOX-1501] - Width of the character "201" .. inconsistent with the width in the PDF dictionary. -[PDFBOX-1504] - Split document issue -[PDFBOX-1505] - [PATCH] CharStringRenderer does not render CharString data correctly for Type 2 - CFF fonts -[PDFBOX-1517] - PDFSplit: split is set to one if no -split argument present -[PDFBOX-1518] - ClassCastException writing text to a page -[PDFBOX-1522] - Some PDF files are causing exception (java.io.IOException: Error: Could not find - font(COSName{F53.0}) in map=) -[PDFBOX-1535] - Extract text from PDF cause Nullpointer Exception in PDFStreamEngine.processEncodedText - Method - -Misc - -[PDFBOX-1366] - Reduce xmpbox code complexity -[PDFBOX-1528] - rename org.apache.padaf.xmpbox to org.apache.xmpbox -[PDFBOX-1530] - Respect PDFBox coding rules in new modules -[PDFBOX-1531] - Reaarange xmpbox and preflight maven modules -[PDFBOX-795] - PDPage convertToImage partially generates image file and throws exception +[PDFBOX-1515] - PDGraphicsState class receives null page argument leading to NPE +[PDFBOX-1538] - Content of annotation not visible in image (converted from pdf) +[PDFBOX-1547] - TextPosition.getX() and getY() do not work properly with CropBox +[PDFBOX-1549] - TTFSubFont generates bug-prone TTF sub fonts screwing some printers +[PDFBOX-1551] - Merging PDFs with interactive forms results in a corrupt PDF +[PDFBOX-1556] - Saving a document containing a xfa form creates invalid pdf +[PDFBOX-1557] - NonSequentialPDFParser incorrectly parsing document info +[PDFBOX-1558] - Unused PDSignature class should be removed +[PDFBOX-1559] - Error when using monospaced Fonts Release Contents ---------------- From 92112173ab6b682fd48b91468b7e93cf0a281e7b Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 7 Apr 2013 13:57:08 +0000 Subject: [PATCH 0005/1017] [maven-release-plugin] prepare release 1.8.1 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1465401 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index 5a873b9e52b..7496cb9650c 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index 4a82e38dcb6..8f737b89085 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 8b94a63114d..6e58227de15 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 8a4818951e9..d3a37c84665 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index 3389dd1be6b..775095ff5db 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index acf16c230c4..8267625f48f 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 07c00ae4e6b..f89a88fdfe5 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.1/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.1/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/tags/1.8.1/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index d33a41e859f..a0cc4a42137 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 404833a8f64..48ae466d27f 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.1 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.1 - http://svn.apache.org/viewvc/pdfbox/branches/1.8 + http://svn.apache.org/viewvc/pdfbox/tags/1.8.1 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 7f5ba10753a..f037a4fdf1e 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index c7caef2a7e2..befee88db4c 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index 89f1cacbf57..c4a41fc937d 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 66e1b74ddca..1d56d882f3f 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1-SNAPSHOT + 1.8.1 ../parent/pom.xml From cd7b630f6c9078c7c5008bb27a1135ba70b67460 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 7 Apr 2013 13:57:17 +0000 Subject: [PATCH 0006/1017] [maven-release-plugin] prepare for next development iteration git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1465403 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index 7496cb9650c..c72f88571bd 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index 8f737b89085..0be6b524906 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 6e58227de15..3acdbdfea58 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index d3a37c84665..561060d3cff 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index 775095ff5db..f5dac195554 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 8267625f48f..1e04490f5e9 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index f89a88fdfe5..04dd01a5529 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.1/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.1/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/tags/1.8.1/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index a0cc4a42137..10c0084e6ba 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 48ae466d27f..3273b23fd5d 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.1 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.1 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 - http://svn.apache.org/viewvc/pdfbox/tags/1.8.1 + http://svn.apache.org/viewvc/pdfbox/branches/1.8 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index f037a4fdf1e..576cd374251 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index befee88db4c..c6b0c8d8d49 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index c4a41fc937d..c4f2af7a695 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 1d56d882f3f..7292096aa00 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.1 + 1.8.2-SNAPSHOT ../parent/pom.xml From c93cc67e806f45a06b0461c41f6cdfba5289ada0 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 May 2013 16:58:07 +0000 Subject: [PATCH 0007/1017] merged the following changes from trunk PDFBOX-1293 rev. 1480030 PDFBOX-1561 rev. 1469558 PDFBOX-1565 rev. 1467638 PDFBOX-1580 rev. 1477806,1478472 PDFBOX-1581 rev. 1486413 PDFBOX-1583 rev. 1476795 PDFBOX-1586 rev. 1479136,1479287 PDFBOX-1592 rev. 1480000 PDFBOX-1599 rev. 1485781,1486337 PDFBOX-1601 rev. 1484089 PDFBOX-1602 rev. 1484277 PDFBOX-1603 rev. 1485771 PDFBOX-1605 rev. 1484547 PDFBOX-1611 rev. 1486423 PDFBOX-1612 rev. 1486440 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1486661 13f79535-47bb-0310-9956-ffa450edef68 --- .../main/java/org/apache/pdfbox/Overlay.java | 6 +- .../org/apache/pdfbox/cos/COSDocument.java | 40 ++- .../java/org/apache/pdfbox/cos/COSName.java | 8 + .../java/org/apache/pdfbox/cos/COSString.java | 295 ++++++++++-------- .../pdfparser/NonSequentialPDFParser.java | 80 ++++- .../apache/pdfbox/pdfparser/PDFParser.java | 1 + .../org/apache/pdfbox/pdmodel/PDDocument.java | 59 +++- .../pdfbox/pdmodel/common/COSStreamArray.java | 1 + .../pdfbox/pdmodel/common/PDObjectStream.java | 14 +- .../pdfbox/pdmodel/common/PDRectangle.java | 10 +- .../pdfbox/pdmodel/common/PDStream.java | 4 +- .../pdfbox/pdmodel/font/PDType0Font.java | 15 +- .../pdfbox/pdmodel/font/PDType1Font.java | 58 +++- .../pdmodel/graphics/color/ColorSpaceLab.java | 186 +++++++++++ .../pdmodel/graphics/color/PDIndexed.java | 13 +- .../pdfbox/pdmodel/graphics/color/PDLab.java | 15 +- .../annotation/PDAppearanceDictionary.java | 62 +++- .../digitalsignature/PDPropBuildDataDict.java | 2 +- .../interactive/form/PDAppearance.java | 2 +- .../org/apache/pdfbox/util/LayerUtility.java | 6 +- .../apache/pdfbox/util/PDFImageWriter.java | 110 ++++--- .../apache/pdfbox/util/operator/Invoke.java | 14 +- .../util/operator/SetNonStrokingColor.java | 12 +- .../util/operator/SetNonStrokingLabColor.java | 49 +++ .../util/operator/SetStrokingColor.java | 12 +- .../util/operator/SetStrokingLabColor.java | 48 +++ .../test/java/org/apache/pdfbox/TestAll.java | 2 + .../apache/pdfbox/pdmodel/TestPDDocument.java | 165 ++++++++++ 28 files changed, 1026 insertions(+), 263 deletions(-) create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/ColorSpaceLab.java create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetNonStrokingLabColor.java create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetStrokingLabColor.java create mode 100644 pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocument.java diff --git a/pdfbox/src/main/java/org/apache/pdfbox/Overlay.java b/pdfbox/src/main/java/org/apache/pdfbox/Overlay.java index bb0b255b4ea..c8d2c576d36 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/Overlay.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/Overlay.java @@ -224,12 +224,12 @@ public PDDocument overlay( PDDocument overlay, PDDocument destination ) throws I collectLayoutPages( overlayCatalog.getAllPages() ); COSDictionary saveGraphicsStateDic = new COSDictionary(); - saveGraphicsStateStream = new COSStream( saveGraphicsStateDic, pdfDocument.getDocument().getScratchFile() ); + saveGraphicsStateStream = pdfDocument.getDocument().createCOSStream(saveGraphicsStateDic); OutputStream saveStream = saveGraphicsStateStream.createUnfilteredStream(); saveStream.write( " q\n".getBytes("ISO-8859-1") ); saveStream.flush(); - restoreGraphicsStateStream = new COSStream( saveGraphicsStateDic, pdfDocument.getDocument().getScratchFile() ); + restoreGraphicsStateStream = pdfDocument.getDocument().createCOSStream(saveGraphicsStateDic); OutputStream restoreStream = restoreGraphicsStateStream.createUnfilteredStream(); restoreStream.write( " Q\n".getBytes("ISO-8859-1") ); restoreStream.flush(); @@ -353,7 +353,7 @@ else if (bInObjectIdent && Character.isWhitespace((char) b)) COSDictionary streamDict = new COSDictionary(); streamDict.setInt(COSName.LENGTH, baos.size()); - COSStream output = new COSStream(streamDict, pdfDocument.getDocument().getScratchFile()); + COSStream output = pdfDocument.getDocument().createCOSStream(streamDict); output.setFilters(stream.getFilters()); OutputStream os = output.createUnfilteredStream(); baos.writeTo(os); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java index 4dc50432f4a..7e3f5aeb91a 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java @@ -70,11 +70,6 @@ public class COSDocument extends COSBase */ private COSDictionary trailer; - /** - * Document signature dictionary. - */ - private COSDictionary signDictionary = null; - /** * Signature interface. */ @@ -178,10 +173,43 @@ public COSDocument(RandomAccess file) * This will get the scratch file for this document. * * @return The scratch file. + * + * */ public RandomAccess getScratchFile() { - return scratchFile; + // TODO the direct access to the scratch file should be removed. + if (!closed) + { + return scratchFile; + } + else + { + LOG.error("Can't access the scratch file as it is already closed!"); + return null; + } + } + + /** + * Create a new COSStream using the underlying scratch file. + * + * @return the new COSStream + */ + public COSStream createCOSStream() + { + return new COSStream( getScratchFile() ); + } + + /** + * Create a new COSStream using the underlying scratch file. + * + * @param dictionary the corresponding dictionary + * + * @return the new COSStream + */ + public COSStream createCOSStream(COSDictionary dictionary) + { + return new COSStream( dictionary, getScratchFile() ); } /** diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java index 9b262a92651..b8182c07182 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java @@ -348,6 +348,10 @@ public final class COSName extends COSBase implements Comparable * A common COSName value. */ public static final COSName CS = new COSName( "CS" ); + /** + * A common COSName value. + */ + public static final COSName DEFAULT = new COSName( "default" ); /** * A common COSName value. */ @@ -600,6 +604,10 @@ public final class COSName extends COSBase implements Comparable /** * A common COSName value. */ + public static final COSName FIRST = new COSName( "First" ); + /** + * A common COSName value. + */ public static final COSName FIRST_CHAR = new COSName( "FirstChar" ); /** * A common COSName value. diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSString.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSString.java index 9aa5ca9fe7d..c2486eae439 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSString.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSString.java @@ -26,7 +26,7 @@ /** * This represents a string object in a PDF document. - * + * * @author Ben Litchfield * @version $Revision: 1.30 $ */ @@ -35,52 +35,50 @@ public class COSString extends COSBase /** * One of the open string tokens. */ - public static final byte[] STRING_OPEN = new byte[]{ 40 }; //"(".getBytes(); + 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" ); + 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" ); + 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" ); + 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" ); + 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" ); + 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" ); + 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" ); + 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" ); + 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" ); - + 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. + * 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; @@ -94,78 +92,86 @@ public COSString() out = new ByteArrayOutputStream(); } - public COSString (boolean isDictionary) { + /** + * Constructor. + * + * @param isDictionaryValue determines if this string represents a dictionary + */ + public COSString(boolean isDictionaryValue) + { this(); - this.isDictionary = isDictionary; + isDictionary = isDictionaryValue; } - /** * Explicit constructor for ease of manual PDF construction. - * - * @param value The string value of the object. + * + * @param value + * The string value of the object. */ - public COSString( String value ) + public COSString(String value) { try { boolean unicode16 = false; char[] chars = value.toCharArray(); int length = chars.length; - for( int i=0; i 255 ) + if (chars[i] > 255) { unicode16 = true; break; } } - if( unicode16 ) + if (unicode16) { - byte[] data = value.getBytes( "UTF-16BE" ); - out = new ByteArrayOutputStream( data.length +2); - out.write( 0xFE ); - out.write( 0xFF ); - out.write( data ); + 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 ); + out = new ByteArrayOutputStream(data.length); + out.write(data); } } catch (IOException ignore) { ignore.printStackTrace(); - //should never happen + // should never happen } } /** * Explicit constructor for ease of manual PDF construction. - * - * @param value The string value of the object. + * + * @param value + * The string value of the object. */ - public COSString( byte[] value ) + public COSString(byte[] value) { try { - out = new ByteArrayOutputStream( value.length ); - out.write( value ); + out = new ByteArrayOutputStream(value.length); + out.write(value); } catch (IOException ignore) { ignore.printStackTrace(); - //should never happen + // 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. + * + * @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) @@ -176,8 +182,9 @@ public void setForceLiteralForm(boolean 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. + * @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) @@ -187,45 +194,54 @@ public void setForceHexForm(boolean v) /** * This will create a COS string from a string of hex characters. - * - * @param hex A hex string. + * + * @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. + * @throws IOException + * If there is an error with the hex string. */ - public static COSString createFromHexString(String hex) - throws IOException { + 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 + * 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. + * @throws IOException + * If there is an error with the hex string. */ - public static COSString createFromHexString(String hex, boolean force) - throws IOException { + 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 ) + 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' ); + 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) { + 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); + } + else + { + IOException exception = new IOException("Invalid hex string: " + hex); exception.initCause(e); throw exception; } @@ -236,17 +252,17 @@ public static COSString createFromHexString(String hex, boolean force) /** * 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 ); + StringBuilder retval = new StringBuilder(out.size() * 2); byte[] data = getBytes(); int length = data.length; - for( int i=0; i 2 ) + if (data.length > 2) { - if( data[0] == (byte)0xFF && data[1] == (byte)0xFE ) + if (data[0] == (byte) 0xFF && data[1] == (byte) 0xFE) { encoding = "UTF-16LE"; - start=2; + start = 2; } - else if( data[0] == (byte)0xFE && data[1] == (byte)0xFF ) + else if (data[0] == (byte) 0xFE && data[1] == (byte) 0xFF) { encoding = "UTF-16BE"; - start=2; + start = 2; } } try { - if (isDictionary && encoding.equals("ISO-8859-1")) { - byte [] tmp = getBytes(); + if (isDictionary && encoding.equals("ISO-8859-1")) + { + byte[] tmp = getBytes(); PdfDocEncoding pde = new PdfDocEncoding(); StringBuilder sb = new StringBuilder(tmp.length); - for (byte b : tmp) { - sb.append(pde.getCharacter((b+256)%256)); + for (byte b : tmp) + { + final String character = pde.getCharacter((b + 256) % 256); + if (character != null) + { + sb.append(character); + } } retval = sb.toString(); - } else { - retval = new String( getBytes(), start, data.length-start, encoding ); + } + else + { + retval = new String(getBytes(), start, data.length - start, encoding); } } - catch( IOException e ) + catch (IOException e) { - //should never happen + // should never happen e.printStackTrace(); - retval = new String( getBytes() ); + retval = new String(getBytes()); } this.str = retval; return retval; @@ -306,27 +330,31 @@ else if( data[0] == (byte)0xFE && data[1] == (byte)0xFF ) /** * 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. + * + * @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 + public void append(byte[] data) throws IOException { - out.write( data ); + 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. + * + * @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 + public void append(int in) throws IOException { - out.write( in ); + out.write(in); this.str = null; } @@ -341,7 +369,7 @@ public void reset() /** * This will get the bytes of the string. - * + * * @return A byte array that represents the string. */ public byte[] getBytes() @@ -360,66 +388,68 @@ public String toString() /** * 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. + * + * @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 + public void writePDF(OutputStream output) throws IOException { boolean outsideASCII = false; - //Lets first check if we need to escape this string. + // Lets first check if we need to escape this string. byte[] bytes = getBytes(); - int length = bytes.length; - for( int i=0; istartxref. + * + * @return the offset of StartXref + * @throws IOException If something went wrong. */ protected final long getStartxrefOffset() throws IOException { @@ -590,6 +632,7 @@ else if (patOff < lastPatternChOff) * Reads given pattern from {@link #pdfSource}. Skipping whitespace at start * and end. * + * @param pattern pattern to be skipped * @throws IOException if pattern could not be read */ protected final void readPattern(final char[] pattern) throws IOException @@ -688,6 +731,7 @@ public void parse() throws IOException try { document.close(); + document = null; } catch (IOException ioe) { @@ -696,6 +740,11 @@ public void parse() throws IOException } } + /** + * Return the pdf file. + * + * @return the pdf file + */ protected File getPdfFile() { return this.pdfFile; @@ -712,7 +761,9 @@ protected void deleteTempFile() try { if (!pdfFile.delete()) + { LOG.warn("Temporary file '" + pdfFile.getName() + "' can't be deleted"); + } } catch (SecurityException e) { @@ -749,7 +800,9 @@ public PDDocument getPDDocument() throws IOException { PDDocument pdDocument = super.getPDDocument(); if (securityHandler != null) + { pdDocument.setSecurityHandler(securityHandler); + } return pdDocument; } @@ -1170,7 +1223,8 @@ else if (offsetOrObjstmObNr > 0) // 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 (offset: " + offsetOrObjstmObNr + ")."); + throw new IOException("Stream not preceded by dictionary (offset: " + + offsetOrObjstmObNr + ")."); } skipSpaces(); endObjectKey = readLine(); @@ -1270,7 +1324,14 @@ else if (pb instanceof COSArray) } // ------------------------------------------------------------------------ - /** Decrypts given COSString. */ + /** + * Decrypts given COSString. + * + * @param str the string to be decrypted + * @param objNr the object number + * @param objGenNr the object generation number + * @throws IOException ff something went wrong + */ protected final void decrypt(COSString str, long objNr, long objGenNr) throws IOException { try @@ -1438,7 +1499,10 @@ else if (whitespace != 0x0A) int bytesRead = 0; boolean unexpectedEndOfStream = false; if (remainBytes == 35090) + { + // TODO debug system out, to be removed?? System.out.println(); + } while (remainBytes > 0) { final int readBytes = pdfSource.read(streamCopyBuf, 0, diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java index 46fc69cb3e0..7e3392ac1c1 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java @@ -234,6 +234,7 @@ public void parse() throws IOException if( document != null ) { document.close(); + document = null; } if( t instanceof IOException ) { diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java index 02f61b777e5..0285d58c70e 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java @@ -420,7 +420,7 @@ else if(options.getPage()<=0) acroFormDict.setItem(COSName.DR, null); // Set empty Appearance-Dictionary PDAppearanceDictionary ap = new PDAppearanceDictionary(); - COSStream apsStream = new COSStream(getDocument().getScratchFile()); + COSStream apsStream = getDocument().createCOSStream(); apsStream.createUnfilteredStream(); PDAppearanceStream aps = new PDAppearanceStream(apsStream); COSDictionary cosObject = (COSDictionary)aps.getCOSObject(); @@ -441,6 +441,11 @@ else if(options.getPage()<=0) for ( COSObject cosObject : cosObjects ) { + if (!annotNotFound && !sigFieldNotFound) + { + break; + } + COSBase base = cosObject.getObject(); if (base != null && base instanceof COSDictionary) { @@ -639,7 +644,7 @@ public PDPage importPage( PDPage page ) throws IOException PDStream src = page.getContents(); if(src != null) { - PDStream dest = new PDStream( new COSStream( src.getStream(), document.getScratchFile() ) ); + PDStream dest = new PDStream( document.createCOSStream()); importedPage.setContents( dest ); os = dest.createOutputStream(); @@ -1242,7 +1247,40 @@ public static PDDocument loadNonSeq( File file, RandomAccess scratchFile, String } /** - * This will save this document to the filesystem. + * Parses PDF with non sequential parser. + * + * @param input stream that contains the document. + * @param scratchFile location to store temp PDFBox data for this document + * + * @return loaded document + * + * @throws IOException in case of a file reading or parsing error + */ + public static PDDocument loadNonSeq( InputStream input, RandomAccess scratchFile) throws IOException + { + return loadNonSeq(input, scratchFile, ""); + } + + /** + * Parses PDF with non sequential parser. + * + * @param input stream that contains the document. + * @param scratchFile location to store temp PDFBox data for this document + * @param password password to be used for decryption + * + * @return loaded document + * + * @throws IOException in case of a file reading or parsing error + */ + public static PDDocument loadNonSeq( InputStream input, RandomAccess scratchFile, String password ) throws IOException + { + NonSequentialPDFParser parser = new NonSequentialPDFParser( input, scratchFile, password ); + parser.parse(); + return parser.getPDDocument(); + } + + /** + * Save the document to a file. * * @param fileName The file to save as. * @@ -1251,7 +1289,20 @@ public static PDDocument loadNonSeq( File file, RandomAccess scratchFile, String */ public void save( String fileName ) throws IOException, COSVisitorException { - save( new FileOutputStream( fileName ) ); + save( new File( fileName ) ); + } + + /** + * Save the document to a file. + * + * @param file 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 file ) throws IOException, COSVisitorException + { + save( new FileOutputStream( file ) ); } /** diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/COSStreamArray.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/COSStreamArray.java index 055502d3ef0..7b9326be907 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/COSStreamArray.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/COSStreamArray.java @@ -94,6 +94,7 @@ public int getStreamCount() * This will get the scratch file associated with this stream. * * @return The scratch file where this stream is being stored. + * */ public RandomAccess getScratchFile() { diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDObjectStream.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDObjectStream.java index b18953bb18a..7fe6d041556 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDObjectStream.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDObjectStream.java @@ -51,9 +51,9 @@ public PDObjectStream( COSStream str ) */ public static PDObjectStream createStream( PDDocument document ) { - COSStream cosStream = new COSStream( document.getDocument().getScratchFile() ); + COSStream cosStream = document.getDocument().createCOSStream(); PDObjectStream strm = new PDObjectStream( cosStream ); - strm.getStream().setName( "Type", "ObjStm" ); + strm.getStream().setName( COSName.TYPE, "ObjStm" ); return strm; } @@ -64,7 +64,7 @@ public static PDObjectStream createStream( PDDocument document ) */ public String getType() { - return getStream().getNameAsString( "Type" ); + return getStream().getNameAsString( COSName.TYPE ); } /** @@ -74,7 +74,7 @@ public String getType() */ public int getNumberOfObjects() { - return getStream().getInt( "N", 0 ); + return getStream().getInt( COSName.N, 0 ); } /** @@ -84,7 +84,7 @@ public int getNumberOfObjects() */ public void setNumberOfObjects( int n ) { - getStream().setInt( "N", n ); + getStream().setInt( COSName.N, n ); } /** @@ -94,7 +94,7 @@ public void setNumberOfObjects( int n ) */ public int getFirstByteOffset() { - return getStream().getInt( "First", 0 ); + return getStream().getInt( COSName.FIRST, 0 ); } /** @@ -104,7 +104,7 @@ public int getFirstByteOffset() */ public void setFirstByteOffset( int n ) { - getStream().setInt( "First", n ); + getStream().setInt( COSName.FIRST, n ); } /** diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDRectangle.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDRectangle.java index a6fd488b4bc..12e65a7dfa8 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDRectangle.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDRectangle.java @@ -67,7 +67,7 @@ public PDRectangle( float width, float height ) /** * Constructor. * - * @param box The non PD bouding box. + * @param box the bounding box to be used for the rectangle */ public PDRectangle( BoundingBox box ) { @@ -85,7 +85,13 @@ public PDRectangle( BoundingBox box ) */ public PDRectangle( COSArray array ) { - rectArray = array; + float[] values = array.toFloatArray(); + rectArray = new COSArray(); + // we have to start with the lower left corner + rectArray.add( new COSFloat( Math.min(values[0],values[2] )) ); + rectArray.add( new COSFloat( Math.min(values[1],values[3] )) ); + rectArray.add( new COSFloat( Math.max(values[0],values[2] )) ); + rectArray.add( new COSFloat( Math.max(values[1],values[3] )) ); } /** diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java index 402ef603fa1..3c6cd326832 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/PDStream.java @@ -67,7 +67,7 @@ protected PDStream() */ public PDStream(PDDocument document) { - stream = new COSStream(document.getDocument().getScratchFile()); + stream = document.getDocument().createCOSStream(); } /** @@ -116,7 +116,7 @@ public PDStream(PDDocument doc, InputStream str, boolean filtered) OutputStream output = null; try { - stream = new COSStream(doc.getDocument().getScratchFile()); + stream = doc.getDocument().createCOSStream(); if (filtered) { output = stream.createFilteredStream(); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType0Font.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType0Font.java index ca334dfb8cc..72842f9b8f2 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType0Font.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType0Font.java @@ -87,12 +87,17 @@ public Font getawtFont() throws IOException if (descendantFont != null) { awtFont = ((PDSimpleFont)descendantFont).getawtFont(); + if (awtFont != null) + { + setIsFontSubstituted(((PDSimpleFont)descendantFont).isFontSubstituted()); + /* + * Fix Oracle JVM Crashes. + * Tested with Oracle JRE 6.0_45-b06 and 7.0_21-b11 + */ + awtFont.canDisplay(1); + } } - if (awtFont != null) - { - setIsFontSubstituted(((PDSimpleFont)descendantFont).isFontSubstituted()); - } - else + if (awtFont == null) { awtFont = FontManager.getStandardFont(); LOG.info("Using font "+awtFont.getName() diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java index 6569efabd42..c87245ab7b1 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java @@ -347,7 +347,8 @@ else if (line.startsWith("dup")) { String name = st.nextToken(); if(encoding == null) { - log.warn("Unable to get character encoding. Encoding defintion found without /Encoding line."); + log.warn("Unable to get character encoding. " + + "Encoding definition found without /Encoding line."); } else { @@ -369,23 +370,60 @@ else if (line.startsWith("dup")) { // 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) + // most likely all matrix values are in the same line than the keyword + if (line.indexOf("[") > -1) { - try + String matrixValues = line.substring(line.indexOf("[")+1,line.lastIndexOf("]")); + StringTokenizer st = new StringTokenizer(matrixValues); + COSArray array = new COSArray(); + if (st.countTokens() >= 6) { - for (int i=0;i<6;i++) + try { - COSFloat floatValue = new COSFloat(Float.parseFloat(st.nextToken())); + for (int i=0;i<6;i++) + { + COSFloat floatValue = new COSFloat(Float.parseFloat(st.nextToken())); + array.add(floatValue); + } + } + catch (NumberFormatException exception) + { + log.error("Can't read the fontmatrix from embedded font file!"); + } + fontMatrix = new PDMatrix(array); + } + } + else + { + // there are fonts where all values are on a separate line, see PDFBOX-1611 + COSArray array = new COSArray(); + while((line = in.readLine()) != null) + { + if (line.startsWith("[")) + { + continue; + } + if (line.endsWith("]")) + { + break; + } + try + { + COSFloat floatValue = new COSFloat(Float.parseFloat(line)); array.add(floatValue); } + catch (NumberFormatException exception) + { + log.error("Can't read the fontmatrix from embedded font file!"); + } + } + if (array.size() == 6) + { fontMatrix = new PDMatrix(array); } - catch (NumberFormatException exception) + else { - log.error("Can't read the fontmatrix from embedded font file!"); + log.error("Can't read the fontmatrix from embedded font file, not enough values!"); } } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/ColorSpaceLab.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/ColorSpaceLab.java new file mode 100644 index 00000000000..52708d577e2 --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/ColorSpaceLab.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.PDRange; + +/** + * This class represents a CalRGB color space. + * + * The color conversion uses the algorithm described on wikipedia. + * + * The blackpoint isn't used, as I can't find any hint how to do that. + * + */ +public class ColorSpaceLab extends ColorSpace +{ + private static final long serialVersionUID = -5769360600770807798L; + + private PDTristimulus whitepoint = null; + // TODO unused?? + private PDTristimulus blackpoint = null; + private PDRange aRange = null; + private PDRange bRange = null; + + /** + * Default Constructor. + * + */ + public ColorSpaceLab() + { + super(ColorSpace.TYPE_3CLR, 3); + } + + /** + * Constructor. + * + * @param whitept whitepoint values + * @param blackpt blackpoint values + * @param a range for value a + * @param b range for value b + */ + public ColorSpaceLab(PDTristimulus whitept, PDTristimulus blackpt, PDRange a, PDRange b) + { + this(); + whitepoint = whitept; + blackpoint = blackpt; + aRange = a; + bRange = b; + } + + /** + * Clip the given value to the given range. + * + * @param x + * the given value to be clipped + * @param range + * the range to be used to clip the value to + * + * @return the clipped value + */ + private float clipToRange(float x, PDRange range) + { + return Math.min(Math.max(x, range.getMin()), range.getMax()); + } + + private static final float VALUE_6_29 = 6 / 29; + private static final float VALUE_4_29 = 4 / 29; + private static final float VALUE_108_841 = 108 / 841; + private static final float VALUE_841_108 = 841 / 108; + private static final float VALUE_216_24389 = 216 / 24389; + + private float calculateStage2ToXYZ(float value) + { + if (value >= VALUE_6_29) + { + return (float) Math.pow(value, 3); + } + else + { + return VALUE_108_841 * (value - VALUE_4_29); + } + } + + private float calculateStage2FromXYZ(float value) + { + if (value >= VALUE_216_24389) + { + return (float) Math.pow(value, 1 / 3); + } + else + { + return VALUE_841_108 * value + VALUE_4_29; + } + } + + /** + * {@inheritDoc} + * + */ + public float[] toRGB(float[] colorvalue) + { + ColorSpace colorspaceXYZ = ColorSpace.getInstance(CS_CIEXYZ); + return colorspaceXYZ.toRGB(toCIEXYZ(colorvalue)); + } + + /** + * {@inheritDoc} + * + */ + public float[] fromRGB(float[] rgbvalue) + { + ColorSpace colorspaceXYZ = ColorSpace.getInstance(CS_CIEXYZ); + return fromCIEXYZ(colorspaceXYZ.fromRGB(rgbvalue)); + } + + /** + * {@inheritDoc} + * + */ + public float[] toCIEXYZ(float[] colorvalue) + { + float a = colorvalue[1]; + if (aRange != null) + { + // clip the a value to the given range + a = clipToRange(a, aRange); + } + float b = colorvalue[2]; + if (bRange != null) + { + // clip the b value to the given range + b = clipToRange(b, bRange); + } + float m = (colorvalue[0] + 16) / 116; + float l = m + (a / 500); + float n = m - (b / 200); + + float x = whitepoint.getX() * calculateStage2ToXYZ(l); + float y = whitepoint.getY() * calculateStage2ToXYZ(m); + float z = whitepoint.getZ() * calculateStage2ToXYZ(n); + return new float[] { x, y, z }; + } + + /** + * {@inheritDoc} + * + */ + public float[] fromCIEXYZ(float[] colorvalue) + { + float x = calculateStage2FromXYZ(colorvalue[0] / whitepoint.getX()); + float y = calculateStage2FromXYZ(colorvalue[1] / whitepoint.getY()); + float z = calculateStage2FromXYZ(colorvalue[2] / whitepoint.getZ()); + + float l = 116 * y - 116; + float a = 500 * (x - y); + float b = 200 * (y - z); + if (aRange != null) + { + // clip the a value to the given range + a = clipToRange(a, aRange); + } + if (bRange != null) + { + // clip the b value to the given range + b = clipToRange(b, bRange); + } + return new float[] { l, a, b }; + } + +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java index 87f5b1af2ec..43e6916b540 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java @@ -317,6 +317,10 @@ private ColorModel getBaseColorModel(int bpc) throws IOException if (baseColorModel == null) { baseColorModel = getBaseColorSpace().createColorModel(bpc); + if( baseColorModel.getTransferType() != DataBuffer.TYPE_BYTE ) + { + throw new IOException( "Not implemented" ); + } } return baseColorModel; } @@ -333,10 +337,10 @@ private void calculateIndexedColorValues(ColorModel colorModel, int bpc) throws // and sometimes there are fewer indexed value than possible maxIndex = Math.min(numberOfColorValues-1, highValue); byte[] index = getLookupData(); - if( baseColorModel.getTransferType() != DataBuffer.TYPE_BYTE ) - { - throw new IOException( "Not implemented" ); - } + // despite all definitions there may be less values within the lookup data + int numberOfColorValuesFromIndex = (index.length / baseColorModel.getNumComponents())-1; + maxIndex = Math.min(maxIndex, numberOfColorValuesFromIndex); + // does the colorspace have an alpha channel? boolean hasAlpha = baseColorModel.hasAlpha(); indexNumOfComponents = 3 + ( hasAlpha ? 1 : 0); int buffersize = (maxIndex+1) * indexNumOfComponents; @@ -346,6 +350,7 @@ private void calculateIndexedColorValues(ColorModel colorModel, int bpc) throws for( int i = 0; i <= maxIndex; i++ ) { System.arraycopy(index, i * inData.length, inData, 0, inData.length); + // calculate RGB values indexedColorValues[bufferIndex] = (byte)colorModel.getRed(inData); indexedColorValues[bufferIndex+1] = (byte)colorModel.getGreen(inData); indexedColorValues[bufferIndex+2] = (byte)colorModel.getBlue(inData); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java index 487e84abc5d..22d36a63b8f 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDLab.java @@ -24,8 +24,11 @@ import org.apache.pdfbox.pdmodel.common.PDRange; +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; @@ -96,7 +99,7 @@ public COSBase getCOSObject() */ protected ColorSpace createColorSpace() throws IOException { - throw new IOException( "Not implemented" ); + return new ColorSpaceLab(getWhitepoint(), getBlackPoint(), getARange(), getBRange()); } /** @@ -110,7 +113,13 @@ protected ColorSpace createColorSpace() throws IOException */ public ColorModel createColorModel( int bpc ) throws IOException { - throw new IOException( "Not implemented" ); + int[] nBits = {bpc, bpc, bpc}; + return new ComponentColorModel( getJavaColorSpace(), + nBits, + false, + false, + Transparency.OPAQUE, + DataBuffer.TYPE_BYTE); } /** @@ -122,8 +131,6 @@ public ColorModel createColorModel( int bpc ) throws IOException */ public int getNumberOfComponents() throws IOException { - //BJL - //hmm is this correct, I am not 100% sure. return 3; } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java index 4ffb8102487..56e11845ec2 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java @@ -16,6 +16,8 @@ */ package org.apache.pdfbox.pdmodel.interactive.annotation; +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.COSName; @@ -35,6 +37,12 @@ */ public class PDAppearanceDictionary implements COSObjectable { + + /** + * Log instance. + */ + private static final Log LOG = LogFactory.getLog(PDAppearanceDictionary.class); + private COSDictionary dictionary; /** @@ -93,15 +101,25 @@ else if( ap instanceof COSStream ) { COSStream aux = (COSStream) ap; ap = new COSDictionary(); - ((COSDictionary)ap).setItem(COSName.getPDFName( "default" ), aux ); + ((COSDictionary)ap).setItem(COSName.DEFAULT, aux ); } COSDictionary map = (COSDictionary)ap; Map actuals = new HashMap(); - Map retval = new COSDictionaryMap( actuals, map ); + Map retval = new COSDictionaryMap( actuals, map ); for( COSName asName : map.keySet() ) { - COSStream as = (COSStream)map.getDictionaryObject( asName ); - actuals.put( asName.getName(), new PDAppearanceStream( as ) ); + COSBase stream = map.getDictionaryObject( asName ); + // PDFBOX-1599: this is just a workaround. The given PDF provides "null" as stream + // which leads to a COSName("null") value and finally to a ClassCastExcpetion + if (stream instanceof COSStream) + { + COSStream as = (COSStream)stream; + actuals.put( asName.getName(), new PDAppearanceStream( as ) ); + } + else + { + LOG.debug("non-conformance workaround: ignore null value for appearance stream."); + } } return retval; } @@ -151,15 +169,25 @@ public Map getRolloverAppearance() { COSStream aux = (COSStream) ap; ap = new COSDictionary(); - ((COSDictionary)ap).setItem(COSName.getPDFName( "default" ), aux ); + ((COSDictionary)ap).setItem(COSName.DEFAULT, aux ); } COSDictionary map = (COSDictionary)ap; Map actuals = new HashMap(); - retval = new COSDictionaryMap( actuals, map ); + retval = new COSDictionaryMap( actuals, map ); for( COSName asName : map.keySet() ) { - COSStream as = (COSStream)map.getDictionaryObject( asName ); - actuals.put( asName.getName(), new PDAppearanceStream( as ) ); + COSBase stream = map.getDictionaryObject( asName ); + // PDFBOX-1599: this is just a workaround. The given PDF provides "null" as stream + // which leads to a COSName("null") value and finally to a ClassCastExcpetion + if (stream instanceof COSStream) + { + COSStream as = (COSStream)stream; + actuals.put( asName.getName(), new PDAppearanceStream( as ) ); + } + else + { + LOG.debug("non-conformance workaround: ignore null value for appearance stream."); + } } } return retval; @@ -210,16 +238,26 @@ public Map getDownAppearance() { COSStream aux = (COSStream) ap; ap = new COSDictionary(); - ((COSDictionary)ap).setItem(COSName.getPDFName( "default" ), aux ); + ((COSDictionary)ap).setItem(COSName.DEFAULT, aux ); } COSDictionary map = (COSDictionary)ap; Map actuals = new HashMap(); - retval = new COSDictionaryMap( actuals, map ); + retval = new COSDictionaryMap( actuals, map ); for( COSName asName : map.keySet() ) { - COSStream as = (COSStream)map.getDictionaryObject( asName ); - actuals.put( asName.getName(), new PDAppearanceStream( as ) ); + COSBase stream = map.getDictionaryObject( asName ); + // PDFBOX-1599: this is just a workaround. The given PDF provides "null" as stream + // which leads to a COSName("null") value and finally to a ClassCastExcpetion + if (stream instanceof COSStream) + { + COSStream as = (COSStream)stream; + actuals.put( asName.getName(), new PDAppearanceStream( as ) ); + } + else + { + LOG.debug("non-conformance workaround: ignore null value for appearance stream."); + } } } return retval; diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDPropBuildDataDict.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDPropBuildDataDict.java index e03e4e63813..e7106b00895 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDPropBuildDataDict.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/PDPropBuildDataDict.java @@ -90,7 +90,7 @@ public String getName() */ public void setName(String name) { - dictionary.setString(COSName.NAME, name); + dictionary.setName(COSName.NAME, name); } /** diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAppearance.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAppearance.java index 4c3a9204e3e..9d1abf888df 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAppearance.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDAppearance.java @@ -257,7 +257,7 @@ public void setAppearanceValue(String apValue) throws IOException PDAppearanceStream appearanceStream = (PDAppearanceStream)normalAppearance.get( "default" ); if( appearanceStream == null ) { - COSStream cosStream = new COSStream( acroForm.getDocument().getDocument().getScratchFile() ); + COSStream cosStream = acroForm.getDocument().getDocument().createCOSStream(); appearanceStream = new PDAppearanceStream( cosStream ); appearanceStream.setBoundingBox( widget.getRectangle().createRetranslatedRectangle() ); appearance.setNormalAppearance( appearanceStream ); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/LayerUtility.java b/pdfbox/src/main/java/org/apache/pdfbox/util/LayerUtility.java index 2e6031f67da..cc75657ca01 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/LayerUtility.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/LayerUtility.java @@ -83,14 +83,12 @@ public PDDocument getDocument() public void wrapInSaveRestore(PDPage page) throws IOException { COSDictionary saveGraphicsStateDic = new COSDictionary(); - COSStream saveGraphicsStateStream = new COSStream(saveGraphicsStateDic, - getDocument().getDocument().getScratchFile()); + COSStream saveGraphicsStateStream = getDocument().getDocument().createCOSStream(saveGraphicsStateDic); OutputStream saveStream = saveGraphicsStateStream.createUnfilteredStream(); saveStream.write("q\n".getBytes("ISO-8859-1")); saveStream.flush(); - COSStream restoreGraphicsStateStream = new COSStream(saveGraphicsStateDic, - getDocument().getDocument().getScratchFile()); + COSStream restoreGraphicsStateStream = getDocument().getDocument().createCOSStream(saveGraphicsStateDic); OutputStream restoreStream = restoreGraphicsStateStream.createUnfilteredStream(); restoreStream.write("Q\n".getBytes("ISO-8859-1")); restoreStream.flush(); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFImageWriter.java b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFImageWriter.java index a08a05e436c..d20a45f8093 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFImageWriter.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFImageWriter.java @@ -23,24 +23,25 @@ import java.util.List; import java.util.Properties; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; - /** - * 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. - *

- * Patterned after PDFTextStripper. - * + * This class writes single pages of a pdf to a file. + * * @author Daniel Wilson * @version $Revision: 1.1 $ */ public class PDFImageWriter extends PDFStreamEngine { + /** + * Log instance. + */ + private static final Log LOG = LogFactory.getLog(PDFImageWriter.class); + /** * Instantiate a new PDFImageWriter object. */ @@ -49,72 +50,89 @@ 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. + * 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 + public PDFImageWriter(Properties props) throws IOException { - super( props ); + 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 + * + * @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 + * @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 + 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 ) + catch (HeadlessException e) { resolution = 96; } - return writeImage(document, imageType, password, startPage, endPage, outputPrefix, - 8, resolution); + 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) + * + * @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 + * @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 + 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++ ) + List pages = document.getDocumentCatalog().getAllPages(); + int pagesSize = pages.size(); + for (int i = startPage - 1; i < endPage && i < pagesSize; i++) { - PDPage page = (PDPage)pages.get( i ); + PDPage page = pages.get(i); BufferedImage image = page.convertToImage(imageType, resolution); String fileName = outputPrefix + (i + 1); - System.out.println( "Writing: " + fileName + "." +imageFormat); + LOG.info("Writing: " + fileName + "." + imageFormat); bSuccess &= ImageIOUtil.writeImage(image, imageFormat, fileName, imageType, resolution); } return bSuccess; diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/Invoke.java b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/Invoke.java index 16e9b047334..5947f368afe 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/Invoke.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/Invoke.java @@ -22,6 +22,7 @@ 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.Matrix; import org.apache.pdfbox.util.PDFMarkedContentExtractor; import org.apache.pdfbox.util.PDFOperator; @@ -53,18 +54,25 @@ public void process(PDFOperator operator, List arguments) throws IOExce Map xobjects = context.getXObjects(); PDXObject xobject = (PDXObject) xobjects.get(name.getName()); - if (this.context instanceof PDFMarkedContentExtractor) + if (context instanceof PDFMarkedContentExtractor) { - ((PDFMarkedContentExtractor) this.context).xobject(xobject); + ((PDFMarkedContentExtractor) context).xobject(xobject); } if(xobject instanceof PDXObjectForm) { PDXObjectForm form = (PDXObjectForm)xobject; COSStream formContentstream = form.getCOSStream(); + // 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); + } // find some optional resources, instead of using the current resources PDResources pdResources = form.getResources(); - getContext().processSubStream( context.getCurrentPage(), pdResources, formContentstream ); + context.processSubStream( context.getCurrentPage(), pdResources, formContentstream ); } } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetNonStrokingColor.java b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetNonStrokingColor.java index 286667ebd48..5ae3e48809f 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetNonStrokingColor.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetNonStrokingColor.java @@ -29,6 +29,7 @@ import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased; import org.apache.pdfbox.pdmodel.graphics.color.PDCalRGB; import org.apache.pdfbox.pdmodel.graphics.color.PDIndexed; +import org.apache.pdfbox.pdmodel.graphics.color.PDLab; import org.apache.pdfbox.pdmodel.graphics.color.PDPattern; import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation; import org.apache.pdfbox.util.PDFOperator; @@ -37,7 +38,6 @@ /** *

Set the non stroking color space.

* - * @author Andreas Lehmkühler * @version $Revision: 1.0 $ */ public class SetNonStrokingColor extends OperatorProcessor @@ -46,7 +46,7 @@ public class SetNonStrokingColor extends OperatorProcessor /** * Log instance. */ - private static final Log log = LogFactory.getLog(SetNonStrokingColor.class); + private static final Log LOG = LogFactory.getLog(SetNonStrokingColor.class); /** * sc,scn Set color space for non stroking operations. @@ -96,6 +96,10 @@ else if (colorSpace instanceof PDIndexed) { newOperator = new SetNonStrokingIndexed(); } + else if (colorSpace instanceof PDLab) + { + newOperator = new SetNonStrokingLabColor(); + } if (newOperator != null) { @@ -104,13 +108,13 @@ else if (colorSpace instanceof PDIndexed) } else { - log.warn("Not supported colorspace "+colorSpace.getName() + LOG.warn("Not supported colorspace "+colorSpace.getName() + " within operator "+operator.getOperation()); } } else { - log.warn("Colorspace not found in "+getClass().getName()+".process!!"); + LOG.warn("Colorspace not found in "+getClass().getName()+".process!!"); } } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetNonStrokingLabColor.java b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetNonStrokingLabColor.java new file mode 100644 index 00000000000..84d989f9bf4 --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetNonStrokingLabColor.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +/** + * Sets the non stroking color values for Lab colorspace. + * + */ +public class SetNonStrokingLabColor extends OperatorProcessor +{ + + /** + * {@inheritDoc} + * + */ + public void process(PDFOperator operator, List 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); + } + +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetStrokingColor.java b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetStrokingColor.java index 0b0497b1895..3927d230f56 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetStrokingColor.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetStrokingColor.java @@ -29,6 +29,7 @@ import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased; import org.apache.pdfbox.pdmodel.graphics.color.PDCalRGB; import org.apache.pdfbox.pdmodel.graphics.color.PDIndexed; +import org.apache.pdfbox.pdmodel.graphics.color.PDLab; import org.apache.pdfbox.pdmodel.graphics.color.PDPattern; import org.apache.pdfbox.pdmodel.graphics.color.PDSeparation; import org.apache.pdfbox.util.PDFOperator; @@ -37,7 +38,6 @@ /** *

Set the stroking color space.

* - * @author Andreas Lehmkühler * @version $Revision: 1.0 $ */ public class SetStrokingColor extends OperatorProcessor @@ -46,7 +46,7 @@ public class SetStrokingColor extends OperatorProcessor /** * Log instance. */ - private static final Log log = LogFactory.getLog(SetStrokingColor.class); + private static final Log LOG = LogFactory.getLog(SetStrokingColor.class); /** * SC,SCN Set color space for stroking operations. @@ -96,6 +96,10 @@ else if (colorSpace instanceof PDIndexed) { newOperator = new SetStrokingIndexed(); } + else if (colorSpace instanceof PDLab) + { + newOperator = new SetStrokingLabColor(); + } if (newOperator != null) { @@ -104,13 +108,13 @@ else if (colorSpace instanceof PDIndexed) } else { - log.info("Not supported colorspace "+colorSpace.getName() + LOG.info("Not supported colorspace "+colorSpace.getName() + " within operator "+operator.getOperation()); } } else { - log.warn("Colorspace not found in "+getClass().getName()+".process!!"); + LOG.warn("Colorspace not found in "+getClass().getName()+".process!!"); } } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetStrokingLabColor.java b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetStrokingLabColor.java new file mode 100644 index 00000000000..0c21d8e08c4 --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/SetStrokingLabColor.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +/** + * Sets the stroking color values for Lab colorspace. + * + */ +public class SetStrokingLabColor extends OperatorProcessor +{ + + /** + * {@inheritDoc} + */ + public void process(PDFOperator operator, List arguments) throws IOException + { + PDColorState color = context.getGraphicsState().getStrokingColor(); + float[] values = new float[3]; + for( int i=0; i 200); + assertEquals("%PDF-1.4", new String(Arrays.copyOfRange(pdf, 0, 8), "UTF-8")); + assertEquals("%%EOF\n", new String(Arrays.copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); + + // Load + PDDocument loadDoc = PDDocument.load(new ByteArrayInputStream(pdf), new RandomAccessBuffer()); + assertEquals(1, loadDoc.getNumberOfPages()); + loadDoc.close(); + } + + /** + * Test document save/load using a file. + * @throws IOException if something went wrong + * @throws COSVisitorException if something went wrong + */ + public void testSaveLoadFile() throws IOException, COSVisitorException + { + // Create PDF with one blank page + PDDocument document = new PDDocument(); + document.addPage(new PDPage()); + + // Save + File targetFile = new File(testResultsDir, "pddocument-saveloadfile.pdf"); + document.save(targetFile); + document.close(); + + // Verify content + assertTrue(targetFile.length() > 200); + InputStream in = new FileInputStream(targetFile); + byte[] pdf = IOUtils.toByteArray(in); + in.close(); + assertTrue(pdf.length > 200); + assertEquals("%PDF-1.4", new String(Arrays.copyOfRange(pdf, 0, 8), "UTF-8")); + assertEquals("%%EOF\n", new String(Arrays.copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); + + // Load + PDDocument loadDoc = PDDocument.load(targetFile, new RandomAccessBuffer()); + assertEquals(1, loadDoc.getNumberOfPages()); + loadDoc.close(); + } + + /** + * Test document save/loadNonSeq using a stream. + * @throws IOException if something went wrong + * @throws COSVisitorException if something went wrong + */ +public void testSaveLoadNonSeqStream() throws IOException, COSVisitorException + { + // Create PDF with one blank page + PDDocument document = new PDDocument(); + document.addPage(new PDPage()); + + // Save + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + document.save(baos); + document.close(); + + // Verify content + byte[] pdf = baos.toByteArray(); + assertTrue(pdf.length > 200); + assertEquals("%PDF-1.4", new String(Arrays.copyOfRange(pdf, 0, 8), "UTF-8")); + assertEquals("%%EOF\n", new String(Arrays.copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); + + // Load + PDDocument loadDoc = PDDocument.loadNonSeq(new ByteArrayInputStream(pdf), new RandomAccessBuffer()); + assertEquals(1, loadDoc.getNumberOfPages()); + loadDoc.close(); + } + + /** + * Test document save/loadNonSeq using a file. + * @throws IOException if something went wrong + * @throws COSVisitorException if something went wrong + */ + public void testSaveLoadNonSeqFile() throws IOException, COSVisitorException + { + // Create PDF with one blank page + PDDocument document = new PDDocument(); + document.addPage(new PDPage()); + + // Save + File targetFile = new File(testResultsDir, "pddocument-saveloadnonseqfile.pdf"); + document.save(targetFile); + document.close(); + + // Verify content + assertTrue(targetFile.length() > 200); + InputStream in = new FileInputStream(targetFile); + byte[] pdf = IOUtils.toByteArray(in); + in.close(); + assertTrue(pdf.length > 200); + assertEquals("%PDF-1.4", new String(Arrays.copyOfRange(pdf, 0, 8), "UTF-8")); + assertEquals("%%EOF\n", new String(Arrays.copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); + + // Load + PDDocument loadDoc = PDDocument.loadNonSeq(targetFile, new RandomAccessBuffer()); + assertEquals(1, loadDoc.getNumberOfPages()); + loadDoc.close(); + } +} From 4c7b1e112a712e2c91ad00c3517d9d6ce4762b7c Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 May 2013 17:08:46 +0000 Subject: [PATCH 0008/1017] updated project info git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1486667 13f79535-47bb-0310-9956-ffa450edef68 --- RELEASE-NOTES.txt | 44 +++++++++++++++++++++++++++++++------------- doap_PDFBox.rdf | 5 +++++ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 2c92d8353e6..083faadea33 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,28 +1,46 @@ -Release Notes -- Apache PDFBox -- Version 1.8.1 +Release Notes -- Apache PDFBox -- Version 1.8.2 Introduction ------------ The Apache PDFBox library is an open source Java tool for working with PDF documents. -This is an incremental bugfix release based on the earlier 1.8.0 release. It -contains a couple of fixes, most notable a fix for an issue which leads to -corrupted files. +This is an incremental bugfix release based on the earlier 1.8.1 release. It +contains a couple of fixes and small improvements. For more details on all fixes included in this release, please refer to the following issues on the PDFBox issue tracker at https://issues.apache.org/jira/browse/PDFBOX. Bug Fixes -[PDFBOX-1515] - PDGraphicsState class receives null page argument leading to NPE -[PDFBOX-1538] - Content of annotation not visible in image (converted from pdf) -[PDFBOX-1547] - TextPosition.getX() and getY() do not work properly with CropBox -[PDFBOX-1549] - TTFSubFont generates bug-prone TTF sub fonts screwing some printers -[PDFBOX-1551] - Merging PDFs with interactive forms results in a corrupt PDF -[PDFBOX-1556] - Saving a document containing a xfa form creates invalid pdf -[PDFBOX-1557] - NonSequentialPDFParser incorrectly parsing document info -[PDFBOX-1558] - Unused PDSignature class should be removed -[PDFBOX-1559] - Error when using monospaced Fonts +[PDFBOX-1093] - Copy Page from one Document to another: Page Content Stream Linked + to Original Document +[PDFBOX-1561] - PDFBox throws exception with PDFTextStripper.getText +[PDFBOX-1580] - Oracle JVM crashes because of embedded fonts. +[PDFBOX-1583] - wasted work in PDDocument.addSignature(...) +[PDFBOX-1586] - IndexOutOfBoundsException when saving a document (at random) +[PDFBOX-1592] - Addition of 'null' in COSString when extracting form fields +[PDFBOX-1599] - ConvertToImage gets ClassCastException with Checkboxes in PDFForm +[PDFBOX-1601] - java.lang.IllegalArgumentException: Width (4032) and height (-2880) + cannot be <= 0 +[PDFBOX-1602] - Erroneous values for TextPosition get{X,Y}[DirAdj] +[PDFBOX-1603] - Regression in PDDocument.loadNonSeq ? +[PDFBOX-1605] - PDPropBuildDataDict: use COSName instead of COSString as name value +[PDFBOX-1609] - EXCEPTION_ACCESS_VIOLATION with PDF file and image conversion +[PDFBOX-1611] - Avoid IndexOutOfBoundsException when extracting the font matrix of + a Type1 font +[PDFBOX-1612] - Avoid ArrayOutOfBoundsException when creating a PDPixelMap using an + indexed colorspace + +Improvement + +[PDFBOX-1293] - PDFImageWriter should use logging instead of System.out.println +[PDFBOX-1581] - Add PDDocument.save(File) and PDDocument.loadNonSeq(InputStream, ...) + +New Feature + +[PDFBOX-1565] - Add support for Lab color spaces + Release Contents ---------------- diff --git a/doap_PDFBox.rdf b/doap_PDFBox.rdf index 33bbbc58195..a87f203fb19 100644 --- a/doap_PDFBox.rdf +++ b/doap_PDFBox.rdf @@ -35,6 +35,11 @@ + + Apache PDFBox + 2013-04-10 + 1.8.1 + Apache PDFBox 2013-03-23 From 59e5ab540d96ae79714c52249e93231fa53def22 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 May 2013 17:09:03 +0000 Subject: [PATCH 0009/1017] updated ant build script git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1486668 13f79535-47bb-0310-9956-ffa450edef68 --- pdfbox/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdfbox/build.xml b/pdfbox/build.xml index 6349b4d5846..0a417a573b1 100644 --- a/pdfbox/build.xml +++ b/pdfbox/build.xml @@ -28,7 +28,7 @@ - + From fb4933e982efec2f5efa3bb617c930683c3f1e15 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 May 2013 17:22:52 +0000 Subject: [PATCH 0010/1017] [maven-release-plugin] prepare release 1.8.2 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1486675 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index c72f88571bd..e590ed71fc9 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index 0be6b524906..cc7e61b8b45 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 3acdbdfea58..704c7c45da0 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 561060d3cff..351b1ba061f 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index f5dac195554..ba5de3c3443 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 1e04490f5e9..80dfe0a0a74 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 04dd01a5529..defcf59ce01 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/tags/1.8.2/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index 10c0084e6ba..e5d11eb75e3 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 3273b23fd5d..264a489164e 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 - http://svn.apache.org/viewvc/pdfbox/branches/1.8 + http://svn.apache.org/viewvc/pdfbox/tags/1.8.2 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 576cd374251..0b77f590b5f 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index c6b0c8d8d49..a7a584c0570 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index c4f2af7a695..065d0c5d0aa 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 7292096aa00..837e9bbed42 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml From 05f53b1a7ea8e65320804ea285768d3fc9a8b24c Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 May 2013 17:23:02 +0000 Subject: [PATCH 0011/1017] [maven-release-plugin] prepare for next development iteration git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1486677 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index e590ed71fc9..86935e437ff 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index cc7e61b8b45..c630251ba22 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 704c7c45da0..210bd882fd0 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 351b1ba061f..641a889682a 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index ba5de3c3443..8102e935408 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 80dfe0a0a74..4ab2cb619de 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index defcf59ce01..50281fb6acc 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/tags/1.8.2/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index e5d11eb75e3..aeb04c1543a 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 264a489164e..bdc3386e989 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 - http://svn.apache.org/viewvc/pdfbox/tags/1.8.2 + http://svn.apache.org/viewvc/pdfbox/branches/1.8 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 0b77f590b5f..9f0f1b06bfa 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index a7a584c0570..a19b1b0f3c9 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index 065d0c5d0aa..8b4c00899d7 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 837e9bbed42..c636d1a7541 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml From b61ad2107ea7b77816ef3495066a7eafb548b380 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 May 2013 17:44:17 +0000 Subject: [PATCH 0012/1017] revert prepare release changes git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1486681 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 2 +- pdfbox/pom.xml | 2 +- pom.xml | 2 +- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index 86935e437ff..c72f88571bd 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index c630251ba22..0be6b524906 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 210bd882fd0..3acdbdfea58 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 641a889682a..561060d3cff 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index 8102e935408..f5dac195554 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 4ab2cb619de..1e04490f5e9 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 50281fb6acc..04dd01a5529 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT pom PDFBox parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index aeb04c1543a..10c0084e6ba 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/pom.xml b/pom.xml index bdc3386e989..3273b23fd5d 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT parent/pom.xml diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 9f0f1b06bfa..576cd374251 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index a19b1b0f3c9..c6b0c8d8d49 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index 8b4c00899d7..c4f2af7a695 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index c636d1a7541..7292096aa00 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml From 21076e1dc2da890db41de273b00541d3b25b5ecb Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 May 2013 18:01:38 +0000 Subject: [PATCH 0013/1017] PDFBOX-1581: backport of Arrays.copyOfRange to restore java5 compatibility git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1486688 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/pdfbox/pdmodel/TestPDDocument.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocument.java b/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocument.java index 2b2f76f3b83..961be8ef478 100644 --- a/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocument.java +++ b/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocument.java @@ -22,7 +22,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; import org.apache.pdfbox.exceptions.COSVisitorException; import org.apache.pdfbox.io.IOUtils; @@ -45,6 +44,14 @@ protected void setUp() throws Exception testResultsDir.mkdirs(); } + private byte[] copyOfRange(byte[] array, int from, int to) + { + // java5 backport of java6-only Arrays.copyOfRange + int length = to-from; + byte[] subArray = new byte[length]; + System.arraycopy(array, from, subArray, 0, length); + return subArray; + } /** * Test document save/load using a stream. * @throws IOException if something went wrong @@ -64,8 +71,8 @@ public void testSaveLoadStream() throws IOException, COSVisitorException // Verify content byte[] pdf = baos.toByteArray(); assertTrue(pdf.length > 200); - assertEquals("%PDF-1.4", new String(Arrays.copyOfRange(pdf, 0, 8), "UTF-8")); - assertEquals("%%EOF\n", new String(Arrays.copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); + assertEquals("%PDF-1.4", new String(copyOfRange(pdf, 0, 8), "UTF-8")); + assertEquals("%%EOF\n", new String(copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); // Load PDDocument loadDoc = PDDocument.load(new ByteArrayInputStream(pdf), new RandomAccessBuffer()); @@ -95,8 +102,8 @@ public void testSaveLoadFile() throws IOException, COSVisitorException byte[] pdf = IOUtils.toByteArray(in); in.close(); assertTrue(pdf.length > 200); - assertEquals("%PDF-1.4", new String(Arrays.copyOfRange(pdf, 0, 8), "UTF-8")); - assertEquals("%%EOF\n", new String(Arrays.copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); + assertEquals("%PDF-1.4", new String(copyOfRange(pdf, 0, 8), "UTF-8")); + assertEquals("%%EOF\n", new String(copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); // Load PDDocument loadDoc = PDDocument.load(targetFile, new RandomAccessBuffer()); @@ -123,8 +130,8 @@ public void testSaveLoadNonSeqStream() throws IOException, COSVisitorException // Verify content byte[] pdf = baos.toByteArray(); assertTrue(pdf.length > 200); - assertEquals("%PDF-1.4", new String(Arrays.copyOfRange(pdf, 0, 8), "UTF-8")); - assertEquals("%%EOF\n", new String(Arrays.copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); + assertEquals("%PDF-1.4", new String(copyOfRange(pdf, 0, 8), "UTF-8")); + assertEquals("%%EOF\n", new String(copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); // Load PDDocument loadDoc = PDDocument.loadNonSeq(new ByteArrayInputStream(pdf), new RandomAccessBuffer()); @@ -154,8 +161,8 @@ public void testSaveLoadNonSeqFile() throws IOException, COSVisitorException byte[] pdf = IOUtils.toByteArray(in); in.close(); assertTrue(pdf.length > 200); - assertEquals("%PDF-1.4", new String(Arrays.copyOfRange(pdf, 0, 8), "UTF-8")); - assertEquals("%%EOF\n", new String(Arrays.copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); + assertEquals("%PDF-1.4", new String(copyOfRange(pdf, 0, 8), "UTF-8")); + assertEquals("%%EOF\n", new String(copyOfRange(pdf, pdf.length - 6, pdf.length), "UTF-8")); // Load PDDocument loadDoc = PDDocument.loadNonSeq(targetFile, new RandomAccessBuffer()); From 2a83e512acf89e137288d359f51932ad7ae678a3 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 May 2013 18:14:33 +0000 Subject: [PATCH 0014/1017] [maven-release-plugin] prepare release 1.8.2 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1486689 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index c72f88571bd..e590ed71fc9 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index 0be6b524906..cc7e61b8b45 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 3acdbdfea58..704c7c45da0 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 561060d3cff..351b1ba061f 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index f5dac195554..ba5de3c3443 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 1e04490f5e9..80dfe0a0a74 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 04dd01a5529..defcf59ce01 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/tags/1.8.2/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index 10c0084e6ba..e5d11eb75e3 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 3273b23fd5d..264a489164e 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 - http://svn.apache.org/viewvc/pdfbox/branches/1.8 + http://svn.apache.org/viewvc/pdfbox/tags/1.8.2 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 576cd374251..0b77f590b5f 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index c6b0c8d8d49..a7a584c0570 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index c4f2af7a695..065d0c5d0aa 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 7292096aa00..837e9bbed42 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml From 20cae75483b4edffc865715729f9e74c8eb7341f Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 May 2013 18:14:43 +0000 Subject: [PATCH 0015/1017] [maven-release-plugin] prepare for next development iteration git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1486691 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index e590ed71fc9..86935e437ff 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index cc7e61b8b45..c630251ba22 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 704c7c45da0..210bd882fd0 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 351b1ba061f..641a889682a 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index ba5de3c3443..8102e935408 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 80dfe0a0a74..4ab2cb619de 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index defcf59ce01..50281fb6acc 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/tags/1.8.2/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index e5d11eb75e3..aeb04c1543a 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 264a489164e..bdc3386e989 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 - http://svn.apache.org/viewvc/pdfbox/tags/1.8.2 + http://svn.apache.org/viewvc/pdfbox/branches/1.8 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 0b77f590b5f..9f0f1b06bfa 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index a7a584c0570..a19b1b0f3c9 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index 065d0c5d0aa..8b4c00899d7 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 837e9bbed42..c636d1a7541 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml From 82d7480c83bb90365b118cd6eda0389294eaaac5 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 29 May 2013 17:56:45 +0000 Subject: [PATCH 0016/1017] revert prepare release changes git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1487570 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 2 +- pdfbox/pom.xml | 2 +- pom.xml | 2 +- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index 86935e437ff..c72f88571bd 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index c630251ba22..0be6b524906 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 210bd882fd0..3acdbdfea58 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 641a889682a..561060d3cff 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index 8102e935408..f5dac195554 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 4ab2cb619de..1e04490f5e9 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 50281fb6acc..04dd01a5529 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT pom PDFBox parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index aeb04c1543a..10c0084e6ba 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/pom.xml b/pom.xml index bdc3386e989..3273b23fd5d 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT parent/pom.xml diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 9f0f1b06bfa..576cd374251 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index a19b1b0f3c9..c6b0c8d8d49 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index 8b4c00899d7..c4f2af7a695 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index c636d1a7541..7292096aa00 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.2-SNAPSHOT ../parent/pom.xml From 14f600acc5bb76887a44cd291e984a48ec83a9bb Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 29 May 2013 17:59:50 +0000 Subject: [PATCH 0017/1017] merged PDFBOX-1615 rev. 1487557 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1487575 13f79535-47bb-0310-9956-ffa450edef68 --- .../src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java index 83e03d2bc82..0839d5d84ec 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java @@ -940,7 +940,8 @@ else if( current == null ) } else if( current instanceof COSString ) { - COSString copy = new COSString(((COSString)current).getString()); + COSString copy = new COSString(true); + copy.append(((COSString)current).getBytes()); copy.accept(this); } else From 7fe109dea45d5fc5e0dac87ddea314f2878e0016 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 29 May 2013 18:07:36 +0000 Subject: [PATCH 0018/1017] updated release notes git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1487581 13f79535-47bb-0310-9956-ffa450edef68 --- RELEASE-NOTES.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 083faadea33..d3fa54b5e2d 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -27,10 +27,12 @@ Bug Fixes [PDFBOX-1603] - Regression in PDDocument.loadNonSeq ? [PDFBOX-1605] - PDPropBuildDataDict: use COSName instead of COSString as name value [PDFBOX-1609] - EXCEPTION_ACCESS_VIOLATION with PDF file and image conversion +[PDFBOX-1610] - Corrupted result pdf when overlay one document with another one [PDFBOX-1611] - Avoid IndexOutOfBoundsException when extracting the font matrix of a Type1 font [PDFBOX-1612] - Avoid ArrayOutOfBoundsException when creating a PDPixelMap using an indexed colorspace +[PDFBOX-1615] - Color map not correctly copied when PDF file is split Improvement From e4782354e0250c8ac1dec82b8045deab87c57431 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 29 May 2013 18:10:56 +0000 Subject: [PATCH 0019/1017] [maven-release-plugin] prepare release 1.8.2 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1487584 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index c72f88571bd..e590ed71fc9 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index 0be6b524906..cc7e61b8b45 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 3acdbdfea58..704c7c45da0 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 561060d3cff..351b1ba061f 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index f5dac195554..ba5de3c3443 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 1e04490f5e9..80dfe0a0a74 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 04dd01a5529..defcf59ce01 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/tags/1.8.2/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index 10c0084e6ba..e5d11eb75e3 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 3273b23fd5d..264a489164e 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 - http://svn.apache.org/viewvc/pdfbox/branches/1.8 + http://svn.apache.org/viewvc/pdfbox/tags/1.8.2 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 576cd374251..0b77f590b5f 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index c6b0c8d8d49..a7a584c0570 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index c4f2af7a695..065d0c5d0aa 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 7292096aa00..837e9bbed42 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2-SNAPSHOT + 1.8.2 ../parent/pom.xml From 4d01cd19ab29d2ebad3b7d40fbe1fdad0365202a Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 29 May 2013 18:11:08 +0000 Subject: [PATCH 0020/1017] [maven-release-plugin] prepare for next development iteration git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1487586 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index e590ed71fc9..86935e437ff 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index cc7e61b8b45..c630251ba22 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 704c7c45da0..210bd882fd0 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 351b1ba061f..641a889682a 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index ba5de3c3443..8102e935408 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 80dfe0a0a74..4ab2cb619de 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index defcf59ce01..50281fb6acc 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.2/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/tags/1.8.2/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index e5d11eb75e3..aeb04c1543a 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 264a489164e..bdc3386e989 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.2 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 - http://svn.apache.org/viewvc/pdfbox/tags/1.8.2 + http://svn.apache.org/viewvc/pdfbox/branches/1.8 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 0b77f590b5f..9f0f1b06bfa 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index a7a584c0570..a19b1b0f3c9 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index 065d0c5d0aa..8b4c00899d7 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 837e9bbed42..c636d1a7541 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.2 + 1.8.3-SNAPSHOT ../parent/pom.xml From 236abafca4f17d53a7ad1469896256c5904035b5 Mon Sep 17 00:00:00 2001 From: Eric Leleu Date: Sun, 2 Jun 2013 14:49:47 +0000 Subject: [PATCH 0021/1017] [PDFBox-1617] Fix NullPointer during Type1C font validation git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1488722 13f79535-47bb-0310-9956-ffa450edef68 --- .../font/container/FontContainer.java | 3 +- .../font/container/Type0Container.java | 3 +- .../font/container/Type1Container.java | 70 ++++++++++++++----- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/FontContainer.java b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/FontContainer.java index 9ab3f6dee43..f500558b9e9 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/FontContainer.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/FontContainer.java @@ -142,8 +142,9 @@ protected boolean isAlreadyComputedCid(int cid) throws GlyphException * * @param cid * @return The Glyph width in 'em' unit. + * @throws GlyphException */ - protected abstract float getFontProgramWidth(int cid); + protected abstract float getFontProgramWidth(int cid) throws GlyphException; /** * Test if both width are consistent. At the end of this method, the CID is marked as valid or invalid. diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type0Container.java b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type0Container.java index b41136c794c..3b30eed5f9d 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type0Container.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type0Container.java @@ -25,6 +25,7 @@ import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; +import org.apache.pdfbox.preflight.font.util.GlyphException; public class Type0Container extends FontContainer { @@ -37,7 +38,7 @@ public Type0Container(PDFont font) } @Override - protected float getFontProgramWidth(int cid) + protected float getFontProgramWidth(int cid) throws GlyphException { float width = 0; if (this.delegateFontContainer != null) diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type1Container.java b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type1Container.java index 5576d636e21..0a13bf08331 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type1Container.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type1Container.java @@ -27,6 +27,7 @@ import org.apache.fontbox.cff.CFFFont; import org.apache.fontbox.cff.CFFFont.Mapping; import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.font.util.GlyphException; import org.apache.pdfbox.preflight.font.util.Type1; @@ -52,7 +53,7 @@ public Type1Container(PDFont font) } @Override - protected float getFontProgramWidth(int cid) + protected float getFontProgramWidth(int cid) throws GlyphException { float widthResult = -1; try @@ -67,41 +68,72 @@ protected float getFontProgramWidth(int cid) else { /* - * Retrieves the SID with the Character Name in the encoding map Need more PDF with a Type1C subfont to - * valid this implementation + * Retrieves the SID with the Character Name in the encoding map Need + * more PDF with a Type1C subfont to valid this implementation + */ + String name = null; + if (this.font.getFontEncoding() != null) { + name = this.font.getFontEncoding().getName(cid); + } + + int SID = -1; + + /* For each CFF, try to found the SID that correspond to the CID. + * Look up by name if the encoding entry is present in the PDFont object + * otherwise use the internal encoding map of the font. */ - String name = this.font.getFontEncoding().getName(cid); for (CFFFont cff : lCFonts) { - int SID = cff.getEncoding().getSID(cid); - for (Mapping m : cff.getMappings()) - { - if (m.getName().equals(name)) + if (name == null) { + SID = cff.getEncoding().getSID(cid); + } else { + SID = getSIDByCharacterName(name, cff); + } + + if (SID > 0) { + widthResult = cff.getWidth(SID); + if (widthResult != defaultGlyphWidth) { - SID = m.getSID(); break; } } - widthResult = cff.getWidth(SID); - if (widthResult != defaultGlyphWidth) - { - break; - } + } + + if (SID < 0) + { + throw new GlyphException(PreflightConstants.ERROR_FONTS_GLYPH_MISSING, cid, "Unknown character CID(" + cid+")"); } } } - catch (GlyphException e) - { - widthResult = -1; - } catch (IOException e) { - widthResult = -1; // TODO validation exception + throw new GlyphException(PreflightConstants.ERROR_FONTS_GLYPH, cid, "Unexpected error during the width validtion for the character CID(" + cid+") : " + e.getMessage()); } return widthResult; } + /** + * Return the SID of the given character name. + * + * @param name the character name looked up + * @param cff Compact Font Format that represents a sub set of the Type1C Font. + * @return -1 if the name is missing from the Font encoding map, the SID of the character if it is present in the CFF. + */ + private int getSIDByCharacterName(String name, CFFFont cff) + { + int SID = -1; + for (Mapping m : cff.getMappings()) + { + if (m.getName().equals(name)) + { + SID = m.getSID(); + break; + } + } + return SID; + } + public void setType1Font(Type1 type1Font) { this.type1Font = type1Font; From e2bac10c79a8f44592b12988f42aef43852c81ca Mon Sep 17 00:00:00 2001 From: Eric Leleu Date: Sun, 2 Jun 2013 21:35:03 +0000 Subject: [PATCH 0022/1017] reverting rev. 1488722 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1488796 13f79535-47bb-0310-9956-ffa450edef68 --- .../font/container/FontContainer.java | 3 +- .../font/container/Type0Container.java | 3 +- .../font/container/Type1Container.java | 70 +++++-------------- 3 files changed, 21 insertions(+), 55 deletions(-) diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/FontContainer.java b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/FontContainer.java index f500558b9e9..9ab3f6dee43 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/FontContainer.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/FontContainer.java @@ -142,9 +142,8 @@ protected boolean isAlreadyComputedCid(int cid) throws GlyphException * * @param cid * @return The Glyph width in 'em' unit. - * @throws GlyphException */ - protected abstract float getFontProgramWidth(int cid) throws GlyphException; + protected abstract float getFontProgramWidth(int cid); /** * Test if both width are consistent. At the end of this method, the CID is marked as valid or invalid. diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type0Container.java b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type0Container.java index 3b30eed5f9d..b41136c794c 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type0Container.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type0Container.java @@ -25,7 +25,6 @@ import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; -import org.apache.pdfbox.preflight.font.util.GlyphException; public class Type0Container extends FontContainer { @@ -38,7 +37,7 @@ public Type0Container(PDFont font) } @Override - protected float getFontProgramWidth(int cid) throws GlyphException + protected float getFontProgramWidth(int cid) { float width = 0; if (this.delegateFontContainer != null) diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type1Container.java b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type1Container.java index 0a13bf08331..5576d636e21 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type1Container.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/font/container/Type1Container.java @@ -27,7 +27,6 @@ import org.apache.fontbox.cff.CFFFont; import org.apache.fontbox.cff.CFFFont.Mapping; import org.apache.pdfbox.pdmodel.font.PDFont; -import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.font.util.GlyphException; import org.apache.pdfbox.preflight.font.util.Type1; @@ -53,7 +52,7 @@ public Type1Container(PDFont font) } @Override - protected float getFontProgramWidth(int cid) throws GlyphException + protected float getFontProgramWidth(int cid) { float widthResult = -1; try @@ -68,72 +67,41 @@ protected float getFontProgramWidth(int cid) throws GlyphException else { /* - * Retrieves the SID with the Character Name in the encoding map Need - * more PDF with a Type1C subfont to valid this implementation - */ - String name = null; - if (this.font.getFontEncoding() != null) { - name = this.font.getFontEncoding().getName(cid); - } - - int SID = -1; - - /* For each CFF, try to found the SID that correspond to the CID. - * Look up by name if the encoding entry is present in the PDFont object - * otherwise use the internal encoding map of the font. + * Retrieves the SID with the Character Name in the encoding map Need more PDF with a Type1C subfont to + * valid this implementation */ + String name = this.font.getFontEncoding().getName(cid); for (CFFFont cff : lCFonts) { - if (name == null) { - SID = cff.getEncoding().getSID(cid); - } else { - SID = getSIDByCharacterName(name, cff); - } - - if (SID > 0) { - widthResult = cff.getWidth(SID); - if (widthResult != defaultGlyphWidth) + int SID = cff.getEncoding().getSID(cid); + for (Mapping m : cff.getMappings()) + { + if (m.getName().equals(name)) { + SID = m.getSID(); break; } } - } - - if (SID < 0) - { - throw new GlyphException(PreflightConstants.ERROR_FONTS_GLYPH_MISSING, cid, "Unknown character CID(" + cid+")"); + widthResult = cff.getWidth(SID); + if (widthResult != defaultGlyphWidth) + { + break; + } } } } + catch (GlyphException e) + { + widthResult = -1; + } catch (IOException e) { - throw new GlyphException(PreflightConstants.ERROR_FONTS_GLYPH, cid, "Unexpected error during the width validtion for the character CID(" + cid+") : " + e.getMessage()); + widthResult = -1; // TODO validation exception } return widthResult; } - /** - * Return the SID of the given character name. - * - * @param name the character name looked up - * @param cff Compact Font Format that represents a sub set of the Type1C Font. - * @return -1 if the name is missing from the Font encoding map, the SID of the character if it is present in the CFF. - */ - private int getSIDByCharacterName(String name, CFFFont cff) - { - int SID = -1; - for (Mapping m : cff.getMappings()) - { - if (m.getName().equals(name)) - { - SID = m.getSID(); - break; - } - } - return SID; - } - public void setType1Font(Type1 type1Font) { this.type1Font = type1Font; From ee5023795b93cd18fcc7c34058c322687c6de3f8 Mon Sep 17 00:00:00 2001 From: Thomas Chojecki Date: Mon, 16 Sep 2013 14:32:10 +0000 Subject: [PATCH 0023/1017] PDFBOX-1719 NPE while signing PDF - acroform without fields git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1523675 13f79535-47bb-0310-9956-ffa450edef68 --- .../src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java index 0285d58c70e..411a4885531 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java @@ -366,6 +366,11 @@ else if(options.getPage()<=0) List fields = acroForm.getFields(); PDSignatureField signatureField = null; + if(fields == null) + { + fields = new ArrayList(); + acroForm.setFields(fields); + } for ( PDField pdField : fields ) { if (pdField instanceof PDSignatureField) From 2b2742a50ddab3c4b35032580330ce1865c39629 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 17 Nov 2013 11:53:40 +0000 Subject: [PATCH 0024/1017] merged the following changes from trunk into b1.8 branch: PDFBOX-823 r1540811 PDFBOX-837 r1538341 PDFBOX-1540 r1469999,r1470242,r1470107,r1494083 PDFBOX-1576 r1530018 PDFBOX-1606 r1521194 PDFBOX-1607 r1536441 PDFBOX-1613 r1488049 PDFBOX-1629 r1490408 PDFBOX-1630 r1497532 PDFBOX-1643 r1495802,r1495803 PDFBOX-1653 r1519174 PDFBOX-1674 r1515101 PDFBOX-1690 r1538394 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1542711 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/fontbox/cmap/CMapParser.java | 1332 +++++++++-------- .../org/apache/jempbox/xmp/XMPSchema.java | 15 +- .../java/org/apache/pdfbox/cos/COSName.java | 26 +- .../apache/pdfbox/pdfparser/BaseParser.java | 5 +- .../pdfparser/NonSequentialPDFParser.java | 44 +- .../apache/pdfbox/pdfparser/PDFParser.java | 3 +- .../apache/pdfbox/pdfwriter/COSWriter.java | 7 +- .../org/apache/pdfbox/pdmodel/PDDocument.java | 18 + .../PDComplexFileSpecification.java | 23 +- .../pdmodel/encryption/AccessPermission.java | 27 +- .../pdmodel/encryption/SecurityHandler.java | 291 ++-- .../encryption/StandardSecurityHandler.java | 82 +- .../preflight/PreflightConfiguration.java | 26 +- .../pdfbox/preflight/PreflightConstants.java | 45 +- .../pdfbox/preflight/ValidationResult.java | 26 +- .../pdfbox/preflight/Validator_A1b.java | 134 +- .../preflight/font/Type0FontValidator.java | 2 +- .../SynchronizedMetaDataValidation.java | 24 +- .../preflight/parser/PreflightParser.java | 2 + .../preflight/parser/XmlResultParser.java | 178 +++ .../preflight/TestPreflightConfiguration.java | 101 ++ .../TestSynchronizedMetadataValidation.java | 13 - .../preflight/parser/TestXmlResultParser.java | 117 ++ 23 files changed, 1603 insertions(+), 938 deletions(-) create mode 100644 preflight/src/main/java/org/apache/pdfbox/preflight/parser/XmlResultParser.java create mode 100644 preflight/src/test/java/org/apache/pdfbox/preflight/TestPreflightConfiguration.java create mode 100644 preflight/src/test/java/org/apache/pdfbox/preflight/parser/TestXmlResultParser.java diff --git a/fontbox/src/main/java/org/apache/fontbox/cmap/CMapParser.java b/fontbox/src/main/java/org/apache/fontbox/cmap/CMapParser.java index f5c4c7a4e76..cca8407b81c 100644 --- a/fontbox/src/main/java/org/apache/fontbox/cmap/CMapParser.java +++ b/fontbox/src/main/java/org/apache/fontbox/cmap/CMapParser.java @@ -18,11 +18,11 @@ import java.io.File; import java.io.FileInputStream; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PushbackInputStream; - import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -30,661 +30,683 @@ import org.apache.fontbox.util.ResourceLoader; /** - * This will parser a CMap stream. + * This will parse a CMap stream. * * @author Ben Litchfield - * @version $Revision: 1.9 $ + * */ public class CMapParser { - private static final String BEGIN_CODESPACE_RANGE = "begincodespacerange"; - private static final String BEGIN_BASE_FONT_CHAR = "beginbfchar"; - private static final String BEGIN_BASE_FONT_RANGE = "beginbfrange"; - private static final String BEGIN_CID_CHAR = "begincidchar"; - private static final String BEGIN_CID_RANGE = "begincidrange"; - private static final String USECMAP = "usecmap"; - - private static final String END_CODESPACE_RANGE = "endcodespacerange"; - private static final String END_BASE_FONT_CHAR = "endbfchar"; - private static final String END_BASE_FONT_RANGE = "endbfrange"; - private static final String END_CID_CHAR = "endcidchar"; - private static final String END_CID_RANGE = "endcidrange"; - - private static final String WMODE = "WMode"; - private static final String CMAP_NAME = "CMapName"; - private static final String CMAP_VERSION = "CMapVersion"; - private static final String CMAP_TYPE = "CMapType"; - private static final String REGISTRY = "Registry"; - private static final String ORDERING = "Ordering"; - private static final String SUPPLEMENT = "Supplement"; - - private static final String MARK_END_OF_DICTIONARY = ">>"; - private static final String MARK_END_OF_ARRAY = "]"; - - - private byte[] tokenParserByteBuffer = new byte[512]; - - /** - * Creates a new instance of CMapParser. - */ - public CMapParser() - { - } - - /** - * Parse a CMAP file on the file system. - * - * @param file The file to parse. - * - * @return A parsed CMAP file. - * - * @throws IOException If there is an issue while parsing the CMAP. - */ - public CMap parse( File file ) throws IOException - { - String rootDir = file.getParent() + File.separator; - FileInputStream input = null; - try - { - input = new FileInputStream( file ); - return parse( rootDir, input ); - } - finally - { - if( input != null ) - { - input.close(); - } - } - - } - - /** - * This will parse the stream and create a cmap object. - * - * @param resourceRoot The root path to the cmap file. This will be used - * to find referenced cmap files. It can be null. - * @param input The CMAP stream to parse. - * - * @return The parsed stream as a java object. - * - * @throws IOException If there is an error parsing the stream. - */ - public CMap parse( String resourceRoot, InputStream input ) throws IOException - { - PushbackInputStream cmapStream = new PushbackInputStream( input ); - CMap result = new CMap(); - Object previousToken = null; - Object token = null; - while( (token = parseNextToken( cmapStream )) != null ) - { - if( token instanceof Operator ) - { - Operator op = (Operator)token; - if( op.op.equals( USECMAP ) ) - { - LiteralName useCmapName = (LiteralName)previousToken; - InputStream useStream = ResourceLoader.loadResource( resourceRoot + useCmapName.name ); - if( useStream == null ) - { - throw new IOException( "Error: Could not find referenced cmap stream " + useCmapName.name ); - } - CMap useCMap = parse( resourceRoot, useStream ); - result.useCmap( useCMap ); - } - else if( op.op.equals( BEGIN_CODESPACE_RANGE ) ) - { - Number cosCount = (Number)previousToken; - for( int j=0; j array = null; - byte[] tokenBytes = null; - if( nextToken instanceof List ) - { - array = (List)nextToken; - tokenBytes = array.get( 0 ); - } - else - { - tokenBytes = (byte[])nextToken; - } - - String value = null; - - int arrayIndex = 0; - boolean done = false; - while( !done ) - { - if( compare( startCode, endCode ) >= 0 ) - { - done = true; - } - value = createStringFromBytes( tokenBytes ); - result.addMapping( startCode, value ); - increment( startCode ); - - if( array == null ) - { - increment( tokenBytes ); - } - else - { - arrayIndex++; - if( arrayIndex < array.size() ) - { - tokenBytes = (byte[])array.get( arrayIndex ); - } - } - } - } - } - else if( op.op.equals( BEGIN_CID_CHAR ) ) - { - Number cosCount = (Number)previousToken; - for( int j=0; j': - { - int secondCloseBrace = is.read(); - if( secondCloseBrace == '>' ) - { - retval = MARK_END_OF_DICTIONARY; - } - else - { - throw new IOException( "Error: expected the end of a dictionary."); - } - break; - } - case ']': - { - retval = MARK_END_OF_ARRAY; - break; - } - case '[': - { - List list = new ArrayList(); - - Object nextToken = parseNextToken( is ); - while( nextToken != null && nextToken != MARK_END_OF_ARRAY ) - { - list.add( nextToken ); - nextToken = parseNextToken( is ); - } - retval = list; - break; - } - case '<': - { - int theNextByte = is.read(); - if( theNextByte == '<' ) - { - Map result = new HashMap(); - //we are reading a dictionary - Object key = parseNextToken( is ); - while( key instanceof LiteralName && key != MARK_END_OF_DICTIONARY ) - { - Object value = parseNextToken( is ); - result.put( ((LiteralName)key).name, value ); - key = parseNextToken( is ); - } - retval = result; - } - else - { - //won't read more than 512 bytes - - int multiplyer = 16; - int bufferIndex = -1; - while( theNextByte != -1 && theNextByte != '>' ) - { - int intValue = 0; - if( theNextByte >= '0' && theNextByte <= '9' ) - { - intValue = theNextByte - '0'; - } - else if( theNextByte >= 'A' && theNextByte <= 'F' ) - { - intValue = 10 + theNextByte - 'A'; - } - else if( theNextByte >= 'a' && theNextByte <= 'f' ) - { - intValue = 10 + theNextByte - 'a'; - } - else if( theNextByte == 0x20 ) - { - // skipping whitespaces - theNextByte = is.read(); - continue; - } - else - { - throw new IOException( "Error: expected hex character and not " + - (char)theNextByte + ":" + theNextByte ); - } - intValue *= multiplyer; - if( multiplyer == 16 ) - { - bufferIndex++; - tokenParserByteBuffer[bufferIndex] = 0; - multiplyer = 1; - } - else - { - multiplyer = 16; - } - tokenParserByteBuffer[bufferIndex]+= intValue; - theNextByte = is.read(); - } - byte[] finalResult = new byte[bufferIndex+1]; - System.arraycopy(tokenParserByteBuffer,0,finalResult, 0, bufferIndex+1); - retval = finalResult; - } - break; - } - case '/': - { - StringBuffer buffer = new StringBuffer(); - int stringByte = is.read(); - - while( !isWhitespaceOrEOF( stringByte ) ) - { - buffer.append( (char)stringByte ); - stringByte = is.read(); - } - retval = new LiteralName( buffer.toString() ); - break; - } - case -1: - { - //EOF return null; - break; - } - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - StringBuffer buffer = new StringBuffer(); - buffer.append( (char)nextByte ); - nextByte = is.read(); - - while( !isWhitespaceOrEOF( nextByte ) && - (Character.isDigit( (char)nextByte )|| - nextByte == '.' ) ) - { - buffer.append( (char)nextByte ); - nextByte = is.read(); - } - is.unread( nextByte ); - String value = buffer.toString(); - if( value.indexOf( '.' ) >=0 ) - { - retval = new Double( value ); - } - else - { - retval = new Integer( value ); - } - break; - } - default: - { - StringBuffer buffer = new StringBuffer(); - buffer.append( (char)nextByte ); - nextByte = is.read(); - - while( !isWhitespaceOrEOF( nextByte ) ) - { - buffer.append( (char)nextByte ); - nextByte = is.read(); - } - retval = new Operator( buffer.toString() ); - - break; - } - } - return retval; - } - - private void readUntilEndOfLine( InputStream is, StringBuffer buf ) throws IOException - { - int nextByte = is.read(); - while( nextByte != -1 && nextByte != 0x0D && nextByte != 0x0A ) - { - buf.append( (char)nextByte ); - nextByte = is.read(); - } - } - - private boolean isWhitespaceOrEOF( int aByte ) - { - return aByte == -1 || aByte == 0x20 || aByte == 0x0D || aByte == 0x0A; - } - - - private void increment( byte[] data ) - { - increment( data, data.length-1 ); - } - - private void increment( byte[] data, int position ) - { - if( position > 0 && (data[position]+256)%256 == 255 ) - { - data[position]=0; - increment( data, position-1); - } - else - { - data[position] = (byte)(data[position]+1); - } - } - - private int createIntFromBytes(byte[] bytes) - { - int intValue = (bytes[0]+256)%256; - if (bytes.length == 2) - { - intValue <<= 8; - intValue += (bytes[1]+256)%256; - } - return intValue; - } - - private String createStringFromBytes( byte[] bytes ) throws IOException - { - String retval = null; - if( bytes.length == 1 ) - { - retval = new String( bytes, "ISO-8859-1" ); - } - else - { - retval = new String( bytes, "UTF-16BE" ); - } - return retval; - } - - private int compare( byte[] first, byte[] second ) - { - int retval = 1; - int firstLength = first.length; - for( int i=0; i" ); - System.exit( -1 ); - } - CMapParser parser = new CMapParser( ); - File cmapFile = new File( args[0] ); - CMap result = parser.parse( cmapFile ); - System.out.println( "Result:" + result ); - } -} \ No newline at end of file + private static final String BEGIN_CODESPACE_RANGE = "begincodespacerange"; + private static final String BEGIN_BASE_FONT_CHAR = "beginbfchar"; + private static final String BEGIN_BASE_FONT_RANGE = "beginbfrange"; + private static final String BEGIN_CID_CHAR = "begincidchar"; + private static final String BEGIN_CID_RANGE = "begincidrange"; + private static final String USECMAP = "usecmap"; + + private static final String END_CODESPACE_RANGE = "endcodespacerange"; + private static final String END_BASE_FONT_CHAR = "endbfchar"; + private static final String END_BASE_FONT_RANGE = "endbfrange"; + private static final String END_CID_CHAR = "endcidchar"; + private static final String END_CID_RANGE = "endcidrange"; + + private static final String WMODE = "WMode"; + private static final String CMAP_NAME = "CMapName"; + private static final String CMAP_VERSION = "CMapVersion"; + private static final String CMAP_TYPE = "CMapType"; + private static final String REGISTRY = "Registry"; + private static final String ORDERING = "Ordering"; + private static final String SUPPLEMENT = "Supplement"; + + private static final String MARK_END_OF_DICTIONARY = ">>"; + private static final String MARK_END_OF_ARRAY = "]"; + + private byte[] tokenParserByteBuffer = new byte[512]; + + /** + * Creates a new instance of CMapParser. + */ + public CMapParser() + { + } + + /** + * Parse a CMAP file on the file system. + * + * @param file The file to parse. + * + * @return A parsed CMAP file. + * + * @throws IOException If there is an issue while parsing the CMAP. + */ + public CMap parse(File file) throws IOException + { + String rootDir = file.getParent() + File.separator; + FileInputStream input = null; + try + { + input = new FileInputStream(file); + return parse(rootDir, input); + } + finally + { + if (input != null) + { + input.close(); + } + } + + } + + /** + * This will parse the stream and create a cmap object. + * + * @param resourceRoot The root path to the cmap file. This will be used + * to find referenced cmap files. It can be null. + * @param input The CMAP stream to parse. + * + * @return The parsed stream as a java object. + * + * @throws IOException If there is an error parsing the stream. + */ + public CMap parse(String resourceRoot, InputStream input) throws IOException + { + PushbackInputStream cmapStream = new PushbackInputStream(input); + CMap result = new CMap(); + Object previousToken = null; + Object token = null; + while ((token = parseNextToken(cmapStream)) != null) + { + if (token instanceof Operator) + { + Operator op = (Operator) token; + if (op.op.equals(USECMAP)) + { + LiteralName useCmapName = (LiteralName) previousToken; + InputStream useStream = ResourceLoader.loadResource(resourceRoot + useCmapName.name); + if (useStream == null) + { + throw new IOException("Error: Could not find referenced cmap stream " + useCmapName.name); + } + CMap useCMap = parse(resourceRoot, useStream); + result.useCmap(useCMap); + } + else if (op.op.equals(BEGIN_CODESPACE_RANGE)) + { + Number cosCount = (Number) previousToken; + for (int j = 0; j < cosCount.intValue(); j++) + { + Object nextToken = parseNextToken(cmapStream); + if (nextToken instanceof Operator) + { + if (!((Operator) nextToken).op.equals(END_CODESPACE_RANGE)) + { + throw new IOException("Error : ~codespacerange contains an unexpected operator : " + + ((Operator) nextToken).op); + } + break; + } + byte[] startRange = (byte[]) nextToken; + byte[] endRange = (byte[]) parseNextToken(cmapStream); + CodespaceRange range = new CodespaceRange(); + range.setStart(startRange); + range.setEnd(endRange); + result.addCodespaceRange(range); + } + } + else if (op.op.equals(BEGIN_BASE_FONT_CHAR)) + { + Number cosCount = (Number) previousToken; + for (int j = 0; j < cosCount.intValue(); j++) + { + Object nextToken = parseNextToken(cmapStream); + if (nextToken instanceof Operator) + { + if (!((Operator) nextToken).op.equals(END_BASE_FONT_CHAR)) + { + throw new IOException("Error : ~bfchar contains an unexpected operator : " + + ((Operator) nextToken).op); + } + break; + } + byte[] inputCode = (byte[]) nextToken; + nextToken = parseNextToken(cmapStream); + if (nextToken instanceof byte[]) + { + byte[] bytes = (byte[]) nextToken; + String value = createStringFromBytes(bytes); + result.addMapping(inputCode, value); + } + else if (nextToken instanceof LiteralName) + { + result.addMapping(inputCode, ((LiteralName) nextToken).name); + } + else + { + throw new IOException("Error parsing CMap beginbfchar, expected{COSString " + + "or COSName} and not " + nextToken); + } + } + } + else if (op.op.equals(BEGIN_BASE_FONT_RANGE)) + { + Number cosCount = (Number) previousToken; + + for (int j = 0; j < cosCount.intValue(); j++) + { + Object nextToken = parseNextToken(cmapStream); + if (nextToken instanceof Operator) + { + if (!((Operator) nextToken).op.equals(END_BASE_FONT_RANGE)) + { + throw new IOException("Error : ~bfrange contains an unexpected operator : " + + ((Operator) nextToken).op); + } + break; + } + byte[] startCode = (byte[]) nextToken; + byte[] endCode = (byte[]) parseNextToken(cmapStream); + nextToken = parseNextToken(cmapStream); + List array = null; + byte[] tokenBytes = null; + if (nextToken instanceof List) + { + array = (List) nextToken; + tokenBytes = array.get(0); + } + else + { + tokenBytes = (byte[]) nextToken; + } + boolean done = false; + // don't add 1:1 mappings to reduce the memory footprint + if (Arrays.equals(startCode, tokenBytes)) + { + done = true; + } + String value = null; + + int arrayIndex = 0; + while (!done) + { + if (compare(startCode, endCode) >= 0) + { + done = true; + } + value = createStringFromBytes(tokenBytes); + result.addMapping(startCode, value); + increment(startCode); + + if (array == null) + { + increment(tokenBytes); + } + else + { + arrayIndex++; + if (arrayIndex < array.size()) + { + tokenBytes = (byte[]) array.get(arrayIndex); + } + } + } + } + } + else if (op.op.equals(BEGIN_CID_CHAR)) + { + Number cosCount = (Number) previousToken; + for (int j = 0; j < cosCount.intValue(); j++) + { + Object nextToken = parseNextToken(cmapStream); + if (nextToken instanceof Operator) + { + if (!((Operator) nextToken).op.equals(END_CID_CHAR)) + { + throw new IOException("Error : ~cidchar contains an unexpected operator : " + + ((Operator) nextToken).op); + } + break; + } + byte[] inputCode = (byte[]) nextToken; + int mappedCode = (Integer) parseNextToken(cmapStream); + String mappedStr = createStringFromBytes(inputCode); + result.addCIDMapping(mappedCode, mappedStr); + } + } + else if (op.op.equals(BEGIN_CID_RANGE)) + { + int numberOfLines = (Integer) previousToken; + for (int n = 0; n < numberOfLines; n++) + { + Object nextToken = parseNextToken(cmapStream); + if (nextToken instanceof Operator) + { + if (!((Operator) nextToken).op.equals(END_CID_RANGE)) + { + throw new IOException("Error : ~cidrange contains an unexpected operator : " + + ((Operator) nextToken).op); + } + break; + } + byte[] startCode = (byte[]) nextToken; + int start = createIntFromBytes(startCode); + byte[] endCode = (byte[]) parseNextToken(cmapStream); + int end = createIntFromBytes(endCode); + int mappedCode = (Integer) parseNextToken(cmapStream); + if (startCode.length <= 2 && endCode.length <= 2) + { + result.addCIDRange((char) start, (char) end, mappedCode); + } + else + { + // TODO Is this even possible? + int endOfMappings = mappedCode + end - start; + while (mappedCode <= endOfMappings) + { + String mappedStr = createStringFromBytes(startCode); + result.addCIDMapping(mappedCode++, mappedStr); + increment(startCode); + } + } + } + } + } + else if (token instanceof LiteralName) + { + LiteralName literal = (LiteralName) token; + if (WMODE.equals(literal.name)) + { + Object next = parseNextToken(cmapStream); + if (next instanceof Integer) + { + result.setWMode((Integer) next); + } + } + else if (CMAP_NAME.equals(literal.name)) + { + Object next = parseNextToken(cmapStream); + if (next instanceof LiteralName) + { + result.setName(((LiteralName) next).name); + } + } + else if (CMAP_VERSION.equals(literal.name)) + { + Object next = parseNextToken(cmapStream); + if (next instanceof Number) + { + result.setVersion(((Number) next).toString()); + } + else if (next instanceof String) + { + result.setVersion((String) next); + } + } + else if (CMAP_TYPE.equals(literal.name)) + { + Object next = parseNextToken(cmapStream); + if (next instanceof Integer) + { + result.setType((Integer) next); + } + } + else if (REGISTRY.equals(literal.name)) + { + Object next = parseNextToken(cmapStream); + if (next instanceof String) + { + result.setRegistry((String) next); + } + } + else if (ORDERING.equals(literal.name)) + { + Object next = parseNextToken(cmapStream); + if (next instanceof String) + { + result.setOrdering((String) next); + } + } + else if (SUPPLEMENT.equals(literal.name)) + { + Object next = parseNextToken(cmapStream); + if (next instanceof Integer) + { + result.setSupplement((Integer) next); + } + } + } + previousToken = token; + } + return result; + } + + private Object parseNextToken(PushbackInputStream is) throws IOException + { + Object retval = null; + int nextByte = is.read(); + // skip whitespace + while (nextByte == 0x09 || nextByte == 0x20 || nextByte == 0x0D || nextByte == 0x0A) + { + nextByte = is.read(); + } + switch (nextByte) + { + case '%': + { + // header operations, for now return the entire line + // may need to smarter in the future + StringBuffer buffer = new StringBuffer(); + buffer.append((char) nextByte); + readUntilEndOfLine(is, buffer); + retval = buffer.toString(); + break; + } + case '(': + { + StringBuffer buffer = new StringBuffer(); + int stringByte = is.read(); + + while (stringByte != -1 && stringByte != ')') + { + buffer.append((char) stringByte); + stringByte = is.read(); + } + retval = buffer.toString(); + break; + } + case '>': + { + int secondCloseBrace = is.read(); + if (secondCloseBrace == '>') + { + retval = MARK_END_OF_DICTIONARY; + } + else + { + throw new IOException("Error: expected the end of a dictionary."); + } + break; + } + case ']': + { + retval = MARK_END_OF_ARRAY; + break; + } + case '[': + { + List list = new ArrayList(); + + Object nextToken = parseNextToken(is); + while (nextToken != null && nextToken != MARK_END_OF_ARRAY) + { + list.add(nextToken); + nextToken = parseNextToken(is); + } + retval = list; + break; + } + case '<': + { + int theNextByte = is.read(); + if (theNextByte == '<') + { + Map result = new HashMap(); + // we are reading a dictionary + Object key = parseNextToken(is); + while (key instanceof LiteralName && key != MARK_END_OF_DICTIONARY) + { + Object value = parseNextToken(is); + result.put(((LiteralName) key).name, value); + key = parseNextToken(is); + } + retval = result; + } + else + { + // won't read more than 512 bytes + + int multiplyer = 16; + int bufferIndex = -1; + while (theNextByte != -1 && theNextByte != '>') + { + int intValue = 0; + if (theNextByte >= '0' && theNextByte <= '9') + { + intValue = theNextByte - '0'; + } + else if (theNextByte >= 'A' && theNextByte <= 'F') + { + intValue = 10 + theNextByte - 'A'; + } + else if (theNextByte >= 'a' && theNextByte <= 'f') + { + intValue = 10 + theNextByte - 'a'; + } + else if (theNextByte == 0x20) + { + // skipping whitespaces + theNextByte = is.read(); + continue; + } + else + { + throw new IOException("Error: expected hex character and not " + (char) theNextByte + ":" + + theNextByte); + } + intValue *= multiplyer; + if (multiplyer == 16) + { + bufferIndex++; + tokenParserByteBuffer[bufferIndex] = 0; + multiplyer = 1; + } + else + { + multiplyer = 16; + } + tokenParserByteBuffer[bufferIndex] += intValue; + theNextByte = is.read(); + } + byte[] finalResult = new byte[bufferIndex + 1]; + System.arraycopy(tokenParserByteBuffer, 0, finalResult, 0, bufferIndex + 1); + retval = finalResult; + } + break; + } + case '/': + { + StringBuffer buffer = new StringBuffer(); + int stringByte = is.read(); + + while (!isWhitespaceOrEOF(stringByte)) + { + buffer.append((char) stringByte); + stringByte = is.read(); + } + retval = new LiteralName(buffer.toString()); + break; + } + case -1: + { + // EOF return null; + break; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + StringBuffer buffer = new StringBuffer(); + buffer.append((char) nextByte); + nextByte = is.read(); + + while (!isWhitespaceOrEOF(nextByte) && (Character.isDigit((char) nextByte) || nextByte == '.')) + { + buffer.append((char) nextByte); + nextByte = is.read(); + } + is.unread(nextByte); + String value = buffer.toString(); + if (value.indexOf('.') >= 0) + { + retval = new Double(value); + } + else + { + retval = new Integer(value); + } + break; + } + default: + { + StringBuffer buffer = new StringBuffer(); + buffer.append((char) nextByte); + nextByte = is.read(); + + while (!isWhitespaceOrEOF(nextByte)) + { + buffer.append((char) nextByte); + nextByte = is.read(); + } + retval = new Operator(buffer.toString()); + + break; + } + } + return retval; + } + + private void readUntilEndOfLine(InputStream is, StringBuffer buf) throws IOException + { + int nextByte = is.read(); + while (nextByte != -1 && nextByte != 0x0D && nextByte != 0x0A) + { + buf.append((char) nextByte); + nextByte = is.read(); + } + } + + private boolean isWhitespaceOrEOF(int aByte) + { + return aByte == -1 || aByte == 0x20 || aByte == 0x0D || aByte == 0x0A; + } + + private void increment(byte[] data) + { + increment(data, data.length - 1); + } + + private void increment(byte[] data, int position) + { + if (position > 0 && (data[position] + 256) % 256 == 255) + { + data[position] = 0; + increment(data, position - 1); + } + else + { + data[position] = (byte) (data[position] + 1); + } + } + + private int createIntFromBytes(byte[] bytes) + { + int intValue = (bytes[0] + 256) % 256; + if (bytes.length == 2) + { + intValue <<= 8; + intValue += (bytes[1] + 256) % 256; + } + return intValue; + } + + private String createStringFromBytes(byte[] bytes) throws IOException + { + String retval = null; + if (bytes.length == 1) + { + retval = new String(bytes, "ISO-8859-1"); + } + else + { + retval = new String(bytes, "UTF-16BE"); + } + return retval; + } + + private int compare(byte[] first, byte[] second) + { + int retval = 1; + int firstLength = first.length; + for (int i = 0; i < firstLength; i++) + { + if (first[i] == second[i]) + { + continue; + } + else if (((first[i] + 256) % 256) < ((second[i] + 256) % 256)) + { + retval = -1; + break; + } + else + { + retval = 1; + break; + } + } + return retval; + } + + /** + * Internal class. + */ + private class LiteralName + { + private String name; + + private LiteralName(String theName) + { + name = theName; + } + } + + /** + * Internal class. + */ + private class Operator + { + private String op; + + private Operator(String theOp) + { + op = theOp; + } + } + + /** + * A simple class to test parsing of cmap files. + * + * @param args Some command line arguments. + * + * @throws Exception If there is an error parsing the file. + */ + public static void main(String[] args) throws Exception + { + if (args.length != 1) + { + System.err.println("usage: java org.apache.fontbox.cmap.CMapParser "); + System.exit(-1); + } + CMapParser parser = new CMapParser(); + File cmapFile = new File(args[0]); + CMap result = parser.parse(cmapFile); + System.out.println("Result:" + result); + } +} diff --git a/jempbox/src/main/java/org/apache/jempbox/xmp/XMPSchema.java b/jempbox/src/main/java/org/apache/jempbox/xmp/XMPSchema.java index eb8feec5f87..b05022a0954 100644 --- a/jempbox/src/main/java/org/apache/jempbox/xmp/XMPSchema.java +++ b/jempbox/src/main/java/org/apache/jempbox/xmp/XMPSchema.java @@ -36,7 +36,7 @@ * subclasses for access to specific properties. * * @author Ben Litchfield - * @version $Revision: 1.8 $ + * */ public class XMPSchema { @@ -246,7 +246,15 @@ public Calendar getDateProperty(String propertyName) throws IOException */ public void setDateProperty(String propertyName, Calendar date) { - setTextProperty(propertyName, DateConverter.toISO8601(date)); + if (date != null) + { + setTextProperty(propertyName, DateConverter.toISO8601(date)); + } + else + { + // remove the value for the given property + setTextProperty(propertyName, null); + } } /** @@ -820,7 +828,8 @@ public String getLanguageProperty(String propertyName, String language) } } } - else if (property.getChildNodes().getLength() == 1 && Node.TEXT_NODE == property.getFirstChild().getNodeType()) + else if (property.getChildNodes().getLength() == 1 + && Node.TEXT_NODE == property.getFirstChild().getNodeType()) { retval = property.getFirstChild().getNodeValue(); } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java index b8182c07182..b2bf162e507 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java @@ -30,7 +30,7 @@ * This class represents a PDF named object. * * @author Ben Litchfield - * @version $Revision: 1.42 $ + * */ public final class COSName extends COSBase implements Comparable { @@ -382,6 +382,10 @@ public final class COSName extends COSBase implements Comparable */ public static final COSName DECODE_PARMS = new COSName( "DecodeParms" ); + /** + * A common COSName value. + */ + public static final COSName DESC = new COSName("Desc"); /** * A common COSName value. */ @@ -1027,6 +1031,26 @@ public final class COSName extends COSBase implements Comparable */ public static final COSName OUTLINES = new COSName("Outlines"); + /** + * A common COSName value. + */ + public static final COSName OUTPUT_INTENT = new COSName("OutputIntent"); + + /** + * A common COSName value. + */ + public static final COSName OUTPUT_INTENTS = new COSName("OutputIntents"); + + /** + * A common COSName value. + */ + public static final COSName OUTPUT_CONDITION = new COSName("OutputCondition"); + + /** + * A common COSName value. + */ + public static final COSName OUTPUT_CONDITION_IDENTIFIER = new COSName("OutputConditionIdentifier"); + /** * A common COSName value. */ diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java index cc9dcf71040..b1cf04f79cc 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java @@ -994,10 +994,11 @@ else if ( ( c == ' ' ) || ( c == '\n' ) || } else { - // if invalid chars was found + // if invalid chars was found: discard last + // hex character if it is not part of a pair if (sBuf.length()%2!=0) { - sBuf.deleteCharAt(strmBuf.length-1); + sBuf.deleteCharAt(sBuf.length()-1); } // read till the closing bracket was found diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java index b373b2351cc..57bf3c1bad9 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java @@ -94,18 +94,15 @@ public class NonSequentialPDFParser extends PDFParser /** * EOF-marker. */ - protected static final char[] EOF_MARKER = new char[] - { '%', '%', 'E', 'O', 'F' }; + protected static final char[] EOF_MARKER = new char[] { '%', '%', 'E', 'O', 'F' }; /** * StartXRef-marker. */ - protected static final char[] STARTXREF_MARKER = new char[] - { 's', 't', 'a', 'r', 't', 'x', 'r', 'e', 'f' }; + protected static final char[] STARTXREF_MARKER = new char[] { 's', 't', 'a', 'r', 't', 'x', 'r', 'e', 'f' }; /** * obj-marker. */ - protected static final char[] OBJ_MARKER = new char[] - { 'o', 'b', 'j' }; + protected static final char[] OBJ_MARKER = new char[] { 'o', 'b', 'j' }; private final File pdfFile; private final RandomAccessBufferedFileInputStream raStream; @@ -365,15 +362,6 @@ protected void initialParse() throws IOException COSDictionary trailer = xrefTrailerResolver.getTrailer(); document.setTrailer(trailer); - // JIRA-1557 - ensure that all COSObject are loaded in the trailer - for (COSBase trailerEntry : trailer.getValues()) - { - if (trailerEntry instanceof COSObject) - { - COSObject tmpObj = (COSObject) trailerEntry; - parseObjectDynamically(tmpObj, false); - } - } // ---- prepare encryption if necessary COSBase trailerEncryptItem = document.getTrailer().getItem(COSName.ENCRYPT); if (trailerEncryptItem != null) @@ -413,11 +401,23 @@ protected void initialParse() throws IOException catch (Exception e) { throw new IOException("Error (" + e.getClass().getSimpleName() - + ") while creating security handler for decryption: " - + e.getMessage() /*, e TODO: remove remark with Java 1.6 */); + + ") while creating security handler for decryption: " + e.getMessage() /* + * , e TODO: remove + * remark with Java 1.6 + */); } } + // PDFBOX-1557 - ensure that all COSObject are loaded in the trailer + // PDFBOX-1606 - after securityHandler has been instantiated + for (COSBase trailerEntry : trailer.getValues()) + { + if (trailerEntry instanceof COSObject) + { + COSObject tmpObj = (COSObject) trailerEntry; + parseObjectDynamically(tmpObj, false); + } + } // ---- parse catalog or root object COSObject root = (COSObject) xrefTrailerResolver.getTrailer().getItem(COSName.ROOT); @@ -1049,9 +1049,7 @@ else if (baseObj instanceof COSObject) .intValue()); if (!(parsedObjects.contains(objId) /* - * || - * document.hasObjectInPool - * ( objKey ) + * || document.hasObjectInPool ( objKey ) */)) { Long fileOffset = xrefTrailerResolver.getXrefTable().get(objKey); @@ -1223,8 +1221,7 @@ else if (offsetOrObjstmObNr > 0) // 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 (offset: " - + offsetOrObjstmObNr + ")."); + throw new IOException("Stream not preceded by dictionary (offset: " + offsetOrObjstmObNr + ")."); } skipSpaces(); endObjectKey = readLine(); @@ -1483,8 +1480,7 @@ else if (whitespace != 0x0A) } /* - * This needs to be dic.getItem because when we are parsing, the - * underlying object might still be null. + * This needs to be dic.getItem because when we are parsing, the underlying object might still be null. */ COSNumber streamLengthObj = getLength(dic.getItem(COSName.LENGTH)); if (streamLengthObj == null) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java index 7e3392ac1c1..d2d0ee5a42f 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java @@ -185,7 +185,8 @@ public void parse() throws IOException } try { - wasLastParsedObjectEOF = parseObject(); + // don't reset flag to false if it is already true + wasLastParsedObjectEOF |= parseObject(); } catch(IOException e) { diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java index 0839d5d84ec..c843a8c2774 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java @@ -1420,7 +1420,10 @@ public void write(COSDocument doc) throws COSVisitorException * @throws COSVisitorException If an error occurs while generating the data. */ public void write(PDDocument doc) throws COSVisitorException - { + { + Long idTime = doc.getDocumentId() == null ? System.currentTimeMillis() : + doc.getDocumentId(); + document = doc; if(incrementalUpdate) { @@ -1473,7 +1476,7 @@ public void write(PDDocument doc) throws COSVisitorException //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") ); + md.update( Long.toString(idTime).getBytes("ISO-8859-1") ); COSDictionary info = (COSDictionary)trailer.getDictionaryObject( COSName.INFO ); if( info != null ) { diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java index 411a4885531..e4e2270dd55 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java @@ -116,6 +116,14 @@ public class PDDocument implements Pageable * from this documents. */ private boolean allSecurityToBeRemoved = false; + + /** + * Keep tracking customized documentId for the trailer. If null, a new + * id will be generated for the document. This ID doesn't represent the + * actual documentId from the trailer. + */ + private Long documentId; + /** * Constructor, creates a new PDF Document with no pages. You need to add @@ -1632,5 +1640,15 @@ public void setAllSecurityToBeRemoved(boolean removeAllSecurity) { allSecurityToBeRemoved = removeAllSecurity; } + + public Long getDocumentId() + { + return documentId; + } + + public void setDocumentId(Long docId) + { + documentId = docId; + } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/filespecification/PDComplexFileSpecification.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/filespecification/PDComplexFileSpecification.java index 79a83ab11cb..50ddcce7948 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/filespecification/PDComplexFileSpecification.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/filespecification/PDComplexFileSpecification.java @@ -25,7 +25,7 @@ * This represents a file specification. * * @author Ben Litchfield - * @version $Revision: 1.4 $ + * */ public class PDComplexFileSpecification extends PDFileSpecification { @@ -353,5 +353,26 @@ public void setEmbeddedFileUnix( PDEmbeddedFile file ) ef.setItem( COSName.UNIX, file ); } } + + /** + * Set the file description. + * + * @param description The file description + */ + public void setFileDescription( String description ) + { + fs.setString( COSName.DESC, description ); + } + + /** + * This will get the description. + * + * @return The file description. + */ + public String getFileDescription() + { + return fs.getString( COSName.DESC ); + } + } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/AccessPermission.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/AccessPermission.java index dc0718ebc13..9c8013c5e49 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/AccessPermission.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/AccessPermission.java @@ -44,7 +44,6 @@ * @author Ben Litchfield * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr) * - * @version $Revision: 1.4 $ */ public class AccessPermission @@ -419,4 +418,30 @@ public boolean isReadOnly() { return readOnly; } + + /** + * Indicates if any revision 3 access permission is set or not. + * + * @return true if any revision 3 access permission is set + */ + protected boolean hasAnyRevision3PermissionSet() + { + if (canFillInForm()) + { + return true; + } + if (canExtractForAccessibility()) + { + return true; + } + if (canAssembleDocument()) + { + return true; + } + if (canPrintDegraded()) + { + return true; + } + return false; + } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java index 509d2343ae1..8f0ab254a1a 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java @@ -59,23 +59,22 @@ * @author Ben Litchfield * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr) * - * @version $Revision: 1.4 $ */ public abstract class SecurityHandler { - /* ------------------------------------------------ - * CONSTANTS - -------------------------------------------------- */ + /** + * 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}; - + private static final byte[] AES_SALT = { (byte) 0x73, (byte) 0x41, (byte) 0x6c, (byte) 0x54 }; + /** * The value of V field of the Encryption dictionary. */ @@ -106,11 +105,11 @@ public abstract class SecurityHandler private Set potentialSignatures = new HashSet(); - /* - * If true, AES will be used + /** + * 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. @@ -142,9 +141,8 @@ public abstract class SecurityHandler * @throws CryptographyException If there is an error with decryption. */ public abstract void prepareForDecryption(PDEncryptionDictionary encDictionary, COSArray documentIDArray, - DecryptionMaterial decryptionMaterial) - throws CryptographyException, IOException; - + DecryptionMaterial decryptionMaterial) throws CryptographyException, IOException; + /** * Prepare the document for decryption. * @@ -153,9 +151,8 @@ public abstract void prepareForDecryption(PDEncryptionDictionary encDictionary, * @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; - + 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 @@ -168,18 +165,18 @@ protected void proceedDecryption() throws IOException, CryptographyException { COSDictionary trailer = document.getDocument().getTrailer(); - COSArray fields = (COSArray)trailer.getObjectFromPath( "Root/AcroForm/Fields" ); + 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 ) + // 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 allObjects = document.getDocument().getObjects(); Iterator objectIter = allObjects.iterator(); - while( objectIter.hasNext() ) + while (objectIter.hasNext()) { - decryptObject( objectIter.next() ); + decryptObject(objectIter.next()); } - document.setEncryptionDictionary( null ); + document.setEncryptionDictionary(null); } - private void addDictionaryAndSubDictionary( Set set, COSDictionary dic ) + private void addDictionaryAndSubDictionary(Set set, COSDictionary dic) { - if ( dic != null ) // in case dictionary is part of object stream we have null value here - { - set.add( dic ); - COSArray kids = (COSArray)dic.getDictionaryObject( COSName.KIDS ); - for( int i=0; kids != null && i set, COSDictionar * reasons and will be removed in the future. */ public void encryptData(long objectNumber, long genNumber, InputStream data, OutputStream output) - throws CryptographyException, IOException + throws CryptographyException, IOException { // default to encrypting since the function is named "encryptData" encryptData(objectNumber, genNumber, data, output, false); @@ -248,97 +245,106 @@ public void encryptData(long objectNumber, long genNumber, InputStream data, Out * @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 + public void encryptData(long objectNumber, long genNumber, InputStream data, OutputStream output, boolean decrypt) + throws CryptographyException, IOException { - if (aes && !decrypt) { + 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[] 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" ); + MessageDigest md = MessageDigest.getInstance("MD5"); md.update(newKey); - if (aes) { + if (aes) + { md.update(AES_SALT); } digestedKey = md.digest(); } - catch( NoSuchAlgorithmException e ) + catch (NoSuchAlgorithmException e) { - throw new CryptographyException( 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 ); + // 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 { + + 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]; - for(int n = 0; -1 != (n = cipherStream.read(buffer));) + + try + { + byte[] buffer = new byte[4096]; + for (int n = 0; -1 != (n = cipherStream.read(buffer));) { output.write(buffer, 0, n); } } - finally { + finally + { cipherStream.close(); } } - catch (InvalidKeyException e) { + catch (InvalidKeyException e) + { throw new WrappedIOException(e); } - catch (InvalidAlgorithmParameterException e) { + catch (InvalidAlgorithmParameterException e) + { throw new WrappedIOException(e); } - catch (NoSuchAlgorithmException e) { + catch (NoSuchAlgorithmException e) + { throw new WrappedIOException(e); } - catch (NoSuchPaddingException e) { + catch (NoSuchPaddingException e) + { throw new WrappedIOException(e); } } - else { - rc4.setKey( finalKey ); - rc4.write( data, output ); + else + { + rc4.setKey(finalKey); + rc4.write(data, output); } - + output.flush(); } - + /** * This will decrypt an object in the document. * @@ -347,13 +353,12 @@ public void encryptData(long objectNumber, long genNumber, InputStream data, Out * @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 + 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 ); + decrypt(base, objNum, genNum); } /** @@ -366,28 +371,27 @@ private void decryptObject( COSObject object ) * @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 + private void decrypt(COSBase obj, long objNum, long genNum) throws CryptographyException, IOException { - if( !objects.contains( obj ) ) + if (!objects.contains(obj)) { - objects.add( obj ); + objects.add(obj); - if( obj instanceof COSString ) + if (obj instanceof COSString) { - decryptString( (COSString)obj, objNum, genNum ); + decryptString((COSString) obj, objNum, genNum); } - else if( obj instanceof COSStream ) + else if (obj instanceof COSStream) { - decryptStream( (COSStream)obj, objNum, genNum ); + decryptStream((COSStream) obj, objNum, genNum); } - else if( obj instanceof COSDictionary ) + else if (obj instanceof COSDictionary) { - decryptDictionary( (COSDictionary)obj, objNum, genNum ); + decryptDictionary((COSDictionary) obj, objNum, genNum); } - else if( obj instanceof COSArray ) + else if (obj instanceof COSArray) { - decryptArray( (COSArray)obj, objNum, genNum ); + decryptArray((COSArray) obj, objNum, genNum); } } } @@ -402,18 +406,13 @@ else if( obj instanceof COSArray ) * @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 + public void decryptStream(COSStream stream, long objNum, long genNum) throws CryptographyException, IOException { - decryptDictionary( stream, objNum, genNum ); + decryptDictionary(stream, objNum, genNum); InputStream encryptedStream = stream.getFilteredStream(); - encryptData( objNum, - genNum, - encryptedStream, - stream.createFilteredStream(), - true /* decrypt */); + 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 @@ -426,15 +425,10 @@ public void decryptStream( COSStream stream, long objNum, long genNum ) * @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 + public void encryptStream(COSStream stream, long objNum, long genNum) throws CryptographyException, IOException { InputStream encryptedStream = stream.getFilteredStream(); - encryptData( objNum, - genNum, - encryptedStream, - stream.createFilteredStream(), - false /* encrypt */); + encryptData(objNum, genNum, encryptedStream, stream.createFilteredStream(), false /* encrypt */); } /** @@ -447,22 +441,22 @@ public void encryptStream( COSStream stream, long objNum, long genNum ) * @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 + private void decryptDictionary(COSDictionary dictionary, long objNum, long genNum) throws CryptographyException, + IOException { - for( Map.Entry entry : dictionary.entrySet() ) + for (Map.Entry entry : dictionary.entrySet()) { COSBase value = entry.getValue(); // within a dictionary only strings and streams have to be decrypted - if (!(value instanceof COSString) && !(value instanceof COSStream)) - continue; - //if we are a signature dictionary and contain a Contents entry then - //we don't decrypt it. - if( !(entry.getKey().getName().equals( "Contents" ) && - value instanceof COSString && - potentialSignatures.contains( dictionary ))) + if (value instanceof COSString || value instanceof COSStream || value instanceof COSArray) { - decrypt( entry.getValue(), objNum, genNum ); + // if we are a signature dictionary and contain a Contents entry then + // we don't decrypt it. + if (!(entry.getKey().getName().equals("Contents") && value instanceof COSString && potentialSignatures + .contains(dictionary))) + { + decrypt(entry.getValue(), objNum, genNum); + } } } } @@ -477,14 +471,13 @@ private void decryptDictionary( COSDictionary dictionary, long objNum, long genN * @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 + public void decryptString(COSString string, long objNum, long genNum) throws CryptographyException, IOException { - ByteArrayInputStream data = new ByteArrayInputStream( string.getBytes() ); + ByteArrayInputStream data = new ByteArrayInputStream(string.getBytes()); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - encryptData( objNum, genNum, data, buffer, true /* decrypt */ ); + encryptData(objNum, genNum, data, buffer, true /* decrypt */); string.reset(); - string.append( buffer.toByteArray() ); + string.append(buffer.toByteArray()); } /** @@ -497,12 +490,11 @@ public void decryptString( COSString string, long objNum, long genNum ) * @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 + private void decryptArray(COSArray array, long objNum, long genNum) throws CryptographyException, IOException { - for( int i=0; iBen Litchfield * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr) * - * @version $Revision: 1.5 $ */ public class StandardSecurityHandler extends SecurityHandler @@ -64,7 +65,7 @@ public class StandardSecurityHandler extends SecurityHandler /** * Protection policy class for this handler. */ - public static final Class PROTECTION_POLICY_CLASS = StandardProtectionPolicy.class; + public static final Class PROTECTION_POLICY_CLASS = StandardProtectionPolicy.class; /** * Standard padding for encryption. @@ -124,14 +125,15 @@ private int computeVersionNumber() */ private int computeRevisionNumber() { - if(version == 2 - && !policy.getPermissions().canFillInForm() - && !policy.getPermissions().canExtractForAccessibility() - && !policy.getPermissions().canPrintDegraded() ) + if(version < 2 && !policy.getPermissions().hasAnyRevision3PermissionSet()) { return 2; } - return 3; + if ( version == 2 || version == 3 || policy.getPermissions().hasAnyRevision3PermissionSet()) + { + return 3; + } + return 4; } /** @@ -170,7 +172,7 @@ public void decryptDocument(PDDocument doc, DecryptionMaterial decryptionMateria * @throws CryptographyException If there is an error with decryption. */ public void prepareForDecryption(PDEncryptionDictionary encDictionary, COSArray documentIDArray, - DecryptionMaterial decryptionMaterial) + DecryptionMaterial decryptionMaterial) throws CryptographyException, IOException { if(!(decryptionMaterial instanceof StandardDecryptionMaterial)) @@ -270,8 +272,8 @@ else if( isOwnerPassword ) if (stdCryptFilterDictionary != null) { COSName cryptFilterMethod = stdCryptFilterDictionary.getCryptFilterMethod(); - - if (cryptFilterMethod != null) { + if (cryptFilterMethod != null) + { setAES("AESV2".equalsIgnoreCase(cryptFilterMethod.getName())); } } @@ -380,6 +382,7 @@ public void prepareDocumentForEncryption(PDDocument doc) throws CryptographyExce * @param id The document id. * @param encRevision The encryption algorithm revision. * @param length The encryption key length. + * @param encryptMetadata The encryption metadata * * @return True If the ownerPassword param is the owner password. * @@ -461,26 +464,7 @@ public final byte[] getUserPassword( } 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 @@ -498,10 +482,7 @@ else if( encRevision == 3 || encRevision == 4) otemp = result.toByteArray(); //sm } } - - return result.toByteArray(); - } catch( NoSuchAlgorithmException e ) { @@ -518,6 +499,7 @@ else if( encRevision == 3 || encRevision == 4) * @param id The document id. * @param encRevision The revision of the encryption algorithm. * @param length The length of the encryption key. + * @param encryptMetadata The encryption metadata * * @return The encrypted key bytes. * @@ -606,6 +588,7 @@ public final byte[] computeEncryptedKey( * @param id The document id. * @param encRevision The revision of the encryption. * @param length The length of the encryption key. + * @param encryptMetadata The encryption metadata * * @return The user password. * @@ -790,6 +773,7 @@ private final byte[] truncateOrPad( byte[] password ) * @param id The document id used for encryption. * @param encRevision The revision of the encryption algorithm. * @param length The length of the encryption key. + * @param encryptMetadata The encryption metadata * * @return true If the plaintext password is the user password. * @@ -814,7 +798,7 @@ public final boolean isUserPassword( if( encRevision == 2 ) { //STEP 2 - matches = arraysEqual( u, computedValue ); + matches = Arrays.equals(u, computedValue); } else if( encRevision == 3 || encRevision == 4 ) { @@ -838,6 +822,7 @@ else if( encRevision == 3 || encRevision == 4 ) * @param id The document id used for encryption. * @param encRevision The revision of the encryption algorithm. * @param length The length of the encryption key. + * @param encryptMetadata The encryption metadata * * @return true If the plaintext password is the user password. * @@ -869,6 +854,7 @@ public final boolean isUserPassword( * @param id The document id. * @param encRevision The encryption algorithm revision. * @param length The encryption key length. + * @param encryptMetadata The encryption metadata * * @return True If the ownerPassword param is the owner password. * @@ -892,29 +878,19 @@ public final boolean isOwnerPassword( private static final boolean arraysEqual( byte[] first, byte[] second, int count ) { - boolean equal = first.length >= count && second.length >= count; - for( int i=0; i process) { - this.processes.put(processName, process); + if (process == null) + { + removeProcess(processName); + } + else + { + this.processes.put(processName, process); + } } + public void removeProcess(String processName) + { + this.processes.remove(processName); + } + public Collection getPageValidationProcessNames() { return this.innerProcesses.keySet(); @@ -203,9 +215,19 @@ public Collection getPageValidationProcessNames() public void replacePageProcess(String processName, Class process) { - this.innerProcesses.put(processName, process); + if (process == null) { + removePageProcess(processName); + } + else { + this.innerProcesses.put(processName, process); + } } + public void removePageProcess(String processName) + { + this.innerProcesses.remove(processName); + } + public boolean isErrorOnMissingProcess() { return errorOnMissingProcess; diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightConstants.java b/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightConstants.java index b01e5eac11e..4ebc2c6e6df 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightConstants.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightConstants.java @@ -414,8 +414,14 @@ public interface PreflightConstants * The stream uses a filter which isn't defined in the PDF Reference document. */ String ERROR_SYNTAX_STREAM_UNDEFINED_FILTER = "1.2.12"; - - String ERROR_SYNTAX_NOCATALOG = "1.2.13"; + /** + * The stream can't be processed + */ + String ERROR_SYNTAX_STREAM_DAMAGED = "1.2.13"; + /** + * There are no catalog dictionary in the PDF File + */ + String ERROR_SYNTAX_NOCATALOG = "1.2.14"; /** * Common error about the cross ref table */ @@ -473,6 +479,7 @@ public interface PreflightConstants * Main error code for graphical problems */ String ERROR_GRAPHIC_MAIN = "2"; + String ERROR_GRAPHIC_INVALID = "2.1"; /** * BBox Entry of a Form XObject is missing or isn't an Array @@ -504,6 +511,10 @@ public interface PreflightConstants String ERROR_GRAPHIC_TOO_MANY_GRAPHIC_STATES = "2.1.8"; + String ERROR_GRAPHIC_MISSING_OBJECT = "2.1.9"; + + String ERROR_GRAPHIC_XOBJECT_INVALID_TYPE = "2.1.10"; + /** * Main error code for graphical transparency problems */ @@ -573,6 +584,10 @@ public interface PreflightConstants * ICC Based color space used in the PDF file is invalid */ String ERROR_GRAPHIC_INVALID_COLOR_SPACE_ICCBASED = "2.4.11"; + /** + * Validation asked on a missing ColorSpace + */ + String ERROR_GRAPHIC_MISSING_COLOR_SPACE_ICCBASED = "2.4.12"; // ----------------------------------------------------------- // ---- FONT ERRORS 3.x... // ----------------------------------------------------------- @@ -636,6 +651,10 @@ public interface PreflightConstants * Encoding entry can't be read due to IOException */ String ERROR_FONTS_ENCODING_IO = "3.1.13"; + /** + * The font type is unknown + */ + String ERROR_FONTS_UNKNOWN_FONT_TYPE = "3.1.14"; /** * The embedded font is damaged */ @@ -711,7 +730,10 @@ public interface PreflightConstants * The AP dictionary of the annotation contains forbidden/invalid entries (only the N entry is authorized) */ String ERROR_ANNOT_MISSING_AP_N_CONTENT = "5.1.2"; - + /** + * An annotation validation is required but there are no element to validate + */ + String ERROR_ANNOT_MISSING_ANNOTATION_DICTIONARY = "5.1.3"; /** * Common forbidden field error in annotation dictionary */ @@ -782,6 +804,10 @@ public interface PreflightConstants * The H entry of a Hide action is set to true (so some annotation can be hide) */ String ERROR_ACTION_HIDE_H_INVALID = "6.1.4"; + /** + * An action validation is required but there are no element to validate + */ + String ERROR_ACTION_MISSING_ACTION_DICTIONARY = "6.1.5"; /** * Common forbidden action error */ @@ -915,4 +941,17 @@ public interface PreflightConstants */ String ERROR_METADATA_CATEGORY_PROPERTY_INVALID = "7.5.1"; + /** + * the infor dictionary is corrupt or value can't be read + */ + String ERROR_METADATA_DICT_INFO_CORRUPT = "7.12"; + /** + * Error about PDF processing : that is not necessary a specific PDF/A validation error + * but a PDF specification requirement that isn't respected. + */ + String ERROR_PDF_PROCESSING = "8"; + /** + * A Mandatory element is missing + */ + String ERROR_PDF_PROCESSING_MISSING = "8.1"; } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/ValidationResult.java b/preflight/src/main/java/org/apache/pdfbox/preflight/ValidationResult.java index a64a6f2183b..2849044e1b9 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/ValidationResult.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/ValidationResult.java @@ -78,7 +78,7 @@ public ValidationResult(ValidationError error) * Create a Validation Result object. This constructor force the isValid to false and add all the given errors to * the list or ValidationErrors. * - * @param error + * @param errors * if error is null, no error is added to the list. */ public ValidationResult(List errors) @@ -334,5 +334,29 @@ public void setWarning(boolean isWarning) this.isWarning = isWarning; } + @Override + public int hashCode() { + return errorCode.hashCode(); + } + + public boolean equals (Object o) { + if (o instanceof ValidationError) { + ValidationError ve = (ValidationError)o; + // check errorCode + if (errorCode==null && ve.errorCode!=null) { + return false; + } else if (!errorCode.equals(ve.errorCode)) { + return false; + } else if (!details.equals(ve.details)) { + return false; + } + // check warning + return isWarning==ve.isWarning; + + } else { + return false; + } + } + } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/Validator_A1b.java b/preflight/src/main/java/org/apache/pdfbox/preflight/Validator_A1b.java index fd4eccc64a0..bcec936530b 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/Validator_A1b.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/Validator_A1b.java @@ -21,12 +21,30 @@ package org.apache.pdfbox.preflight; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.activation.DataSource; import javax.activation.FileDataSource; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.apache.commons.io.IOUtils; import org.apache.pdfbox.Version; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; import org.apache.pdfbox.preflight.exception.SyntaxValidationException; import org.apache.pdfbox.preflight.parser.PreflightParser; +import org.apache.pdfbox.preflight.parser.XmlResultParser; +import org.w3c.dom.Document; +import org.w3c.dom.Element; /** * This class is a simple main class used to check the validity of a pdf file. @@ -43,13 +61,95 @@ public static void main(String[] args) throws Exception { if (args.length == 0) { - System.out.println("Usage : java org.apache.pdfbox.preflight.Validator_A1b "); - System.out.println("Version : " + Version.getVersion()); + usage(); System.exit(1); } + // is output xml ? + int posFile = 0; + boolean outputXml = "xml".equals(args[posFile]); + posFile += outputXml?1:0; + // list + boolean isGroup = "group".equals(args[posFile]); + posFile += isGroup?1:0; + // list + boolean isBatch = "batch".equals(args[posFile]); + posFile += isBatch?1:0; + + if (isGroup||isBatch) { + // prepare the list + List ftp = listFiles(args[posFile]); + int status = 0; + if (!outputXml) { + // simple list of files + for (File file2 : ftp) + { + status |= runSimple(new FileDataSource(file2)); + } + System.exit(status); + } else { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + XmlResultParser xrp = new XmlResultParser(); + if (isGroup) { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + Element root = document.createElement("preflights"); + document.appendChild(root); + root.setAttribute("count", String.format("%d", ftp.size())); + for (File file : ftp) + { + Element result = xrp.validate(document,new FileDataSource(file)); + root.appendChild(result); + } + transformer.transform(new DOMSource(document), new StreamResult(new File(args[posFile]+".preflight.xml"))); + } else { + // isBatch + for (File file : ftp) + { + Element result = xrp.validate(new FileDataSource(file)); + Document document = result.getOwnerDocument(); + document.appendChild(result); + transformer.transform(new DOMSource(document), new StreamResult(new File(file.getAbsolutePath()+".preflight.xml"))); + } + } + } + + + + } else { + // only one file + FileDataSource fd = new FileDataSource(args[posFile]); + if (!outputXml) { + // simple validation + System.exit(runSimple(fd)); + } else { + // generate xml output + XmlResultParser xrp = new XmlResultParser(); + Element result = xrp.validate(fd); + Document document = result.getOwnerDocument(); + document.appendChild(result); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.transform(new DOMSource(document), new StreamResult(System.out)); + } + } + + } + + private static void usage () { + System.out.println("Usage : java org.apache.pdfbox.preflight.Validator_A1b [xml] [mode] "); + System.out.println(); + System.out.println(" * xml : if set, generate xml output"); + System.out.println(" * mode : if set, must be a file containing PDF to parse, can have 2 values"); + System.out.println(" batch : for each file of the list and xml file is generated"); + System.out.println(" group : generate an xml result for all the file of the list."); + System.out.println("Version : " + Version.getVersion()); + } + + private static int runSimple (DataSource fd) throws Exception { ValidationResult result = null; - FileDataSource fd = new FileDataSource(args[0]); PreflightParser parser = new PreflightParser(fd); try { @@ -66,18 +166,36 @@ public static void main(String[] args) throws Exception if (result.isValid()) { - System.out.println("The file " + args[0] + " is a valid PDF/A-1b file"); - System.exit(0); + System.out.println("The file " + fd.getName() + " is a valid PDF/A-1b file"); + System.out.println(); + return 0; } else { - System.out.println("The file" + args[0] + " is not valid, error(s) :"); + System.out.println("The file" + fd.getName() + " is not valid, error(s) :"); for (ValidationError error : result.getErrorsList()) { System.out.println(error.getErrorCode() + " : " + error.getDetails()); } - - System.exit(-1); + System.out.println(); + return -1; + } + + } + + + private static List listFiles (String path) throws IOException { + List files = new ArrayList(); + File f = new File(path); + FileReader fr = new FileReader(f); + BufferedReader buf = new BufferedReader(fr); + while (buf.ready()) { + File fn = new File(buf.readLine()); + if (fn.exists()) { + files.add(fn); + } // else warn ? } + IOUtils.closeQuietly(buf); + return files; } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/font/Type0FontValidator.java b/preflight/src/main/java/org/apache/pdfbox/preflight/font/Type0FontValidator.java index bbb27d7304b..c00b66fae4e 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/font/Type0FontValidator.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/font/Type0FontValidator.java @@ -254,7 +254,7 @@ private void processCMapAsStream(COSStream aCMap) this.fontContainer.push(new ValidationError(ERROR_FONTS_CIDKEYED_CMAP_INVALID_OR_MISSING, "Some elements in the CMap dictionary are missing or invalid")); } - else if (!(wmValue == wmode && cmnValue.equals(cmapName))) + else if (!(wmValue == wmode && cmapName.equals(cmnValue))) { this.fontContainer.push(new ValidationError(ERROR_FONTS_CIDKEYED_CMAP_INVALID_OR_MISSING, "CMapName or WMode is inconsistent")); diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/metadata/SynchronizedMetaDataValidation.java b/preflight/src/main/java/org/apache/pdfbox/preflight/metadata/SynchronizedMetaDataValidation.java index b77258eed3f..f9774992ef5 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/metadata/SynchronizedMetaDataValidation.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/metadata/SynchronizedMetaDataValidation.java @@ -341,7 +341,7 @@ protected void analyzeCreatorToolProperty(PDDocumentInformation dico, XMPBasicSc protected void analyzeCreationDateProperty(PDDocumentInformation dico, XMPBasicSchema xmp, List ve) throws ValidationException { - Calendar creationDate; + Calendar creationDate = null; try { creationDate = dico.getCreationDate(); @@ -349,7 +349,7 @@ protected void analyzeCreationDateProperty(PDDocumentInformation dico, XMPBasicS catch (IOException e) { // If there is an error while converting this property to a date - throw formatAccessException("Document Information", "CreationDate", e); + ve.add(new ValidationError(PreflightConstants.ERROR_METADATA_DICT_INFO_CORRUPT, "Document Information 'CreationDate' can't be read : " + e.getMessage())); } if (creationDate != null) { @@ -424,7 +424,7 @@ protected void analyzeModifyDateProperty(PDDocumentInformation dico, XMPBasicSch catch (IOException e) { // If there is an error while converting this property to a date - throw formatAccessException("Document Information", "ModifyDate", e); + ve.add(new ValidationError(PreflightConstants.ERROR_METADATA_DICT_INFO_CORRUPT, "Document Information 'ModifyDate' can't be read : " + e.getMessage())); } } @@ -507,24 +507,6 @@ protected ValidationError unexpectedPrefixFoundError(String prefFound, String pr return new ValidationError(PreflightConstants.ERROR_METADATA_WRONG_NS_PREFIX, sb.toString()); } - /** - * Return an exception formatted on IOException when accessing metadata - * - * @param type - * type of property (Document Info or XMP) - * @param target - * the name of the metadata - * @param cause - * the raised IOException - * @return the generated exception - */ - protected ValidationException formatAccessException(String type, String target, Throwable cause) - { - StringBuilder sb = new StringBuilder(80); - sb.append("Cannot treat ").append(type).append(" ").append(target).append(" property"); - return new ValidationException(sb.toString(), cause); - } - /** * Return an exception formatted on IOException when accessing on metadata schema * diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java b/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java index a214d77de7f..3753d110621 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java @@ -694,10 +694,12 @@ else if (offsetOrObjstmObNr > 0) { addValidationError(new ValidationError(ERROR_SYNTAX_OBJ_DELIMITER, "Single space expected")); + // reset pdfSource cursor to read object information pdfSource.seek(offset); readObjNr = readInt(); readObjGen = readInt(); + skipSpaces(); // skip spaces between Object Generation number and the 'obj' keyword for (char c : OBJ_MARKER) { if (pdfSource.read() != c) diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/parser/XmlResultParser.java b/preflight/src/main/java/org/apache/pdfbox/preflight/parser/XmlResultParser.java new file mode 100644 index 00000000000..11afcde00e3 --- /dev/null +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/parser/XmlResultParser.java @@ -0,0 +1,178 @@ +/***************************************************************************** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +package org.apache.pdfbox.preflight.parser; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.activation.DataSource; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.pdfbox.preflight.PreflightDocument; +import org.apache.pdfbox.preflight.ValidationResult; +import org.apache.pdfbox.preflight.ValidationResult.ValidationError; +import org.apache.pdfbox.preflight.exception.SyntaxValidationException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class XmlResultParser +{ + + + public Element validate (DataSource source) throws IOException { + try { + Document rdocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + return validate(rdocument,source); + } catch (ParserConfigurationException e) { + IOException ioe = new IOException("Failed to init document builder"); + ioe.initCause(e); + throw ioe; + } + } + + + public Element validate (Document rdocument, DataSource source) throws IOException { + String pdfType = null; + ValidationResult result = null; + long before = System.currentTimeMillis(); + try { + PreflightParser parser = new PreflightParser(source); + try + { + parser.parse(); + PreflightDocument document = parser.getPreflightDocument(); + document.validate(); + pdfType = document.getSpecification().getFname(); + result = document.getResult(); + document.close(); + } + catch (SyntaxValidationException e) + { + result = e.getResult(); + } + } + catch(Exception e) + { + long after = System.currentTimeMillis(); + return generateFailureResponse(rdocument, source.getName(), after-before, pdfType, e); + } + + long after = System.currentTimeMillis(); + if (result.isValid()) { + Element preflight = generateResponseSkeleton(rdocument, source.getName(), after-before); + // valid ? + Element valid = rdocument.createElement("isValid"); + valid.setAttribute("type", pdfType); + valid.setTextContent("true"); + preflight.appendChild(valid); + return preflight; + } else { + Element preflight = generateResponseSkeleton(rdocument, source.getName(), after-before); + // valid ? + createResponseWithError(rdocument, pdfType, result, preflight); + return preflight; + } + + } + + protected void createResponseWithError(Document rdocument, String pdfType, ValidationResult result, Element preflight) { + Element valid = rdocument.createElement("isValid"); + valid.setAttribute("type", pdfType); + valid.setTextContent("false"); + preflight.appendChild(valid); + // errors list + Element errors = rdocument.createElement("errors"); + Map cleaned = cleanErrorList(result.getErrorsList()); + preflight.appendChild(errors); + int totalCount = 0; + for (Map.Entry entry : cleaned.entrySet()) + { + Element error = rdocument.createElement("error"); + int count = entry.getValue().intValue(); + error.setAttribute("count", String.format("%d",count)); + totalCount += count; + Element code = rdocument.createElement("code"); + code.setTextContent(entry.getKey().getErrorCode()); + error.appendChild(code); + Element detail = rdocument.createElement("details"); + detail.setTextContent(entry.getKey().getDetails()); + error.appendChild(detail); + errors.appendChild(error); + } + errors.setAttribute("count", String.format("%d", totalCount)); + } + + private Map cleanErrorList (List errors) { + Map cleaned = new HashMap(errors.size()); + for (ValidationError ve: errors) { + Integer found = cleaned.get(ve); + if (found!=null) { + cleaned.put(ve,found+1); + } else { + cleaned.put(ve,1); + } + + } + return cleaned; + } + + protected Element generateFailureResponse (Document rdocument, String name,long duration, String pdfType, Exception e) { + Element preflight = generateResponseSkeleton(rdocument, name, duration); + // valid ? + Element valid = rdocument.createElement("isValid"); + valid.setAttribute("type", pdfType); + valid.setTextContent("false"); + preflight.appendChild(valid); + // exception + Element exception = rdocument.createElement("exceptionThrown"); + Element message = rdocument.createElement("message"); + message.setTextContent(e.getMessage()); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + pw.close(); + Element stack = rdocument.createElement("stackTrace"); + stack.setTextContent(sw.toString()); + exception.appendChild(message); + exception.appendChild(stack); + preflight.appendChild(exception); + return preflight; + } + + protected Element generateResponseSkeleton (Document rdocument, String name, long duration) { + Element preflight = rdocument.createElement("preflight"); + preflight.setAttribute("name", name); + // duration + Element eduration = rdocument.createElement("executionTimeMS"); + eduration.setTextContent(String.format("%d", duration)); + preflight.appendChild(eduration); + // return skeleton + return preflight; + } + + +} diff --git a/preflight/src/test/java/org/apache/pdfbox/preflight/TestPreflightConfiguration.java b/preflight/src/test/java/org/apache/pdfbox/preflight/TestPreflightConfiguration.java new file mode 100644 index 00000000000..9342d3bbc2e --- /dev/null +++ b/preflight/src/test/java/org/apache/pdfbox/preflight/TestPreflightConfiguration.java @@ -0,0 +1,101 @@ +/***************************************************************************** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ +package org.apache.pdfbox.preflight; + +import org.apache.pdfbox.preflight.exception.ValidationException; +import org.apache.pdfbox.preflight.process.BookmarkValidationProcess; +import org.apache.pdfbox.preflight.process.EmptyValidationProcess; +import org.apache.pdfbox.preflight.process.ValidationProcess; +import org.apache.pdfbox.preflight.process.reflect.ResourcesValidationProcess; +import org.junit.Assert; +import org.junit.Test; + +public class TestPreflightConfiguration +{ + + @Test + public void testGetValidationProcess() throws Exception { + PreflightConfiguration confg = PreflightConfiguration.createPdfA1BConfiguration(); + ValidationProcess vp = confg.getInstanceOfProcess(PreflightConfiguration.BOOKMARK_PROCESS); + Assert.assertNotNull(vp); + Assert.assertTrue(vp instanceof BookmarkValidationProcess); + } + + @Test + public void testGetValidationPageProcess() throws Exception { + PreflightConfiguration confg = PreflightConfiguration.createPdfA1BConfiguration(); + ValidationProcess vp = confg.getInstanceOfProcess(PreflightConfiguration.RESOURCES_PROCESS); + Assert.assertNotNull(vp); + Assert.assertTrue(vp instanceof ResourcesValidationProcess); + } + + @Test + public void testGetValidationProcess_noError() throws Exception { + PreflightConfiguration confg = PreflightConfiguration.createPdfA1BConfiguration(); + confg.setErrorOnMissingProcess(false); + confg.removeProcess(PreflightConfiguration.BOOKMARK_PROCESS); + ValidationProcess vp = confg.getInstanceOfProcess(PreflightConfiguration.BOOKMARK_PROCESS); + Assert.assertNotNull(vp); + Assert.assertTrue(vp instanceof EmptyValidationProcess); + } + + @Test + public void testGetValidationPageProcess_noError() throws Exception { + PreflightConfiguration confg = PreflightConfiguration.createPdfA1BConfiguration(); + confg.setErrorOnMissingProcess(false); + confg.removePageProcess(PreflightConfiguration.RESOURCES_PROCESS); + ValidationProcess vp = confg.getInstanceOfProcess(PreflightConfiguration.RESOURCES_PROCESS); + Assert.assertNotNull(vp); + Assert.assertTrue(vp instanceof EmptyValidationProcess); + } + + @Test(expected=ValidationException.class) + public void testGetMissingValidationProcess() throws Exception { + PreflightConfiguration confg = PreflightConfiguration.createPdfA1BConfiguration(); + confg.removeProcess(PreflightConfiguration.BOOKMARK_PROCESS); + confg.getInstanceOfProcess(PreflightConfiguration.BOOKMARK_PROCESS); + Assert.fail(); + } + + @Test(expected=ValidationException.class) + public void testGetMissingValidationPageProcess() throws Exception { + PreflightConfiguration confg = PreflightConfiguration.createPdfA1BConfiguration(); + confg.removePageProcess(PreflightConfiguration.RESOURCES_PROCESS); + confg.getInstanceOfProcess(PreflightConfiguration.RESOURCES_PROCESS); + Assert.fail(); + } + + @Test(expected=ValidationException.class) + public void testGetMissingValidationProcess2() throws Exception { + PreflightConfiguration confg = PreflightConfiguration.createPdfA1BConfiguration(); + confg.replaceProcess(PreflightConfiguration.BOOKMARK_PROCESS, null); + confg.getInstanceOfProcess(PreflightConfiguration.BOOKMARK_PROCESS); + Assert.fail(); + } + + @Test(expected=ValidationException.class) + public void testGetMissingValidationPageProcess2() throws Exception { + PreflightConfiguration confg = PreflightConfiguration.createPdfA1BConfiguration(); + confg.replacePageProcess(PreflightConfiguration.RESOURCES_PROCESS, null); + confg.getInstanceOfProcess(PreflightConfiguration.RESOURCES_PROCESS); + Assert.fail(); + } +} diff --git a/preflight/src/test/java/org/apache/pdfbox/preflight/metadata/TestSynchronizedMetadataValidation.java b/preflight/src/test/java/org/apache/pdfbox/preflight/metadata/TestSynchronizedMetadataValidation.java index 566ec4f76ca..7d9ebd79bc5 100644 --- a/preflight/src/test/java/org/apache/pdfbox/preflight/metadata/TestSynchronizedMetadataValidation.java +++ b/preflight/src/test/java/org/apache/pdfbox/preflight/metadata/TestSynchronizedMetadataValidation.java @@ -436,19 +436,6 @@ public void testAllInfoSynhcronized() throws Exception } } - - /** - * Check if FormatAccessException Generator is ok - * - * @throws Exception - */ - @Test - public void checkformatAccessException() throws Exception - { - Throwable cause = new Throwable(); - Assert.assertSame(cause, sync.formatAccessException("test", "test", cause).getCause()); - } - /** * Check if SchemaAccessException Generator is ok * diff --git a/preflight/src/test/java/org/apache/pdfbox/preflight/parser/TestXmlResultParser.java b/preflight/src/test/java/org/apache/pdfbox/preflight/parser/TestXmlResultParser.java new file mode 100644 index 00000000000..fff90790529 --- /dev/null +++ b/preflight/src/test/java/org/apache/pdfbox/preflight/parser/TestXmlResultParser.java @@ -0,0 +1,117 @@ +/***************************************************************************** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ +package org.apache.pdfbox.preflight.parser; + + +import org.apache.pdfbox.preflight.ValidationResult; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; + +public class TestXmlResultParser { + + public static final String ERROR_CODE = "000"; + + protected XmlResultParser parser = new XmlResultParser(); + + protected Document document; + + protected Element preflight; + + protected XPath xpath; + + @Before + public void before () throws Exception { + document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + preflight = parser.generateResponseSkeleton(document, "myname", 14); + xpath = XPathFactory.newInstance().newXPath(); + } + + @Test + public void testOneError () throws Exception { + ValidationResult result = new ValidationResult(false); + result.addError(new ValidationResult.ValidationError("7")); + parser.createResponseWithError(document, "pdftype", result, preflight); + Assert.assertNotNull(xpath.evaluate("errors[@count='1']", preflight, XPathConstants.NODE)); + NodeList nl = (NodeList)xpath.evaluate("errors/error[@count='1']", preflight, XPathConstants.NODESET); + Assert.assertEquals(1,nl.getLength()); + } + + @Test + public void testTwoError () throws Exception { + ValidationResult result = new ValidationResult(false); + result.addError(new ValidationResult.ValidationError("7")); + result.addError(new ValidationResult.ValidationError(ERROR_CODE)); + parser.createResponseWithError(document, "pdftype", result, preflight); + Assert.assertNotNull(xpath.evaluate("errors[@count='2']", preflight, XPathConstants.NODE)); + NodeList nl = (NodeList)xpath.evaluate("errors/error[@count='1']", preflight, XPathConstants.NODESET); + Assert.assertEquals(2,nl.getLength()); + } + + @Test + public void testSameErrorTwice () throws Exception { + ValidationResult result = new ValidationResult(false); + result.addError(new ValidationResult.ValidationError(ERROR_CODE)); + result.addError(new ValidationResult.ValidationError(ERROR_CODE)); + parser.createResponseWithError(document,"pdftype",result,preflight); + Assert.assertNotNull(xpath.evaluate("errors[@count='2']", preflight, XPathConstants.NODE)); + Assert.assertNotNull(xpath.evaluate("errors/error[@count='2']", preflight, XPathConstants.NODE)); + Element code = (Element)xpath.evaluate("errors/error[@count='2']/code", preflight, XPathConstants.NODE); + Assert.assertNotNull(code); + Assert.assertEquals(ERROR_CODE,code.getTextContent()); + } + + @Test + public void testSameCodeWithDifferentMessages () throws Exception { + ValidationResult result = new ValidationResult(false); + result.addError(new ValidationResult.ValidationError(ERROR_CODE,"message 1")); + result.addError(new ValidationResult.ValidationError(ERROR_CODE,"message 2")); + parser.createResponseWithError(document, "pdftype", result, preflight); + Assert.assertNotNull(xpath.evaluate("errors[@count='2']", preflight, XPathConstants.NODE)); + NodeList nl = (NodeList)xpath.evaluate("errors/error[@count='1']", preflight, XPathConstants.NODESET); + Assert.assertEquals(2,nl.getLength()); + } + + +// private void dump (Element element) throws Exception { +// Transformer transformer = TransformerFactory.newInstance().newTransformer(); +// transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); +// transformer.setOutputProperty(OutputKeys.METHOD, "xml"); +// transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); +// transformer.setOutputProperty(OutputKeys.INDENT, "yes"); +// transformer.transform(new DOMSource(element), new StreamResult(System.out)); +// System.out.flush(); +// } + +} From 4bc93edf2dba85c2a97237ab2157ada6ddfea086 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 17 Nov 2013 14:31:02 +0000 Subject: [PATCH 0025/1017] merged the following changes from trunk into 1.8 branch: PDFBOX-1622 r1512433 PDFBOX-1627 r1490022,r1490023 PDFBOX-1632 r1495799,r1497507 PDFBOX-1638 r1504214,r1515165,r1515905 PDFBOX-1639 r1493503 PDFBOX-1655 r1505737 PDFBOX-1657 r1504210 PDFBOX-1663 r1512661 PDFBOX-1681 r1512367 PDFBOX-1694 r1517273,r1517281 PDFBOX-1702 r1517288 PDFBOX-1735 r1528826 PDFBOX-1737 r1528833 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1542735 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/fontbox/ttf/GlyphTable.java | 27 +- .../org/apache/pdfbox/filter/JPXFilter.java | 37 +- .../apache/pdfbox/io/ASCII85InputStream.java | 93 +- .../apache/pdfbox/io/ASCII85OutputStream.java | 168 ++-- .../apache/pdfbox/pdfparser/BaseParser.java | 108 +- .../pdfparser/NonSequentialPDFParser.java | 10 +- .../pdfparser/PDFObjectStreamParser.java | 10 +- .../apache/pdfbox/pdfparser/PDFParser.java | 27 +- .../pdfparser/VisualSignatureParser.java | 8 +- .../pdmodel/edit/PDPageContentStream.java | 919 +++++++++--------- .../pdmodel/font/PDCIDFontType2Font.java | 8 +- .../apache/pdfbox/pdmodel/font/PDFont.java | 58 +- .../pdfbox/pdmodel/font/PDTrueTypeFont.java | 21 +- .../pdmodel/font/PDType1AfmPfbFont.java | 102 +- .../pdmodel/graphics/color/PDColorState.java | 188 ++-- .../pdmodel/graphics/color/PDIndexed.java | 190 ++-- .../pdmodel/graphics/xobject/PDCcitt.java | 488 +++++----- .../pdmodel/graphics/xobject/PDPixelMap.java | 11 +- .../pdmodel/graphics/xobject/PDXObject.java | 19 +- .../graphics/xobject/PDXObjectImage.java | 14 + .../org/apache/pdfbox/util/TextNormalize.java | 87 +- .../pdfbox/preflight/PreflightConstants.java | 10 +- .../pdfbox/preflight/PreflightDocument.java | 2 +- .../action/ActionManagerFactory.java | 5 +- .../content/ContentStreamWrapper.java | 1 + .../preflight/font/Type3FontValidator.java | 5 +- .../preflight/parser/PreflightParser.java | 14 +- .../process/AcroFormValidationProcess.java | 4 +- .../process/BookmarkValidationProcess.java | 4 +- .../process/CatalogValidationProcess.java | 21 +- .../process/PageTreeValidationProcess.java | 4 +- .../process/StreamValidationProcess.java | 6 +- .../reflect/ActionsValidationProcess.java | 31 +- .../reflect/AnnotationValidationProcess.java | 27 +- .../reflect/ExtGStateValidationProcess.java | 22 +- .../reflect/FontValidationProcess.java | 32 +- .../GraphicObjectPageValidationProcess.java | 11 +- .../reflect/ResourcesValidationProcess.java | 25 +- .../ShaddingPatternValidationProcess.java | 29 +- .../reflect/SinglePageValidationProcess.java | 35 +- .../TilingPatternValidationProcess.java | 30 +- .../pdfbox/preflight/utils/ContextHelper.java | 22 +- .../preflight/xobject/XObjFormValidator.java | 21 +- 43 files changed, 1604 insertions(+), 1350 deletions(-) diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphTable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphTable.java index 4c0df9306d6..147c2c42326 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphTable.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphTable.java @@ -22,7 +22,7 @@ * A table in a true type font. * * @author Ben Litchfield (ben@benlitchfield.com) - * @version $Revision: 1.1 $ + * */ public class GlyphTable extends TTFTable { @@ -30,9 +30,9 @@ public class GlyphTable extends TTFTable * Tag to identify this table. */ public static final String TAG = "glyf"; - + private GlyphData[] glyphs; - + /** * This will read the required data from the stream. * @@ -40,7 +40,7 @@ public class GlyphTable extends TTFTable * @param data The stream to read the data from. * @throws IOException If there is an error reading the data. */ - public void initData( TrueTypeFont ttf, TTFDataStream data ) throws IOException + public void initData(TrueTypeFont ttf, TTFDataStream data) throws IOException { MaximumProfileTable maxp = ttf.getMaximumProfile(); IndexToLocationTable loc = ttf.getIndexToLocation(); @@ -50,27 +50,26 @@ public void initData( TrueTypeFont ttf, TTFDataStream data ) throws IOException int numGlyphs = maxp.getNumGlyphs(); // the end of the glyph table long endOfGlyphs = offsets[numGlyphs]; - long currentOffset = -1; long offset = getOffset(); glyphs = new GlyphData[numGlyphs]; - for( int i=0; iTimo Böhme + * @author Timo Boehme * */ public class JPXFilter implements Filter { /** Log instance. */ - private static final Log log = LogFactory.getLog(JPXFilter.class); + private static final Log LOG = LogFactory.getLog(JPXFilter.class); /** * Decode JPEG2000 data using Java ImageIO library. @@ -47,20 +50,30 @@ public class JPXFilter implements Filter * {@inheritDoc} * */ - public void decode( InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) - throws IOException + public void decode(InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex) + throws IOException { - BufferedImage bi = ImageIO.read( compressedData ); - if ( bi != null ) + BufferedImage bi = ImageIO.read(compressedData); + if (bi != null) { DataBuffer dBuf = bi.getData().getDataBuffer(); - if ( dBuf.getDataType() == DataBuffer.TYPE_BYTE ) + if (dBuf.getDataType() == DataBuffer.TYPE_BYTE) { - result.write( ( ( DataBufferByte ) dBuf ).getData() ); + // maybe some wrong/missing values have to be revised/added + ColorModel colorModel = bi.getColorModel(); + if (options.getItem(COSName.COLORSPACE) == null) + { + options.setItem(COSName.COLORSPACE, + PDColorSpaceFactory.createColorSpace(null, colorModel.getColorSpace())); + } + options.setInt(COSName.BITS_PER_COMPONENT, colorModel.getPixelSize() / colorModel.getNumComponents()); + options.setInt(COSName.HEIGHT, bi.getHeight()); + options.setInt(COSName.WIDTH, bi.getWidth()); + result.write(((DataBufferByte) dBuf).getData()); } else { - log.error( "Image data buffer not of type byte but type " + dBuf.getDataType() ); + LOG.error("Image data buffer not of type byte but type " + dBuf.getDataType()); } } } @@ -68,9 +81,9 @@ public void decode( InputStream compressedData, OutputStream result, COSDictiona /** * {@inheritDoc} */ - public void encode( InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) - throws IOException + 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." ); + LOG.error("Warning: JPXFilter.encode is not implemented yet, skipping this stream."); } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/io/ASCII85InputStream.java b/pdfbox/src/main/java/org/apache/pdfbox/io/ASCII85InputStream.java index aee64d98145..87eefe1838a 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/io/ASCII85InputStream.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/io/ASCII85InputStream.java @@ -17,14 +17,14 @@ package org.apache.pdfbox.io; import java.io.FilterInputStream; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; /** * This class represents an ASCII85 stream. * * @author Ben Litchfield - * @version $Revision: 1.6 $ + * */ public class ASCII85InputStream extends FilterInputStream { @@ -35,12 +35,20 @@ public class ASCII85InputStream extends FilterInputStream private byte[] ascii; private byte[] b; + private static final char TERMINATOR = '~'; + private static final char OFFSET = '!'; + private static final char NEWLINE = '\n'; + private static final char RETURN = '\r'; + private static final char SPACE = ' '; + private static final char PADDING_U = 'u'; + private static final char Z = 'z'; + /** * Constructor. * * @param is The input stream to actually read from. */ - public ASCII85InputStream( InputStream is ) + public ASCII85InputStream(InputStream is) { super(is); index = 0; @@ -59,9 +67,9 @@ public ASCII85InputStream( InputStream is ) */ public final int read() throws IOException { - if( index >= n ) + if (index >= n) { - if(eof) + if (eof) { return -1; } @@ -70,69 +78,72 @@ public final int read() throws IOException byte z; do { - int zz=(byte)in.read(); - if(zz==-1) + int zz = (byte) in.read(); + if (zz == -1) { - eof=true; + eof = true; return -1; } - z = (byte)zz; - } while( z=='\n' || z=='\r' || z==' '); + z = (byte) zz; + } while (z == NEWLINE || z == RETURN || z == SPACE); - if (z == '~' || z=='x') + if (z == TERMINATOR) { - eof=true; - ascii=b=null; + eof = true; + ascii = b = null; n = 0; return -1; } - else if (z == 'z') + else if (z == Z) { - b[0]=b[1]=b[2]=b[3]=0; + 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) + ascii[0] = z; // may be EOF here.... + for (k = 1; k < 5; ++k) { do { - int zz=(byte)in.read(); - if(zz==-1) + int zz = (byte) in.read(); + if (zz == -1) { - eof=true; + eof = true; return -1; } - z=(byte)zz; - } while ( z=='\n' || z=='\r' || z==' ' ); - ascii[k]=z; - if (z == '~' || z=='x') + z = (byte) zz; + } while (z == NEWLINE || z == RETURN || z == SPACE); + ascii[k] = z; + if (z == TERMINATOR) { + // don't include ~ as padding byte + ascii[k] = (byte) PADDING_U; break; } } n = k - 1; - if ( n==0 ) + if (n == 0) { eof = true; ascii = null; b = null; return -1; } - if ( k < 5 ) + if (k < 5) { - for (++k; k < 5; ++k ) + for (++k; k < 5; ++k) { - ascii[k] = 0x21; + // use 'u' for padding + ascii[k] = (byte) PADDING_U; } - eof=true; + eof = true; } // decode stream - long t=0; - for ( k=0; k<5; ++k) + long t = 0; + for (k = 0; k < 5; ++k) { - z=(byte)(ascii[k] - 0x21); + z = (byte) (ascii[k] - OFFSET); if (z < 0 || z > 93) { n = 0; @@ -143,10 +154,10 @@ else if (z == 'z') } t = (t * 85L) + z; } - for ( k = 3; k>=0; --k) + for (k = 3; k >= 0; --k) { - b[k] = (byte)(t & 0xFFL); - t>>>=8; + b[k] = (byte) (t & 0xFFL); + t >>>= 8; } } } @@ -166,24 +177,24 @@ else if (z == 'z') */ public final int read(byte[] data, int offset, int len) throws IOException { - if(eof && index>=n) + if (eof && index >= n) { return -1; } - for(int i=0;iBen Litchfield - * @version $Revision: 1.7 $ + * */ public class ASCII85OutputStream extends FilterOutputStream { @@ -42,22 +42,25 @@ public class ASCII85OutputStream extends FilterOutputStream private int maxline; private boolean flushed; private char terminator; + private static final char OFFSET = '!'; + private static final char NEWLINE = '\n'; + private static final char Z = 'z'; /** * Constructor. * * @param out The output stream to write to. */ - public ASCII85OutputStream( OutputStream out ) + 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='~'; + super(out); + lineBreak = 36 * 2; + maxline = 36 * 2; + count = 0; + indata = new byte[4]; + outdata = new byte[5]; + flushed = true; + terminator = '~'; } /** @@ -67,11 +70,11 @@ public ASCII85OutputStream( OutputStream out ) */ public void setTerminator(char term) { - if(term<118 || term>126 || term=='z') + if (term < 118 || term > 126 || term == Z) { throw new IllegalArgumentException("Terminator must be 118-126 excluding z"); } - terminator=term; + terminator = term; } /** @@ -91,11 +94,11 @@ public char getTerminator() */ public void setLineLength(int l) { - if( lineBreak > l ) + if (lineBreak > l) { lineBreak = l; } - maxline=l; + maxline = l; } /** @@ -111,44 +114,33 @@ public int getLineLength() /** * This will transform the next four ascii bytes. */ - private final void transformASCII85() + 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); + long word = ((((indata[0] << 8) | (indata[1] & 0xFF)) << 16) | ((indata[2] & 0xFF) << 8) | (indata[3] & 0xFF)) & 0xFFFFFFFFL; - if (word == 0 ) + if (word == 0) { - outdata[0]=(byte)'z'; - outdata[1]=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 * 85L); + outdata[0] = (byte) (x + OFFSET); + 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 * 85L); + outdata[1] = (byte) (x + OFFSET); + 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 * 85L); + outdata[2] = (byte) (x + OFFSET); + word -= x * 85L * 85L; - x=word/85L; - // System.out.println("x3="+x); - outdata[3]=(byte)(x+'!'); + x = word / 85L; + outdata[3] = (byte) (x + OFFSET); - // word-=x*85L; - - // System.out.println("x4="+(word % 85L)); - outdata[4]=(byte)((word%85L)+'!'); + outdata[4] = (byte) ((word % 85L) + OFFSET); } /** @@ -160,53 +152,29 @@ private final void transformASCII85() */ public final void write(int b) throws IOException { - flushed=false; - indata[count++]=(byte)b; - if(count < 4 ) + flushed = false; + indata[count++] = (byte) b; + if (count < 4) { return; } transformASCII85(); - for(int i=0;i<5;i++) + for (int i = 0; i < 5; i++) { - if(outdata[i]==0) + if (outdata[i] == 0) { break; } out.write(outdata[i]); - if(--lineBreak==0) + if (--lineBreak == 0) { - out.write('\n'); - lineBreak=maxline; + out.write(NEWLINE); + 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 0 ) + if (count > 0) { - for( int i=count; i<4; i++ ) + for (int i = count; i < 4; i++) { - indata[i]=0; + indata[i] = 0; } transformASCII85(); - if(outdata[0]=='z') + if (outdata[0] == Z) { - for(int i=0;i<5;i++) // expand 'z', + for (int i = 0; i < 5; i++) // expand 'z', { - outdata[i]=(byte)'!'; + outdata[i] = (byte) OFFSET; } } - for(int i=0;i= OBJECT_NUMBER_THRESHOLD) { + throw new IOException("Object Number '" + retval + "' has more than 10 digits or is negative"); + } + return retval; + } + + /** + * This will read a integer from the Stream and throw an {@link IllegalArgumentException} if the integer value + * has more than the maximum object revision (i.e. : bigger than {@link #GENERATION_NUMBER_THRESHOLD}) + * @return + * @throws IOException + */ + protected int readGenerationNumber() throws IOException + { + int retval = readInt(); + if(retval < 0 || retval >= GENERATION_NUMBER_THRESHOLD) { + throw new IOException("Generation Number '" + retval + "' has more than 5 digits"); + } + return retval; + } + /** * This will read an integer from the stream. * @@ -1597,8 +1629,57 @@ protected int readInt() throws IOException skipSpaces(); int retval = 0; + StringBuilder intBuffer = readStringNumber(); + + 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; + } + + + /** + * This will read an long from the stream. + * + * @return The long that was read from the stream. + * + * @throws IOException If there is an error reading from the stream. + */ + protected long readLong() throws IOException + { + skipSpaces(); + long retval = 0; + + StringBuilder longBuffer = readStringNumber(); + + try + { + retval = Long.parseLong( longBuffer.toString() ); + } + catch( NumberFormatException e ) + { + pdfSource.unread(longBuffer.toString().getBytes("ISO-8859-1")); + throw new IOException( "Error: Expected a long type, actual='" + longBuffer + "'" ); + } + return retval; + } + + /** + * This method is used to read a token by the {@linkplain #readInt()} method and the {@linkplain #readLong()} method. + * + * @return the token to parse as integer or long by the calling method. + * @throws IOException throws by the {@link #pdfSource} methods. + */ + protected final StringBuilder readStringNumber() throws IOException + { int lastByte = 0; - StringBuffer intBuffer = new StringBuffer(); + StringBuilder buffer = new StringBuilder(); while( (lastByte = pdfSource.read() ) != 32 && lastByte != 10 && lastByte != 13 && @@ -1606,22 +1687,13 @@ protected int readInt() throws IOException lastByte != 0 && //See sourceforge bug 853328 lastByte != -1 ) { - intBuffer.append( (char)lastByte ); + buffer.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; + return buffer; } + } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java index 57bf3c1bad9..3e270323345 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java @@ -335,6 +335,8 @@ protected void initialParse() throws IOException // seek to xref table setPdfSource(prev); + // skip white spaces + skipSpaces(); // -- parse xref if (pdfSource.peek() == 'x') { @@ -455,8 +457,8 @@ protected void initialParse() throws IOException private long parseXrefObjStream(long objByteOffset) throws IOException { // ---- parse indirect object head - readInt(); - readInt(); + readObjectNumber(); + readGenerationNumber(); readPattern(OBJ_MARKER); COSDictionary dict = parseCOSDictionary(); @@ -1178,8 +1180,8 @@ else if (offsetOrObjstmObNr > 0) setPdfSource(offsetOrObjstmObNr); // ---- we must have an indirect object - final int readObjNr = readInt(); - final int readObjGen = readInt(); + final long readObjNr = readObjectNumber(); + final long readObjGen = readGenerationNumber(); readPattern(OBJ_MARKER); // ---- consistency check diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFObjectStreamParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFObjectStreamParser.java index 7e5b54fbac0..b3f3f4fc53b 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFObjectStreamParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFObjectStreamParser.java @@ -44,7 +44,7 @@ public class PDFObjectStreamParser extends BaseParser LogFactory.getLog(PDFObjectStreamParser.class); private List streamObjects = null; - private List objectNumbers = null; + private List objectNumbers = null; private COSStream stream; /** @@ -92,13 +92,13 @@ public void parse() throws IOException { //need to first parse the header. int numberOfObjects = stream.getInt( "N" ); - objectNumbers = new ArrayList( numberOfObjects ); + objectNumbers = new ArrayList( numberOfObjects ); streamObjects = new ArrayList( numberOfObjects ); for( int i=0; iBen Litchfield - * @version $Revision: 1.19 $ + * */ public class PDPageContentStream { @@ -74,63 +74,77 @@ public class PDPageContentStream private PDColorSpace currentStrokingColorSpace = new PDDeviceGray(); private PDColorSpace currentNonStrokingColorSpace = new PDDeviceGray(); - //cached storage component for getting color values + // 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 LINE_JOIN_STYLE = "j\n"; - private static final String LINE_CAP_STYLE = "J\n"; - private static final String LINE_DASH_PATTERN = "d\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 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 NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US); + private static final String ISO8859 = "ISO-8859-1"; + private static byte[] getISOBytes(final String s) + { + try + { + return s.getBytes(ISO8859); + } + catch (final UnsupportedEncodingException ex) + { + throw new IllegalStateException(ex); + } + } - private static final int SPACE = 32; + private static final byte[] BEGIN_TEXT = getISOBytes("BT\n"); + private static final byte[] END_TEXT = getISOBytes("ET\n"); + private static final byte[] SET_FONT = getISOBytes("Tf\n"); + private static final byte[] MOVE_TEXT_POSITION = getISOBytes("Td\n"); + private static final byte[] SET_TEXT_MATRIX = getISOBytes("Tm\n"); + private static final byte[] SHOW_TEXT = getISOBytes("Tj\n"); + + private static final byte[] SAVE_GRAPHICS_STATE = getISOBytes("q\n"); + private static final byte[] RESTORE_GRAPHICS_STATE = getISOBytes("Q\n"); + private static final byte[] CONCATENATE_MATRIX = getISOBytes("cm\n"); + private static final byte[] XOBJECT_DO = getISOBytes("Do\n"); + private static final byte[] RG_STROKING = getISOBytes("RG\n"); + private static final byte[] RG_NON_STROKING = getISOBytes("rg\n"); + private static final byte[] K_STROKING = getISOBytes("K\n"); + private static final byte[] K_NON_STROKING = getISOBytes("k\n"); + private static final byte[] G_STROKING = getISOBytes("G\n"); + private static final byte[] G_NON_STROKING = getISOBytes("g\n"); + private static final byte[] RECTANGLE = getISOBytes("re\n"); + private static final byte[] FILL_NON_ZERO = getISOBytes("f\n"); + private static final byte[] FILL_EVEN_ODD = getISOBytes("f*\n"); + private static final byte[] LINE_TO = getISOBytes("l\n"); + private static final byte[] MOVE_TO = getISOBytes("m\n"); + private static final byte[] CLOSE_STROKE = getISOBytes("s\n"); + private static final byte[] STROKE = getISOBytes("S\n"); + private static final byte[] LINE_WIDTH = getISOBytes("w\n"); + private static final byte[] LINE_JOIN_STYLE = getISOBytes("j\n"); + private static final byte[] LINE_CAP_STYLE = getISOBytes("J\n"); + private static final byte[] LINE_DASH_PATTERN = getISOBytes("d\n"); + private static final byte[] CLOSE_SUBPATH = getISOBytes("h\n"); + private static final byte[] CLIP_PATH_NON_ZERO = getISOBytes("W\n"); + private static final byte[] CLIP_PATH_EVEN_ODD = getISOBytes("W*\n"); + private static final byte[] NOP = getISOBytes("n\n"); + private static final byte[] BEZIER_312 = getISOBytes("c\n"); + private static final byte[] BEZIER_32 = getISOBytes("v\n"); + private static final byte[] BEZIER_313 = getISOBytes("y\n"); + + private static final byte[] BMC = getISOBytes("BMC\n"); + private static final byte[] BDC = getISOBytes("BDC\n"); + private static final byte[] EMC = getISOBytes("EMC\n"); + + private static final byte[] SET_STROKING_COLORSPACE = getISOBytes("CS\n"); + private static final byte[] SET_NON_STROKING_COLORSPACE = getISOBytes("cs\n"); + + private static final byte[] SET_STROKING_COLOR_SIMPLE = getISOBytes("SC\n"); + private static final byte[] SET_STROKING_COLOR_COMPLEX = getISOBytes("SCN\n"); + private static final byte[] SET_NON_STROKING_COLOR_SIMPLE = getISOBytes("sc\n"); + private static final byte[] SET_NON_STROKING_COLOR_COMPLEX = getISOBytes("scn\n"); + + private static final byte[] OPENING_BRACKET = getISOBytes("["); + private static final byte[] CLOSING_BRACKET = getISOBytes("]"); + private static final int SPACE = 32; /** * Create a new PDPage content stream. @@ -139,9 +153,9 @@ public class PDPageContentStream * @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 + public PDPageContentStream(PDDocument document, PDPage sourcePage) throws IOException { - this(document,sourcePage,false,true); + this(document, sourcePage, false, true); } /** @@ -153,11 +167,12 @@ public PDPageContentStream( PDDocument document, PDPage sourcePage ) throws IOEx * @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 + public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appendContent, boolean compress) + throws IOException { - this(document,sourcePage,appendContent,compress,false); + this(document, sourcePage, appendContent, compress, false); } + /** * Create a new PDPage content stream. * @@ -168,17 +183,16 @@ public PDPageContentStream( PDDocument document, PDPage sourcePage, boolean appe * @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 + public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appendContent, boolean compress, + boolean resetContext) throws IOException { - + page = sourcePage; resources = page.getResources(); - if( resources == null ) + if (resources == null) { resources = new PDResources(); - page.setResources( resources ); + page.setResources(resources); } // Get the pdstream from the source page instead of creating a new one @@ -186,20 +200,20 @@ public PDPageContentStream( PDDocument document, PDPage sourcePage, boolean appe boolean hasContent = contents != null; // If request specifies the need to append to the document - if(appendContent && hasContent) + if (appendContent && hasContent) { - + // Create a pdstream to append new content - PDStream contentsToAppend = new PDStream( document ); + 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) + if (contents.getStream() instanceof COSStreamArray) { - compoundStream = (COSStreamArray)contents.getStream(); - compoundStream.appendStream( contentsToAppend.getStream()); + compoundStream = (COSStreamArray) contents.getStream(); + compoundStream.appendStream(contentsToAppend.getStream()); } else { @@ -210,33 +224,33 @@ public PDPageContentStream( PDDocument document, PDPage sourcePage, boolean appe compoundStream = new COSStreamArray(newArray); } - if( compress ) + if (compress) { List filters = new ArrayList(); - filters.add( COSName.FLATE_DECODE ); - contentsToAppend.setFilters( filters ); + filters.add(COSName.FLATE_DECODE); + contentsToAppend.setFilters(filters); } if (resetContext) { - // create a new stream to encapsulate the existing stream - PDStream saveGraphics = new PDStream( document ); + // 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 ) + if (compress) { List filters = new ArrayList(); - filters.add( COSName.FLATE_DECODE ); - saveGraphics.setFilters( filters ); + 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) ); + sourcePage.setContents(new PDStream(compoundStream)); output = contentsToAppend.createOutputStream(); if (resetContext) { @@ -250,18 +264,18 @@ public PDPageContentStream( PDDocument document, PDPage sourcePage, boolean appe { LOG.warn("You are overwriting an existing content, you should use the append mode"); } - contents = new PDStream( document ); - if( compress ) + contents = new PDStream(document); + if (compress) { List filters = new ArrayList(); - filters.add( COSName.FLATE_DECODE ); - contents.setFilters( filters ); + filters.add(COSName.FLATE_DECODE); + contents.setFilters(filters); } - sourcePage.setContents( contents ); + sourcePage.setContents(contents); output = contents.createOutputStream(); } - formatDecimal.setMaximumFractionDigits( 10 ); - formatDecimal.setGroupingUsed( false ); + formatDecimal.setMaximumFractionDigits(10); + formatDecimal.setGroupingUsed(false); } /** @@ -272,11 +286,11 @@ public PDPageContentStream( PDDocument document, PDPage sourcePage, boolean appe */ public void beginText() throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: Nested beginText() calls are not allowed." ); + throw new IOException("Error: Nested beginText() calls are not allowed."); } - appendRawCommands( BEGIN_TEXT ); + appendRawCommands(BEGIN_TEXT); inTextMode = true; } @@ -288,11 +302,11 @@ public void beginText() throws IOException */ public void endText() throws IOException { - if( !inTextMode ) + if (!inTextMode) { - throw new IOException( "Error: You must call beginText() before calling endText." ); + throw new IOException("Error: You must call beginText() before calling endText."); } - appendRawCommands( END_TEXT ); + appendRawCommands(END_TEXT); inTextMode = false; } @@ -303,15 +317,15 @@ public void endText() throws IOException * @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 + public void setFont(PDFont font, float fontSize) throws IOException { String fontMapping = resources.addFont(font); - appendRawCommands( "/"); - appendRawCommands( fontMapping ); - appendRawCommands( SPACE ); - appendRawCommands( formatDecimal.format( fontSize ) ); - appendRawCommands( SPACE ); - appendRawCommands( SET_FONT ); + appendRawCommands("/"); + appendRawCommands(fontMapping); + appendRawCommands(SPACE); + appendRawCommands(fontSize); + appendRawCommands(SPACE); + appendRawCommands(SET_FONT); } /** @@ -323,9 +337,9 @@ public void setFont( PDFont font, float fontSize ) throws IOException * * @throws IOException If there is an error writing to the stream. */ - public void drawImage( PDXObjectImage image, float x, float y ) throws IOException + public void drawImage(PDXObjectImage image, float x, float y) throws IOException { - drawXObject( image, x, y, image.getWidth(), image.getHeight() ); + drawXObject(image, x, y, image.getWidth(), image.getHeight()); } /** @@ -339,7 +353,7 @@ public void drawImage( PDXObjectImage image, float x, float y ) throws IOExcepti * * @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 + 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); @@ -353,14 +367,14 @@ public void drawXObject( PDXObject xobject, float x, float y, float width, float * @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 + public void drawXObject(PDXObject xobject, AffineTransform transform) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: drawXObject is not allowed within a text block." ); + throw new IOException("Error: drawXObject is not allowed within a text block."); } String xObjectPrefix = null; - if( xobject instanceof PDXObjectImage ) + if (xobject instanceof PDXObjectImage) { xObjectPrefix = "Im"; } @@ -370,17 +384,16 @@ public void drawXObject( PDXObject xobject, AffineTransform transform ) throws I } String objMapping = resources.addXObject(xobject, xObjectPrefix); saveGraphicsState(); - appendRawCommands( SPACE ); + appendRawCommands(SPACE); concatenate2CTM(transform); - appendRawCommands( SPACE ); - appendRawCommands( "/" ); - appendRawCommands( objMapping ); - appendRawCommands( SPACE ); - appendRawCommands( XOBJECT_DO ); + 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). @@ -388,17 +401,17 @@ public void drawXObject( PDXObject xobject, AffineTransform transform ) throws I * @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 + public void moveTextPositionByAmount(float x, float y) throws IOException { - if( !inTextMode ) + if (!inTextMode) { - throw new IOException( "Error: must call beginText() before moveTextPositionByAmount"); + 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 ); + appendRawCommands(x); + appendRawCommands(SPACE); + appendRawCommands(y); + appendRawCommands(SPACE); + appendRawCommands(MOVE_TEXT_POSITION); } /** @@ -412,25 +425,25 @@ public void moveTextPositionByAmount( float x, float y ) throws IOException * @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 + public void setTextMatrix(double a, double b, double c, double d, double e, double f) throws IOException { - if( !inTextMode ) + if (!inTextMode) { - throw new IOException( "Error: must call beginText() before setTextMatrix"); + 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 ); + appendRawCommands(a); + appendRawCommands(SPACE); + appendRawCommands(b); + appendRawCommands(SPACE); + appendRawCommands(c); + appendRawCommands(SPACE); + appendRawCommands(d); + appendRawCommands(SPACE); + appendRawCommands(e); + appendRawCommands(SPACE); + appendRawCommands(f); + appendRawCommands(SPACE); + appendRawCommands(SET_TEXT_MATRIX); } /** @@ -441,9 +454,9 @@ public void setTextMatrix( double a, double b, double c, double d, double e, dou */ public void setTextMatrix(AffineTransform matrix) throws IOException { - if( !inTextMode ) + if (!inTextMode) { - throw new IOException( "Error: must call beginText() before setTextMatrix"); + throw new IOException("Error: must call beginText() before setTextMatrix"); } appendMatrix(matrix); appendRawCommands(SET_TEXT_MATRIX); @@ -458,7 +471,7 @@ public void setTextMatrix(AffineTransform matrix) throws IOException * @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 + public void setTextScaling(double sx, double sy, double tx, double ty) throws IOException { setTextMatrix(sx, 0, 0, sy, tx, ty); } @@ -470,7 +483,7 @@ public void setTextScaling( double sx, double sy, double tx, double ty ) throws * @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 + public void setTextTranslation(double tx, double ty) throws IOException { setTextMatrix(1, 0, 0, 1, tx, ty); } @@ -483,11 +496,11 @@ public void setTextTranslation( double tx, double ty ) throws IOException * @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 + 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); + setTextMatrix(angleCos, angleSin, -angleSin, angleCos, tx, ty); } /** @@ -500,21 +513,21 @@ public void setTextRotation( double angle, double tx, double ty ) throws IOExcep * @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 + 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 ); + appendRawCommands(a); + appendRawCommands(SPACE); + appendRawCommands(b); + appendRawCommands(SPACE); + appendRawCommands(c); + appendRawCommands(SPACE); + appendRawCommands(d); + appendRawCommands(SPACE); + appendRawCommands(e); + appendRawCommands(SPACE); + appendRawCommands(f); + appendRawCommands(SPACE); + appendRawCommands(CONCATENATE_MATRIX); } /** @@ -535,18 +548,18 @@ public void concatenate2CTM(AffineTransform at) throws IOException * @param text The text to draw. * @throws IOException If an io exception occurs. */ - public void drawString( String text ) throws IOException + public void drawString(String text) throws IOException { - if( !inTextMode ) + if (!inTextMode) { - throw new IOException( "Error: must call beginText() before drawString"); + throw new IOException("Error: must call beginText() before drawString"); } - COSString string = new COSString( text ); + 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 ); + string.writePDF(buffer); + appendRawCommands(buffer.toByteArray()); + appendRawCommands(SPACE); + appendRawCommands(SHOW_TEXT); } /** @@ -556,11 +569,11 @@ public void drawString( String text ) throws IOException * @param colorSpace The colorspace to write. * @throws IOException If there is an error writing the colorspace. */ - public void setStrokingColorSpace( PDColorSpace colorSpace ) throws IOException + public void setStrokingColorSpace(PDColorSpace colorSpace) throws IOException { currentStrokingColorSpace = colorSpace; - writeColorSpace( colorSpace ); - appendRawCommands( SET_STROKING_COLORSPACE ); + writeColorSpace(colorSpace); + appendRawCommands(SET_STROKING_COLORSPACE); } /** @@ -570,47 +583,46 @@ public void setStrokingColorSpace( PDColorSpace colorSpace ) throws IOException * @param colorSpace The colorspace to write. * @throws IOException If there is an error writing the colorspace. */ - public void setNonStrokingColorSpace( PDColorSpace colorSpace ) throws IOException + public void setNonStrokingColorSpace(PDColorSpace colorSpace) throws IOException { currentNonStrokingColorSpace = colorSpace; - writeColorSpace( colorSpace ); - appendRawCommands( SET_NON_STROKING_COLORSPACE ); + writeColorSpace(colorSpace); + appendRawCommands(SET_NON_STROKING_COLORSPACE); } - private void writeColorSpace( PDColorSpace colorSpace ) throws IOException + private void writeColorSpace(PDColorSpace colorSpace) throws IOException { COSName key = null; - if( colorSpace instanceof PDDeviceGray || - colorSpace instanceof PDDeviceRGB || - colorSpace instanceof PDDeviceCMYK ) + if (colorSpace instanceof PDDeviceGray || colorSpace instanceof PDDeviceRGB + || colorSpace instanceof PDDeviceCMYK) { - key = COSName.getPDFName( colorSpace.getName() ); + key = COSName.getPDFName(colorSpace.getName()); } else { - COSDictionary colorSpaces = - (COSDictionary)resources.getCOSDictionary().getDictionaryObject(COSName.COLORSPACE); - if( colorSpaces == null ) + COSDictionary colorSpaces = (COSDictionary) resources.getCOSDictionary().getDictionaryObject( + COSName.COLORSPACE); + if (colorSpaces == null) { colorSpaces = new COSDictionary(); - resources.getCOSDictionary().setItem( COSName.COLORSPACE, colorSpaces ); + resources.getCOSDictionary().setItem(COSName.COLORSPACE, colorSpaces); } - key = colorSpaces.getKeyForValue( colorSpace.getCOSObject() ); + key = colorSpaces.getKeyForValue(colorSpace.getCOSObject()); - if( key == null ) + if (key == null) { int counter = 0; String csName = "CS"; - while( colorSpaces.containsValue( csName + counter ) ) + while (colorSpaces.containsValue(csName + counter)) { counter++; } - key = COSName.getPDFName( csName + counter ); - colorSpaces.setItem( key, colorSpace ); + key = COSName.getPDFName(csName + counter); + colorSpaces.setItem(key, colorSpace); } } - key.writePDF( output ); - appendRawCommands( SPACE ); + key.writePDF(output); + appendRawCommands(SPACE); } /** @@ -619,23 +631,21 @@ private void writeColorSpace( PDColorSpace colorSpace ) throws IOException * @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 + public void setStrokingColor(float[] components) throws IOException { - for( int i=0; i< components.length; i++ ) + for (int i = 0; i < components.length; i++) { - appendRawCommands( formatDecimal.format( components[i] ) ); - appendRawCommands( SPACE ); + appendRawCommands(components[i]); + appendRawCommands(SPACE); } - if( currentStrokingColorSpace instanceof PDSeparation || - currentStrokingColorSpace instanceof PDPattern || - currentStrokingColorSpace instanceof PDDeviceN || - currentStrokingColorSpace instanceof PDICCBased ) + if (currentStrokingColorSpace instanceof PDSeparation || currentStrokingColorSpace instanceof PDPattern + || currentStrokingColorSpace instanceof PDDeviceN || currentStrokingColorSpace instanceof PDICCBased) { - appendRawCommands( SET_STROKING_COLOR_COMPLEX ); + appendRawCommands(SET_STROKING_COLOR_COMPLEX); } else { - appendRawCommands( SET_STROKING_COLOR_SIMPLE ); + appendRawCommands(SET_STROKING_COLOR_SIMPLE); } } @@ -645,26 +655,26 @@ public void setStrokingColor( float[] components ) throws IOException * @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 + public void setStrokingColor(Color color) throws IOException { ColorSpace colorSpace = color.getColorSpace(); - if( colorSpace.getType() == ColorSpace.TYPE_RGB ) + if (colorSpace.getType() == ColorSpace.TYPE_RGB) { - setStrokingColor( color.getRed(), color.getGreen(), color.getBlue() ); + setStrokingColor(color.getRed(), color.getGreen(), color.getBlue()); } - else if( colorSpace.getType() == ColorSpace.TYPE_GRAY ) + else if (colorSpace.getType() == ColorSpace.TYPE_GRAY) { - color.getColorComponents( colorComponents ); - setStrokingColor( colorComponents[0] ); + color.getColorComponents(colorComponents); + setStrokingColor(colorComponents[0]); } - else if( colorSpace.getType() == ColorSpace.TYPE_CMYK ) + else if (colorSpace.getType() == ColorSpace.TYPE_CMYK) { - color.getColorComponents( colorComponents ); - setStrokingColor( colorComponents[0], colorComponents[1], colorComponents[2], colorComponents[3] ); + color.getColorComponents(colorComponents); + setStrokingColor(colorComponents[0], colorComponents[1], colorComponents[2], colorComponents[3]); } else { - throw new IOException( "Error: unknown colorspace:" + colorSpace ); + throw new IOException("Error: unknown colorspace:" + colorSpace); } } @@ -674,26 +684,26 @@ else if( colorSpace.getType() == ColorSpace.TYPE_CMYK ) * @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 + public void setNonStrokingColor(Color color) throws IOException { ColorSpace colorSpace = color.getColorSpace(); - if( colorSpace.getType() == ColorSpace.TYPE_RGB ) + if (colorSpace.getType() == ColorSpace.TYPE_RGB) { - setNonStrokingColor( color.getRed(), color.getGreen(), color.getBlue() ); + setNonStrokingColor(color.getRed(), color.getGreen(), color.getBlue()); } - else if( colorSpace.getType() == ColorSpace.TYPE_GRAY ) + else if (colorSpace.getType() == ColorSpace.TYPE_GRAY) { - color.getColorComponents( colorComponents ); - setNonStrokingColor( colorComponents[0] ); + color.getColorComponents(colorComponents); + setNonStrokingColor(colorComponents[0]); } - else if( colorSpace.getType() == ColorSpace.TYPE_CMYK ) + else if (colorSpace.getType() == ColorSpace.TYPE_CMYK) { - color.getColorComponents( colorComponents ); - setNonStrokingColor( colorComponents[0], colorComponents[1], colorComponents[2], colorComponents[3] ); + color.getColorComponents(colorComponents); + setNonStrokingColor(colorComponents[0], colorComponents[1], colorComponents[2], colorComponents[3]); } else { - throw new IOException( "Error: unknown colorspace:" + colorSpace ); + throw new IOException("Error: unknown colorspace:" + colorSpace); } } @@ -705,15 +715,15 @@ else if( colorSpace.getType() == ColorSpace.TYPE_CMYK ) * @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 + 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 ); + appendRawCommands(r / 255d); + appendRawCommands(SPACE); + appendRawCommands(g / 255d); + appendRawCommands(SPACE); + appendRawCommands(b / 255d); + appendRawCommands(SPACE); + appendRawCommands(RG_STROKING); } /** @@ -725,17 +735,17 @@ public void setStrokingColor( int r, int g, int b ) throws IOException * @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 + 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 ); + appendRawCommands(c / 255d); + appendRawCommands(SPACE); + appendRawCommands(m / 255d); + appendRawCommands(SPACE); + appendRawCommands(y / 255d); + appendRawCommands(SPACE); + appendRawCommands(k / 255d); + appendRawCommands(SPACE); + appendRawCommands(K_STROKING); } /** @@ -747,17 +757,17 @@ public void setStrokingColor( int c, int m, int y, int k) throws IOException * @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 + 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 ); + appendRawCommands(c); + appendRawCommands(SPACE); + appendRawCommands(m); + appendRawCommands(SPACE); + appendRawCommands(y); + appendRawCommands(SPACE); + appendRawCommands(k); + appendRawCommands(SPACE); + appendRawCommands(K_STROKING); } /** @@ -766,11 +776,11 @@ public void setStrokingColor( double c, double m, double y, double k) throws IOE * @param g The gray value. * @throws IOException If an IO error occurs while writing to the stream. */ - public void setStrokingColor( int g ) throws IOException + public void setStrokingColor(int g) throws IOException { - appendRawCommands( formatDecimal.format( g/255d ) ); - appendRawCommands( SPACE ); - appendRawCommands( G_STROKING ); + appendRawCommands(g / 255d); + appendRawCommands(SPACE); + appendRawCommands(G_STROKING); } /** @@ -779,11 +789,11 @@ public void setStrokingColor( int g ) throws IOException * @param g The gray value. * @throws IOException If an IO error occurs while writing to the stream. */ - public void setStrokingColor( double g ) throws IOException + public void setStrokingColor(double g) throws IOException { - appendRawCommands( formatDecimal.format( g ) ); - appendRawCommands( SPACE ); - appendRawCommands( G_STROKING ); + appendRawCommands(g); + appendRawCommands(SPACE); + appendRawCommands(G_STROKING); } /** @@ -792,23 +802,22 @@ public void setStrokingColor( double g ) throws IOException * @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 + public void setNonStrokingColor(float[] components) throws IOException { - for( int i=0; i< components.length; i++ ) + for (int i = 0; i < components.length; i++) { - appendRawCommands( formatDecimal.format( components[i] ) ); - appendRawCommands( SPACE ); + appendRawCommands(components[i]); + appendRawCommands(SPACE); } - if( currentNonStrokingColorSpace instanceof PDSeparation || - currentNonStrokingColorSpace instanceof PDPattern || - currentNonStrokingColorSpace instanceof PDDeviceN || - currentNonStrokingColorSpace instanceof PDICCBased ) + if (currentNonStrokingColorSpace instanceof PDSeparation || currentNonStrokingColorSpace instanceof PDPattern + || currentNonStrokingColorSpace instanceof PDDeviceN + || currentNonStrokingColorSpace instanceof PDICCBased) { - appendRawCommands( SET_NON_STROKING_COLOR_COMPLEX ); + appendRawCommands(SET_NON_STROKING_COLOR_COMPLEX); } else { - appendRawCommands( SET_NON_STROKING_COLOR_SIMPLE ); + appendRawCommands(SET_NON_STROKING_COLOR_SIMPLE); } } @@ -820,15 +829,15 @@ public void setNonStrokingColor( float[] components ) throws IOException * @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 + 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 ); + appendRawCommands(r / 255d); + appendRawCommands(SPACE); + appendRawCommands(g / 255d); + appendRawCommands(SPACE); + appendRawCommands(b / 255d); + appendRawCommands(SPACE); + appendRawCommands(RG_NON_STROKING); } /** @@ -840,17 +849,17 @@ public void setNonStrokingColor( int r, int g, int b ) throws IOException * @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 + 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 ); + appendRawCommands(c / 255d); + appendRawCommands(SPACE); + appendRawCommands(m / 255d); + appendRawCommands(SPACE); + appendRawCommands(y / 255d); + appendRawCommands(SPACE); + appendRawCommands(k / 255d); + appendRawCommands(SPACE); + appendRawCommands(K_NON_STROKING); } /** @@ -862,17 +871,17 @@ public void setNonStrokingColor( int c, int m, int y, int k) throws IOException * @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 + 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 ); + appendRawCommands(c); + appendRawCommands(SPACE); + appendRawCommands(m); + appendRawCommands(SPACE); + appendRawCommands(y); + appendRawCommands(SPACE); + appendRawCommands(k); + appendRawCommands(SPACE); + appendRawCommands(K_NON_STROKING); } /** @@ -881,11 +890,11 @@ public void setNonStrokingColor( double c, double m, double y, double k) throws * @param g The gray value. * @throws IOException If an IO error occurs while writing to the stream. */ - public void setNonStrokingColor( int g ) throws IOException + public void setNonStrokingColor(int g) throws IOException { - appendRawCommands( formatDecimal.format( g/255d ) ); - appendRawCommands( SPACE ); - appendRawCommands( G_NON_STROKING ); + appendRawCommands(g / 255d); + appendRawCommands(SPACE); + appendRawCommands(G_NON_STROKING); } /** @@ -894,11 +903,11 @@ public void setNonStrokingColor( int g ) throws IOException * @param g The gray value. * @throws IOException If an IO error occurs while writing to the stream. */ - public void setNonStrokingColor( double g ) throws IOException + public void setNonStrokingColor(double g) throws IOException { - appendRawCommands( formatDecimal.format( g ) ); - appendRawCommands( SPACE ); - appendRawCommands( G_NON_STROKING ); + appendRawCommands(g); + appendRawCommands(SPACE); + appendRawCommands(G_NON_STROKING); } /** @@ -910,21 +919,21 @@ public void setNonStrokingColor( double g ) throws IOException * @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 + public void addRect(float x, float y, float width, float height) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: addRect is not allowed within a text block." ); + throw new IOException("Error: addRect is not allowed within a text block."); } - 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 ); + appendRawCommands(x); + appendRawCommands(SPACE); + appendRawCommands(y); + appendRawCommands(SPACE); + appendRawCommands(width); + appendRawCommands(SPACE); + appendRawCommands(height); + appendRawCommands(SPACE); + appendRawCommands(RECTANGLE); } /** @@ -936,11 +945,11 @@ public void addRect( float x, float y, float width, float height ) throws IOExce * @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 + public void fillRect(float x, float y, float width, float height) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: fillRect is not allowed within a text block." ); + throw new IOException("Error: fillRect is not allowed within a text block."); } addRect(x, y, width, height); fill(PathIterator.WIND_NON_ZERO); @@ -959,23 +968,23 @@ public void fillRect( float x, float y, float width, float height ) throws IOExc */ public void addBezier312(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: addBezier312 is not allowed within a text block." ); + throw new IOException("Error: addBezier312 is not allowed within a text block."); } - 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 ); + appendRawCommands(x1); + appendRawCommands(SPACE); + appendRawCommands(y1); + appendRawCommands(SPACE); + appendRawCommands(x2); + appendRawCommands(SPACE); + appendRawCommands(y2); + appendRawCommands(SPACE); + appendRawCommands(x3); + appendRawCommands(SPACE); + appendRawCommands(y3); + appendRawCommands(SPACE); + appendRawCommands(BEZIER_312); } /** @@ -989,19 +998,19 @@ public void addBezier312(float x1, float y1, float x2, float y2, float x3, float */ public void addBezier32(float x2, float y2, float x3, float y3) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: addBezier32 is not allowed within a text block." ); + throw new IOException("Error: addBezier32 is not allowed within a text block."); } - 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 ); + appendRawCommands(x2); + appendRawCommands(SPACE); + appendRawCommands(y2); + appendRawCommands(SPACE); + appendRawCommands(x3); + appendRawCommands(SPACE); + appendRawCommands(y3); + appendRawCommands(SPACE); + appendRawCommands(BEZIER_32); } /** @@ -1015,22 +1024,21 @@ public void addBezier32(float x2, float y2, float x3, float y3) throws IOExcepti */ public void addBezier31(float x1, float y1, float x3, float y3) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: addBezier31 is not allowed within a text block." ); + throw new IOException("Error: addBezier31 is not allowed within a text block."); } - 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 ); + appendRawCommands(x1); + appendRawCommands(SPACE); + appendRawCommands(y1); + appendRawCommands(SPACE); + appendRawCommands(x3); + appendRawCommands(SPACE); + appendRawCommands(y3); + appendRawCommands(SPACE); + appendRawCommands(BEZIER_313); } - /** * Add a line to the given coordinate. * @@ -1038,17 +1046,17 @@ public void addBezier31(float x1, float y1, float x3, float y3) throws IOExcepti * @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 + public void moveTo(float x, float y) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: moveTo is not allowed within a text block." ); + throw new IOException("Error: moveTo is not allowed within a text block."); } - appendRawCommands( formatDecimal.format( x) ); - appendRawCommands( SPACE ); - appendRawCommands( formatDecimal.format( y) ); - appendRawCommands( SPACE ); - appendRawCommands( MOVE_TO ); + appendRawCommands(x); + appendRawCommands(SPACE); + appendRawCommands(y); + appendRawCommands(SPACE); + appendRawCommands(MOVE_TO); } /** @@ -1058,18 +1066,19 @@ public void moveTo( float x, float y) throws IOException * @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 + public void lineTo(float x, float y) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: lineTo is not allowed within a text block." ); + throw new IOException("Error: lineTo is not allowed within a text block."); } - appendRawCommands( formatDecimal.format( x) ); - appendRawCommands( SPACE ); - appendRawCommands( formatDecimal.format( y) ); - appendRawCommands( SPACE ); - appendRawCommands( LINE_TO ); + appendRawCommands(x); + appendRawCommands(SPACE); + appendRawCommands(y); + appendRawCommands(SPACE); + appendRawCommands(LINE_TO); } + /** * add a line to the current path. * @@ -1079,11 +1088,11 @@ public void lineTo( float x, float y) throws IOException * @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 + public void addLine(float xStart, float yStart, float xEnd, float yEnd) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: addLine is not allowed within a text block." ); + throw new IOException("Error: addLine is not allowed within a text block."); } // moveTo moveTo(xStart, yStart); @@ -1100,11 +1109,11 @@ public void addLine( float xStart, float yStart, float xEnd, float yEnd ) throws * @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 + public void drawLine(float xStart, float yStart, float xEnd, float yEnd) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: drawLine is not allowed within a text block." ); + throw new IOException("Error: drawLine is not allowed within a text block."); } addLine(xStart, yStart, xEnd, yEnd); // stroke @@ -1119,13 +1128,13 @@ public void drawLine( float xStart, float yStart, float xEnd, float yEnd ) throw */ public void addPolygon(float[] x, float[] y) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: addPolygon is not allowed within a text block." ); + throw new IOException("Error: addPolygon is not allowed within a text block."); } if (x.length != y.length) { - throw new IOException( "Error: some points are missing coordinate" ); + throw new IOException("Error: some points are missing coordinate"); } for (int i = 0; i < x.length; i++) { @@ -1149,9 +1158,9 @@ public void addPolygon(float[] x, float[] y) throws IOException */ public void drawPolygon(float[] x, float[] y) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: drawPolygon is not allowed within a text block." ); + throw new IOException("Error: drawPolygon is not allowed within a text block."); } addPolygon(x, y); stroke(); @@ -1165,9 +1174,9 @@ public void drawPolygon(float[] x, float[] y) throws IOException */ public void fillPolygon(float[] x, float[] y) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: fillPolygon is not allowed within a text block." ); + throw new IOException("Error: fillPolygon is not allowed within a text block."); } addPolygon(x, y); fill(PathIterator.WIND_NON_ZERO); @@ -1180,11 +1189,11 @@ public void fillPolygon(float[] x, float[] y) throws IOException */ public void stroke() throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: stroke is not allowed within a text block." ); + throw new IOException("Error: stroke is not allowed within a text block."); } - appendRawCommands( STROKE ); + appendRawCommands(STROKE); } /** @@ -1194,11 +1203,11 @@ public void stroke() throws IOException */ public void closeAndStroke() throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: closeAndStroke is not allowed within a text block." ); + throw new IOException("Error: closeAndStroke is not allowed within a text block."); } - appendRawCommands( CLOSE_STROKE ); + appendRawCommands(CLOSE_STROKE); } /** @@ -1210,21 +1219,21 @@ public void closeAndStroke() throws IOException */ public void fill(int windingRule) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: fill is not allowed within a text block." ); + throw new IOException("Error: fill is not allowed within a text block."); } if (windingRule == PathIterator.WIND_NON_ZERO) { - appendRawCommands( FILL_NON_ZERO ); + appendRawCommands(FILL_NON_ZERO); } else if (windingRule == PathIterator.WIND_EVEN_ODD) { - appendRawCommands( FILL_EVEN_ODD ); + appendRawCommands(FILL_EVEN_ODD); } else { - throw new IOException( "Error: unknown value for winding rule" ); + throw new IOException("Error: unknown value for winding rule"); } } @@ -1236,11 +1245,11 @@ else if (windingRule == PathIterator.WIND_EVEN_ODD) */ public void closeSubPath() throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: closeSubPath is not allowed within a text block." ); + throw new IOException("Error: closeSubPath is not allowed within a text block."); } - appendRawCommands( CLOSE_SUBPATH ); + appendRawCommands(CLOSE_SUBPATH); } /** @@ -1252,23 +1261,23 @@ public void closeSubPath() throws IOException */ public void clipPath(int windingRule) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: clipPath is not allowed within a text block." ); + throw new IOException("Error: clipPath is not allowed within a text block."); } if (windingRule == PathIterator.WIND_NON_ZERO) { - appendRawCommands( CLIP_PATH_NON_ZERO ); - appendRawCommands( NOP ); + appendRawCommands(CLIP_PATH_NON_ZERO); + appendRawCommands(NOP); } else if (windingRule == PathIterator.WIND_EVEN_ODD) { - appendRawCommands( CLIP_PATH_EVEN_ODD ); - appendRawCommands( NOP ); + appendRawCommands(CLIP_PATH_EVEN_ODD); + appendRawCommands(NOP); } else { - throw new IOException( "Error: unknown value for winding rule" ); + throw new IOException("Error: unknown value for winding rule"); } } @@ -1280,15 +1289,15 @@ else if (windingRule == PathIterator.WIND_EVEN_ODD) */ public void setLineWidth(float lineWidth) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: setLineWidth is not allowed within a text block." ); + throw new IOException("Error: setLineWidth is not allowed within a text block."); } - appendRawCommands( formatDecimal.format( lineWidth ) ); - appendRawCommands( SPACE ); - appendRawCommands( LINE_WIDTH ); + appendRawCommands(lineWidth); + appendRawCommands(SPACE); + appendRawCommands(LINE_WIDTH); } - + /** * Set the line join style. * @param lineJoinStyle 0 for miter join, 1 for round join, and 2 for bevel join. @@ -1296,23 +1305,22 @@ public void setLineWidth(float lineWidth) throws IOException */ public void setLineJoinStyle(int lineJoinStyle) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: setLineJoinStyle is not allowed within a text block." ); + throw new IOException("Error: setLineJoinStyle is not allowed within a text block."); } if (lineJoinStyle >= 0 && lineJoinStyle <= 2) { - appendRawCommands( Integer.toString( lineJoinStyle ) ); - appendRawCommands( SPACE ); - appendRawCommands( LINE_JOIN_STYLE ); + appendRawCommands(Integer.toString(lineJoinStyle)); + appendRawCommands(SPACE); + appendRawCommands(LINE_JOIN_STYLE); } else { - throw new IOException( "Error: unknown value for line join style" ); + throw new IOException("Error: unknown value for line join style"); } } - - + /** * Set the line cap style. * @param lineCapStyle 0 for butt cap, 1 for round cap, and 2 for projecting square cap. @@ -1320,22 +1328,22 @@ public void setLineJoinStyle(int lineJoinStyle) throws IOException */ public void setLineCapStyle(int lineCapStyle) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: setLineCapStyle is not allowed within a text block." ); + throw new IOException("Error: setLineCapStyle is not allowed within a text block."); } if (lineCapStyle >= 0 && lineCapStyle <= 2) { - appendRawCommands( Integer.toString( lineCapStyle ) ); - appendRawCommands( SPACE ); - appendRawCommands( LINE_CAP_STYLE ); + appendRawCommands(Integer.toString(lineCapStyle)); + appendRawCommands(SPACE); + appendRawCommands(LINE_CAP_STYLE); } else { - throw new IOException( "Error: unknown value for line cap style" ); + throw new IOException("Error: unknown value for line cap style"); } } - + /** * Set the line dash pattern. * @param pattern The pattern array @@ -1344,20 +1352,21 @@ public void setLineCapStyle(int lineCapStyle) throws IOException */ public void setLineDashPattern(float[] pattern, float phase) throws IOException { - if( inTextMode ) + if (inTextMode) { - throw new IOException( "Error: setLineDashPattern is not allowed within a text block." ); + throw new IOException("Error: setLineDashPattern is not allowed within a text block."); } - appendRawCommands( "[" ); + appendRawCommands(OPENING_BRACKET); for (float value : pattern) { - appendRawCommands( formatDecimal.format( value ) ); - appendRawCommands( SPACE ); + appendRawCommands(value); + appendRawCommands(SPACE); } - appendRawCommands( "] "); - appendRawCommands( formatDecimal.format(phase) ); - appendRawCommands( SPACE ); - appendRawCommands( LINE_DASH_PATTERN ); + appendRawCommands(CLOSING_BRACKET); + appendRawCommands(SPACE); + appendRawCommands(phase); + appendRawCommands(SPACE); + appendRawCommands(LINE_DASH_PATTERN); } /** @@ -1403,7 +1412,7 @@ public void endMarkedContentSequence() throws IOException */ public void saveGraphicsState() throws IOException { - appendRawCommands( SAVE_GRAPHICS_STATE); + appendRawCommands(SAVE_GRAPHICS_STATE); } /** @@ -1412,7 +1421,7 @@ public void saveGraphicsState() throws IOException */ public void restoreGraphicsState() throws IOException { - appendRawCommands( RESTORE_GRAPHICS_STATE ); + appendRawCommands(RESTORE_GRAPHICS_STATE); } /** @@ -1421,9 +1430,9 @@ public void restoreGraphicsState() throws IOException * @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 + public void appendRawCommands(String commands) throws IOException { - appendRawCommands( commands.getBytes( "ISO-8859-1" ) ); + appendRawCommands(commands.getBytes("ISO-8859-1")); } /** @@ -1432,9 +1441,9 @@ public void appendRawCommands( String commands ) throws IOException * @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 + public void appendRawCommands(byte[] commands) throws IOException { - output.write( commands ); + output.write(commands); } /** @@ -1444,9 +1453,33 @@ public void appendRawCommands( byte[] commands ) throws IOException * * @throws IOException If an error occurs while writing to the stream. */ - public void appendRawCommands( int data ) throws IOException + public void appendRawCommands(int data) throws IOException + { + output.write(data); + } + + /** + * This will append raw commands to the content stream. + * + * @param data Append a formatted double value to the stream. + * + * @throws IOException If an error occurs while writing to the stream. + */ + public void appendRawCommands(double data) throws IOException + { + appendRawCommands(formatDecimal.format(data)); + } + + /** + * This will append raw commands to the content stream. + * + * @param data Append a formatted float value to the stream. + * + * @throws IOException If an error occurs while writing to the stream. + */ + public void appendRawCommands(float data) throws IOException { - output.write( data ); + appendRawCommands(formatDecimal.format(data)); } /** @@ -1465,7 +1498,7 @@ private void appendMatrix(AffineTransform transform) throws IOException transform.getMatrix(values); for (double v : values) { - appendRawCommands(formatDecimal.format(v)); + appendRawCommands(v); appendRawCommands(SPACE); } } @@ -1478,5 +1511,9 @@ private void appendMatrix(AffineTransform transform) throws IOException public void close() throws IOException { output.close(); + currentNonStrokingColorSpace = null; + currentStrokingColorSpace = null; + page = null; + resources = null; } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Font.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Font.java index 4c0743a991b..ed43ff8037e 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Font.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2Font.java @@ -33,7 +33,7 @@ * This is implementation of the CIDFontType2 Font. * * @author Ben Litchfield - * @version $Revision: 1.5 $ + * */ public class PDCIDFontType2Font extends PDCIDFont { @@ -94,7 +94,6 @@ public Font getawtFont() throws IOException setIsFontSubstituted(true); } } - // TODO FontFile3 return awtFont; } @@ -112,10 +111,11 @@ private void readCIDToGIDMapping() byte[] mapAsBytes = IOUtils.toByteArray(stream.getUnfilteredStream()); int numberOfInts = mapAsBytes.length / 2; cid2gid = new int[numberOfInts]; - int index = 0; - for(int offset = 0;offset < numberOfInts;offset++) + int offset = 0; + for(int index = 0;index < numberOfInts;index++) { cid2gid[index] = getCodeFromArray(mapAsBytes, offset, 2); + offset+=2; } } catch(IOException exception) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java index 7b42e2d8137..ac1d5a4dafc 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java @@ -16,6 +16,14 @@ */ package org.apache.pdfbox.pdmodel.font; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.apache.fontbox.afm.AFMParser; import org.apache.fontbox.afm.FontMetric; import org.apache.fontbox.cmap.CMapParser; @@ -54,7 +62,7 @@ * This is the base class for all PDF fonts. * * @author Ben Litchfield - * @version $Revision: 1.46 $ + * */ public abstract class PDFont implements COSObjectable { @@ -98,7 +106,7 @@ public abstract class PDFont implements COSObjectable /** * A list a floats representing the widths. */ - private List widths = null; + private List widths = null; /** * The static map of the default Adobe font metrics. @@ -645,8 +653,8 @@ public String getType() // Memorized values to avoid repeated dictionary lookups private String subtype = null; private boolean type1Font; + private boolean type3Font; private boolean trueTypeFont; - private boolean typeFont; private boolean type0Font; /** @@ -662,7 +670,7 @@ public String getSubType() type1Font = "Type1".equals(subtype); trueTypeFont = "TrueType".equals(subtype); type0Font = "Type0".equals(subtype); - typeFont = type1Font || "Type0".equals(subtype) || trueTypeFont; + type3Font = "Type3".equals(subtype); } return subtype; } @@ -677,6 +685,17 @@ protected boolean isType1Font() return type1Font; } + /** + * Determines if the font is a type 3 font. + * + * @return returns true if the font is a type 3 font + */ + public boolean isType3Font() + { + getSubType(); + return type3Font; + } + /** * Determines if the font is a type 0 font. * @return returns true if the font is a type 0 font @@ -693,10 +712,18 @@ private boolean isTrueTypeFont() return trueTypeFont; } - private boolean isTypeFont() + /** + * Determines if the font is a symbolic font. + * + * @return returns true if the font is a symbolic font + */ + public boolean isSymbolicFont() { - getSubType(); - return typeFont; + if (getFontDescriptor() != null) + { + return getFontDescriptor().isSymbolic(); + } + return false; } /** @@ -764,14 +791,14 @@ public void setLastChar( int lastChar ) * * @return The widths of the characters. */ - public List getWidths() + public List getWidths() { if (widths == null) { COSArray array = (COSArray)font.getDictionaryObject( COSName.WIDTHS ); if (array != null) { - widths = COSArrayList.convertFloatCOSArrayToList( array ); + widths = COSArrayList.convertIntegerCOSArrayToList(array); } } return widths; @@ -782,7 +809,7 @@ public List getWidths() * * @param widthsList The widths of the character codes. */ - public void setWidths( List widthsList ) + public void setWidths(List widthsList) { widths = widthsList; font.setItem( COSName.WIDTHS, COSArrayList.converterToCOSArray( widths ) ); @@ -896,5 +923,14 @@ protected void setHasToUnicode(boolean hasToUnicodeValue) * @return the width of the space character */ public abstract float getSpaceWidth(); - + + /** + * Returns the toUnicode mapping if present. + * + * @return the CMap representing the toUnicode mapping + */ + public CMap getToUnicodeCMap() + { + return toUnicodeCmap; + } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java index d0226cf8de0..e01fda437eb 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java @@ -61,7 +61,7 @@ * This is the TrueType implementation of fonts. * * @author Ben Litchfield - * @version $Revision: 1.17 $ + * */ public class PDTrueTypeFont extends PDSimpleFont { @@ -307,7 +307,6 @@ else if( nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME ) fd.setSymbolic( isSymbolic ); fd.setNonSymbolic( !isSymbolic ); - //todo retval.setFixedPitch //todo retval.setItalic //todo retval.setAllCap //todo retval.setSmallCap @@ -382,14 +381,16 @@ else if( nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME ) int[] widthValues = hMet.getAdvanceWidth(); // some monospaced fonts provide only one value for the width // instead of an array containing the same value for every glyphid - boolean isMonospaced = widthValues.length == 1; + boolean isMonospaced = fd.isFixedPitch(); int nWidths=lastChar-firstChar+1; - List widths = new ArrayList(nWidths); - // width of the .notdef character. - Float zero = Float.valueOf(widthValues[0]*scaling); - for( int i=0; i widths = new ArrayList(nWidths); + // use the first width as default + // proportional fonts -> width of the .notdef character + // monospaced-fonts -> the first width + int defaultWidth = Math.round(widthValues[0] * scaling); + for (int i = 0; i < nWidths; i++) { - widths.add( zero ); + widths.add(defaultWidth); } // Encoding singleton to have acces to the chglyph name to // unicode cpoint point mapping of Adobe's glyphlist.txt @@ -411,11 +412,11 @@ else if( nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME ) { if (isMonospaced) { - widths.set( e.getKey().intValue()-firstChar,widthValues[0]*scaling ); + widths.set(e.getKey().intValue() - firstChar, defaultWidth); } else { - widths.set( e.getKey().intValue()-firstChar,widthValues[gid]*scaling ); + widths.set(e.getKey().intValue() - firstChar, Math.round(widthValues[gid] * scaling)); } } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java index 18e72fe056e..81e8c2ce2b8 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java @@ -27,24 +27,21 @@ 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.cos.COSArray; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSInteger; +import org.apache.pdfbox.cos.COSName; 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. - * + * This is implementation of the Type1 Font with a afm and a pfb file. + * * @author Michael Niedermair * @version $Revision: 1.5 $ */ @@ -62,12 +59,12 @@ public class PDType1AfmPfbFont extends PDType1Font /** * Create a new object. - * @param doc The PDF document that will hold the embedded font. - * @param afmname The font filename. + * + * @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 + public PDType1AfmPfbFont(final PDDocument doc, final String afmname) throws IOException { super(); @@ -79,13 +76,13 @@ public PDType1AfmPfbFont(final PDDocument doc, final String afmname) /** * 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. + * + * @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 + public PDType1AfmPfbFont(final PDDocument doc, final InputStream afm, final InputStream pfb) throws IOException { super(); load(doc, afm, pfb); @@ -93,14 +90,13 @@ public PDType1AfmPfbFont(final PDDocument doc, final InputStream afm, final Inpu /** * 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. + * + * @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 + private void load(final PDDocument doc, final InputStream afm, final InputStream pfb) throws IOException { PDFontDescriptorDictionary fd = new PDFontDescriptorDictionary(); @@ -110,13 +106,11 @@ private void load(final PDDocument doc, final InputStream afm, PfbParser pfbparser = new PfbParser(pfb); pfb.close(); - PDStream fontStream = new PDStream(doc, pfbparser.getInputStream(), - false); + 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.getStream().setInt("Length" + (i + 1), pfbparser.getLengths()[i]); } fontStream.addCompression(); fd.setFontFile(fontStream); @@ -149,10 +143,10 @@ private void load(final PDDocument doc, final InputStream afm, List listmetric = metric.getCharMetrics(); Encoding encoding = getFontEncoding(); int maxWidths = 256; - List widths = new ArrayList(maxWidths); - float zero = 250; + List widths = new ArrayList(maxWidths); + int zero = 250; Iterator iter = listmetric.iterator(); - for( int i=0; i 0) { - float width = m.getWx(); - widths.set(n,new Float(width)); + int width = Math.round(m.getWx()); + widths.set(n, 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)); + widths.set(0337, width); } } } - else + 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"))); + widths.set(0344, widths.get(encoding.getCode("a"))); } else if (m.getName().equals("odieresis")) { - widths.set(0366,(Float)widths.get(encoding.getCode("o"))); + widths.set(0366, widths.get(encoding.getCode("o"))); } else if (m.getName().equals("udieresis")) { - widths.set(0374,(Float)widths.get(encoding.getCode("u"))); + widths.set(0374, widths.get(encoding.getCode("u"))); } else if (m.getName().equals("Adieresis")) { - widths.set(0304,(Float)widths.get(encoding.getCode("A"))); + widths.set(0304, widths.get(encoding.getCode("A"))); } else if (m.getName().equals("Odieresis")) { - widths.set(0326,(Float)widths.get(encoding.getCode("O"))); + widths.set(0326, widths.get(encoding.getCode("O"))); } else if (m.getName().equals("Udieresis")) { - widths.set(0334,(Float)widths.get(encoding.getCode("U"))); + widths.set(0334, widths.get(encoding.getCode("U"))); } } } @@ -213,28 +207,28 @@ else if (m.getName().equals("Udieresis")) } /* - * 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 + * 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 + private DictionaryEncoding afmToDictionary(AFMEncoding encoding) throws java.io.IOException { COSArray array = new COSArray(); array.add(COSInteger.ZERO); - for (int i = 0; i < 256; i++) + 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")); + 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); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDColorState.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDColorState.java index a63d3121353..2662de252ab 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDColorState.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDColorState.java @@ -30,9 +30,9 @@ /** * This class represents a color space and the color value for that colorspace. - * + * * @author Ben Litchfield - * @version $Revision: 1.7 $ + * */ public class PDColorState implements Cloneable { @@ -43,32 +43,25 @@ public class PDColorState implements Cloneable 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}. - * + * 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"); + 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. + * 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. *

* The default override can be specified by setting the color code in - * org.apache.pdfbox.ICC_override_color 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 null to disable the override + * org.apache.pdfbox.ICC_override_color 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 null to disable the override * @see PDFBOX-511 * @since Apache PDFBox 0.8.1 */ - public static void setIccOverrideColor(Color color) + public static void setIccOverrideColor(Color color) { iccOverrideColor = color; } @@ -78,9 +71,9 @@ public static void setIccOverrideColor(Color color) private PDPatternResources pattern = null; /** - * Cached Java AWT color based on the current color space and value. - * The value is cleared whenever the color space or value is set. - * + * 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; @@ -88,11 +81,11 @@ public static void setIccOverrideColor(Color color) /** * Default constructor. - * + * */ public PDColorState() { - setColorSpaceValue( new float[] {0}); + setColorSpaceValue(new float[] { 0 }); } /** @@ -101,22 +94,22 @@ public PDColorState() public Object clone() { PDColorState retval = new PDColorState(); - retval.colorSpace = this.colorSpace; + retval.colorSpace = colorSpace; retval.colorSpaceValue.clear(); - retval.colorSpaceValue.addAll( this.colorSpaceValue ); + retval.colorSpaceValue.addAll(colorSpaceValue); retval.setPattern(getPattern()); 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 && colorSpaceValue.size() > 0) + if (color == null && colorSpaceValue.size() > 0) { color = createColor(); } @@ -125,7 +118,7 @@ public Color getJavaColor() throws IOException /** * Returns the Java AWT paint based on the current pattern. - * + * * @param pageHeight the height of the current page * @return current Java AWT paint * @@ -133,7 +126,7 @@ public Color getJavaColor() throws IOException */ public Paint getPaint(int pageHeight) throws IOException { - if (paint == null && pattern != null) + if (paint == null && pattern != null) { paint = pattern.getPaint(pageHeight); } @@ -142,6 +135,7 @@ public Paint getPaint(int pageHeight) throws IOException /** * Create the current color from the colorspace and values. + * * @return The current awt color. * @throws IOException If there is an error creating the color. */ @@ -150,44 +144,49 @@ private Color createColor() throws IOException float[] components = colorSpaceValue.toFloatArray(); try { - if( colorSpace.getName().equals(PDDeviceRGB.NAME) && components.length == 3 ) + String csName = colorSpace.getName(); + if (PDDeviceRGB.NAME.equals(csName) && 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 (PDLab.NAME.equals(csName)) { - //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] ); + // transform the color values from Lab- to RGB-space + float[] csComponents = colorSpace.getJavaColorSpace().toRGB(components); + return new Color(csComponents[0], csComponents[1], csComponents[2]); } else { - if (components.length == 1) + if (components.length == 1) { - if (colorSpace.getName().equals(PDSeparation.NAME)) + if (PDSeparation.NAME.equals(csName)) { - //Use that component as a single-integer RGB value - return new Color((int)components[0]); + // Use that component as a single-integer RGB value + return new Color((int) components[0]); } - if (colorSpace.getName().equals(PDDeviceGray.NAME)) + if (PDDeviceGray.NAME.equals(csName)) { - // Handling DeviceGray as a special case as with JVM 1.5.0_15 - // and maybe others printing on Windows fails with an + // 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]); + 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)"); + 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 ); + return new Color(cs, components, 1f); } } } @@ -197,42 +196,44 @@ private Color createColor() throws IOException catch (Exception e) { Color cGuess; - String sMsg = "Unable to create the color instance " - + Arrays.toString(components) + " in color space " + String sMsg = "Unable to create the color instance " + Arrays.toString(components) + " in color space " + colorSpace + "; guessing color ... "; try { - switch(components.length) + 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]; + 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); - 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; + 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) @@ -247,19 +248,18 @@ private Color createColor() throws IOException } /** - * Constructor with an existing color set. Default colorspace is PDDeviceGray. - * + * Constructor with an existing color set. Default colorspace is PDDeviceGray. + * * @param csValues The color space values. */ - public PDColorState( COSArray csValues ) + public PDColorState(COSArray csValues) { colorSpaceValue = csValues; } - /** * This will get the current colorspace. - * + * * @return The current colorspace. */ public PDColorSpace getColorSpace() @@ -269,7 +269,7 @@ public PDColorSpace getColorSpace() /** * This will set the current colorspace. - * + * * @param value The new colorspace. */ public void setColorSpace(PDColorSpace value) @@ -281,8 +281,8 @@ public void setColorSpace(PDColorSpace value) } /** - * This will get the color space values. Either 1 for gray or 3 for RGB. - * + * This will get the color space values. Either 1 for gray or 3 for RGB. + * * @return The colorspace values. */ public float[] getColorSpaceValue() @@ -291,8 +291,8 @@ public float[] getColorSpaceValue() } /** - * This will get the color space values. Either 1 for gray or 3 for RGB. - * + * This will get the color space values. Either 1 for gray or 3 for RGB. + * * @return The colorspace values. */ public COSArray getCOSColorSpaceValue() @@ -302,12 +302,12 @@ public COSArray getCOSColorSpaceValue() /** * This will update the colorspace values. - * + * * @param value The new colorspace values. */ public void setColorSpaceValue(float[] value) { - colorSpaceValue.setFloatArray( value ); + colorSpaceValue.setFloatArray(value); // Clear color cache and current pattern color = null; pattern = null; @@ -315,7 +315,7 @@ public void setColorSpaceValue(float[] value) /** * This will get the current pattern. - * + * * @return The current pattern. */ public PDPatternResources getPattern() @@ -325,7 +325,7 @@ public PDPatternResources getPattern() /** * This will update the current pattern. - * + * * @param patternValue The new pattern. */ public void setPattern(PDPatternResources patternValue) @@ -334,5 +334,5 @@ public void setPattern(PDPatternResources patternValue) // Clear color cache color = null; } - + } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java index 43e6916b540..7dfc0562557 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java @@ -16,6 +16,14 @@ */ package org.apache.pdfbox.pdmodel.graphics.color; +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.IndexColorModel; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSInteger; @@ -24,20 +32,11 @@ 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 Ben Litchfield - * @version $Revision: 1.4 $ + * */ public class PDIndexed extends PDColorSpace { @@ -56,7 +55,7 @@ public class PDIndexed extends PDColorSpace private PDColorSpace baseColorspace = null; private ColorModel baseColorModel = null; - + /** * The lookup data as byte array. */ @@ -65,35 +64,40 @@ public class PDIndexed extends PDColorSpace private byte[] indexedColorValues; private int indexNumOfComponents; private int maxIndex; - + + /** + * Indexed color values are always 8bit based. + */ + private static final int INDEXED_BPC = 8; + /** * 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 ); + 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 ) + 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. - * + * 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 @@ -103,7 +107,7 @@ public int getNumberOfComponents() throws IOException /** * This will return the name of the color space. - * + * * @return The name of the color space. */ public String getName() @@ -113,9 +117,9 @@ public String getName() /** * 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 @@ -125,107 +129,106 @@ protected 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 ColorModel createColorModel( int bpc ) throws IOException + public ColorModel createColorModel(int bpc) throws IOException { return createColorModel(bpc, -1); } /** * Create a Java color model for this colorspace including the given mask value. - * - * @param bpc The number of bits per component. + * + * @param bpc The number of bits per component of the indexed color model. * @param mask the mask value, -1 indicates no mask - * + * * @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, int mask ) throws IOException + public ColorModel createColorModel(int bpc, int mask) throws IOException { - ColorModel colorModel = getBaseColorModel(bpc); + ColorModel colorModel = getBaseColorModel(INDEXED_BPC); calculateIndexedColorValues(colorModel, bpc); if (mask > -1) { - return new IndexColorModel(bpc, maxIndex+1, indexedColorValues, 0, colorModel.hasAlpha(), mask); + return new IndexColorModel(bpc, maxIndex + 1, indexedColorValues, 0, colorModel.hasAlpha(), mask); } else { - return new IndexColorModel(bpc, maxIndex+1, indexedColorValues, 0, colorModel.hasAlpha()); + return new IndexColorModel(bpc, maxIndex + 1, indexedColorValues, 0, colorModel.hasAlpha()); } } /** * 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 { if (baseColorspace == null) { - COSBase base = array.getObject( 1 ); - baseColorspace = PDColorSpaceFactory.createColorSpace( base ); + COSBase base = array.getObject(1); + baseColorspace = PDColorSpaceFactory.createColorSpace(base); } return baseColorspace; } /** * This will set the base color space. - * + * * @param base The base color space to use as the index. */ - public void setBaseColorSpace( PDColorSpace base ) + public void setBaseColorSpace(PDColorSpace base) { - array.set( 1, base.getCOSObject() ); + array.set(1, base.getCOSObject()); baseColorspace = base; } /** * Get the highest value for the lookup. - * + * * @return The hival entry. */ public int getHighValue() { - return ((COSNumber)array.getObject( 2 )).intValue(); + return ((COSNumber) array.getObject(2)).intValue(); } /** - * This will set the highest value that is allowed. This cannot be higher - * than 255. - * + * 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 ) + public void setHighValue(int high) { - array.set( 2, 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 + 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; + return (data[lookupIndex * numberOfComponents + componentNumber] + 256) % 256; } /** @@ -236,35 +239,35 @@ public int lookupColor( int lookupIndex, int componentNumber ) throws IOExceptio */ public byte[] getLookupData() throws IOException { - if ( lookupData == null) + if (lookupData == null) { - COSBase lookupTable = array.getObject( 3 ); - if( lookupTable instanceof COSString ) + COSBase lookupTable = array.getObject(3); + if (lookupTable instanceof COSString) { - lookupData = ((COSString)lookupTable).getBytes(); + lookupData = ((COSString) lookupTable).getBytes(); } - else if( lookupTable instanceof COSStream ) + else if (lookupTable instanceof COSStream) { - //Data will be small so just load the whole thing into memory for - //easier processing - COSStream lookupStream = (COSStream)lookupTable; + // 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 ]; + byte[] buffer = new byte[1024]; int amountRead; - while( (amountRead = input.read(buffer, 0, buffer.length)) != -1 ) + while ((amountRead = input.read(buffer, 0, buffer.length)) != -1) { - output.write( buffer, 0, amountRead ); + output.write(buffer, 0, amountRead); } lookupData = output.toByteArray(); } - else if( lookupTable == null ) + else if (lookupTable == null) { lookupData = new byte[0]; } else { - throw new IOException( "Error: Unknown type for lookup table " + lookupTable ); + throw new IOException("Error: Unknown type for lookup table " + lookupTable); } } return lookupData; @@ -272,25 +275,26 @@ else if( lookupTable == null ) /** * 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 + 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 ); + data[lookupIndex * numberOfComponents + componentNumber] = (byte) color; + COSString string = new COSString(data); + array.set(3, string); } /** * Returns the components of the color for the given index. + * * @param index the index of the color value * @return COSArray with the color components * @throws IOException If the tint function is not supported @@ -298,33 +302,33 @@ public void setLookupColor( int lookupIndex, int componentNumber, int color ) th public float[] calculateColorValues(int index) throws IOException { // TODO bpc != 8 ?? - calculateIndexedColorValues(getBaseColorModel(8), 8); + calculateIndexedColorValues(getBaseColorModel(INDEXED_BPC), 8); float[] colorValues = null; if (index < maxIndex) { int bufferIndex = index * indexNumOfComponents; colorValues = new float[indexNumOfComponents]; - for (int i=0; i < indexNumOfComponents; i++) + for (int i = 0; i < indexNumOfComponents; i++) { - colorValues[i] = (float)indexedColorValues[bufferIndex+i]; + colorValues[i] = (float) indexedColorValues[bufferIndex + i]; } } return colorValues; } - + private ColorModel getBaseColorModel(int bpc) throws IOException { if (baseColorModel == null) { baseColorModel = getBaseColorSpace().createColorModel(bpc); - if( baseColorModel.getTransferType() != DataBuffer.TYPE_BYTE ) + if (baseColorModel.getTransferType() != DataBuffer.TYPE_BYTE) { - throw new IOException( "Not implemented" ); + throw new IOException("Not implemented"); } } return baseColorModel; } - + private void calculateIndexedColorValues(ColorModel colorModel, int bpc) throws IOException { if (indexedColorValues == null) @@ -335,28 +339,28 @@ private void calculateIndexedColorValues(ColorModel colorModel, int bpc) throws int highValue = getHighValue(); // choose the correct size, sometimes there are more indexed values than needed // and sometimes there are fewer indexed value than possible - maxIndex = Math.min(numberOfColorValues-1, highValue); + maxIndex = Math.min(numberOfColorValues - 1, highValue); byte[] index = getLookupData(); // despite all definitions there may be less values within the lookup data - int numberOfColorValuesFromIndex = (index.length / baseColorModel.getNumComponents())-1; + int numberOfColorValuesFromIndex = (index.length / baseColorModel.getNumComponents()) - 1; maxIndex = Math.min(maxIndex, numberOfColorValuesFromIndex); // does the colorspace have an alpha channel? boolean hasAlpha = baseColorModel.hasAlpha(); - indexNumOfComponents = 3 + ( hasAlpha ? 1 : 0); - int buffersize = (maxIndex+1) * indexNumOfComponents; + indexNumOfComponents = 3 + (hasAlpha ? 1 : 0); + int buffersize = (maxIndex + 1) * indexNumOfComponents; indexedColorValues = new byte[buffersize]; byte[] inData = new byte[baseColorModel.getNumComponents()]; int bufferIndex = 0; - for( int i = 0; i <= maxIndex; i++ ) + for (int i = 0; i <= maxIndex; i++) { System.arraycopy(index, i * inData.length, inData, 0, inData.length); // calculate RGB values - indexedColorValues[bufferIndex] = (byte)colorModel.getRed(inData); - indexedColorValues[bufferIndex+1] = (byte)colorModel.getGreen(inData); - indexedColorValues[bufferIndex+2] = (byte)colorModel.getBlue(inData); - if(hasAlpha) + indexedColorValues[bufferIndex] = (byte) colorModel.getRed(inData); + indexedColorValues[bufferIndex + 1] = (byte) colorModel.getGreen(inData); + indexedColorValues[bufferIndex + 2] = (byte) colorModel.getBlue(inData); + if (hasAlpha) { - indexedColorValues[bufferIndex+3] = (byte)colorModel.getAlpha(inData); + indexedColorValues[bufferIndex + 3] = (byte) colorModel.getAlpha(inData); } bufferIndex += indexNumOfComponents; } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDCcitt.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDCcitt.java index 1d4d22c0467..7fb47a78e9d 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDCcitt.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDCcitt.java @@ -18,6 +18,7 @@ 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; @@ -37,14 +38,16 @@ 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.PDColorSpace; import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray; +import org.apache.pdfbox.pdmodel.graphics.color.PDIndexed; /** * An image class for CCITT Fax. - * + * * @author Ben Litchfield * @author paul king - * @version $Revision: 1.6 $ + * */ public class PDCcitt extends PDXObjectImage { @@ -53,13 +56,13 @@ public class PDCcitt extends PDXObjectImage static { - FAX_FILTERS.add( COSName.CCITTFAX_DECODE.getName() ); - FAX_FILTERS.add( COSName.CCITTFAX_DECODE_ABBREVIATION.getName() ); + 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) @@ -70,38 +73,37 @@ public PDCcitt(PDStream ccitt) /** * 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 + public PDCcitt(PDDocument doc, RandomAccess raf) throws IOException { - super( new PDStream(doc),"tiff"); - // super( new PDStream( doc, null, true ), "tiff" ); + super(new PDStream(doc), "tiff"); COSDictionary decodeParms = new COSDictionary(); COSDictionary dic = getCOSStream(); - extractFromTiff(raf, getCOSStream().createFilteredStream(),decodeParms); + 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); + 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) ); + 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 @@ -111,19 +113,19 @@ public BufferedImage getRGBImage() throws IOException COSDictionary decodeParms = null; if (decodeP instanceof COSDictionary) { - decodeParms = (COSDictionary)decodeP; + decodeParms = (COSDictionary) decodeP; } else if (decodeP instanceof COSArray) { int index = 0; // determine the index for the CCITT-filter COSBase filters = stream.getFilters(); - if (filters instanceof COSArray) + if (filters instanceof COSArray) { - COSArray filterArray = (COSArray)filters; + COSArray filterArray = (COSArray) filters; while (index < filterArray.size()) { - COSName filtername = (COSName)filterArray.get(index); + COSName filtername = (COSName) filterArray.get(index); if (COSName.CCITTFAX_DECODE.equals(filtername)) { break; @@ -131,7 +133,7 @@ else if (decodeP instanceof COSArray) index++; } } - decodeParms = (COSDictionary)((COSArray)decodeP).getObject(index); + decodeParms = (COSDictionary) ((COSArray) decodeP).getObject(index); } int cols = decodeParms.getInt(COSName.COLUMNS, 1728); int rows = decodeParms.getInt(COSName.ROWS, 0); @@ -147,39 +149,65 @@ else if (decodeP instanceof COSArray) rows = Math.max(rows, height); } boolean blackIsOne = decodeParms.getBoolean(COSName.BLACK_IS_1, false); - - BufferedImage image = new BufferedImage(cols, rows, BufferedImage.TYPE_BYTE_BINARY); - WritableRaster raster = image.getRaster(); - DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer(); - byte[] bufferData = buffer.getData(); + // maybe a decode array is defined + COSArray decode = getDecode(); + if (decode != null && decode.getInt(0) == 1) + { + // [1.0, 0.0] -> invert the "color" values + blackIsOne = !blackIsOne; + } + byte[] bufferData = null; + ColorModel colorModel = null; + PDColorSpace colorspace = getColorSpace(); + // most likely there is no colorspace as a CCITT-filter uses 1-bit values mapped to black/white + // in some rare cases other colorspaces maybe used such as an indexed colorspace, see PDFBOX-1638 + if (colorspace instanceof PDIndexed) + { + PDIndexed csIndexed = (PDIndexed) colorspace; + COSBase maskArray = getMask(); + if (maskArray != null && maskArray instanceof COSArray) + { + colorModel = csIndexed.createColorModel(1, ((COSArray) maskArray).getInt(0)); + } + else + { + colorModel = csIndexed.createColorModel(1); + } + } + else + { + byte[] map = new byte[] { (byte) 0x00, (byte) 0xFF }; + colorModel = new IndexColorModel(1, map.length, map, map, map, Transparency.OPAQUE); + } + WritableRaster raster = colorModel.createCompatibleWritableRaster(cols, rows); + DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer(); + bufferData = buffer.getData(); IOUtils.populateBuffer(stream.getUnfilteredStream(), bufferData); + BufferedImage image = new BufferedImage(colorModel, raster, false, null); if (!blackIsOne) { - //Inverting the bitmap - //Note the previous approach with starting from an IndexColorModel didn't work - //reliably. In some cases the image wouldn't be painted for some reason. - //So a safe but slower approach was taken. + // Inverting the bitmap + // Note the previous approach with starting from an IndexColorModel didn't work + // reliably. In some cases the image wouldn't be painted for some reason. + // So a safe but slower approach was taken. invertBitmap(bufferData); } - /* * If we have an image mask we need to add an alpha channel to the data */ - if(hasMask()) + if (hasMask()) { - byte map[] = new byte[] {(byte)0x00, (byte)0xff}; - IndexColorModel cm = new IndexColorModel(1, map.length, map, map, map, Transparency.OPAQUE); - raster = cm.createCompatibleWritableRaster( image.getWidth(), image.getHeight() ); - buffer = (DataBufferByte)raster.getDataBuffer(); - bufferData = buffer.getData(); - - byte array[] = ((DataBufferByte)image.getData().getDataBuffer()).getData(); - System.arraycopy( array, 0,bufferData, 0, - (array.length 50) { throw new IOException("Not a valid tiff file"); @@ -252,11 +278,11 @@ private void extractFromTiff(RandomAccess raf, OutputStream os, COSDictionary pa // 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; + int k = -1000; // Default Non CCITT compression + int dataoffset = 0; + int datalength = 0; - for (int i=0; i < numtags; i++) + for (int i = 0; i < numtags; i++) { int tag = readshort(endianess, raf); int type = readshort(endianess, raf); @@ -270,102 +296,102 @@ private void extractFromTiff(RandomAccess raf, OutputStream os, COSDictionary pa { 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 - } + case 1: + { + val = val >> 24; + break; // byte value } - } - switch (tag) - { - case 256: + case 3: { - parms.setInt(COSName.COLUMNS,val); - break; + val = val >> 16; + break; // short value } - case 257: + case 4: { - parms.setInt(COSName.ROWS,val); - break; + break; // long value } - case 259: + default: { - if (val == 4) - { - k=-1; - } - if (val == 3) - { - k=0; - } - break; // T6/T4 Compression + // do nothing + } } - case 262: + } + switch (tag) + { + case 256: + { + parms.setInt(COSName.COLUMNS, val); + break; + } + case 257: + { + parms.setInt(COSName.ROWS, val); + break; + } + case 259: + { + if (val == 4) { - if (val == 1) - { - parms.setBoolean(COSName.BLACK_IS_1, true); - } - break; + k = -1; } - case 273: + if (val == 3) { - if (count == 1) - { - dataoffset=val; - } - break; + k = 0; } - case 279: + break; // T6/T4 Compression + } + case 262: + { + if (val == 1) { - if (count == 1) - { - datalength=val; - } - break; + parms.setBoolean(COSName.BLACK_IS_1, true); } - case 292: + break; + } + case 273: + { + if (count == 1) { - if (val == 1) - { - k=50; // T4 2D - arbitary K value - } - break; + dataoffset = val; } - case 324: + break; + } + case 279: + { + if (count == 1) { - if (count == 1) - { - dataoffset=val; - } - break; + datalength = val; } - case 325: + break; + } + case 292: + { + if (val == 1) { - if (count == 1) - { - datalength=val; - } - break; + k = 50; // T4 2D - arbitary K value } - default: + break; + } + case 324: + { + if (count == 1) + { + dataoffset = val; + } + break; + } + case 325: + { + if (count == 1) { - //do nothing + datalength = val; } + break; + } + default: + { + // do nothing + } } } @@ -378,16 +404,16 @@ private void extractFromTiff(RandomAccess raf, OutputStream os, COSDictionary pa throw new IOException("First image in tiff is not a single tile/strip"); } - parms.setInt(COSName.K,k); + 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 ) + while ((amountRead = raf.read(buf, 0, Math.min(8192, datalength))) > 0) { datalength -= amountRead; - os.write( buf, 0, amountRead ); + os.write(buf, 0, amountRead); } } @@ -415,25 +441,23 @@ private int readlong(char endianess, RandomAccess raf) throws IOException 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 - * + * 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 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; + buildHeader(options); + currentOffset = 0; + datastream = rawstream; } // Implement basic methods from InputStream @@ -444,6 +468,7 @@ public boolean markSupported() { return false; } + /** * {@inheritDoc} */ @@ -454,7 +479,7 @@ public void reset() throws IOException /** * For simple read, take a byte from the tiffheader array or pass through. - * + * * {@inheritDoc} */ public int read() throws IOException @@ -467,9 +492,9 @@ public int read() throws IOException } /** - * 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. - * + * 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 @@ -491,10 +516,10 @@ public int read(byte[] data) throws IOException } /** - * 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} + * 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 { @@ -510,14 +535,14 @@ public int read(byte[] data, int off, int len) throws IOException } else { - return datastream.read(data,off,len); + 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. - * + * 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 @@ -535,12 +560,10 @@ public long skip(long n) throws IOException } // 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 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 + private int additionalOffset; // Offset in header to additional data // Builds up the tiffheader based on the options passed through. private void buildHeader(COSDictionary options) @@ -548,16 +571,16 @@ 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) + // 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; + 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); + 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; @@ -579,26 +602,26 @@ private void buildHeader(COSDictionary options) } COSBase dicOrArrayParms = options.getDictionaryObject(COSName.DECODE_PARMS); COSDictionary decodeParms = null; - if( dicOrArrayParms instanceof COSDictionary ) + if (dicOrArrayParms instanceof COSDictionary) { - decodeParms = (COSDictionary)dicOrArrayParms; + decodeParms = (COSDictionary) dicOrArrayParms; } else { - COSArray parmsArray = (COSArray)dicOrArrayParms; - if( parmsArray.size() == 1 ) + COSArray parmsArray = (COSArray) dicOrArrayParms; + if (parmsArray.size() == 1) { - decodeParms = (COSDictionary)parmsArray.getObject( 0 ); + decodeParms = (COSDictionary) parmsArray.getObject(0); } else { - //else find the first dictionary with Row/Column info and use that. - for( int i=0; i 0) { - //T4 2D + // T4 2D comptype = 3; t4options = 1; } @@ -641,7 +664,7 @@ private void buildHeader(COSDictionary options) addTag(256, cols); // Columns addTag(257, rows); // Rows - addTag(259, comptype); // T6 + 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 @@ -656,52 +679,51 @@ private void buildHeader(COSDictionary options) /* Tiff types 1 = byte, 2=ascii, 3=short, 4=ulong 5=rational */ - private void addTag(int tag,long value) + private void addTag(int tag, long value) { - // Adds a tag of type 4 (ulong) + // 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); + 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); + 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 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); + 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()); + System.arraycopy(value.getBytes("US-ASCII"), 0, tiffheader, additionalOffset, value.length()); } catch (UnsupportedEncodingException e) { @@ -714,23 +736,23 @@ 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); + 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); } } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java index 2db4bd56119..a9e18456a3b 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java @@ -178,24 +178,23 @@ public BufferedImage getRGBImage() throws IOException try { - int width = getWidth(); - int height = getHeight(); - int bpc = getBitsPerComponent(); - byte[] array = getPDStream().getByteArray(); if (array.length == 0) { LOG.error("Something went wrong ... the pixelmap doesn't contain any data."); return null; } - // Get the ColorModel right + int width = getWidth(); + int height = getHeight(); + int bpc = getBitsPerComponent(); + PDColorSpace colorspace = getColorSpace(); if (colorspace == null) { LOG.error("getColorSpace() returned NULL."); return null; } - + // Get the ColorModel right ColorModel cm = null; if (colorspace instanceof PDIndexed) { diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java index 3eb625a62d1..95764120be1 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java @@ -118,6 +118,20 @@ public PDStream getPDStream() * @throws IOException If there is an error creating the xobject. */ public static PDXObject createXObject( COSBase xobject ) throws IOException + { + PDXObject retval = commonXObjectCreation(xobject, false); + return retval; + } + + /** + * Create the correct xobject from the cos base. + * + * @param xobject The cos level xobject to create. + * @param isthumb specify if the xobject represent a Thumbnail Image (in this case, the subtype null must be considered as an Image) + * @return a pdmodel xobject + * @throws IOException If there is an error creating the xobject. + */ + protected static PDXObject commonXObjectCreation(COSBase xobject, boolean isThumb) { PDXObject retval = null; if( xobject == null ) @@ -128,7 +142,8 @@ else if( xobject instanceof COSStream ) { COSStream xstream = (COSStream)xobject; String subtype = xstream.getNameAsString( COSName.SUBTYPE ); - if( subtype.equals( PDXObjectImage.SUB_TYPE ) ) + // according to the PDF Reference : a thumbnail subtype must be Image if it is not null + if( PDXObjectImage.SUB_TYPE.equals( subtype ) || (subtype == null && isThumb)) { PDStream image = new PDStream( xstream ); // See if filters are DCT or JPX otherwise treat as Bitmap-like @@ -157,7 +172,7 @@ else if( filters != null && filters.contains(COSName.JPX_DECODE)) retval = new PDPixelMap(image); } } - else if( subtype.equals( PDXObjectForm.SUB_TYPE ) ) + else if( PDXObjectForm.SUB_TYPE.equals( subtype ) ) { retval = new PDXObjectForm( xstream ); } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java index af313592356..5b121938188 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java @@ -90,6 +90,20 @@ public PDXObjectImage(PDDocument doc, String fileSuffix) suffix = fileSuffix; } + /** + * Create the correct thumbnail 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 createThumbnailXObject( COSBase xobject ) throws IOException + { + PDXObject retval = commonXObjectCreation(xobject, true); + return retval; + } + /** * Returns an java.awt.Image, that can be used for display etc. * diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/TextNormalize.java b/pdfbox/src/main/java/org/apache/pdfbox/util/TextNormalize.java index 3994df1242d..a2274238f82 100755 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/TextNormalize.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/TextNormalize.java @@ -19,52 +19,55 @@ 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. + * 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 Brian Carrier - * @version $Revision: 1.0 $ + * */ -public class TextNormalize +public class TextNormalize { private ICU4JImpl icu4j = null; - private static final HashMap DIACHASH = new HashMap(); + private static final HashMap DIACHASH = new HashMap(); private String outputEncoding; + static + { + populateDiacHash(); + } + /** * * @param encoding The Encoding that the text will eventually be written as (or null) */ - public TextNormalize(String encoding) + public TextNormalize(String encoding) { findICU4J(); - populateDiacHash(); - this.outputEncoding = encoding; + outputEncoding = encoding; } - private void findICU4J() + private void findICU4J() { // see if we can load the icu4j classes from the classpath - try + 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) + } + 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. + * 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() + private static void populateDiacHash() { DIACHASH.put(new Integer(0x0060), "\u0300"); DIACHASH.put(new Integer(0x02CB), "\u0300"); @@ -100,74 +103,70 @@ private void populateDiacHash() } /** - * 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. + * 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) + public String makeLineLogicalOrder(String str, boolean isRtlDominant) { - if (icu4j != null) + if (icu4j != null) { return icu4j.makeLineLogicalOrder(str, isRtlDominant); } - else + else { return str; } } /** - * Normalize the presentation forms of characters in the string. - * For example, convert the single "fi" ligature to "f" and "i". + * 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) + public String normalizePres(String str) { - if (icu4j != null) + if (icu4j != null) { return icu4j.normalizePres(str); } - else + else { return str; } } - + /** - * Normalize the diacritic, for example, - * convert non-combining diacritic characters to their combining - * counterparts. + * Normalize the diacritic, for example, convert non-combining diacritic characters to their combining counterparts. * - * @param str String to normalize + * @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. + * Unicode contains special combining forms of the diacritic characters and we want to use these. */ - if(outputEncoding != null && outputEncoding.toUpperCase().startsWith("UTF")) + 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)) + if (DIACHASH.containsKey(c)) { - return (String)DIACHASH.get(c); + return (String) DIACHASH.get(c); } - else if (icu4j != null) + else if (icu4j != null) { return icu4j.normalizeDiac(str); } - else + else { return str; } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightConstants.java b/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightConstants.java index 4ebc2c6e6df..d1c4a0ab801 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightConstants.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightConstants.java @@ -940,11 +940,7 @@ public interface PreflightConstants * a category has an invalid value in one property description (must be internal or external) */ String ERROR_METADATA_CATEGORY_PROPERTY_INVALID = "7.5.1"; - - /** - * the infor dictionary is corrupt or value can't be read - */ - String ERROR_METADATA_DICT_INFO_CORRUPT = "7.12"; + /** * Error about PDF processing : that is not necessary a specific PDF/A validation error * but a PDF specification requirement that isn't respected. @@ -954,4 +950,8 @@ public interface PreflightConstants * A Mandatory element is missing */ String ERROR_PDF_PROCESSING_MISSING = "8.1"; + /** + * the infor dictionary is corrupt or value can't be read + */ + String ERROR_METADATA_DICT_INFO_CORRUPT = "7.12"; } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightDocument.java b/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightDocument.java index a30a6133337..aa255d76cb6 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightDocument.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/PreflightDocument.java @@ -165,7 +165,7 @@ public void validate() throws ValidationException Collection processes = config.getProcessNames(); for (String name : processes) { - ContextHelper.validateElement(context, name); + ContextHelper.validateElement(context, name); } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/action/ActionManagerFactory.java b/preflight/src/main/java/org/apache/pdfbox/preflight/action/ActionManagerFactory.java index 00d1ca0847d..0e5b7f05d91 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/action/ActionManagerFactory.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/action/ActionManagerFactory.java @@ -40,7 +40,7 @@ import static org.apache.pdfbox.preflight.PreflightConstants.ACTION_DICTIONARY_VALUE_TYPE; import static org.apache.pdfbox.preflight.PreflightConstants.DICTIONARY_KEY_ADDITIONAL_ACTION; import static org.apache.pdfbox.preflight.PreflightConstants.DICTIONARY_KEY_OPEN_ACTION; - +import org.apache.pdfbox.preflight.ValidationResult.ValidationError; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -53,6 +53,7 @@ import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSObject; import org.apache.pdfbox.persistence.util.COSObjectKey; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.exception.ValidationException; import org.apache.pdfbox.preflight.utils.COSUtils; @@ -176,7 +177,7 @@ private void callCreateAction(COSBase aDict, PreflightContext ctx, List widths = font.getWidths(); + List widths = font.getWidths(); if (widths == null || widths.isEmpty()) { this.fontContainer.push(new ValidationError(ERROR_FONTS_DICTIONARY_INVALID, @@ -460,7 +460,8 @@ private void checkResources() throws ValidationException } catch (IOException e) { - throw new ValidationException("Unable to valid the Type3 : " + e.getMessage()); + context.addValidationError(new ValidationError(PreflightConstants.ERROR_FONTS_DAMAGED, + "Unable to valid the Type3 : " + e.getMessage())); } } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java b/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java index 3753d110621..281f37bd335 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java @@ -361,8 +361,8 @@ protected boolean parseXrefTable(long startByteOffset) throws IOException while (true) { // just after the xref there are an integer - int currObjID = 0; // first obj id - int count = 0; // the number of objects in the xref table + long currObjID = 0; // first obj id + long count = 0; // the number of objects in the xref table long offset = pdfSource.getOffset(); String line = readLine(); @@ -379,8 +379,8 @@ protected boolean parseXrefTable(long startByteOffset) throws IOException "Cross reference subsection header is invalid")); // reset pdfSource cursor to read xref information pdfSource.seek(offset); - currObjID = readInt(); // first obj id - count = readInt(); // the number of objects in the xref table + currObjID = readObjectNumber(); // first obj id + count = readLong(); // the number of objects in the xref table } skipSpaces(); @@ -678,7 +678,7 @@ else if (offsetOrObjstmObNr > 0) // ---- go to object start setPdfSource(offsetOrObjstmObNr); // ---- we must have an indirect object - int readObjNr = 0; + long readObjNr = 0; int readObjGen = 0; long offset = pdfSource.getOffset(); @@ -697,8 +697,8 @@ else if (offsetOrObjstmObNr > 0) // reset pdfSource cursor to read object information pdfSource.seek(offset); - readObjNr = readInt(); - readObjGen = readInt(); + readObjNr = readObjectNumber(); + readObjGen = readGenerationNumber(); skipSpaces(); // skip spaces between Object Generation number and the 'obj' keyword for (char c : OBJ_MARKER) { diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/AcroFormValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/AcroFormValidationProcess.java index 6f3abab9b54..143cc7bb4be 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/AcroFormValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/AcroFormValidationProcess.java @@ -26,6 +26,7 @@ import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_ACTION_FORBIDDEN_ADDITIONAL_ACTIONS_FIELD; import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_ACTION_FORBIDDEN_WIDGET_ACTION_FIELD; import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_DICT_INVALID; +import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_NOCATALOG; import java.io.IOException; import java.util.List; @@ -37,6 +38,7 @@ import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; import org.apache.pdfbox.preflight.exception.ValidationException; @@ -66,7 +68,7 @@ public void validate(PreflightContext ctx) throws ValidationException } else { - throw new ValidationException("There are no Catalog entry in the Document."); + ctx.addValidationError(new ValidationError(ERROR_SYNTAX_NOCATALOG, "There are no Catalog entry in the Document.")); } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/BookmarkValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/BookmarkValidationProcess.java index c15ee33bbbd..468f40956e4 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/BookmarkValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/BookmarkValidationProcess.java @@ -22,6 +22,7 @@ package org.apache.pdfbox.preflight.process; import static org.apache.pdfbox.preflight.PreflightConfiguration.ACTIONS_PROCESS; +import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_NOCATALOG; import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_TRAILER_OUTLINES_INVALID; import org.apache.pdfbox.cos.COSBase; @@ -31,6 +32,7 @@ import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; import org.apache.pdfbox.preflight.exception.ValidationException; @@ -69,7 +71,7 @@ else if (isCountEntryPositive(ctx, outlineHierarchy.getCOSDictionary()) } else { - throw new ValidationException("There are no Catalog entry in the Document."); + ctx.addValidationError(new ValidationError(ERROR_SYNTAX_NOCATALOG, "There are no Catalog entry in the Document.")); } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/CatalogValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/CatalogValidationProcess.java index 24098a6b359..f892d406baa 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/CatalogValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/CatalogValidationProcess.java @@ -202,15 +202,16 @@ public void validate(PreflightContext ctx) throws ValidationException if (this.catalog == null) { - new ValidationError(ERROR_SYNTAX_NOCATALOG, "There are no Catalog entry in the Document."); - throw new ValidationException("There are no Catalog entry in the Document."); + ctx.addValidationError(new ValidationError(ERROR_SYNTAX_NOCATALOG, "There are no Catalog entry in the Document.")); + } + else + { + validateActions(ctx); + validateLang(ctx); + validateNames(ctx); + validateOCProperties(ctx); + validateOutputIntent(ctx); } - - validateActions(ctx); - validateLang(ctx); - validateNames(ctx); - validateOCProperties(ctx); - validateOutputIntent(ctx); } /** @@ -379,7 +380,7 @@ public void validateOutputIntent(PreflightContext ctx) throws ValidationExceptio */ protected void validateICCProfile(COSBase destOutputProfile, Map mapDestOutputProfile, PreflightContext ctx) throws ValidationException - { + { try { if (destOutputProfile == null) @@ -461,5 +462,5 @@ else if (iccp.getMajorVersion() > 2) { throw new ValidationException("Unable to parse the ICC Profile", e); } - } + } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/PageTreeValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/PageTreeValidationProcess.java index ad3445244f1..997a489d043 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/PageTreeValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/PageTreeValidationProcess.java @@ -22,12 +22,14 @@ package org.apache.pdfbox.preflight.process; import static org.apache.pdfbox.preflight.PreflightConfiguration.PAGE_PROCESS; +import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_NOCATALOG; import java.util.List; import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.preflight.PreflightContext; +import org.apache.pdfbox.preflight.ValidationResult.ValidationError; import org.apache.pdfbox.preflight.exception.ValidationException; import org.apache.pdfbox.preflight.utils.ContextHelper; @@ -47,7 +49,7 @@ public void validate(PreflightContext context) throws ValidationException } else { - throw new ValidationException("There are no Catalog entry in the Document."); + context.addValidationError(new ValidationError(ERROR_SYNTAX_NOCATALOG, "There are no Catalog entry in the Document.")); } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/StreamValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/StreamValidationProcess.java index 8bfe252c41c..d03624d6c5f 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/StreamValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/StreamValidationProcess.java @@ -21,6 +21,7 @@ package org.apache.pdfbox.preflight.process; +import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_STREAM_DAMAGED; import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_STREAM_FX_KEYS; import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_STREAM_INVALID_FILTER; import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_STREAM_LENGTH_INVALID; @@ -226,7 +227,9 @@ protected void checkStreamLength(PreflightContext context, COSObject cObj) throw long curSkip = ra.skip(offset - skipped); if (curSkip < 0) { - throw new ValidationException("Unable to skip bytes in the PDFFile to check stream length"); + org.apache.pdfbox.io.IOUtils.closeQuietly(ra); + addValidationError(context, new ValidationError(ERROR_SYNTAX_STREAM_DAMAGED, "Unable to skip bytes in the PDFFile to check stream length")); + return; } skipped += curSkip; } @@ -261,6 +264,7 @@ protected void checkStreamLength(PreflightContext context, COSObject cObj) throw { addValidationError(context, new ValidationError(ERROR_SYNTAX_STREAM_LENGTH_INVALID, "Stream length is invalide")); + org.apache.pdfbox.io.IOUtils.closeQuietly(ra); return; } else diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ActionsValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ActionsValidationProcess.java index 24b2423e199..da45b21d9b6 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ActionsValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ActionsValidationProcess.java @@ -26,12 +26,14 @@ import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.preflight.PreflightConfiguration; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.action.AbstractActionManager; import org.apache.pdfbox.preflight.action.ActionManagerFactory; import org.apache.pdfbox.preflight.exception.ValidationException; import org.apache.pdfbox.preflight.process.AbstractProcess; +import org.apache.pdfbox.preflight.ValidationResult.ValidationError; public class ActionsValidationProcess extends AbstractProcess { @@ -39,21 +41,26 @@ public class ActionsValidationProcess extends AbstractProcess public void validate(PreflightContext context) throws ValidationException { PreflightPath vPath = context.getValidationPath(); - if (vPath.isEmpty() || !vPath.isExpectedType(COSDictionary.class)) + if (vPath.isEmpty()) { + return; + } + else if (!vPath.isExpectedType(COSDictionary.class)) { - throw new ValidationException("Action validation process needs at least one COSDictionary object"); + context.addValidationError(new ValidationError(PreflightConstants.ERROR_ACTION_INVALID_TYPE, "Action validation process needs at least one COSDictionary object")); } - - COSDictionary actionsDict = (COSDictionary) vPath.peek(); - // AA entry is authorized only for Page, in this case A Page is just before the Action Dictionary in the path - boolean aaEntryAuth = ((vPath.size() - vPath.getClosestTypePosition(PDPage.class)) == 2); - - PreflightConfiguration config = context.getConfig(); - ActionManagerFactory factory = config.getActionFact(); - List la = factory.getActionManagers(context, actionsDict); - for (AbstractActionManager aMng : la) + else { - aMng.valid(aaEntryAuth); + COSDictionary actionsDict = (COSDictionary) vPath.peek(); + // AA entry is authorized only for Page, in this case A Page is just before the Action Dictionary in the path + boolean aaEntryAuth = ((vPath.size() - vPath.getClosestTypePosition(PDPage.class)) == 2); + + PreflightConfiguration config = context.getConfig(); + ActionManagerFactory factory = config.getActionFact(); + List la = factory.getActionManagers(context, actionsDict); + for (AbstractActionManager aMng : la) + { + aMng.valid(aaEntryAuth); + } } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/AnnotationValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/AnnotationValidationProcess.java index 9357a23215e..797abcd2ba9 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/AnnotationValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/AnnotationValidationProcess.java @@ -21,8 +21,10 @@ package org.apache.pdfbox.preflight.process.reflect; +import org.apache.pdfbox.preflight.ValidationResult.ValidationError; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.preflight.PreflightConfiguration; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.annotation.AnnotationValidator; @@ -36,19 +38,24 @@ public class AnnotationValidationProcess extends AbstractProcess public void validate(PreflightContext context) throws ValidationException { PreflightPath vPath = context.getValidationPath(); - if (vPath.isEmpty() || !vPath.isExpectedType(COSDictionary.class)) + if (vPath.isEmpty()) { + return; + } + else if (!vPath.isExpectedType(COSDictionary.class)) { - throw new ValidationException("Annotation validation process needs at least one COSDictionary object"); + context.addValidationError(new ValidationError(PreflightConstants.ERROR_ANNOT_INVALID_ELEMENT, "Annotation validation process needs at least one COSDictionary object")); } - - COSDictionary annotDict = (COSDictionary) vPath.peek(); - - PreflightConfiguration config = context.getConfig(); - AnnotationValidatorFactory factory = config.getAnnotFact(); - AnnotationValidator annotValidator = factory.getAnnotationValidator(context, annotDict); - if (annotValidator != null) + else { - annotValidator.validate(); + COSDictionary annotDict = (COSDictionary) vPath.peek(); + + PreflightConfiguration config = context.getConfig(); + AnnotationValidatorFactory factory = config.getAnnotFact(); + AnnotationValidator annotValidator = factory.getAnnotationValidator(context, annotDict); + if (annotValidator != null) + { + annotValidator.validate(); + } } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ExtGStateValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ExtGStateValidationProcess.java index c4a177fc213..422e78242d4 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ExtGStateValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ExtGStateValidationProcess.java @@ -41,6 +41,7 @@ import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSDocument; import org.apache.pdfbox.cos.COSName; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; @@ -54,14 +55,19 @@ public class ExtGStateValidationProcess extends AbstractProcess public void validate(PreflightContext context) throws ValidationException { PreflightPath vPath = context.getValidationPath(); - if (vPath.isEmpty() || !vPath.isExpectedType(COSDictionary.class)) + if (vPath.isEmpty()) { + return; + } + else if (!vPath.isExpectedType(COSDictionary.class)) { - throw new ValidationException("ExtGState validation required at least a Resource dictionary"); + context.addValidationError(new ValidationError(PreflightConstants.ERROR_GRAPHIC_XOBJECT_INVALID_TYPE, "ExtGState validation required at least a Resource dictionary")); + } + else + { + COSDictionary extGStatesDict = (COSDictionary) vPath.peek(); + List listOfExtGState = extractExtGStateDictionaries(context, extGStatesDict); + validateTransparencyRules(context, listOfExtGState); } - - COSDictionary extGStatesDict = (COSDictionary) vPath.peek(); - List listOfExtGState = extractExtGStateDictionaries(context, extGStatesDict); - validateTransparencyRules(context, listOfExtGState); } /** @@ -76,7 +82,7 @@ public void validate(PreflightContext context) throws ValidationException */ public List extractExtGStateDictionaries(PreflightContext context, COSDictionary egsEntry) throws ValidationException - { + { List listOfExtGState = new ArrayList(0); COSDocument cosDocument = context.getDocument().getDocument(); COSDictionary extGStates = COSUtils.getAsDictionary(egsEntry, cosDocument); @@ -99,7 +105,7 @@ public List extractExtGStateDictionaries(PreflightContext context } } return listOfExtGState; - } + } /** * Validate all ExtGState dictionaries of this container diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/FontValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/FontValidationProcess.java index 07fb25a87fe..3edb9ad7548 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/FontValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/FontValidationProcess.java @@ -32,6 +32,7 @@ import static org.apache.pdfbox.preflight.PreflightConstants.FONT_DICTIONARY_VALUE_TYPE3; import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.exception.ValidationException; @@ -42,6 +43,8 @@ import org.apache.pdfbox.preflight.font.Type3FontValidator; import org.apache.pdfbox.preflight.font.container.FontContainer; import org.apache.pdfbox.preflight.process.AbstractProcess; +import org.apache.pdfbox.preflight.ValidationResult.ValidationError; + public class FontValidationProcess extends AbstractProcess { @@ -49,17 +52,22 @@ public class FontValidationProcess extends AbstractProcess public void validate(PreflightContext context) throws ValidationException { PreflightPath vPath = context.getValidationPath(); - if (vPath.isEmpty() || !vPath.isExpectedType(PDFont.class)) - { - throw new ValidationException("Font validation process needs at least one PDFont object"); + if (vPath.isEmpty()) { + return; } - - PDFont font = (PDFont) vPath.peek(); - FontContainer fontContainer = context.getFontContainer(font.getCOSObject()); - if (fontContainer == null) - { // if fontContainer isn't null the font is already checked - FontValidator validator = getFontValidator(context, font); - validator.validate(); + else if (!vPath.isExpectedType(PDFont.class)) + { + context.addValidationError(new ValidationError(PreflightConstants.ERROR_FONTS_INVALID_DATA, "Font validation process needs at least one PDFont object")); + } + else + { + PDFont font = (PDFont) vPath.peek(); + FontContainer fontContainer = context.getFontContainer(font.getCOSObject()); + if (fontContainer == null) + { // if fontContainer isn't null the font is already checked + FontValidator validator = getFontValidator(context, font); + if (validator != null) validator.validate(); + } } } @@ -70,7 +78,6 @@ public void validate(PreflightContext context) throws ValidationException * @return */ protected FontValidator getFontValidator(PreflightContext context, PDFont font) - throws ValidationException { String subtype = font.getSubType(); if (FONT_DICTIONARY_VALUE_TRUETYPE.equals(subtype)) @@ -98,7 +105,8 @@ else if (FONT_DICTIONARY_VALUE_TYPE2.equals(subtype) || FONT_DICTIONARY_VALUE_TY } else { - throw new ValidationException("Unknown font type : " + subtype); + context.addValidationError(new ValidationError(PreflightConstants.ERROR_FONTS_UNKNOWN_FONT_TYPE, "Unknown font type : " + subtype)); + return null; } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/GraphicObjectPageValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/GraphicObjectPageValidationProcess.java index b896f12f261..34d4af82acc 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/GraphicObjectPageValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/GraphicObjectPageValidationProcess.java @@ -27,6 +27,7 @@ import org.apache.pdfbox.cos.COSStream; import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm; import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.exception.ValidationException; @@ -36,6 +37,8 @@ import org.apache.pdfbox.preflight.xobject.XObjPostscriptValidator; import org.apache.pdfbox.preflight.xobject.XObjectValidator; +import org.apache.pdfbox.preflight.ValidationResult.ValidationError; + public class GraphicObjectPageValidationProcess extends AbstractProcess { @@ -62,14 +65,16 @@ else if (!vPath.isEmpty() && vPath.isExpectedType(COSStream.class)) } else { - throw new ValidationException("Invalid XObject subtype"); + context.addValidationError(new ValidationError(PreflightConstants.ERROR_GRAPHIC_XOBJECT_INVALID_TYPE, "Invalid XObject subtype")); } } else { - throw new ValidationException("Graphic validation process needs at least one PDFont object"); + context.addValidationError(new ValidationError(PreflightConstants.ERROR_GRAPHIC_MISSING_OBJECT, "Graphic validation process needs at least one PDXObject")); } - validator.validate(); + if (validator != null) { + validator.validate(); + } } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ResourcesValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ResourcesValidationProcess.java index 20040f92697..5674374b193 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ResourcesValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ResourcesValidationProcess.java @@ -44,6 +44,7 @@ import org.apache.pdfbox.pdmodel.graphics.pattern.PDTilingPatternResources; import org.apache.pdfbox.pdmodel.graphics.shading.PDShadingResources; import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; @@ -58,18 +59,24 @@ public class ResourcesValidationProcess extends AbstractProcess public void validate(PreflightContext ctx) throws ValidationException { PreflightPath vPath = ctx.getValidationPath(); - if (vPath.isEmpty() && !vPath.isExpectedType(PDResources.class)) - { - throw new ValidationException("Resources validation process needs at least one PDResources object"); + if (vPath.isEmpty()) { + return; } + else if (!vPath.isExpectedType(PDResources.class)) + { + addValidationError(ctx, new ValidationError(PreflightConstants.ERROR_PDF_PROCESSING_MISSING, "Resources validation process needs at least one PDResources object")); + } + else + { - PDResources resources = (PDResources) vPath.peek(); + PDResources resources = (PDResources) vPath.peek(); - validateFonts(ctx, resources); - validateExtGStates(ctx, resources); - validateShadingPattern(ctx, resources); - validateTilingPattern(ctx, resources); - validateXObjects(ctx, resources); + validateFonts(ctx, resources); + validateExtGStates(ctx, resources); + validateShadingPattern(ctx, resources); + validateTilingPattern(ctx, resources); + validateXObjects(ctx, resources); + } } /** diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ShaddingPatternValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ShaddingPatternValidationProcess.java index 94887152b94..915102358ef 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ShaddingPatternValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ShaddingPatternValidationProcess.java @@ -29,10 +29,10 @@ import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace; import org.apache.pdfbox.pdmodel.graphics.shading.PDShadingResources; import org.apache.pdfbox.preflight.PreflightConfiguration; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; @@ -49,15 +49,20 @@ public class ShaddingPatternValidationProcess extends AbstractProcess public void validate(PreflightContext context) throws ValidationException { PreflightPath vPath = context.getValidationPath(); - if (vPath.isEmpty() && !vPath.isExpectedType(PDResources.class)) + if (vPath.isEmpty()) { + return; + } + else if (!vPath.isExpectedType(PDShadingResources.class)) + { + context.addValidationError(new ValidationError(PreflightConstants.ERROR_GRAPHIC_MISSING_OBJECT, "ShadingPattern validation required at least a PDResources")); + } + else { - throw new ValidationException("ShadingPattern validation required at least a PDResources"); + PDShadingResources shaddingResource = (PDShadingResources) vPath.peek(); + PDPage page = vPath.getClosestPathElement(PDPage.class); + checkColorSpace(context, page, shaddingResource); + checkGraphicState(context, page, shaddingResource); } - - PDShadingResources shaddingResource = (PDShadingResources) vPath.peek(); - PDPage page = vPath.getClosestPathElement(PDPage.class); - checkColorSpace(context, page, shaddingResource); - checkGraphicState(context, page, shaddingResource); } /** @@ -73,7 +78,7 @@ public void validate(PreflightContext context) throws ValidationException */ protected void checkColorSpace(PreflightContext context, PDPage page, PDShadingResources shadingRes) throws ValidationException - { + { try { PDColorSpace pColorSpace = shadingRes.getColorSpace(); @@ -86,7 +91,7 @@ protected void checkColorSpace(PreflightContext context, PDPage page, PDShadingR { context.addValidationError(new ValidationError(ERROR_GRAPHIC_INVALID_UNKNOWN_COLOR_SPACE, e.getMessage())); } - } + } /** * Check the Extended Graphic State contains in the ShadingPattern dictionary if it is present. To check this @@ -97,12 +102,12 @@ protected void checkColorSpace(PreflightContext context, PDPage page, PDShadingR */ protected void checkGraphicState(PreflightContext context, PDPage page, PDShadingResources shadingRes) throws ValidationException - { + { COSDictionary resources = (COSDictionary) shadingRes.getCOSDictionary().getDictionaryObject( TRANPARENCY_DICTIONARY_KEY_EXTGSTATE); if (resources != null) { ContextHelper.validateElement(context, resources, EXTGSTATE_PROCESS); } - } + } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/SinglePageValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/SinglePageValidationProcess.java index 4baf39ca066..857016fac6f 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/SinglePageValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/SinglePageValidationProcess.java @@ -47,6 +47,7 @@ import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import org.apache.pdfbox.preflight.PreflightConfiguration; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; @@ -65,22 +66,26 @@ public class SinglePageValidationProcess extends AbstractProcess public void validate(PreflightContext context) throws ValidationException { PreflightPath vPath = context.getValidationPath(); - if (vPath.isEmpty() && !vPath.isExpectedType(PDPage.class)) + if (vPath.isEmpty()){ + return; + } + else if (!vPath.isExpectedType(PDPage.class)) + { + addValidationError(context, new ValidationError(PreflightConstants.ERROR_PDF_PROCESSING_MISSING, "Page validation required at least a PDPage")); + } + else { - throw new ValidationException("Page validation required at least a PDPage"); + PDPage page = (PDPage) vPath.peek(); + validateActions(context, page); + validateAnnotation(context, page); + validateColorSpaces(context, page); + validateResources(context, page); + validateGraphicObjects(context, page); + validateGroupTransparency(context, page); + // TODO + // add MetaData validation ? + validateContent(context, page); } - - PDPage page = (PDPage) vPath.peek(); - validateActions(context, page); - validateAnnotation(context, page); - validateColorSpaces(context, page); - validateResources(context, page); - validateGraphicObjects(context, page); - validateGroupTransparency(context, page); - // TODO - // add MetaData validation ? - - validateContent(context, page); } /** @@ -142,7 +147,7 @@ protected void validateGraphicObjects(PreflightContext context, PDPage page) thr { thumbBase = ((COSObject) thumbBase).getObject(); } - PDXObject thumbImg = PDXObjectImage.createXObject(thumbBase); + PDXObject thumbImg = PDXObjectImage.createThumbnailXObject(thumbBase); ContextHelper.validateElement(context, thumbImg, GRAPHIC_PROCESS); } catch (IOException e) diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/TilingPatternValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/TilingPatternValidationProcess.java index 1fc70c5125b..9991511fa88 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/TilingPatternValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/TilingPatternValidationProcess.java @@ -30,6 +30,7 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.graphics.pattern.PDTilingPatternResources; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; @@ -44,38 +45,43 @@ public class TilingPatternValidationProcess extends AbstractProcess public void validate(PreflightContext context) throws ValidationException { PreflightPath vPath = context.getValidationPath(); - if (vPath.isEmpty() && !vPath.isExpectedType(PDPage.class)) + if (vPath.isEmpty()) { + return; + } + else if (!vPath.isExpectedType(PDTilingPatternResources.class)) { - throw new ValidationException("Tiling pattern validation required at least a PDPage"); + context.addValidationError(new ValidationError(PreflightConstants.ERROR_GRAPHIC_MISSING_OBJECT, "Tiling pattern validation required at least a PDPage")); } + else + { + PDTilingPatternResources tilingPattern = (PDTilingPatternResources) vPath.peek(); + PDPage page = vPath.getClosestPathElement(PDPage.class); - PDTilingPatternResources tilingPattern = (PDTilingPatternResources) vPath.peek(); - PDPage page = vPath.getClosestPathElement(PDPage.class); - - checkMandatoryFields(context, page, tilingPattern); - parseResources(context, page, tilingPattern); - parsePatternContent(context, page, tilingPattern); + checkMandatoryFields(context, page, tilingPattern); + parseResources(context, page, tilingPattern); + parsePatternContent(context, page, tilingPattern); + } } protected void parseResources(PreflightContext context, PDPage page, PDTilingPatternResources pattern) throws ValidationException - { + { PDResources resources = pattern.getResources(); if (resources != null) { ContextHelper.validateElement(context, resources, RESOURCES_PROCESS); } - } + } /** * Validate the Pattern content like Color and Show Text Operators using an instance of ContentStreamWrapper. */ protected void parsePatternContent(PreflightContext context, PDPage page, PDTilingPatternResources pattern) throws ValidationException - { + { ContentStreamWrapper csWrapper = new ContentStreamWrapper(context, page); csWrapper.validPatternContentStream((COSStream) pattern.getCOSObject()); - } + } /** * This method checks if required fields are present. diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/utils/ContextHelper.java b/preflight/src/main/java/org/apache/pdfbox/preflight/utils/ContextHelper.java index c83840d55f7..c9f537ea411 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/utils/ContextHelper.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/utils/ContextHelper.java @@ -21,11 +21,13 @@ package org.apache.pdfbox.preflight.utils; +import static org.apache.pdfbox.preflight.PreflightConstants.*; import org.apache.pdfbox.preflight.PreflightConfiguration; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.exception.ValidationException; import org.apache.pdfbox.preflight.process.ValidationProcess; +import org.apache.pdfbox.preflight.ValidationResult.ValidationError; public class ContextHelper { @@ -39,14 +41,16 @@ public class ContextHelper * @param processName * @throws ValidationException */ - public static void validateElement(PreflightContext context, Object element, String processName) - throws ValidationException + public static void validateElement(PreflightContext context, Object element, String processName) throws ValidationException { if (element == null) { - throw new ValidationException("Unable to process an element if it is null."); + context.addValidationError(new ValidationError(ERROR_PDF_PROCESSING_MISSING, "Unable to process an element if it is null.")); + } + else + { + callValidation(context, element, processName); } - callValidation(context, element, processName); } /** @@ -60,20 +64,16 @@ public static void validateElement(PreflightContext context, Object element, Str * @throws ValidationException */ private static void callValidation(PreflightContext context, Object element, String processName) - throws ValidationException + throws ValidationException { - if (context == null) - { - throw new ValidationException("Unable to process an element without context."); - } - PreflightPath validationPath = context.getValidationPath(); boolean needPop = validationPath.pushObject(element); PreflightConfiguration config = context.getConfig(); ValidationProcess process = config.getInstanceOfProcess(processName); process.validate(context); - if (needPop) + if (needPop) { validationPath.pop(); + } } /** diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/xobject/XObjFormValidator.java b/preflight/src/main/java/org/apache/pdfbox/preflight/xobject/XObjFormValidator.java index bfeeebb7698..2b19f9da833 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/xobject/XObjFormValidator.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/xobject/XObjFormValidator.java @@ -36,6 +36,7 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectForm; +import org.apache.pdfbox.preflight.PreflightConstants; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightPath; import org.apache.pdfbox.preflight.ValidationResult.ValidationError; @@ -116,9 +117,8 @@ protected void validateXObjectContent() throws ValidationException * A Form XObject may contain a Group object (Key =" Group"). If a Group object is present, this method checks if * the S entry is present and if its value is different from "Transparency". * - * @throws ValidationException */ - protected void checkGroup() throws ValidationException + protected void checkGroup() { COSBase baseGroup = this.xobject.getItem(XOBJECT_DICTIONARY_KEY_GROUP); COSDictionary groupDictionary = COSUtils.getAsDictionary(baseGroup, cosDocument); @@ -126,15 +126,16 @@ protected void checkGroup() throws ValidationException { if (!XOBJECT_DICTIONARY_KEY_GROUP.equals(groupDictionary.getNameAsString(COSName.TYPE))) { - throw new ValidationException("The Group dictionary hasn't Group as Type value"); - } - - String sVal = groupDictionary.getNameAsString(COSName.S); - if (sVal == null || XOBJECT_DICTIONARY_VALUE_S_TRANSPARENCY.equals(sVal)) + context.addValidationError(new ValidationError(PreflightConstants.ERROR_GRAPHIC_MISSING_FIELD, "The Group dictionary hasn't Group as Type value")); + } + else { - context.addValidationError(new ValidationError(ERROR_GRAPHIC_TRANSPARENCY_GROUP, - "Group has a transparency S entry or the S entry is null.")); - return; + String sVal = groupDictionary.getNameAsString(COSName.S); + if (sVal == null || XOBJECT_DICTIONARY_VALUE_S_TRANSPARENCY.equals(sVal)) + { + context.addValidationError(new ValidationError(ERROR_GRAPHIC_TRANSPARENCY_GROUP, "Group has a transparency S entry or the S entry is null.")); + return; + } } } } From a004e99c5c07a663bb683ef20bb4f53157f39c9e Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 17 Nov 2013 15:27:33 +0000 Subject: [PATCH 0026/1017] merged the following changes from trunk into 1.8 branch: PDFBOX-1342 r1536136 PDFBOX-1412 r1536173 PDFBOX-1633 r1540801 PDFBOX-1743 r1530740 PDFBOX-1744 r1536463 PDFBOX-1749 r1535953 PDFBOX-1753 r1535956 PDFBOX-1758 r1538371 PDFBOX-1764 r1538191 PDFBOX-1776 r1541625,r1542291 PDFBOX-1780 r1541987 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1542748 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdfbox/examples/fdf/PrintFields.java | 102 +- .../signature/CreateVisibleSignature.java | 265 +++++ .../org/apache/fontbox/cff/CFFParser.java | 282 +++-- .../apache/fontbox/ttf/GlyfCompositeComp.java | 94 +- .../fontbox/ttf/GlyfCompositeDescript.java | 418 ++++---- .../fontbox/ttf/GlyfSimpleDescript.java | 93 +- .../fontbox/ttf/OS2WindowsMetricsTable.java | 92 +- .../apache/fontbox/ttf/PostScriptTable.java | 79 +- .../org/apache/fontbox/ttf/TTFDataStream.java | 121 ++- .../java/org/apache/pdfbox/cos/COSName.java | 29 +- .../apache/pdfbox/pdfparser/BaseParser.java | 3 +- .../apache/pdfbox/pdfparser/PDFParser.java | 39 +- .../apache/pdfbox/pdfwriter/COSWriter.java | 12 +- .../org/apache/pdfbox/pdmodel/PDPage.java | 20 + .../apache/pdfbox/pdmodel/PDResources.java | 334 +++--- .../pdmodel/common/function/PDFunction.java | 24 +- .../common/function/PDFunctionType0.java | 43 +- .../logicalstructure/PDStructureTreeRoot.java | 103 +- .../graphics/shading/AxialShadingContext.java | 43 +- .../shading/RadialShadingContext.java | 43 +- .../pdmodel/graphics/xobject/PDXObject.java | 116 +- .../graphics/xobject/PDXObjectForm.java | 98 +- .../interactive/annotation/PDAnnotation.java | 352 ++++--- .../digitalsignature/SignatureOptions.java | 15 + .../visible/PDFTemplateBuilder.java | 249 +++++ .../visible/PDFTemplateCreator.java | 174 +++ .../visible/PDFTemplateStructure.java | 616 +++++++++++ .../visible/PDVisibleSigBuilder.java | 371 +++++++ .../visible/PDVisibleSigProperties.java | 210 ++++ .../visible/PDVisibleSignDesigner.java | 464 ++++++++ .../digitalsignature/visible/package.html | 25 + .../pdmodel/interactive/form/PDField.java | 369 ++++--- .../org/apache/pdfbox/util/DateConverter.java | 990 +++++++++++++----- .../apache/pdfbox/util/PDFMergerUtility.java | 417 ++++++-- .../org/apache/pdfbox/util/TestDateUtil.java | 413 +++++++- .../FileSpecificationValidationProcess.java | 2 +- 36 files changed, 5446 insertions(+), 1674 deletions(-) create mode 100644 examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateBuilder.java create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateCreator.java create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateStructure.java create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigBuilder.java create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigProperties.java create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSignDesigner.java create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/package.html diff --git a/examples/src/main/java/org/apache/pdfbox/examples/fdf/PrintFields.java b/examples/src/main/java/org/apache/pdfbox/examples/fdf/PrintFields.java index 8a456241717..f294e7681c4 100644 --- a/examples/src/main/java/org/apache/pdfbox/examples/fdf/PrintFields.java +++ b/examples/src/main/java/org/apache/pdfbox/examples/fdf/PrintFields.java @@ -17,36 +17,35 @@ 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; +import org.apache.pdfbox.pdmodel.common.COSObjectable; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; /** * This example will take a PDF document and print all the fields from the file. - * + * * @author Ben Litchfield - * @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 + public void printFields(PDDocument pdfDocument) throws IOException { PDDocumentCatalog docCatalog = pdfDocument.getDocumentCatalog(); PDAcroForm acroForm = docCatalog.getAcroForm(); @@ -55,51 +54,59 @@ public void printFields( PDDocument pdfDocument ) throws IOException System.out.println(new Integer(fields.size()).toString() + " top-level fields were found on the form"); - while( fieldsIter.hasNext()) + while (fieldsIter.hasNext()) { - PDField field = (PDField)fieldsIter.next(); - processField(field, "|--", field.getPartialName()); + 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) + List kids = field.getKids(); + if (kids != null) { - Iterator kidsIter = kids.iterator(); - if(!sParent.equals(field.getPartialName())) + Iterator kidsIter = kids.iterator(); + if (!sParent.equals(field.getPartialName())) { - sParent = sParent + "." + field.getPartialName(); + sParent = sParent + "." + field.getPartialName(); } System.out.println(sLevel + sParent); - //System.out.println(sParent + " is of type " + field.getClass().getName()); - while(kidsIter.hasNext()) + // 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 fieldValue = null; + if (field instanceof PDSignatureField) { - Object pdfObj = kidsIter.next(); - if(pdfObj instanceof PDField) - { - PDField kid = (PDField)pdfObj; - processField(kid, "| " + sLevel, sParent); - } + // PDSignature doesn't have a value + fieldValue = "PDSignatureField"; } - } - else - { - String outputString = sLevel + sParent + "." + field.getPartialName() + " = " + field.getValue() + - ", type=" + field.getClass().getName(); - - System.out.println(outputString); - } + else + { + fieldValue = field.getValue(); + } + String outputString = sLevel + sParent + "." + field.getPartialName() + " = " + fieldValue + ", type=" + + field.getClass().getName(); + System.out.println(outputString); + } } /** - * This will read a PDF file and print out the form elements. - *
+ * This will read a PDF file and print out the form elements.
* 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. */ @@ -108,42 +115,43 @@ public static void main(String[] args) throws IOException, CryptographyException PDDocument pdf = null; try { - if( args.length != 1 ) + if (args.length != 1) { usage(); } else { - pdf = PDDocument.load( args[0] ); + pdf = PDDocument.load(args[0]); PrintFields exporter = new PrintFields(); - if( pdf.isEncrypted() ) + if (pdf.isEncrypted()) { try { - pdf.decrypt( "" ); + pdf.decrypt(""); } - catch( InvalidPasswordException e ) + catch (InvalidPasswordException e) { - System.err.println( "Error: The document is encrypted." ); + System.err.println("Error: The document is encrypted."); usage(); } } - exporter.printFields( pdf ); + exporter.printFields(pdf); } } finally { - if( pdf != null ) + 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 " ); + System.err.println("usage: org.apache.pdfbox.examples.fdf.PrintFields "); } } diff --git a/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java b/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java new file mode 100644 index 00000000000..21199c6fe64 --- /dev/null +++ b/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java @@ -0,0 +1,265 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CollectionCertStoreParameters; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Enumeration; +import java.util.List; + +import org.apache.pdfbox.exceptions.COSVisitorException; +import org.apache.pdfbox.exceptions.SignatureException; +import org.apache.pdfbox.pdmodel.PDDocument; +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.digitalsignature.visible.PDVisibleSigProperties; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSignDesigner; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.CMSSignedDataGenerator; +import org.bouncycastle.cms.CMSSignedGenerator; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +/** + *

+ * This is an example for signing a pdf with bouncy castle. + *

+ *

+ * And also you can create visible signature too + *

+ *

+ * A keystore can be created with the java keytool (e.g. keytool -genkeypair -storepass 123456 -storetype pkcs12 -alias + * test -validity 365 -v -keyalg RSA -keystore keystore.p12 ) + *

+ * + * @author Vakhtang koroghlishvili (Gogebashvili) + */ +public class CreateVisibleSignature implements SignatureInterface +{ + + private static BouncyCastleProvider provider = new BouncyCastleProvider(); + + private PrivateKey privKey; + + private Certificate[] cert; + + private SignatureOptions options; + + /** + * Initialize the signature creator with a keystore (pkcs12) and pin that + * should be used for the signature. + * + * @param keystore + * is a pkcs12 keystore. + * @param pin + * is the pin for the keystore / private key + */ + public CreateVisibleSignature(KeyStore keystore, char[] pin) + { + try { + /* + * grabs the first alias from the keystore and get the private key. An + * alternative method or constructor could be used for setting a specific + * alias that should be used. + */ + Enumeration aliases = keystore.aliases(); + String alias = null; + if (aliases.hasMoreElements()) { + alias = aliases.nextElement(); + } else { + throw new RuntimeException("Could not find alias"); + } + privKey = (PrivateKey) keystore.getKey(alias, pin); + cert = keystore.getCertificateChain(alias); + } catch (KeyStoreException e) { + e.printStackTrace(); + } catch (UnrecoverableKeyException e) { + System.err.println("Could not extract private key."); + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + System.err.println("Unknown algorithm."); + e.printStackTrace(); + } + } + + /** + * Signs the given pdf file. + * + * @param document is the pdf document + * @param signatureProperties + * @return the signed pdf document + * @throws IOException + * @throws COSVisitorException + * @throws SignatureException + */ + public File signPDF(File document, PDVisibleSigProperties signatureProperties) throws IOException, + COSVisitorException, SignatureException + { + byte[] buffer = new byte[8 * 1024]; + if (document == null || !document.exists()) { + new RuntimeException("Document for signing does not exist"); + } + + // creating output document and prepare the IO streams. + String name = document.getName(); + String substring = name.substring(0, name.lastIndexOf(".")); + + File outputDocument = new File(document.getParent(), substring + "_signed.pdf"); + FileInputStream fis = new FileInputStream(document); + FileOutputStream fos = new FileOutputStream(outputDocument); + + int c; + while ((c = fis.read(buffer)) != -1) { + fos.write(buffer, 0, c); + } + fis.close(); + fis = new FileInputStream(outputDocument); + + // load document + PDDocument doc = PDDocument.load(document); + + // create signature dictionary + PDSignature signature = new PDSignature(); + signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter + // subfilter for basic and PAdES Part 2 signatures + signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); + signature.setName("signer name"); + signature.setLocation("signer location"); + signature.setReason("reason for signature"); + + // the signing date, needed for valid signature + signature.setSignDate(Calendar.getInstance()); + + // register signature dictionary and sign interface + + if (signatureProperties != null && signatureProperties.isVisualSignEnabled()) { + options = new SignatureOptions(); + options.setVisualSignature(signatureProperties); + // options.setPage(signatureProperties.getPage()); + // options.setPreferedSignatureSize(signatureProperties.getPreferredSize()); + doc.addSignature(signature, this, options); + } else { + doc.addSignature(signature, this); + } + + // write incremental (only for signing purpose) + doc.saveIncremental(fis, fos); + + return outputDocument; + } + + /** + *

+ * SignatureInterface implementation. + *

+ *

+ * This method will be called from inside of the pdfbox and create the pkcs7 signature. The given InputStream contains + * the bytes that are providen by the byte range. + *

+ *

+ * This method is for internal use only. + *

+ *

+ * Here the user should use his favorite cryptographic library and implement a pkcs7 signature creation. + *

+ */ + public byte[] sign(InputStream content) throws SignatureException, IOException + { + CMSProcessableInputStream input = new CMSProcessableInputStream(content); + CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); + // CertificateChain + List certList = Arrays.asList(cert); + + CertStore certStore = null; + try { + certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), provider); + gen.addSigner(privKey, (X509Certificate) certList.get(0), CMSSignedGenerator.DIGEST_SHA256); + gen.addCertificatesAndCRLs(certStore); + CMSSignedData signedData = gen.generate(input, false, provider); + return signedData.getEncoded(); + } catch (Exception e) { + // should be handled + System.err.println("Error while creating pkcs7 signature."); + e.printStackTrace(); + } + throw new RuntimeException("Problem while preparing signature"); + } + + /** + * Arguments are + * [0] key store + * [1] pin + * [2] document that will be signed + * [3] image of visible signature + */ + public static void main(String[] args) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, + FileNotFoundException, IOException, COSVisitorException, SignatureException + { + + if (args.length != 4) { + usage(); + System.exit(1); + } else { + File ksFile = new File(args[0]); + KeyStore keystore = KeyStore.getInstance("PKCS12", provider); + char[] pin = args[1].toCharArray(); + keystore.load(new FileInputStream(ksFile), pin); + + File document = new File(args[2]); + + CreateVisibleSignature signing = new CreateVisibleSignature(keystore, pin.clone()); + + FileInputStream image = new FileInputStream(args[3]); + + PDVisibleSignDesigner visibleSig = new PDVisibleSignDesigner(args[2], image, 1); + visibleSig.xAxis(0).yAxis(0).zoom(-50).signatureFieldName("signature"); + + PDVisibleSigProperties signatureProperties = new PDVisibleSigProperties(); + + signatureProperties.signerName("name").signerLocation("location").signatureReason("Security").preferredSize(0) + .page(1).visualSignEnabled(true).setPdVisibleSignature(visibleSig).buildSignature(); + + signing.signPDF(document, signatureProperties); + } + + } + + /** + * This will print the usage for this program. + */ + private static void usage() + { + System.err.println("Usage: java " + CreateSignature.class.getName() + + " "); + } +} diff --git a/fontbox/src/main/java/org/apache/fontbox/cff/CFFParser.java b/fontbox/src/main/java/org/apache/fontbox/cff/CFFParser.java index 6f5500a3f89..7c527135a48 100644 --- a/fontbox/src/main/java/org/apache/fontbox/cff/CFFParser.java +++ b/fontbox/src/main/java/org/apache/fontbox/cff/CFFParser.java @@ -20,8 +20,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedList; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -43,6 +43,10 @@ public class CFFParser { + private static final String TAG_OTTO = "OTTO"; + private static final String TAG_TTCF = "ttcf"; + private static final String TAG_TTFONLY = "\u0000\u0001\u0000\u0000"; + private CFFDataInput input = null; private Header header = null; private IndexData nameIndex = null; @@ -58,9 +62,10 @@ public class CFFParser public List parse(byte[] bytes) throws IOException { input = new CFFDataInput(bytes); - + String firstTag = readTagName(input); - if (firstTag.equals("OTTO")) + // try to determine which kind of font we have + if (TAG_OTTO.equals(firstTag)) { // this is OpenType font containing CFF data // so find CFF tag @@ -68,35 +73,41 @@ public List parse(byte[] bytes) throws IOException short searchRange = input.readShort(); short entrySelector = input.readShort(); short rangeShift = input.readShort(); - + boolean cffFound = false; - for (int q=0; q parse(byte[] bytes) throws IOException } return fonts; } - - private static String readTagName(CFFDataInput input) throws IOException + + private static String readTagName(CFFDataInput input) throws IOException { - byte[] b=input.readBytes(4); + byte[] b = input.readBytes(4); return new String(b); } - private static long readLong(CFFDataInput input) throws IOException + private static long readLong(CFFDataInput input) throws IOException { - return (input.readCard16()<<16) | input.readCard16(); + return (input.readCard16() << 16) | input.readCard16(); } private static Header readHeader(CFFDataInput input) throws IOException @@ -134,8 +145,7 @@ private static Header readHeader(CFFDataInput input) throws IOException return cffHeader; } - private static IndexData readIndexData(CFFDataInput input) - throws IOException + private static IndexData readIndexData(CFFDataInput input) throws IOException { int count = input.readCard16(); IndexData index = new IndexData(count); @@ -169,8 +179,7 @@ private static DictData readDictData(CFFDataInput input) throws IOException return dict; } - private static DictData.Entry readEntry(CFFDataInput input) - throws IOException + private static DictData.Entry readEntry(CFFDataInput input) throws IOException { DictData.Entry entry = new DictData.Entry(); while (true) @@ -181,19 +190,19 @@ private static DictData.Entry readEntry(CFFDataInput input) { entry.operator = readOperator(input, b0); break; - } + } else if (b0 == 28 || b0 == 29) { entry.operands.add(readIntegerNumber(input, b0)); - } + } else if (b0 == 30) { entry.operands.add(readRealNumber(input, b0)); - } + } else if (b0 >= 32 && b0 <= 254) { entry.operands.add(readIntegerNumber(input, b0)); - } + } else { throw new IllegalArgumentException(); @@ -202,15 +211,13 @@ else if (b0 >= 32 && b0 <= 254) return entry; } - private static CFFOperator readOperator(CFFDataInput input, int b0) - throws IOException + private static CFFOperator readOperator(CFFDataInput input, int b0) throws IOException { CFFOperator.Key key = readOperatorKey(input, b0); return CFFOperator.getOperator(key); } - private static CFFOperator.Key readOperatorKey(CFFDataInput input, int b0) - throws IOException + private static CFFOperator.Key readOperatorKey(CFFDataInput input, int b0) throws IOException { if (b0 == 12) { @@ -220,15 +227,14 @@ private static CFFOperator.Key readOperatorKey(CFFDataInput input, int b0) return new CFFOperator.Key(b0); } - private static Integer readIntegerNumber(CFFDataInput input, int b0) - throws IOException + private static Integer readIntegerNumber(CFFDataInput input, int b0) throws IOException { if (b0 == 28) { int b1 = input.readUnsignedByte(); int b2 = input.readUnsignedByte(); return Integer.valueOf((short) (b1 << 8 | b2)); - } + } else if (b0 == 29) { int b1 = input.readUnsignedByte(); @@ -236,29 +242,28 @@ else if (b0 == 29) int b3 = input.readUnsignedByte(); int b4 = input.readUnsignedByte(); return Integer.valueOf(b1 << 24 | b2 << 16 | b3 << 8 | b4); - } + } else if (b0 >= 32 && b0 <= 246) { return Integer.valueOf(b0 - 139); - } + } else if (b0 >= 247 && b0 <= 250) { int b1 = input.readUnsignedByte(); return Integer.valueOf((b0 - 247) * 256 + b1 + 108); - } + } else if (b0 >= 251 && b0 <= 254) { int b1 = input.readUnsignedByte(); return Integer.valueOf(-(b0 - 251) * 256 - b1 - 108); - } + } else { throw new IllegalArgumentException(); } } - private static Double readRealNumber(CFFDataInput input, int b0) - throws IOException + private static Double readRealNumber(CFFDataInput input, int b0) throws IOException { StringBuffer sb = new StringBuffer(); boolean done = false; @@ -336,9 +341,9 @@ private CFFFont parseFont(int index) throws IOException if (rosEntry != null) { font = new CFFFontROS(); - ((CFFFontROS)font).setRegistry(readString(rosEntry.getNumber(0).intValue())); - ((CFFFontROS)font).setOrdering(readString(rosEntry.getNumber(1).intValue())); - ((CFFFontROS)font).setSupplement(rosEntry.getNumber(2).intValue()); + ((CFFFontROS) font).setRegistry(readString(rosEntry.getNumber(0).intValue())); + ((CFFFontROS) font).setOrdering(readString(rosEntry.getNumber(1).intValue())); + ((CFFFontROS) font).setSupplement(rosEntry.getNumber(2).intValue()); } if (font == null) @@ -346,29 +351,36 @@ private CFFFont parseFont(int index) throws IOException // -- No specific behavior for this font font = new CFFFont(); } - + font.setName(name); - font.addValueToTopDict("version", getString(topDict,"version")); - font.addValueToTopDict("Notice", getString(topDict,"Notice")); - font.addValueToTopDict("Copyright", getString(topDict,"Copyright")); - font.addValueToTopDict("FullName", getString(topDict,"FullName")); - font.addValueToTopDict("FamilyName", getString(topDict,"FamilyName")); - font.addValueToTopDict("Weight", getString(topDict,"Weight")); + font.addValueToTopDict("version", getString(topDict, "version")); + font.addValueToTopDict("Notice", getString(topDict, "Notice")); + font.addValueToTopDict("Copyright", getString(topDict, "Copyright")); + font.addValueToTopDict("FullName", getString(topDict, "FullName")); + font.addValueToTopDict("FamilyName", getString(topDict, "FamilyName")); + font.addValueToTopDict("Weight", getString(topDict, "Weight")); font.addValueToTopDict("isFixedPitch", getBoolean(topDict, "isFixedPitch", false)); font.addValueToTopDict("ItalicAngle", getNumber(topDict, "ItalicAngle", 0)); font.addValueToTopDict("UnderlinePosition", getNumber(topDict, "UnderlinePosition", -100)); font.addValueToTopDict("UnderlineThickness", getNumber(topDict, "UnderlineThickness", 50)); font.addValueToTopDict("PaintType", getNumber(topDict, "PaintType", 0)); font.addValueToTopDict("CharstringType", getNumber(topDict, "CharstringType", 2)); - font.addValueToTopDict("FontMatrix", getArray(topDict, "FontMatrix", Arrays - . asList(Double.valueOf(0.001), Double.valueOf(0), - Double.valueOf(0), Double.valueOf(0.001), Double - .valueOf(0), Double.valueOf(0)))); + font.addValueToTopDict( + "FontMatrix", + getArray( + topDict, + "FontMatrix", + Arrays. asList(Double.valueOf(0.001), Double.valueOf(0), Double.valueOf(0), + Double.valueOf(0.001), Double.valueOf(0), Double.valueOf(0)))); font.addValueToTopDict("UniqueID", getNumber(topDict, "UniqueID", null)); - font.addValueToTopDict("FontBBox", getArray(topDict, "FontBBox", Arrays - . asList(Integer.valueOf(0), Integer.valueOf(0), - Integer.valueOf(0), Integer.valueOf(0)))); + font.addValueToTopDict( + "FontBBox", + getArray( + topDict, + "FontBBox", + Arrays. asList(Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0), + Integer.valueOf(0)))); font.addValueToTopDict("StrokeWidth", getNumber(topDict, "StrokeWidth", 0)); font.addValueToTopDict("XUID", getArray(topDict, "XUID", null)); @@ -378,20 +390,19 @@ private CFFFont parseFont(int index) throws IOException IndexData charStringsIndex = readIndexData(input); DictData.Entry charsetEntry = topDict.getEntry("charset"); CFFCharset charset; - int charsetId = charsetEntry != null ? charsetEntry.getNumber(0) - .intValue() : 0; + int charsetId = charsetEntry != null ? charsetEntry.getNumber(0).intValue() : 0; if (charsetId == 0) { charset = CFFISOAdobeCharset.getInstance(); - } + } else if (charsetId == 1) { charset = CFFExpertCharset.getInstance(); - } + } else if (charsetId == 2) { charset = CFFExpertSubsetCharset.getInstance(); - } + } else { input.setPosition(charsetId); @@ -413,18 +424,17 @@ else if (charsetId == 2) if (encodingId == 0 || rosEntry != null) // --- ROS uses StandardEncoding { encoding = CFFStandardEncoding.getInstance(); - } + } else if (encodingId == 1) { encoding = CFFExpertEncoding.getInstance(); - } + } else { input.setPosition(encodingId); encoding = readEncoding(input, gids); } font.setEncoding(encoding); - if (rosEntry != null) { @@ -442,7 +452,7 @@ else if (encodingId == 1) List> privateDictionaries = new LinkedList>(); List> fontDictionaries = new LinkedList>(); - CFFFontROS fontRos = (CFFFontROS)font; + CFFFontROS fontRos = (CFFFontROS) font; for (int i = 0; i < fdIndex.getCount(); ++i) { @@ -457,10 +467,10 @@ else if (encodingId == 1) fontDictMap.put("FontMatrix", getDelta(fontDictData, "FontMatrix", null)); // TODO OD-4 : Add here other keys fontDictionaries.add(fontDictMap); - + DictData.Entry privateEntry = fontDictData.getEntry("Private"); // --- Font DICT is invalid without "Private" entry - if ( privateEntry == null) + if (privateEntry == null) { throw new IOException("Missing Private Dictionary"); } @@ -490,7 +500,7 @@ else if (encodingId == 1) privDict.put("defaultWidthX", getNumber(privateDict, "defaultWidthX", Integer.valueOf(0))); privDict.put("nominalWidthX", getNumber(privateDict, "nominalWidthX", Integer.valueOf(0))); - int localSubrOffset = (Integer)getNumber(privateDict, "Subrs", Integer.valueOf(0)); + int localSubrOffset = (Integer) getNumber(privateDict, "Subrs", Integer.valueOf(0)); if (localSubrOffset == 0) { font.setLocalSubrIndex(new IndexData(0)); @@ -500,13 +510,13 @@ else if (encodingId == 1) input.setPosition(privateOffset + localSubrOffset); font.setLocalSubrIndex(readIndexData(input)); } - + privateDictionaries.add(privDict); } fontRos.setFontDict(fontDictionaries); fontRos.setPrivDict(privateDictionaries); - + DictData.Entry fdSelectEntry = topDict.getEntry("FDSelect"); int fdSelectPos = fdSelectEntry.getNumber(0).intValue(); input.setPosition(fdSelectPos); @@ -514,7 +524,7 @@ else if (encodingId == 1) font.addValueToPrivateDict("defaultWidthX", Integer.valueOf(1000)); font.addValueToPrivateDict("nominalWidthX", Integer.valueOf(0)); - + fontRos.setFdSelect(fdSelect); } @@ -539,14 +549,14 @@ else if (encodingId == 1) font.addValueToPrivateDict("StemSnapV", getDelta(privateDict, "StemSnapV", null)); font.addValueToPrivateDict("ForceBold", getBoolean(privateDict, "ForceBold", false)); font.addValueToPrivateDict("LanguageGroup", getNumber(privateDict, "LanguageGroup", Integer.valueOf(0))); - font.addValueToPrivateDict("ExpansionFactor", + font.addValueToPrivateDict("ExpansionFactor", getNumber(privateDict, "ExpansionFactor", Double.valueOf(0.06))); - font.addValueToPrivateDict("initialRandomSeed", + font.addValueToPrivateDict("initialRandomSeed", getNumber(privateDict, "initialRandomSeed", Integer.valueOf(0))); font.addValueToPrivateDict("defaultWidthX", getNumber(privateDict, "defaultWidthX", Integer.valueOf(0))); font.addValueToPrivateDict("nominalWidthX", getNumber(privateDict, "nominalWidthX", Integer.valueOf(0))); - - int localSubrOffset = (Integer)getNumber(privateDict, "Subrs", Integer.valueOf(0)); + + int localSubrOffset = (Integer) getNumber(privateDict, "Subrs", Integer.valueOf(0)); if (localSubrOffset == 0) { font.setLocalSubrIndex(new IndexData(0)); @@ -610,8 +620,7 @@ private List getDelta(DictData dict, String name, List defaultVa return entry != null ? entry.getArray() : defaultValue; } - private CFFEncoding readEncoding(CFFDataInput dataInput, int[] gids) - throws IOException + private CFFEncoding readEncoding(CFFDataInput dataInput, int[] gids) throws IOException { int format = dataInput.readCard8(); int baseFormat = format & 0x7f; @@ -619,19 +628,18 @@ private CFFEncoding readEncoding(CFFDataInput dataInput, int[] gids) if (baseFormat == 0) { return readFormat0Encoding(dataInput, format, gids); - } + } else if (baseFormat == 1) { return readFormat1Encoding(dataInput, format, gids); - } + } else { throw new IllegalArgumentException(); } } - private Format0Encoding readFormat0Encoding(CFFDataInput dataInput, int format, - int[] gids) throws IOException + private Format0Encoding readFormat0Encoding(CFFDataInput dataInput, int format, int[] gids) throws IOException { Format0Encoding encoding = new Format0Encoding(); encoding.format = format; @@ -649,8 +657,7 @@ private Format0Encoding readFormat0Encoding(CFFDataInput dataInput, int format, return encoding; } - private Format1Encoding readFormat1Encoding(CFFDataInput dataInput, int format, - int[] gids) throws IOException + private Format1Encoding readFormat1Encoding(CFFDataInput dataInput, int format, int[] gids) throws IOException { Format1Encoding encoding = new Format1Encoding(); encoding.format = format; @@ -676,8 +683,7 @@ private Format1Encoding readFormat1Encoding(CFFDataInput dataInput, int format, return encoding; } - private void readSupplement(CFFDataInput dataInput, EmbeddedEncoding encoding) - throws IOException + private void readSupplement(CFFDataInput dataInput, EmbeddedEncoding encoding) throws IOException { encoding.nSups = dataInput.readCard8(); encoding.supplement = new EmbeddedEncoding.Supplement[encoding.nSups]; @@ -698,18 +704,17 @@ private void readSupplement(CFFDataInput dataInput, EmbeddedEncoding encoding) * @return * @throws IOException */ - private CIDKeyedFDSelect readFDSelect(CFFDataInput dataInput, int nGlyphs, CFFFontROS ros) - throws IOException + private CIDKeyedFDSelect readFDSelect(CFFDataInput dataInput, int nGlyphs, CFFFontROS ros) throws IOException { int format = dataInput.readCard8(); if (format == 0) { return readFormat0FDSelect(dataInput, format, nGlyphs, ros); - } + } else if (format == 3) { return readFormat3FDSelect(dataInput, format, nGlyphs, ros); - } + } else { throw new IllegalArgumentException(); @@ -769,7 +774,7 @@ private Format3FDSelect readFormat3FDSelect(CFFDataInput dataInput, int format, fdselect.sentinel = dataInput.readCard16(); return fdselect; } - + /** * Container of a Format 3 FDSelect data (see "The Compact Font Format Specification" chapter "FDSelect" ). */ @@ -785,7 +790,9 @@ private Format3FDSelect(CFFFontROS owner) super(owner); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.apache.fontbox.cff.CIDKeyedFDSelect#getFd(int) */ @Override @@ -795,9 +802,9 @@ public int getFd(int glyph) { if (range3[i].first >= glyph) { - if (i + 1 < nbRanges ) + if (i + 1 < nbRanges) { - if (range3[i+1].first > glyph ) + if (range3[i + 1].first > glyph) { return range3[i].fd; } @@ -812,7 +819,7 @@ public int getFd(int glyph) if (sentinel > glyph) { return range3[i].fd; - } + } else { return -1; @@ -838,14 +845,14 @@ private static class Range3 { private int first; private int fd; - + @Override public String toString() { return getClass().getName() + "[first=" + first + ", fd=" + fd + "]"; } } - + /** * Container of a Format 0 FDSelect data (see "The Compact Font Format Specification" chapter "FDSelect" ). */ @@ -859,7 +866,9 @@ private Format0FDSelect(CFFFontROS owner) super(owner); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.apache.fontbox.cff.CIDKeyedFDSelect#getFd(int) */ @Override @@ -868,7 +877,7 @@ public int getFd(int glyph) Map charString = owner.getCharStringsDict(); Set keys = charString.keySet(); // ---- search the position of the given glyph - for (Mapping mapping: owner.getMappings()) + for (Mapping mapping : owner.getMappings()) { if (mapping.getSID() == glyph && charString.containsKey(mapping.getName())) { @@ -889,23 +898,21 @@ public int getFd(int glyph) @Override public String toString() { - return getClass().getName() + "[format=" + format + ", fds=" - + Arrays.toString(fds) + "]"; + return getClass().getName() + "[format=" + format + ", fds=" + Arrays.toString(fds) + "]"; } } - - private CFFCharset readCharset(CFFDataInput dataInput, int nGlyphs) - throws IOException + + private CFFCharset readCharset(CFFDataInput dataInput, int nGlyphs) throws IOException { int format = dataInput.readCard8(); if (format == 0) { return readFormat0Charset(dataInput, format, nGlyphs); - } + } else if (format == 1) { return readFormat1Charset(dataInput, format, nGlyphs); - } + } else if (format == 2) { return readFormat2Charset(dataInput, format, nGlyphs); @@ -916,8 +923,7 @@ else if (format == 2) } } - private Format0Charset readFormat0Charset(CFFDataInput dataInput, int format, - int nGlyphs) throws IOException + private Format0Charset readFormat0Charset(CFFDataInput dataInput, int format, int nGlyphs) throws IOException { Format0Charset charset = new Format0Charset(); charset.format = format; @@ -930,8 +936,7 @@ private Format0Charset readFormat0Charset(CFFDataInput dataInput, int format, return charset; } - private Format1Charset readFormat1Charset(CFFDataInput dataInput, int format, - int nGlyphs) throws IOException + private Format1Charset readFormat1Charset(CFFDataInput dataInput, int format, int nGlyphs) throws IOException { Format1Charset charset = new Format1Charset(); charset.format = format; @@ -951,9 +956,8 @@ private Format1Charset readFormat1Charset(CFFDataInput dataInput, int format, charset.range = ranges.toArray(new Format1Charset.Range1[0]); return charset; } - - private Format2Charset readFormat2Charset(CFFDataInput dataInput, int format, - int nGlyphs) throws IOException + + private Format2Charset readFormat2Charset(CFFDataInput dataInput, int format, int nGlyphs) throws IOException { Format2Charset charset = new Format2Charset(); charset.format = format; @@ -961,8 +965,7 @@ private Format2Charset readFormat2Charset(CFFDataInput dataInput, int format, for (int i = 0; i < nGlyphs - 1;) { Format2Charset.Range2[] newRange = new Format2Charset.Range2[charset.range.length + 1]; - System.arraycopy(charset.range, 0, newRange, 0, - charset.range.length); + System.arraycopy(charset.range, 0, newRange, 0, charset.range.length); charset.range = newRange; Format2Charset.Range2 range = new Format2Charset.Range2(); range.first = dataInput.readSID(); @@ -990,9 +993,8 @@ private static class Header @Override public String toString() { - return getClass().getName() + "[major=" + major + ", minor=" - + minor + ", hdrSize=" + hdrSize + ", offSize=" + offSize - + "]"; + return getClass().getName() + "[major=" + major + ", minor=" + minor + ", hdrSize=" + hdrSize + + ", offSize=" + offSize + "]"; } } @@ -1019,8 +1021,7 @@ private Entry getEntry(CFFOperator operator) for (Entry entry : entries) { // Check for null entry before comparing the Font - if (entry != null && entry.operator != null && - entry.operator.equals(operator)) + if (entry != null && entry.operator != null && entry.operator.equals(operator)) { return entry; } @@ -1093,8 +1094,7 @@ public List getDelta() @Override public String toString() { - return getClass().getName() + "[operands=" + operands - + ", operator=" + operator + "]"; + return getClass().getName() + "[operands=" + operands + ", operator=" + operator + "]"; } } } @@ -1116,8 +1116,9 @@ public boolean isFontSpecific() List getSupplements() { - if(supplement == null){ - return Collections.emptyList(); + if (supplement == null) + { + return Collections. emptyList(); } return Arrays.asList(supplement); } @@ -1143,8 +1144,7 @@ int getGlyph() @Override public String toString() { - return getClass().getName() + "[code=" + code + ", glyph=" - + glyph + "]"; + return getClass().getName() + "[code=" + code + ", glyph=" + glyph + "]"; } } } @@ -1161,9 +1161,8 @@ private static class Format0Encoding extends EmbeddedEncoding @Override public String toString() { - return getClass().getName() + "[format=" + format + ", nCodes=" - + nCodes + ", code=" + Arrays.toString(code) - + ", supplement=" + Arrays.toString(super.supplement) + "]"; + return getClass().getName() + "[format=" + format + ", nCodes=" + nCodes + ", code=" + + Arrays.toString(code) + ", supplement=" + Arrays.toString(super.supplement) + "]"; } } @@ -1179,9 +1178,8 @@ private static class Format1Encoding extends EmbeddedEncoding @Override public String toString() { - return getClass().getName() + "[format=" + format + ", nRanges=" - + nRanges + ", range=" + Arrays.toString(range) - + ", supplement=" + Arrays.toString(super.supplement) + "]"; + return getClass().getName() + "[format=" + format + ", nRanges=" + nRanges + ", range=" + + Arrays.toString(range) + ", supplement=" + Arrays.toString(super.supplement) + "]"; } /** @@ -1195,8 +1193,7 @@ private static class Range1 @Override public String toString() { - return getClass().getName() + "[first=" + first + ", nLeft=" - + nLeft + "]"; + return getClass().getName() + "[first=" + first + ", nLeft=" + nLeft + "]"; } } } @@ -1224,8 +1221,7 @@ private static class Format0Charset extends EmbeddedCharset @Override public String toString() { - return getClass().getName() + "[format=" + format + ", glyph=" - + Arrays.toString(glyph) + "]"; + return getClass().getName() + "[format=" + format + ", glyph=" + Arrays.toString(glyph) + "]"; } } @@ -1240,8 +1236,7 @@ private static class Format1Charset extends EmbeddedCharset @Override public String toString() { - return getClass().getName() + "[format=" + format + ", range=" - + Arrays.toString(range) + "]"; + return getClass().getName() + "[format=" + format + ", range=" + Arrays.toString(range) + "]"; } /** @@ -1255,8 +1250,7 @@ private static class Range1 @Override public String toString() { - return getClass().getName() + "[first=" + first + ", nLeft=" - + nLeft + "]"; + return getClass().getName() + "[first=" + first + ", nLeft=" + nLeft + "]"; } } } @@ -1272,8 +1266,7 @@ private static class Format2Charset extends EmbeddedCharset @Override public String toString() { - return getClass().getName() + "[format=" + format + ", range=" - + Arrays.toString(range) + "]"; + return getClass().getName() + "[format=" + format + ", range=" + Arrays.toString(range) + "]"; } /** @@ -1287,9 +1280,8 @@ private static class Range2 @Override public String toString() { - return getClass().getName() + "[first=" + first + ", nLeft=" - + nLeft + "]"; + return getClass().getName() + "[first=" + first + ", nLeft=" + nLeft + "]"; } } } -} \ No newline at end of file +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeComp.java b/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeComp.java index fdcac842c3c..cbb67529607 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeComp.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeComp.java @@ -21,14 +21,14 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.io.IOException; /** - * This class is based on code from Apache Batik a subproject of Apache XMLGraphics. - * see http://xmlgraphics.apache.org/batik/ for further details. + * This class is based on code from Apache Batik a subproject of Apache XMLGraphics. see + * http://xmlgraphics.apache.org/batik/ for further details. */ -public class GlyfCompositeComp +public class GlyfCompositeComp { // Flags for composite glyphs. - + /** * If set, the arguments are words; otherwise, they are bytes. */ @@ -62,8 +62,7 @@ public class GlyfCompositeComp */ protected static final short WE_HAVE_INSTRUCTIONS = 0x0100; /** - * If set, this forces the aw and lsb (and rsb) for the composite to be equal to - * those from this original glyph. + * If set, this forces the aw and lsb (and rsb) for the composite to be equal to those from this original glyph. */ protected static final short USE_MY_METRICS = 0x0200; @@ -72,7 +71,7 @@ public class GlyfCompositeComp private short argument1; private short argument2; private short flags; - private int glyphIndex; + private int glyphIndex; private double xscale = 1.0; private double yscale = 1.0; private double scale01 = 0.0; @@ -88,49 +87,50 @@ public class GlyfCompositeComp * @param bais the stream to be read * @throws IOException is thrown if something went wrong */ - protected GlyfCompositeComp(TTFDataStream bais) throws IOException + protected GlyfCompositeComp(TTFDataStream bais) throws IOException { flags = bais.readSignedShort(); glyphIndex = bais.readUnsignedShort();// number of glyph in a font is uint16 // Get the arguments as just their raw values - if ((flags & ARG_1_AND_2_ARE_WORDS) != 0) + if ((flags & ARG_1_AND_2_ARE_WORDS) != 0) { argument1 = bais.readSignedShort(); argument2 = bais.readSignedShort(); - } - else + } + else { - argument1 = (short) bais.read(); - argument2 = (short) bais.read(); + argument1 = (short) bais.readUnsignedByte(); + argument2 = (short) bais.readUnsignedByte(); } // Assign the arguments according to the flags - if ((flags & ARGS_ARE_XY_VALUES) != 0) + if ((flags & ARGS_ARE_XY_VALUES) != 0) { xtranslate = argument1; ytranslate = argument2; - } - else + } + else { + // TODO unused? point1 = argument1; point2 = argument2; } // Get the scale values (if any) - if ((flags & WE_HAVE_A_SCALE) != 0) + if ((flags & WE_HAVE_A_SCALE) != 0) { int i = bais.readSignedShort(); xscale = yscale = (double) i / (double) 0x4000; - } - else if ((flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0) + } + else if ((flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0) { short i = bais.readSignedShort(); xscale = (double) i / (double) 0x4000; i = bais.readSignedShort(); yscale = (double) i / (double) 0x4000; - } - else if ((flags & WE_HAVE_A_TWO_BY_TWO) != 0) + } + else if ((flags & WE_HAVE_A_TWO_BY_TWO) != 0) { int i = bais.readSignedShort(); xscale = (double) i / (double) 0x4000; @@ -145,149 +145,165 @@ else if ((flags & WE_HAVE_A_TWO_BY_TWO) != 0) /** * Sets the first index. + * * @param idx the first index */ - public void setFirstIndex(int idx) + public void setFirstIndex(int idx) { firstIndex = idx; } /** * Returns the first index. + * * @return the first index. */ - public int getFirstIndex() + public int getFirstIndex() { return firstIndex; } /** * Sets the index for the first contour. + * * @param idx the index of the first contour */ - public void setFirstContour(int idx) + public void setFirstContour(int idx) { firstContour = idx; } /** * Returns the index of the first contour. + * * @return the index of the first contour. */ - public int getFirstContour() + public int getFirstContour() { return firstContour; } /** * Returns argument 1. + * * @return argument 1. */ - public short getArgument1() + public short getArgument1() { return argument1; } /** * Returns argument 2. + * * @return argument 2. */ - public short getArgument2() + public short getArgument2() { return argument2; } /** * Returns the flags of the glyph. + * * @return the flags. */ - public short getFlags() + public short getFlags() { return flags; } /** * Returns the index of the first contour. + * * @return index of the first contour. */ - public int getGlyphIndex() + public int getGlyphIndex() { return glyphIndex; } /** * Returns the scale-01 value. + * * @return the scale-01 value. */ - public double getScale01() + public double getScale01() { return scale01; } /** * Returns the scale-10 value. + * * @return the scale-10 value. */ - public double getScale10() + public double getScale10() { return scale10; } /** * Returns the x-scaling value. + * * @return the x-scaling value. */ - public double getXScale() + public double getXScale() { return xscale; } /** * Returns the y-scaling value. + * * @return the y-scaling value. */ - public double getYScale() + public double getYScale() { return yscale; } /** * Returns the x-translation value. + * * @return the x-translation value. */ - public int getXTranslate() + public int getXTranslate() { return xtranslate; } /** * Returns the y-translation value. + * * @return the y-translation value. */ - public int getYTranslate() + public int getYTranslate() { return ytranslate; } /** * Transforms an x-coordinate of a point for this component. + * * @param x The x-coordinate of the point to transform * @param y The y-coordinate of the point to transform * @return The transformed x-coordinate */ - public int scaleX(int x, int y) + public int scaleX(int x, int y) { - return Math.round((float)(x * xscale + y * scale10)); + return Math.round((float) (x * xscale + y * scale10)); } /** * Transforms a y-coordinate of a point for this component. + * * @param x The x-coordinate of the point to transform * @param y The y-coordinate of the point to transform * @return The transformed y-coordinate */ - public int scaleY(int x, int y) + public int scaleY(int x, int y) { - return Math.round((float)(x * scale01 + y * yscale)); + return Math.round((float) (x * scale01 + y * yscale)); } } diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeDescript.java b/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeDescript.java index de98cd1955a..12f70753dea 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeDescript.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfCompositeDescript.java @@ -23,236 +23,236 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.util.Iterator; import java.util.List; - /** - * Glyph description for composite glyphs. Composite glyphs are made up of one - * or more simple glyphs, usually with some sort of transformation applied to each. - * - * This class is based on code from Apache Batik a subproject of Apache XMLGraphics. - * see http://xmlgraphics.apache.org/batik/ for further details. + * Glyph description for composite glyphs. Composite glyphs are made up of one or more simple glyphs, usually with some + * sort of transformation applied to each. + * + * This class is based on code from Apache Batik a subproject of Apache XMLGraphics. see + * http://xmlgraphics.apache.org/batik/ for further details. */ -public class GlyfCompositeDescript extends GlyfDescript +public class GlyfCompositeDescript extends GlyfDescript { - private List components = new ArrayList(); - private GlyphData[] glyphs = null; - private boolean beingResolved = false; - private boolean resolved = false; + private List components = new ArrayList(); + private GlyphData[] glyphs = null; + private boolean beingResolved = false; + private boolean resolved = false; - /** - * Constructor. - * - * @param bais the stream to be read - * @param glyphTable the Glyphtable containing all glyphs - * @throws IOException is thrown if something went wrong - */ - public GlyfCompositeDescript(TTFDataStream bais, GlyphTable glyphTable) throws IOException - { - super((short) -1, bais); + /** + * Constructor. + * + * @param bais the stream to be read + * @param glyphTable the Glyphtable containing all glyphs + * @throws IOException is thrown if something went wrong + */ + public GlyfCompositeDescript(TTFDataStream bais, GlyphTable glyphTable) throws IOException + { + super((short) -1, bais); - glyphs = glyphTable.getGlyphs(); + glyphs = glyphTable.getGlyphs(); - // Get all of the composite components - GlyfCompositeComp comp; - do - { - comp = new GlyfCompositeComp(bais); - components.add(comp); - } - while ((comp.getFlags() & GlyfCompositeComp.MORE_COMPONENTS) != 0); + // Get all of the composite components + GlyfCompositeComp comp; + do + { + comp = new GlyfCompositeComp(bais); + components.add(comp); + } while ((comp.getFlags() & GlyfCompositeComp.MORE_COMPONENTS) != 0); - // Are there hinting instructions to read? - if ((comp.getFlags() & GlyfCompositeComp.WE_HAVE_INSTRUCTIONS) != 0) - { - readInstructions(bais, (bais.read()<<8 | bais.read())); - } - } + // Are there hinting instructions to read? + if ((comp.getFlags() & GlyfCompositeComp.WE_HAVE_INSTRUCTIONS) != 0) + { + readInstructions(bais, (bais.readUnsignedShort())); + } + } - /** - * {@inheritDoc} - */ - public void resolve() - { - if (resolved) - { - return; - } - if (beingResolved) - { - System.err.println("Circular reference in GlyfCompositeDesc"); - return; - } - beingResolved = true; + /** + * {@inheritDoc} + */ + public void resolve() + { + if (resolved) + { + return; + } + if (beingResolved) + { + System.err.println("Circular reference in GlyfCompositeDesc"); + return; + } + beingResolved = true; - int firstIndex = 0; - int firstContour = 0; + int firstIndex = 0; + int firstContour = 0; - Iterator i = components.iterator(); - while (i.hasNext()) - { - GlyfCompositeComp comp = (GlyfCompositeComp)i.next(); - comp.setFirstIndex(firstIndex); - comp.setFirstContour(firstContour); + Iterator i = components.iterator(); + while (i.hasNext()) + { + GlyfCompositeComp comp = (GlyfCompositeComp) i.next(); + comp.setFirstIndex(firstIndex); + comp.setFirstContour(firstContour); - GlyphDescription desc; - desc = getGlypDescription(comp.getGlyphIndex()); - if (desc != null) - { - desc.resolve(); - firstIndex += desc.getPointCount(); - firstContour += desc.getContourCount(); - } - } - resolved = true; - beingResolved = false; - } + GlyphDescription desc; + desc = getGlypDescription(comp.getGlyphIndex()); + if (desc != null) + { + desc.resolve(); + firstIndex += desc.getPointCount(); + firstContour += desc.getContourCount(); + } + } + resolved = true; + beingResolved = false; + } - /** - * {@inheritDoc} - */ - public int getEndPtOfContours(int i) - { - GlyfCompositeComp c = getCompositeCompEndPt(i); - if (c != null) - { - GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); - return gd.getEndPtOfContours(i - c.getFirstContour()) + c.getFirstIndex(); - } - return 0; - } + /** + * {@inheritDoc} + */ + public int getEndPtOfContours(int i) + { + GlyfCompositeComp c = getCompositeCompEndPt(i); + if (c != null) + { + GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); + return gd.getEndPtOfContours(i - c.getFirstContour()) + c.getFirstIndex(); + } + return 0; + } - /** - * {@inheritDoc} - */ - public byte getFlags(int i) - { - GlyfCompositeComp c = getCompositeComp(i); - if (c != null) - { - GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); - return gd.getFlags(i - c.getFirstIndex()); - } - return 0; - } + /** + * {@inheritDoc} + */ + public byte getFlags(int i) + { + GlyfCompositeComp c = getCompositeComp(i); + if (c != null) + { + GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); + return gd.getFlags(i - c.getFirstIndex()); + } + return 0; + } - /** - * {@inheritDoc} - */ - public short getXCoordinate(int i) - { - GlyfCompositeComp c = getCompositeComp(i); - if (c != null) - { - GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); - int n = i - c.getFirstIndex(); - int x = gd.getXCoordinate(n); - int y = gd.getYCoordinate(n); - short x1 = (short) c.scaleX(x, y); - x1 += c.getXTranslate(); - return x1; - } - return 0; - } + /** + * {@inheritDoc} + */ + public short getXCoordinate(int i) + { + GlyfCompositeComp c = getCompositeComp(i); + if (c != null) + { + GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); + int n = i - c.getFirstIndex(); + int x = gd.getXCoordinate(n); + int y = gd.getYCoordinate(n); + short x1 = (short) c.scaleX(x, y); + x1 += c.getXTranslate(); + return x1; + } + return 0; + } - /** - * {@inheritDoc} - */ - public short getYCoordinate(int i) - { - GlyfCompositeComp c = getCompositeComp(i); - if (c != null) - { - GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); - int n = i - c.getFirstIndex(); - int x = gd.getXCoordinate(n); - int y = gd.getYCoordinate(n); - short y1 = (short) c.scaleY(x, y); - y1 += c.getYTranslate(); - return y1; - } - return 0; - } + /** + * {@inheritDoc} + */ + public short getYCoordinate(int i) + { + GlyfCompositeComp c = getCompositeComp(i); + if (c != null) + { + GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); + int n = i - c.getFirstIndex(); + int x = gd.getXCoordinate(n); + int y = gd.getYCoordinate(n); + short y1 = (short) c.scaleY(x, y); + y1 += c.getYTranslate(); + return y1; + } + return 0; + } - /** - * {@inheritDoc} - */ - public boolean isComposite() - { - return true; - } + /** + * {@inheritDoc} + */ + public boolean isComposite() + { + return true; + } - /** - * {@inheritDoc} - */ - public int getPointCount() - { - if (!resolved) - { - System.err.println("getPointCount called on unresolved GlyfCompositeDescript"); - } - GlyfCompositeComp c = (GlyfCompositeComp) components.get(components.size()-1); - return c.getFirstIndex() + getGlypDescription(c.getGlyphIndex()).getPointCount(); - } + /** + * {@inheritDoc} + */ + public int getPointCount() + { + if (!resolved) + { + System.err.println("getPointCount called on unresolved GlyfCompositeDescript"); + } + GlyfCompositeComp c = (GlyfCompositeComp) components.get(components.size() - 1); + return c.getFirstIndex() + getGlypDescription(c.getGlyphIndex()).getPointCount(); + } - /** - * {@inheritDoc} - */ - public int getContourCount() - { - if (!resolved) - { - System.err.println("getContourCount called on unresolved GlyfCompositeDescript"); - } - GlyfCompositeComp c = (GlyfCompositeComp) components.get(components.size()-1); - return c.getFirstContour() + getGlypDescription(c.getGlyphIndex()).getContourCount(); - } + /** + * {@inheritDoc} + */ + public int getContourCount() + { + if (!resolved) + { + System.err.println("getContourCount called on unresolved GlyfCompositeDescript"); + } + GlyfCompositeComp c = (GlyfCompositeComp) components.get(components.size() - 1); + return c.getFirstContour() + getGlypDescription(c.getGlyphIndex()).getContourCount(); + } - /** - * {@inheritDoc} - */ - public int getComponentCount() - { - return components.size(); - } + /** + * {@inheritDoc} + */ + public int getComponentCount() + { + return components.size(); + } - private GlyfCompositeComp getCompositeComp(int i) - { - GlyfCompositeComp c; - for (int n = 0; n < components.size(); n++) - { - c = (GlyfCompositeComp) components.get(n); - GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); - if (c.getFirstIndex() <= i && i < (c.getFirstIndex() + gd.getPointCount())) - { - return c; - } - } - return null; - } + private GlyfCompositeComp getCompositeComp(int i) + { + GlyfCompositeComp c; + for (int n = 0; n < components.size(); n++) + { + c = (GlyfCompositeComp) components.get(n); + GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); + if (c.getFirstIndex() <= i && i < (c.getFirstIndex() + gd.getPointCount())) + { + return c; + } + } + return null; + } - private GlyfCompositeComp getCompositeCompEndPt(int i) - { - GlyfCompositeComp c; - for (int j = 0; j < components.size(); j++) - { - c = (GlyfCompositeComp) components.get(j); - GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); - if (c.getFirstContour() <= i && i < (c.getFirstContour() + gd.getContourCount())) - { - return c; - } - } - return null; - } + private GlyfCompositeComp getCompositeCompEndPt(int i) + { + GlyfCompositeComp c; + for (int j = 0; j < components.size(); j++) + { + c = (GlyfCompositeComp) components.get(j); + GlyphDescription gd = getGlypDescription(c.getGlyphIndex()); + if (c.getFirstContour() <= i && i < (c.getFirstContour() + gd.getContourCount())) + { + return c; + } + } + return null; + } - private GlyphDescription getGlypDescription(int index) - { - if (glyphs != null && index < glyphs.length) - { - GlyphData glyph = glyphs[index]; - if (glyph != null) - return glyph.getDescription(); - } - return null; - } + private GlyphDescription getGlypDescription(int index) + { + if (glyphs != null && index < glyphs.length) + { + GlyphData glyph = glyphs[index]; + if (glyph != null) + { + return glyph.getDescription(); + } + } + return null; + } } diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfSimpleDescript.java b/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfSimpleDescript.java index dad10bb25cb..1a78f784f6d 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfSimpleDescript.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/GlyfSimpleDescript.java @@ -21,10 +21,10 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.io.IOException; /** - * This class is based on code from Apache Batik a subproject of Apache XMLGraphics. - * see http://xmlgraphics.apache.org/batik/ for further details. + * This class is based on code from Apache Batik a subproject of Apache XMLGraphics. see + * http://xmlgraphics.apache.org/batik/ for further details. */ -public class GlyfSimpleDescript extends GlyfDescript +public class GlyfSimpleDescript extends GlyfDescript { private int[] endPtsOfContours; @@ -38,18 +38,18 @@ public class GlyfSimpleDescript extends GlyfDescript * * @param numberOfContours number of contours * @param bais the stream to be read - * @throws IOException is thrown if something went wrong + * @throws IOException is thrown if something went wrong */ public GlyfSimpleDescript(short numberOfContours, TTFDataStream bais) throws IOException { super(numberOfContours, bais); - - /* https://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html - * "If a glyph has zero contours, it need not have any glyph data." - * set the pointCount to zero to initialize attributes and avoid nullpointer but - * maybe there shouldn't have GlyphDescript in the GlyphData? + + /* + * https://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html + * "If a glyph has zero contours, it need not have any glyph data." set the pointCount to zero to initialize + * attributes and avoid nullpointer but maybe there shouldn't have GlyphDescript in the GlyphData? */ - if (numberOfContours == 0) + if (numberOfContours == 0) { pointCount = 0; return; @@ -60,8 +60,8 @@ public GlyfSimpleDescript(short numberOfContours, TTFDataStream bais) throws IOE endPtsOfContours = bais.readUnsignedShortArray(numberOfContours); // The last end point index reveals the total number of points - pointCount = endPtsOfContours[numberOfContours -1] + 1; - + pointCount = endPtsOfContours[numberOfContours - 1] + 1; + flags = new byte[pointCount]; xCoordinates = new short[pointCount]; yCoordinates = new short[pointCount]; @@ -75,7 +75,7 @@ public GlyfSimpleDescript(short numberOfContours, TTFDataStream bais) throws IOE /** * {@inheritDoc} */ - public int getEndPtOfContours(int i) + public int getEndPtOfContours(int i) { return endPtsOfContours[i]; } @@ -83,7 +83,7 @@ public int getEndPtOfContours(int i) /** * {@inheritDoc} */ - public byte getFlags(int i) + public byte getFlags(int i) { return flags[i]; } @@ -91,7 +91,7 @@ public byte getFlags(int i) /** * {@inheritDoc} */ - public short getXCoordinate(int i) + public short getXCoordinate(int i) { return xCoordinates[i]; } @@ -99,7 +99,7 @@ public short getXCoordinate(int i) /** * {@inheritDoc} */ - public short getYCoordinate(int i) + public short getYCoordinate(int i) { return yCoordinates[i]; } @@ -107,7 +107,7 @@ public short getYCoordinate(int i) /** * {@inheritDoc} */ - public boolean isComposite() + public boolean isComposite() { return false; } @@ -115,7 +115,7 @@ public boolean isComposite() /** * {@inheritDoc} */ - public int getPointCount() + public int getPointCount() { return pointCount; } @@ -127,22 +127,22 @@ private void readCoords(int count, TTFDataStream bais) throws IOException { short x = 0; short y = 0; - for (int i = 0; i < count; i++) + for (int i = 0; i < count; i++) { - if ((flags[i] & X_DUAL) != 0) + if ((flags[i] & X_DUAL) != 0) { - if ((flags[i] & X_SHORT_VECTOR) != 0) + if ((flags[i] & X_SHORT_VECTOR) != 0) { - x += (short) bais.read(); + x += (short) bais.readUnsignedByte(); } - } - else + } + else { - if ((flags[i] & X_SHORT_VECTOR) != 0) + if ((flags[i] & X_SHORT_VECTOR) != 0) { - x += (short) -((short) bais.read()); - } - else + x += (short) -((short) bais.readUnsignedByte()); + } + else { x += bais.readSignedShort(); } @@ -150,22 +150,22 @@ private void readCoords(int count, TTFDataStream bais) throws IOException xCoordinates[i] = x; } - for (int i = 0; i < count; i++) + for (int i = 0; i < count; i++) { - if ((flags[i] & Y_DUAL) != 0) + if ((flags[i] & Y_DUAL) != 0) { - if ((flags[i] & Y_SHORT_VECTOR) != 0) + if ((flags[i] & Y_SHORT_VECTOR) != 0) { - y += (short) bais.read(); + y += (short) bais.readUnsignedByte(); } - } - else + } + else { - if ((flags[i] & Y_SHORT_VECTOR) != 0) + if ((flags[i] & Y_SHORT_VECTOR) != 0) { - y += (short) -((short) bais.read()); - } - else + y += (short) -((short) bais.readUnsignedByte()); + } + else { y += bais.readSignedShort(); } @@ -179,25 +179,26 @@ private void readCoords(int count, TTFDataStream bais) throws IOException */ private void readFlags(int flagCount, TTFDataStream bais) throws IOException { - try + try { - for (int index = 0; index < flagCount; index++) + for (int index = 0; index < flagCount; index++) { - flags[index] = (byte) bais.read(); - if ((flags[index] & REPEAT) != 0) + flags[index] = (byte) bais.readUnsignedByte(); + if ((flags[index] & REPEAT) != 0) { - int repeats = bais.read(); - for (int i = 1; i <= repeats; i++) + int repeats = bais.readUnsignedByte(); + for (int i = 1; i <= repeats; i++) { flags[index + i] = flags[index]; } index += repeats; } } - } - catch (ArrayIndexOutOfBoundsException e) + } + catch (ArrayIndexOutOfBoundsException e) { System.out.println("error: array index out of bounds"); } } + } diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/OS2WindowsMetricsTable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/OS2WindowsMetricsTable.java index e48ddb65726..8636b534a1e 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/OS2WindowsMetricsTable.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/OS2WindowsMetricsTable.java @@ -22,11 +22,11 @@ * A table in a true type font. * * @author Ben Litchfield (ben@benlitchfield.com) - * @version $Revision: 1.1 $ + * */ public class OS2WindowsMetricsTable extends TTFTable { - + /** * Weight class constant. */ @@ -63,7 +63,7 @@ public class OS2WindowsMetricsTable extends TTFTable * Weight class constant. */ public static final int WEIGHT_CLASS_BLACK = 900; - + /** * Width class constant. */ @@ -100,7 +100,7 @@ public class OS2WindowsMetricsTable extends TTFTable * Width class constant. */ public static final int WIDTH_CLASS_ULTRA_EXPANDED = 9; - + /** * Family class constant. */ @@ -145,7 +145,7 @@ public class OS2WindowsMetricsTable extends TTFTable * Family class constant. */ public static final int FAMILY_CLASS_SYMBOLIC = 12; - + /** * @return Returns the achVendId. */ @@ -153,6 +153,7 @@ public String getAchVendId() { return achVendId; } + /** * @param achVendIdValue The achVendId to set. */ @@ -160,6 +161,7 @@ public void setAchVendId(String achVendIdValue) { this.achVendId = achVendIdValue; } + /** * @return Returns the averageCharWidth. */ @@ -167,6 +169,7 @@ public short getAverageCharWidth() { return averageCharWidth; } + /** * @param averageCharWidthValue The averageCharWidth to set. */ @@ -174,6 +177,7 @@ public void setAverageCharWidth(short averageCharWidthValue) { this.averageCharWidth = averageCharWidthValue; } + /** * @return Returns the codePageRange1. */ @@ -181,6 +185,7 @@ public long getCodePageRange1() { return codePageRange1; } + /** * @param codePageRange1Value The codePageRange1 to set. */ @@ -188,6 +193,7 @@ public void setCodePageRange1(long codePageRange1Value) { this.codePageRange1 = codePageRange1Value; } + /** * @return Returns the codePageRange2. */ @@ -195,6 +201,7 @@ public long getCodePageRange2() { return codePageRange2; } + /** * @param codePageRange2Value The codePageRange2 to set. */ @@ -202,6 +209,7 @@ public void setCodePageRange2(long codePageRange2Value) { this.codePageRange2 = codePageRange2Value; } + /** * @return Returns the familyClass. */ @@ -209,6 +217,7 @@ public int getFamilyClass() { return familyClass; } + /** * @param familyClassValue The familyClass to set. */ @@ -216,6 +225,7 @@ public void setFamilyClass(int familyClassValue) { this.familyClass = familyClassValue; } + /** * @return Returns the familySubClass. */ @@ -223,6 +233,7 @@ public int getFamilySubClass() { return familySubClass; } + /** * @param familySubClassValue The familySubClass to set. */ @@ -230,6 +241,7 @@ public void setFamilySubClass(int familySubClassValue) { this.familySubClass = familySubClassValue; } + /** * @return Returns the firstCharIndex. */ @@ -237,6 +249,7 @@ public int getFirstCharIndex() { return firstCharIndex; } + /** * @param firstCharIndexValue The firstCharIndex to set. */ @@ -244,6 +257,7 @@ public void setFirstCharIndex(int firstCharIndexValue) { this.firstCharIndex = firstCharIndexValue; } + /** * @return Returns the fsSelection. */ @@ -251,6 +265,7 @@ public int getFsSelection() { return fsSelection; } + /** * @param fsSelectionValue The fsSelection to set. */ @@ -258,6 +273,7 @@ public void setFsSelection(int fsSelectionValue) { this.fsSelection = fsSelectionValue; } + /** * @return Returns the fsType. */ @@ -265,6 +281,7 @@ public short getFsType() { return fsType; } + /** * @param fsTypeValue The fsType to set. */ @@ -272,6 +289,7 @@ public void setFsType(short fsTypeValue) { this.fsType = fsTypeValue; } + /** * @return Returns the lastCharIndex. */ @@ -279,6 +297,7 @@ public int getLastCharIndex() { return lastCharIndex; } + /** * @param lastCharIndexValue The lastCharIndex to set. */ @@ -286,6 +305,7 @@ public void setLastCharIndex(int lastCharIndexValue) { this.lastCharIndex = lastCharIndexValue; } + /** * @return Returns the panose. */ @@ -293,6 +313,7 @@ public byte[] getPanose() { return panose; } + /** * @param panoseValue The panose to set. */ @@ -300,6 +321,7 @@ public void setPanose(byte[] panoseValue) { this.panose = panoseValue; } + /** * @return Returns the strikeoutPosition. */ @@ -307,6 +329,7 @@ public short getStrikeoutPosition() { return strikeoutPosition; } + /** * @param strikeoutPositionValue The strikeoutPosition to set. */ @@ -314,6 +337,7 @@ public void setStrikeoutPosition(short strikeoutPositionValue) { this.strikeoutPosition = strikeoutPositionValue; } + /** * @return Returns the strikeoutSize. */ @@ -321,6 +345,7 @@ public short getStrikeoutSize() { return strikeoutSize; } + /** * @param strikeoutSizeValue The strikeoutSize to set. */ @@ -328,6 +353,7 @@ public void setStrikeoutSize(short strikeoutSizeValue) { this.strikeoutSize = strikeoutSizeValue; } + /** * @return Returns the subscriptXOffset. */ @@ -335,6 +361,7 @@ public short getSubscriptXOffset() { return subscriptXOffset; } + /** * @param subscriptXOffsetValue The subscriptXOffset to set. */ @@ -342,6 +369,7 @@ public void setSubscriptXOffset(short subscriptXOffsetValue) { this.subscriptXOffset = subscriptXOffsetValue; } + /** * @return Returns the subscriptXSize. */ @@ -349,6 +377,7 @@ public short getSubscriptXSize() { return subscriptXSize; } + /** * @param subscriptXSizeValue The subscriptXSize to set. */ @@ -356,6 +385,7 @@ public void setSubscriptXSize(short subscriptXSizeValue) { this.subscriptXSize = subscriptXSizeValue; } + /** * @return Returns the subscriptYOffset. */ @@ -363,6 +393,7 @@ public short getSubscriptYOffset() { return subscriptYOffset; } + /** * @param subscriptYOffsetValue The subscriptYOffset to set. */ @@ -370,6 +401,7 @@ public void setSubscriptYOffset(short subscriptYOffsetValue) { this.subscriptYOffset = subscriptYOffsetValue; } + /** * @return Returns the subscriptYSize. */ @@ -377,6 +409,7 @@ public short getSubscriptYSize() { return subscriptYSize; } + /** * @param subscriptYSizeValue The subscriptYSize to set. */ @@ -384,6 +417,7 @@ public void setSubscriptYSize(short subscriptYSizeValue) { this.subscriptYSize = subscriptYSizeValue; } + /** * @return Returns the superscriptXOffset. */ @@ -391,6 +425,7 @@ public short getSuperscriptXOffset() { return superscriptXOffset; } + /** * @param superscriptXOffsetValue The superscriptXOffset to set. */ @@ -398,6 +433,7 @@ public void setSuperscriptXOffset(short superscriptXOffsetValue) { this.superscriptXOffset = superscriptXOffsetValue; } + /** * @return Returns the superscriptXSize. */ @@ -405,6 +441,7 @@ public short getSuperscriptXSize() { return superscriptXSize; } + /** * @param superscriptXSizeValue The superscriptXSize to set. */ @@ -412,6 +449,7 @@ public void setSuperscriptXSize(short superscriptXSizeValue) { this.superscriptXSize = superscriptXSizeValue; } + /** * @return Returns the superscriptYOffset. */ @@ -419,6 +457,7 @@ public short getSuperscriptYOffset() { return superscriptYOffset; } + /** * @param superscriptYOffsetValue The superscriptYOffset to set. */ @@ -426,6 +465,7 @@ public void setSuperscriptYOffset(short superscriptYOffsetValue) { this.superscriptYOffset = superscriptYOffsetValue; } + /** * @return Returns the superscriptYSize. */ @@ -433,6 +473,7 @@ public short getSuperscriptYSize() { return superscriptYSize; } + /** * @param superscriptYSizeValue The superscriptYSize to set. */ @@ -440,6 +481,7 @@ public void setSuperscriptYSize(short superscriptYSizeValue) { this.superscriptYSize = superscriptYSizeValue; } + /** * @return Returns the typeLineGap. */ @@ -447,6 +489,7 @@ public int getTypeLineGap() { return typeLineGap; } + /** * @param typeLineGapValue The typeLineGap to set. */ @@ -454,6 +497,7 @@ public void setTypeLineGap(int typeLineGapValue) { this.typeLineGap = typeLineGapValue; } + /** * @return Returns the typoAscender. */ @@ -461,6 +505,7 @@ public int getTypoAscender() { return typoAscender; } + /** * @param typoAscenderValue The typoAscender to set. */ @@ -468,6 +513,7 @@ public void setTypoAscender(int typoAscenderValue) { this.typoAscender = typoAscenderValue; } + /** * @return Returns the typoDescender. */ @@ -475,6 +521,7 @@ public int getTypoDescender() { return typoDescender; } + /** * @param typoDescenderValue The typoDescender to set. */ @@ -482,6 +529,7 @@ public void setTypoDescender(int typoDescenderValue) { this.typoDescender = typoDescenderValue; } + /** * @return Returns the unicodeRange1. */ @@ -489,6 +537,7 @@ public long getUnicodeRange1() { return unicodeRange1; } + /** * @param unicodeRange1Value The unicodeRange1 to set. */ @@ -496,6 +545,7 @@ public void setUnicodeRange1(long unicodeRange1Value) { this.unicodeRange1 = unicodeRange1Value; } + /** * @return Returns the unicodeRange2. */ @@ -503,6 +553,7 @@ public long getUnicodeRange2() { return unicodeRange2; } + /** * @param unicodeRange2Value The unicodeRange2 to set. */ @@ -510,6 +561,7 @@ public void setUnicodeRange2(long unicodeRange2Value) { this.unicodeRange2 = unicodeRange2Value; } + /** * @return Returns the unicodeRange3. */ @@ -517,6 +569,7 @@ public long getUnicodeRange3() { return unicodeRange3; } + /** * @param unicodeRange3Value The unicodeRange3 to set. */ @@ -524,6 +577,7 @@ public void setUnicodeRange3(long unicodeRange3Value) { this.unicodeRange3 = unicodeRange3Value; } + /** * @return Returns the unicodeRange4. */ @@ -531,6 +585,7 @@ public long getUnicodeRange4() { return unicodeRange4; } + /** * @param unicodeRange4Value The unicodeRange4 to set. */ @@ -538,6 +593,7 @@ public void setUnicodeRange4(long unicodeRange4Value) { this.unicodeRange4 = unicodeRange4Value; } + /** * @return Returns the version. */ @@ -545,6 +601,7 @@ public int getVersion() { return version; } + /** * @param versionValue The version to set. */ @@ -552,6 +609,7 @@ public void setVersion(int versionValue) { this.version = versionValue; } + /** * @return Returns the weightClass. */ @@ -559,6 +617,7 @@ public int getWeightClass() { return weightClass; } + /** * @param weightClassValue The weightClass to set. */ @@ -566,6 +625,7 @@ public void setWeightClass(int weightClassValue) { this.weightClass = weightClassValue; } + /** * @return Returns the widthClass. */ @@ -573,6 +633,7 @@ public int getWidthClass() { return widthClass; } + /** * @param widthClassValue The widthClass to set. */ @@ -580,6 +641,7 @@ public void setWidthClass(int widthClassValue) { this.widthClass = widthClassValue; } + /** * @return Returns the winAscent. */ @@ -587,6 +649,7 @@ public int getWinAscent() { return winAscent; } + /** * @param winAscentValue The winAscent to set. */ @@ -594,6 +657,7 @@ public void setWinAscent(int winAscentValue) { this.winAscent = winAscentValue; } + /** * @return Returns the winDescent. */ @@ -601,6 +665,7 @@ public int getWinDescent() { return winDescent; } + /** * @param winDescentValue The winDescent to set. */ @@ -608,6 +673,7 @@ public void setWinDescent(int winDescentValue) { this.winDescent = winDescentValue; } + private int version; private short averageCharWidth; private int weightClass; @@ -641,12 +707,12 @@ public void setWinDescent(int winDescentValue) private int winDescent; private long codePageRange1 = -1; private long codePageRange2 = -1; - + /** * A tag that identifies this table type. */ public static final String TAG = "OS/2"; - + /** * This will read the required data from the stream. * @@ -654,7 +720,7 @@ public void setWinDescent(int winDescentValue) * @param data The stream to read the data from. * @throws IOException If there is an error reading the data. */ - public void initData( TrueTypeFont ttf, TTFDataStream data ) throws IOException + public void initData(TrueTypeFont ttf, TTFDataStream data) throws IOException { version = data.readUnsignedShort(); averageCharWidth = data.readSignedShort(); @@ -671,14 +737,14 @@ public void initData( TrueTypeFont ttf, TTFDataStream data ) throws IOException superscriptYOffset = data.readSignedShort(); strikeoutSize = data.readSignedShort(); strikeoutPosition = data.readSignedShort(); - familyClass = data.read(); - familySubClass = data.read(); - panose = data.read( 10 ); + familyClass = data.readUnsignedByte(); + familySubClass = data.readUnsignedByte(); + panose = data.read(10); unicodeRange1 = data.readUnsignedInt(); unicodeRange2 = data.readUnsignedInt(); unicodeRange3 = data.readUnsignedInt(); unicodeRange4 = data.readUnsignedInt(); - achVendId = data.readString( 4 ); + achVendId = data.readString(4); fsSelection = data.readUnsignedShort(); firstCharIndex = data.readUnsignedShort(); lastCharIndex = data.readUnsignedShort(); @@ -687,7 +753,7 @@ public void initData( TrueTypeFont ttf, TTFDataStream data ) throws IOException typeLineGap = data.readSignedShort(); winAscent = data.readUnsignedShort(); winDescent = data.readUnsignedShort(); - if( version >= 1 ) + if (version >= 1) { codePageRange1 = data.readUnsignedInt(); codePageRange2 = data.readUnsignedInt(); diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java index 26fd0ef07e8..1a88330df07 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/PostScriptTable.java @@ -17,13 +17,14 @@ package org.apache.fontbox.ttf; import java.io.IOException; + import org.apache.fontbox.encoding.Encoding; /** * A table in a true type font. * * @author Ben Litchfield (ben@benlitchfield.com) - * @version $Revision: 1.1 $ + * */ public class PostScriptTable extends TTFTable { @@ -37,13 +38,12 @@ public class PostScriptTable extends TTFTable private long mimMemType1; private long maxMemType1; private String[] glyphNames = null; - /** * A tag that identifies this table type. */ public static final String TAG = "post"; - + /** * This will read the required data from the stream. * @@ -51,7 +51,7 @@ public class PostScriptTable extends TTFTable * @param data The stream to read the data from. * @throws IOException If there is an error reading the data. */ - public void initData( TrueTypeFont ttf, TTFDataStream data ) throws IOException + public void initData(TrueTypeFont ttf, TTFDataStream data) throws IOException { MaximumProfileTable maxp = ttf.getMaximumProfile(); formatType = data.read32Fixed(); @@ -63,53 +63,52 @@ public void initData( TrueTypeFont ttf, TTFDataStream data ) throws IOException maxMemType42 = data.readUnsignedInt(); mimMemType1 = data.readUnsignedInt(); maxMemType1 = data.readUnsignedInt(); - - if( formatType == 1.0f ) + + if (formatType == 1.0f) { /* - * This TrueType font file contains exactly the 258 glyphs in the standard - * Macintosh TrueType. + * This TrueType font file contains exactly the 258 glyphs in the standard Macintosh TrueType. */ glyphNames = new String[Encoding.NUMBER_OF_MAC_GLYPHS]; System.arraycopy(Encoding.MAC_GLYPH_NAMES, 0, glyphNames, 0, Encoding.NUMBER_OF_MAC_GLYPHS); } - else if( formatType == 2.0f ) + else if (formatType == 2.0f) { int numGlyphs = data.readUnsignedShort(); int[] glyphNameIndex = new int[numGlyphs]; - glyphNames = new String[ numGlyphs ]; + glyphNames = new String[numGlyphs]; int maxIndex = Integer.MIN_VALUE; - for( int i=0; i= Encoding.NUMBER_OF_MAC_GLYPHS ) + if (maxIndex >= Encoding.NUMBER_OF_MAC_GLYPHS) { - nameArray = new String[ maxIndex-Encoding.NUMBER_OF_MAC_GLYPHS +1 ]; - for( int i=0; i= Encoding.NUMBER_OF_MAC_GLYPHS && index <= 32767 ) + else if (index >= Encoding.NUMBER_OF_MAC_GLYPHS && index <= 32767) { - glyphNames[i] = nameArray[index-Encoding.NUMBER_OF_MAC_GLYPHS]; + glyphNames[i] = nameArray[index - Encoding.NUMBER_OF_MAC_GLYPHS]; } else { @@ -119,30 +118,31 @@ else if( index >= Encoding.NUMBER_OF_MAC_GLYPHS && index <= 32767 ) } } } - else if( formatType == 2.5f ) + else if (formatType == 2.5f) { int[] glyphNameIndex = new int[maxp.getNumGlyphs()]; - for( int i=0; i */ public static final COSName DESTS = new COSName( "Dests" ); + /** + * A common COSName value. + */ + public static final COSName DEST_OUTPUT_PROFILE = new COSName("DestOutputProfile"); + /** * A common COSName value. */ @@ -713,6 +717,10 @@ public final class COSName extends COSBase implements Comparable * A common COSName value. */ public static final COSName H = new COSName( "H" ); + /** + * A common COSName value. + */ + public static final COSName GTS_PDFA1 = new COSName("GTS_PDFA1"); /** * A common COSName value. */ @@ -1099,6 +1107,10 @@ public final class COSName extends COSBase implements Comparable * A common COSName value. */ public static final COSName PARENT = new COSName( "Parent" ); + /** + * A common COSName value. + */ + public static final COSName PARENT_TREE = new COSName("ParentTree"); /** * A common COSName value. */ @@ -1192,6 +1204,12 @@ public final class COSName extends COSBase implements Comparable * A common COSName value. */ public static final COSName REGISTRY = new COSName( "Registry" ); + + /** + * A common COSName value. + */ + public static final COSName REGISTRY_NAME = new COSName("RegistryName"); + /** * A common COSName value. */ @@ -1302,7 +1320,14 @@ public final class COSName extends COSBase implements Comparable * A common COSName value. */ public static final COSName STR_F = new COSName( "StrF" ); - + /** + * A common COSName value. + */ + public static final COSName STRUCT_PARENT = new COSName("StructParent"); + /** + * A common COSName value. + */ + public static final COSName STRUCT_PARENTS = new COSName("StructParents"); /** * A common COSName value. */ diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java index b772501aa7d..0130d7111e3 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java @@ -47,7 +47,7 @@ * PDFParser and the COSStreamParser. * * @author Ben Litchfield - * @version $Revision: 1.61 $ + * */ public abstract class BaseParser { @@ -371,6 +371,7 @@ else if(read==O) } else { + value.setDirect(true); obj.setItem( key, value ); } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java index 44e0bf7fb60..d3f1b5da7a1 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java @@ -58,6 +58,10 @@ public class PDFParser extends BaseParser private static final String PDF_HEADER = "%PDF-"; private static final String FDF_HEADER = "%FDF-"; + + private static final String PDF_DEFAULT_VERSION = "1.4"; + private static final String FDF_DEFAULT_VERSION = "1.0"; + /** * A list of duplicate objects found when Parsing the PDF * File. @@ -352,20 +356,39 @@ private void parseHeader() throws IOException */ if (header.startsWith(PDF_HEADER)) { - if(!header.matches(PDF_HEADER + "\\d.\\d")) + 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")); + + if (header.length() < PDF_HEADER.length() + 3) + { + // No version number at all, set to 1.4 as default + header = PDF_HEADER + PDF_DEFAULT_VERSION; + LOG.debug("No pdf version found, set to " + PDF_DEFAULT_VERSION + " as default."); + } + else + { + 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")) + 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")); + if (header.length() < FDF_HEADER.length() + 3) + { + // No version number at all, set to 1.0 as default + header = FDF_HEADER + FDF_DEFAULT_VERSION; + LOG.debug("No fdf version found, set to " + FDF_DEFAULT_VERSION + " as default."); + } + else + { + 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); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java index c843a8c2774..f17c0a3ca72 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java @@ -71,7 +71,7 @@ * * @author Michael Traut * @author Ben Litchfield - * @version $Revision: 1.36 $ + * */ public class COSWriter implements ICOSVisitor { @@ -1054,7 +1054,15 @@ else if( value instanceof COSObject ) } else { - subValue.accept( this ); + if (subValue.isDirect()) + { + subValue.accept( this ); + } + else + { + addObjectToWrite( subValue ); + writeReference( subValue ); + } } } else diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java index 8a8b13876de..65fae1f299f 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java @@ -279,6 +279,26 @@ public void setResources( PDResources resources ) } } + /** + * This will get the key of this Page in the structural parent tree. + * + * @return the integer key of the page's entry in the structural parent tree + */ + public int getStructParents() + { + return page.getInt(COSName.STRUCT_PARENTS, 0); + } + + /** + * This will set the key for this page in the structural parent tree. + * + * @param structParents The new key for this page. + */ + public void setStructParents(int structParents) + { + page.setInt(COSName.STRUCT_PARENTS, structParents); + } + /** * A rectangle, expressed * in default user space units, defining the boundaries of the physical diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDResources.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDResources.java index d35f611ade7..e73bac14fc1 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDResources.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDResources.java @@ -42,22 +42,22 @@ /** * This represents a set of resources available at the page/pages/stream level. - * + * * @author Ben Litchfield - * @version $Revision: 1.16 $ + * */ public class PDResources implements COSObjectable { private COSDictionary resources; - private Map fonts = null; + private Map fonts = null; private Map fontMappings = new HashMap(); - private Map colorspaces = null; - private Map xobjects = null; - private Map xobjectMappings = null; - private HashMap images = null; - private Map graphicsStates = null; - private Map patterns = null; - private Map shadings = null; + private Map colorspaces = null; + private Map xobjects = null; + private Map xobjectMappings = null; + private HashMap images = null; + private Map graphicsStates = null; + private Map patterns = null; + private Map shadings = null; /** * Log instance. @@ -74,17 +74,17 @@ public PDResources() /** * Prepopulated resources. - * + * * @param resourceDictionary The cos dictionary for this resource. */ - public PDResources( COSDictionary resourceDictionary ) + public PDResources(COSDictionary resourceDictionary) { resources = resourceDictionary; } /** * This will get the underlying dictionary. - * + * * @return The dictionary for these resources. */ public COSDictionary getCOSDictionary() @@ -94,7 +94,7 @@ public COSDictionary getCOSDictionary() /** * Convert this standard java object to a COS object. - * + * * @return The cos object that matches this Java object. */ public COSBase getCOSObject() @@ -106,7 +106,7 @@ public COSBase getCOSObject() * Calling this will release all cached information. * */ - public void clear() + public void clear() { if (fonts != null) { @@ -154,53 +154,54 @@ public void clear() shadings = null; } } + /** - * This will get the map of fonts. This will never return null. The keys are string - * and the values are PDFont objects. - * + * 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. * * @deprecated due to some side effects font caching is no longer supported, use {@link #getFonts()} instead */ - public Map getFonts( Map fontCache ) throws IOException + public Map getFonts(Map fontCache) throws IOException { return getFonts(); } /** * This will get the map of fonts. This will never return null. - * + * * @return The map of fonts. */ - public Map getFonts() + public Map getFonts() { if (fonts == null) { // at least an empty map will be returned // TODO we should return null instead of an empty map - fonts = new HashMap(); - COSDictionary fontsDictionary = (COSDictionary)resources.getDictionaryObject( COSName.FONT ); - if( fontsDictionary == null ) + fonts = new HashMap(); + COSDictionary fontsDictionary = (COSDictionary) resources.getDictionaryObject(COSName.FONT); + if (fontsDictionary == null) { fontsDictionary = new COSDictionary(); - resources.setItem( COSName.FONT, fontsDictionary ); + resources.setItem(COSName.FONT, fontsDictionary); } else { - for( COSName fontName : fontsDictionary.keySet() ) + for (COSName fontName : fontsDictionary.keySet()) { - COSBase font = fontsDictionary.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 ) + COSBase font = fontsDictionary.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) { PDFont newFont = null; try { - newFont = PDFontFactory.createFont( (COSDictionary)font ); + newFont = PDFontFactory.createFont((COSDictionary) font); } catch (IOException exception) { @@ -208,80 +209,81 @@ public Map getFonts() } if (newFont != null) { - fonts.put( fontName.getName(), newFont); + fonts.put(fontName.getName(), newFont); } } } } + setFonts(fonts); } return fonts; } - + /** - * This will get the map of PDXObjects that are in the resource dictionary. - * This will never return null. - * + * This will get the map of PDXObjects that are in the resource dictionary. This will never return null. + * * @return The map of xobjects. */ - public Map getXObjects() + public Map getXObjects() { if (xobjects == null) { // at least an empty map will be returned // TODO we should return null instead of an empty map - xobjects = new HashMap(); - COSDictionary xobjectsDictionary = (COSDictionary)resources.getDictionaryObject( COSName.XOBJECT ); - if( xobjectsDictionary == null ) + xobjects = new HashMap(); + COSDictionary xobjectsDictionary = (COSDictionary) resources.getDictionaryObject(COSName.XOBJECT); + if (xobjectsDictionary == null) { xobjectsDictionary = new COSDictionary(); - resources.setItem( COSName.XOBJECT, xobjectsDictionary ); + resources.setItem(COSName.XOBJECT, xobjectsDictionary); } else { - xobjects = new HashMap(); - for( COSName objName : xobjectsDictionary.keySet() ) + xobjects = new HashMap(); + for (COSName objName : xobjectsDictionary.keySet()) { PDXObject xobject = null; try { - xobject = PDXObject.createXObject( xobjectsDictionary.getDictionaryObject(objName) ); + xobject = PDXObject.createXObject(xobjectsDictionary.getDictionaryObject(objName)); } catch (IOException exception) { LOG.error("error while creating a xobject", exception); } - if( xobject != null ) + if (xobject != null) { - xobjects.put( objName.getName(), xobject); + xobjects.put(objName.getName(), xobject); } } } + setXObjects(xobjects); } return xobjects; } /** - * 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. - * + * 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. + * + * @deprecated use {@link #getXObjects()} instead, as the images map isn't synchronized with the XObjects map. */ - public Map getImages() throws IOException + public Map getImages() throws IOException { - if (images == null) + if (images == null) { - Map allXObjects = getXObjects(); - images = new HashMap(); - for( Map.Entry entry: allXObjects.entrySet() ) + Map allXObjects = getXObjects(); + images = new HashMap(); + for (Map.Entry entry : allXObjects.entrySet()) { PDXObject xobject = entry.getValue(); - if( xobject instanceof PDXObjectImage ) + if (xobject instanceof PDXObjectImage) { - images.put( entry.getKey(), (PDXObjectImage)xobject); + images.put(entry.getKey(), (PDXObjectImage) xobject); } } } @@ -290,62 +292,65 @@ public Map getImages() throws IOException /** * This will set the map of fonts. - * + * * @param fontsValue The new map of fonts. */ - public void setFonts( Map fontsValue ) + public void setFonts(Map fontsValue) { fonts = fontsValue; if (fontsValue != null) { - resources.setItem( COSName.FONT, COSDictionaryMap.convert( fontsValue ) ); + resources.setItem(COSName.FONT, COSDictionaryMap.convert(fontsValue)); + fontMappings = reverseMap(fontsValue, PDFont.class); } else { resources.removeItem(COSName.FONT); + fontMappings = null; } } /** * This will set the map of xobjects. - * + * * @param xobjectsValue The new map of xobjects. */ - public void setXObjects( Map xobjectsValue ) + public void setXObjects(Map xobjectsValue) { xobjects = xobjectsValue; if (xobjectsValue != null) { - resources.setItem( COSName.XOBJECT, COSDictionaryMap.convert( xobjectsValue ) ); + resources.setItem(COSName.XOBJECT, COSDictionaryMap.convert(xobjectsValue)); + xobjectMappings = reverseMap(xobjects, PDXObject.class); } else { resources.removeItem(COSName.XOBJECT); + xobjectMappings = null; } } /** - * 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. - * + * 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. */ - public Map getColorSpaces() + public Map getColorSpaces() { - if (colorspaces == null) + if (colorspaces == null) { - COSDictionary csDictionary = (COSDictionary)resources.getDictionaryObject( COSName.COLORSPACE ); - if( csDictionary != null ) + COSDictionary csDictionary = (COSDictionary) resources.getDictionaryObject(COSName.COLORSPACE); + if (csDictionary != null) { - colorspaces = new HashMap(); - for( COSName csName : csDictionary.keySet() ) + colorspaces = new HashMap(); + for (COSName csName : csDictionary.keySet()) { - COSBase cs = csDictionary.getDictionaryObject( csName ); + COSBase cs = csDictionary.getDictionaryObject(csName); PDColorSpace colorspace = null; - try + try { - colorspace = PDColorSpaceFactory.createColorSpace( cs ); + colorspace = PDColorSpaceFactory.createColorSpace(cs); } catch (IOException exception) { @@ -353,7 +358,7 @@ public Map getColorSpaces() } if (colorspace != null) { - colorspaces.put( csName.getName(), colorspace ); + colorspaces.put(csName.getName(), colorspace); } } } @@ -363,15 +368,15 @@ public Map getColorSpaces() /** * This will set the map of colorspaces. - * + * * @param csValue The new map of colorspaces. */ - public void setColorSpaces( Map csValue ) + public void setColorSpaces(Map csValue) { colorspaces = csValue; if (csValue != null) { - resources.setItem( COSName.COLORSPACE, COSDictionaryMap.convert( csValue ) ); + resources.setItem(COSName.COLORSPACE, COSDictionaryMap.convert(csValue)); } else { @@ -380,24 +385,24 @@ public void setColorSpaces( Map csValue ) } /** - * 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. - * + * 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() + public Map getGraphicsStates() { if (graphicsStates == null) { - COSDictionary states = (COSDictionary)resources.getDictionaryObject( COSName.EXT_G_STATE ); - if( states != null ) + COSDictionary states = (COSDictionary) resources.getDictionaryObject(COSName.EXT_G_STATE); + if (states != null) { - graphicsStates = new HashMap(); - for( COSName name : states.keySet() ) + graphicsStates = new HashMap(); + for (COSName name : states.keySet()) { - COSDictionary dictionary = (COSDictionary)states.getDictionaryObject( name ); - graphicsStates.put( name.getName(), new PDExtendedGraphicsState( dictionary ) ); + COSDictionary dictionary = (COSDictionary) states.getDictionaryObject(name); + graphicsStates.put(name.getName(), new PDExtendedGraphicsState(dictionary)); } } } @@ -406,23 +411,23 @@ public Map getGraphicsStates() /** * This will set the map of graphics states. - * + * * @param states The new map of states. */ - public void setGraphicsStates( Map states ) + public void setGraphicsStates(Map states) { graphicsStates = states; if (states != null) { Iterator iter = states.keySet().iterator(); COSDictionary dic = new COSDictionary(); - while( iter.hasNext() ) + while (iter.hasNext()) { - String name = (String)iter.next(); - PDExtendedGraphicsState state = states.get( name ); - dic.setItem( COSName.getPDFName( name ), state.getCOSObject() ); + String name = (String) iter.next(); + PDExtendedGraphicsState state = states.get(name); + dic.setItem(COSName.getPDFName(name), state.getCOSObject()); } - resources.setItem( COSName.EXT_G_STATE, dic ); + resources.setItem(COSName.EXT_G_STATE, dic); } else { @@ -431,14 +436,14 @@ public void setGraphicsStates( Map states ) } /** - * Returns the dictionary mapping resource names to property list dictionaries for marked - * content. + * 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); + COSDictionary props = (COSDictionary) resources.getDictionaryObject(COSName.PROPERTIES); if (props != null) { @@ -448,8 +453,8 @@ public PDPropertyList getProperties() } /** - * Sets the dictionary mapping resource names to property list dictionaries for marked - * content. + * Sets the dictionary mapping resource names to property list dictionaries for marked content. + * * @param props the property list */ public void setProperties(PDPropertyList props) @@ -458,26 +463,25 @@ public void setProperties(PDPropertyList props) } /** - * This will get the map of patterns. This will return null if the underlying - * resources dictionary does not have a patterns dictionary. The keys are the pattern - * name as a String and the values are PDPatternResources objects. - * + * This will get the map of patterns. This will return null if the underlying resources dictionary does not have a + * patterns dictionary. The keys are the pattern name as a String and the values are PDPatternResources objects. + * * @return The map of pattern resources objects. * * @throws IOException If there is an error getting the pattern resources. */ - public Map getPatterns() throws IOException + public Map getPatterns() throws IOException { if (patterns == null) { - COSDictionary patternsDictionary = (COSDictionary)resources.getDictionaryObject( COSName.PATTERN ); - if( patternsDictionary != null ) + COSDictionary patternsDictionary = (COSDictionary) resources.getDictionaryObject(COSName.PATTERN); + if (patternsDictionary != null) { - patterns = new HashMap(); - for( COSName name : patternsDictionary.keySet() ) + patterns = new HashMap(); + for (COSName name : patternsDictionary.keySet()) { - COSDictionary dictionary = (COSDictionary)patternsDictionary.getDictionaryObject( name ); - patterns.put( name.getName(), PDPatternResources.create( dictionary ) ); + COSDictionary dictionary = (COSDictionary) patternsDictionary.getDictionaryObject(name); + patterns.put(name.getName(), PDPatternResources.create(dictionary)); } } } @@ -486,23 +490,23 @@ public Map getPatterns() throws IOException /** * This will set the map of patterns. - * + * * @param patternsValue The new map of patterns. */ - public void setPatterns( Map patternsValue ) + public void setPatterns(Map patternsValue) { patterns = patternsValue; if (patternsValue != null) { Iterator iter = patternsValue.keySet().iterator(); COSDictionary dic = new COSDictionary(); - while( iter.hasNext() ) + while (iter.hasNext()) { String name = iter.next(); - PDPatternResources pattern = patternsValue.get( name ); - dic.setItem( COSName.getPDFName( name ), pattern.getCOSObject() ); + PDPatternResources pattern = patternsValue.get(name); + dic.setItem(COSName.getPDFName(name), pattern.getCOSObject()); } - resources.setItem( COSName.PATTERN, dic ); + resources.setItem(COSName.PATTERN, dic); } else { @@ -511,26 +515,25 @@ public void setPatterns( Map patternsValue ) } /** - * This will get the map of shadings. This will return null if the underlying - * resources dictionary does not have a shading dictionary. The keys are the shading - * name as a String and the values are PDShadingResources objects. - * + * This will get the map of shadings. This will return null if the underlying resources dictionary does not have a + * shading dictionary. The keys are the shading name as a String and the values are PDShadingResources objects. + * * @return The map of shading resources objects. * * @throws IOException If there is an error getting the shading resources. */ - public Map getShadings() throws IOException + public Map getShadings() throws IOException { if (shadings == null) { - COSDictionary shadingsDictionary = (COSDictionary)resources.getDictionaryObject( COSName.SHADING ); - if( shadingsDictionary != null ) + COSDictionary shadingsDictionary = (COSDictionary) resources.getDictionaryObject(COSName.SHADING); + if (shadingsDictionary != null) { - shadings = new HashMap(); - for( COSName name : shadingsDictionary.keySet() ) + shadings = new HashMap(); + for (COSName name : shadingsDictionary.keySet()) { - COSDictionary dictionary = (COSDictionary)shadingsDictionary.getDictionaryObject( name ); - shadings.put( name.getName(), PDShadingResources.create( dictionary ) ); + COSDictionary dictionary = (COSDictionary) shadingsDictionary.getDictionaryObject(name); + shadings.put(name.getName(), PDShadingResources.create(dictionary)); } } } @@ -539,23 +542,23 @@ public Map getShadings() throws IOException /** * This will set the map of shadings. - * + * * @param shadingsValue The new map of shadings. */ - public void setShadings( Map shadingsValue ) + public void setShadings(Map shadingsValue) { shadings = shadingsValue; if (shadingsValue != null) { Iterator iter = shadingsValue.keySet().iterator(); COSDictionary dic = new COSDictionary(); - while( iter.hasNext() ) + while (iter.hasNext()) { String name = iter.next(); - PDShadingResources shading = shadingsValue.get( name ); - dic.setItem( COSName.getPDFName( name ), shading.getCOSObject() ); + PDShadingResources shading = shadingsValue.get(name); + dic.setItem(COSName.getPDFName(name), shading.getCOSObject()); } - resources.setItem( COSName.SHADING, dic ); + resources.setItem(COSName.SHADING, dic); } else { @@ -569,9 +572,10 @@ public void setShadings( Map shadingsValue ) * @param font the font to be added * @return the font name to be used within the content stream. */ - public String addFont(PDFont font) + public String addFont(PDFont font) { - return addFont(font, MapUtil.getNextUniqueKey( fonts, "F" )); + // use the getter to initialize a possible empty fonts map + return addFont(font, MapUtil.getNextUniqueKey(getFonts(), "F")); } /** @@ -581,31 +585,31 @@ public String addFont(PDFont font) * @param fontKey key to used to map to the given font * @return the font name to be used within the content stream. */ - public String addFont(PDFont font, String fontKey) + public String addFont(PDFont font, String fontKey) { - if (fonts == null) + if (fonts == null) { - fonts = getFonts(); - fontMappings = reverseMap(fonts, PDFont.class); - setFonts(fonts); + // initialize fonts map + getFonts(); } - - String fontMapping = fontMappings.get( font ); - if( fontMapping == null ) + + String fontMapping = fontMappings.get(font); + if (fontMapping == null) { fontMapping = fontKey; - fontMappings.put( font, fontMapping ); - fonts.put( fontMapping, font ); + fontMappings.put(font, fontMapping); + fonts.put(fontMapping, font); addFontToDictionary(font, fontMapping); } return fontMapping; } - + private void addFontToDictionary(PDFont font, String fontName) { - COSDictionary fontsDictionary = (COSDictionary)resources.getDictionaryObject(COSName.FONT); + COSDictionary fontsDictionary = (COSDictionary) resources.getDictionaryObject(COSName.FONT); fontsDictionary.setItem(fontName, font); } + /** * Adds the given XObject to the resources of the current the page. * @@ -614,20 +618,19 @@ private void addFontToDictionary(PDFont font, String fontName) * * @return the XObject name to be used within the content stream. */ - public String addXObject(PDXObject xobject, String prefix) + public String addXObject(PDXObject xobject, String prefix) { - if (xobjects == null) + if (xobjects == null) { - xobjects = getXObjects(); - xobjectMappings = reverseMap(xobjects, PDXObject.class); - setXObjects(xobjects); + // initialize XObject map + getXObjects(); } - String objMapping = xobjectMappings.get( xobject ); - if( objMapping == null ) + String objMapping = xobjectMappings.get(xobject); + if (objMapping == null) { - objMapping = MapUtil.getNextUniqueKey( xobjects, prefix ); - xobjectMappings.put( xobject, objMapping ); - xobjects.put( objMapping, xobject ); + objMapping = MapUtil.getNextUniqueKey(xobjects, prefix); + xobjectMappings.put(xobject, objMapping); + xobjects.put(objMapping, xobject); addXObjectToDictionary(xobject, objMapping); } return objMapping; @@ -635,7 +638,7 @@ public String addXObject(PDXObject xobject, String prefix) private void addXObjectToDictionary(PDXObject xobject, String xobjectName) { - COSDictionary xobjectsDictionary = (COSDictionary)resources.getDictionaryObject(COSName.XOBJECT); + COSDictionary xobjectsDictionary = (COSDictionary) resources.getDictionaryObject(COSName.XOBJECT); xobjectsDictionary.setItem(xobjectName, xobject); } @@ -644,10 +647,9 @@ private Map reverseMap(Map map, Class keyClass) Map reversed = new java.util.HashMap(); for (Map.Entry entry : map.entrySet()) { - reversed.put(keyClass.cast(entry.getValue()), (String)entry.getKey()); + reversed.put(keyClass.cast(entry.getValue()), (String) entry.getKey()); } return reversed; } - } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/function/PDFunction.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/function/PDFunction.java index d0cfa3ec70c..329518ae479 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/function/PDFunction.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/function/PDFunction.java @@ -18,11 +18,11 @@ 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.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; @@ -32,7 +32,7 @@ * This class represents a function in a PDF document. * * @author Ben Litchfield - * @version $Revision: 1.3 $ + * */ public abstract class PDFunction implements COSObjectable { @@ -41,6 +41,8 @@ public abstract class PDFunction implements COSObjectable private COSDictionary functionDictionary = null; private COSArray domain = null; private COSArray range = null; + private int numberOfInputValues = -1; + private int numberOfOutputValues = -1; /** * Constructor. @@ -53,7 +55,7 @@ public PDFunction( COSBase function ) if (function instanceof COSStream) { functionStream = new PDStream( (COSStream)function ); - functionStream.getStream().setName( COSName.TYPE, "Function" ); + functionStream.getStream().setItem( COSName.TYPE, COSName.FUNCTION ); } else if (function instanceof COSDictionary) { @@ -169,8 +171,12 @@ else if( functionType == 4 ) */ public int getNumberOfOutputParameters() { - COSArray rangeValues = getRangeValues(); - return rangeValues.size() / 2; + if (numberOfOutputValues == -1) + { + COSArray rangeValues = getRangeValues(); + numberOfOutputValues = rangeValues.size() / 2; + } + return numberOfOutputValues; } /** @@ -208,8 +214,12 @@ public void setRangeValues(COSArray rangeValues) */ public int getNumberOfInputParameters() { - COSArray array = getDomainValues(); - return array.size() / 2; + if (numberOfInputValues == -1) + { + COSArray array = getDomainValues(); + numberOfInputValues = array.size() / 2; + } + return numberOfInputValues; } /** diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/function/PDFunctionType0.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/function/PDFunctionType0.java index acb1cd386f9..34b7bbdf647 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/function/PDFunctionType0.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/common/function/PDFunctionType0.java @@ -30,7 +30,7 @@ * This class represents a type 0 function in a PDF document. * * @author Ben Litchfield - * @version $Revision: 1.2 $ + * */ public class PDFunctionType0 extends PDFunction { @@ -41,14 +41,14 @@ public class PDFunctionType0 extends PDFunction 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) … ]. + * An array of 2 x 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. + * An array of 2 x 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; @@ -298,16 +298,16 @@ public float[] eval(float[] input) throws IOException int bitsPerSample = getBitsPerSample(); int numberOfInputValues = input.length; int numberOfOutputValues = getNumberOfOutputParameters(); - int[] intInputValuesPrevious = new int[numberOfInputValues]; - int[] intInputValuesNext = new int[numberOfInputValues]; + int intInputValuesPrevious = 0; + int intInputValuesNext = 0; for (int i=0; iBen Litchfield, - * Johannes Koch - * @version $Revision: 1.2 $ + * + * @author Ben Litchfield, Johannes Koch + * */ public class PDStructureTreeRoot extends PDStructureNode { - public static final String TYPE = "StructTreeRoot"; - + private static final String TYPE = "StructTreeRoot"; /** * Default Constructor. - * + * */ public PDStructureTreeRoot() { @@ -50,14 +51,60 @@ public PDStructureTreeRoot() /** * Constructor for an existing structure element. - * + * * @param dic The existing dictionary. */ - public PDStructureTreeRoot( COSDictionary dic ) + public PDStructureTreeRoot(COSDictionary dic) { super(dic); } + /** + * Returns the K array entry. + * + * @return the K array entry + */ + public COSArray getKArray() + { + COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K); + if (k != null) + { + if (k instanceof COSDictionary) + { + COSDictionary kdict = (COSDictionary) k; + k = kdict.getDictionaryObject(COSName.K); + if (k instanceof COSArray) + { + return (COSArray) k; + } + } + else + { + return (COSArray) k; + } + } + return null; + } + + /** + * Returns the K entry. + * + * @return the K entry + */ + public COSBase getK() + { + return this.getCOSDictionary().getDictionaryObject(COSName.K); + } + + /** + * Sets the K entry. + * + * @param k the K value + */ + public void setK(COSBase k) + { + this.getCOSDictionary().setItem(COSName.K, k); + } /** * Returns the ID tree. @@ -66,8 +113,7 @@ public PDStructureTreeRoot( COSDictionary dic ) */ public PDNameTreeNode getIDTree() { - COSDictionary idTreeDic = (COSDictionary) this.getCOSDictionary() - .getDictionaryObject(COSName.ID_TREE); + COSDictionary idTreeDic = (COSDictionary) this.getCOSDictionary().getDictionaryObject(COSName.ID_TREE); if (idTreeDic != null) { return new PDNameTreeNode(idTreeDic, PDStructureElement.class); @@ -85,6 +131,31 @@ public void setIDTree(PDNameTreeNode idTree) this.getCOSDictionary().setItem(COSName.ID_TREE, idTree); } + /** + * Returns the parent tree. + * + * @return the parent tree + */ + public PDNumberTreeNode getParentTree() + { + COSDictionary parentTreeDic = (COSDictionary) this.getCOSDictionary().getDictionaryObject(COSName.PARENT_TREE); + if (parentTreeDic != null) + { + return new PDNumberTreeNode(parentTreeDic, COSBase.class); + } + return null; + } + + /** + * Sets the parent tree. + * + * @param parentTree the parent tree + */ + public void setParentTree(PDNumberTreeNode parentTree) + { + this.getCOSDictionary().setItem(COSName.PARENT_TREE, parentTree); + } + /** * Returns the next key in the parent tree. * @@ -95,6 +166,16 @@ public int getParentTreeNextKey() return this.getCOSDictionary().getInt(COSName.PARENT_TREE_NEXT_KEY); } + /** + * Sets the next key in the parent tree. + * + * @param parentTreeNextkey the next key in the parent tree. + */ + public void setParentTreeNextKey(int parentTreeNextkey) + { + this.getCOSDictionary().setInt(COSName.PARENT_TREE_NEXT_KEY, parentTreeNextkey); + } + /** * Returns the role map. * diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/shading/AxialShadingContext.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/shading/AxialShadingContext.java index 6bcf7848440..a978edf4576 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/shading/AxialShadingContext.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/shading/AxialShadingContext.java @@ -39,7 +39,6 @@ * This class represents the PaintContext of an axial shading. * * @author lehmi - * @version $Revision: $ * */ public class AxialShadingContext implements PaintContext @@ -52,6 +51,8 @@ public class AxialShadingContext implements PaintContext private float[] coords; private float[] domain; + private int[] extend0Values; + private int[] extend1Values; private boolean[] extend; private double x1x0; private double y1y0; @@ -205,10 +206,13 @@ public Raster getRaster(int x, int y, int w, int h) WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h); float[] input = new float[1]; int[] data = new int[w * h * 3]; + boolean saveExtend0 = false; + boolean saveExtend1 = false; for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { + int index = (j * w + i) * 3; double inputValue = x1x0 * (x + i - coords[0]); inputValue += y1y0 * (y + j - coords[1]); inputValue /= denom; @@ -218,7 +222,17 @@ public Raster getRaster(int x, int y, int w, int h) // the shading has to be extended if extend[0] == true if (extend[0]) { - inputValue = domain[0]; + if (extend0Values == null) + { + inputValue = domain[0]; + saveExtend0 = true; + } + else + { + // use the chached values + System.arraycopy(extend0Values, 0, data, index, 3); + continue; + } } else { @@ -231,7 +245,17 @@ else if (inputValue > domain[1]) // the shading has to be extended if extend[1] == true if (extend[1]) { - inputValue = domain[1]; + if (extend1Values == null) + { + inputValue = domain[1]; + saveExtend1 = true; + } + else + { + // use the chached values + System.arraycopy(extend1Values, 0, data, index, 3); + continue; + } } else { @@ -240,7 +264,6 @@ else if (inputValue > domain[1]) } input[0] = (float)(domain[0] + (d1d0*inputValue)); float[] values = null; - int index = (j * w + i) * 3; try { values = function.eval(input); @@ -261,6 +284,18 @@ else if (inputValue > domain[1]) data[index] = (int)(values[0]*255); data[index+1] = (int)(values[1]*255); data[index+2] = (int)(values[2]*255); + if (saveExtend0) + { + // chache values + extend0Values = new int[3]; + System.arraycopy(data, index, extend0Values, 0, 3); + } + if (saveExtend1) + { + // chache values + extend1Values = new int[3]; + System.arraycopy(data, index, extend1Values, 0, 3); + } } } raster.setPixels(0, 0, w, h, data); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/shading/RadialShadingContext.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/shading/RadialShadingContext.java index b5420d22fe3..ebb10cf6ea0 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/shading/RadialShadingContext.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/shading/RadialShadingContext.java @@ -39,7 +39,6 @@ * This class represents the PaintContext of an radial shading. * * @author lehmi - * @version $Revision: $ * */ public class RadialShadingContext implements PaintContext @@ -52,6 +51,8 @@ public class RadialShadingContext implements PaintContext private float[] coords; private float[] domain; + private int[] extend0Values; + private int[] extend1Values; private boolean[] extend; private double x1x0; private double y1y0; @@ -216,11 +217,14 @@ public Raster getRaster(int x, int y, int w, int h) WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h); float[] input = new float[1]; float inputValue; + boolean saveExtend0 = false; + boolean saveExtend1 = false; int[] data = new int[w * h * 3]; for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { + int index = (j * w + i) * 3; float[] inputValues = calculateInputValues(x + i, y + j); // choose 1 of the 2 values if (inputValues[0] >= domain[0] && inputValues[0] <= domain[1]) @@ -256,7 +260,17 @@ public Raster getRaster(int x, int y, int w, int h) // the shading has to be extended if extend[0] == true if (extend[0]) { - inputValue = domain[0]; + if (extend0Values == null) + { + inputValue = domain[0]; + saveExtend0 = true; + } + else + { + // use the chached values + System.arraycopy(extend0Values, 0, data, index, 3); + continue; + } } else { @@ -269,7 +283,17 @@ else if (inputValue > domain[1]) // the shading has to be extended if extend[1] == true if (extend[1]) { - inputValue = domain[1]; + if (extend1Values == null) + { + inputValue = domain[1]; + saveExtend1 = true; + } + else + { + // use the chached values + System.arraycopy(extend1Values, 0, data, index, 3); + continue; + } } else { @@ -278,7 +302,6 @@ else if (inputValue > domain[1]) } input[0] = (float)(domain[0] + (d1d0*inputValue)); float[] values = null; - int index = (j * w + i) * 3; try { values = function.eval(input); @@ -299,6 +322,18 @@ else if (inputValue > domain[1]) data[index] = (int)(values[0]*255); data[index+1] = (int)(values[1]*255); data[index+2] = (int)(values[2]*255); + if (saveExtend0) + { + // chache values + extend0Values = new int[3]; + System.arraycopy(data, index, extend0Values, 0, 3); + } + if (saveExtend1) + { + // chache values + extend1Values = new int[3]; + System.arraycopy(data, index, extend1Values, 0, 3); + } } } raster.setPixels(0, 0, w, h, data); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java index 95764120be1..524e4d83785 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObject.java @@ -24,7 +24,6 @@ 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; @@ -32,15 +31,14 @@ /** * The base class for all XObjects in the PDF document. - * + * * @author Ben Litchfield * @author mathiak * @author Marcel Kammer - * @version $Revision: 1.14 $ */ public abstract class PDXObject implements COSObjectable { - + /** * Log instance. */ @@ -50,40 +48,40 @@ public abstract class PDXObject implements COSObjectable /** * Standard constructor. - * + * * @param xobj The XObject dictionary. */ public PDXObject(COSStream xobj) { - xobject = new PDStream( xobj ); - xobject.getStream().setName( COSName.TYPE, "XObject" ); + xobject = new PDStream(xobj); + getCOSStream().setItem(COSName.TYPE, COSName.XOBJECT); } /** * Standard constuctor. - * + * * @param xobj The XObject dictionary. */ public PDXObject(PDStream xobj) { xobject = xobj; - xobject.getStream().setName( COSName.TYPE, "XObject" ); + getCOSStream().setItem(COSName.TYPE, COSName.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" ); + getCOSStream().setItem(COSName.TYPE, COSName.XOBJECT); } /** * Returns the stream. - * + * * {@inheritDoc} */ public COSBase getCOSObject() @@ -93,6 +91,7 @@ public COSBase getCOSObject() /** * Returns the stream. + * * @return The stream for this object. */ public COSStream getCOSStream() @@ -102,6 +101,7 @@ public COSStream getCOSStream() /** * Returns the stream. + * * @return The stream for this object. */ public PDStream getPDStream() @@ -111,103 +111,125 @@ public PDStream getPDStream() /** * 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 + public static PDXObject createXObject(COSBase xobject) throws IOException { - PDXObject retval = commonXObjectCreation(xobject, false); - return retval; + return commonXObjectCreation(xobject, false); } /** * Create the correct xobject from the cos base. - * + * * @param xobject The cos level xobject to create. - * @param isthumb specify if the xobject represent a Thumbnail Image (in this case, the subtype null must be considered as an Image) + * @param isthumb specify if the xobject represent a Thumbnail Image (in this case, the subtype null must be + * considered as an Image) * @return a pdmodel xobject * @throws IOException If there is an error creating the xobject. */ protected static PDXObject commonXObjectCreation(COSBase xobject, boolean isThumb) { PDXObject retval = null; - if( xobject == null ) + if (xobject == null) { retval = null; } - else if( xobject instanceof COSStream ) + else if (xobject instanceof COSStream) { - COSStream xstream = (COSStream)xobject; - String subtype = xstream.getNameAsString( COSName.SUBTYPE ); + COSStream xstream = (COSStream) xobject; + String subtype = xstream.getNameAsString(COSName.SUBTYPE); // according to the PDF Reference : a thumbnail subtype must be Image if it is not null - if( PDXObjectImage.SUB_TYPE.equals( subtype ) || (subtype == null && isThumb)) + if (PDXObjectImage.SUB_TYPE.equals(subtype) || (subtype == null && isThumb)) { - PDStream image = new PDStream( xstream ); + 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 ) ) + if (filters != null && filters.contains(COSName.DCT_DECODE)) { return new PDJpeg(image); } - else if ( filters != null && filters.contains( COSName.CCITTFAX_DECODE ) ) + else if (filters != null && filters.contains(COSName.CCITTFAX_DECODE)) { return new PDCcitt(image); } - else if( filters != null && filters.contains(COSName.JPX_DECODE)) + else if (filters != null && filters.contains(COSName.JPX_DECODE)) { - //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 ); + // 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( PDXObjectForm.SUB_TYPE.equals( subtype ) ) + else if (PDXObjectForm.SUB_TYPE.equals(subtype)) { - retval = new PDXObjectForm( xstream ); + retval = new PDXObjectForm(xstream); } else { - LOG.warn( "Skipping unknown XObject subtype '" + subtype + "'" ); + 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. - * + * 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 ) + COSStream mdStream = (COSStream) getCOSStream().getDictionaryObject(COSName.METADATA); + if (mdStream != null) { - retval = new PDMetadata( mdStream ); + retval = new PDMetadata(mdStream); } return retval; } /** - * Set the metadata for this object. This can be null. - * + * Set the metadata for this object. This can be null. + * * @param meta The meta data for this object. */ - public void setMetadata( PDMetadata meta ) + public void setMetadata(PDMetadata meta) { - xobject.getStream().setItem( COSName.METADATA, meta ); + getCOSStream().setItem(COSName.METADATA, meta); } + + /** + * This will get the key of this XObject in the structural parent tree. Required if the form XObject is a structural + * content item. + * + * @return the integer key of the XObject's entry in the structural parent tree + */ + public int getStructParent() + { + return getCOSStream().getInt(COSName.STRUCT_PARENT, 0); + } + + /** + * This will set the key for this XObject in the structural parent tree. + * + * @param structParent The new key for this XObject. + */ + public void setStructParent(int structParent) + { + getCOSStream().setInt(COSName.STRUCT_PARENT, structParent); + } + } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java index 5886fb06564..cec0b7eb402 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectForm.java @@ -31,7 +31,7 @@ /** * A form xobject. - * + * * @author Ben Litchfield * @version $Revision: 1.6 $ */ @@ -44,120 +44,118 @@ public class PDXObjectForm extends PDXObject /** * Standard constuctor. - * + * * @param formStream The XObject is passed as a COSStream. */ public PDXObjectForm(PDStream formStream) { - super( formStream ); - getCOSStream().setName( COSName.SUBTYPE, SUB_TYPE ); + super(formStream); + getCOSStream().setName(COSName.SUBTYPE, SUB_TYPE); } /** * Standard constuctor. - * + * * @param formStream The XObject is passed as a COSStream. */ public PDXObjectForm(COSStream formStream) { - super( formStream ); - getCOSStream().setName( COSName.SUBTYPE, SUB_TYPE ); + super(formStream); + getCOSStream().setName(COSName.SUBTYPE, SUB_TYPE); } /** * This will get the form type, currently 1 is the only form type. - * + * * @return The form type. */ public int getFormType() { - return getCOSStream().getInt( "FormType",1 ); + return getCOSStream().getInt("FormType", 1); } /** * Set the form type. - * + * * @param formType The new form type. */ - public void setFormType( int formType ) + public void setFormType(int formType) { - getCOSStream().setInt( "FormType", formType ); + getCOSStream().setInt("FormType", formType); } /** - * This will get the resources at this page and not look up the hierarchy. - * This attribute is inheritable, and findResources() should probably used. - * This will return null if no resources are available at this level. - * + * This will get the resources at this page and not look up the hierarchy. This attribute is inheritable, and + * findResources() should probably used. This will return null if no resources are available at this level. + * * @return The resources at this level in the hierarchy. */ public PDResources getResources() { PDResources retval = null; - COSDictionary resources = (COSDictionary)getCOSStream().getDictionaryObject( COSName.RESOURCES ); - if( resources != null ) + COSDictionary resources = (COSDictionary) getCOSStream().getDictionaryObject(COSName.RESOURCES); + if (resources != null) { - retval = new PDResources( resources ); + retval = new PDResources(resources); } return retval; } /** * This will set the resources for this page. - * + * * @param resources The new resources for this page. */ - public void setResources( PDResources resources ) + public void setResources(PDResources resources) { - getCOSStream().setItem( COSName.RESOURCES, resources ); + getCOSStream().setItem(COSName.RESOURCES, resources); } /** - * An array of four numbers in the form coordinate system (see - * below), giving the coordinates of the left, bottom, right, and top edges, - * respectively, of the form XObject's bounding box. These boundaries are used - * to clip the form XObject and to determine its size for caching. - * + * An array of four numbers in the form coordinate system (see below), giving the coordinates of the left, bottom, + * right, and top edges, respectively, of the form XObject's bounding box. These boundaries are used to clip the + * form XObject and to determine its size for caching. + * * @return The BBox of the form. */ public PDRectangle getBBox() { PDRectangle retval = null; - COSArray array = (COSArray)getCOSStream().getDictionaryObject( COSName.BBOX ); - if( array != null ) + COSArray array = (COSArray) getCOSStream().getDictionaryObject(COSName.BBOX); + if (array != null) { - retval = new PDRectangle( array ); + retval = new PDRectangle(array); } return retval; } /** * This will set the BBox (bounding box) for this form. - * + * * @param bbox The new BBox for this form. */ public void setBBox(PDRectangle bbox) { - if( bbox == null ) + if (bbox == null) { - getCOSStream().removeItem( COSName.BBOX ); + getCOSStream().removeItem(COSName.BBOX); } else { - getCOSStream().setItem( COSName.BBOX, bbox.getCOSArray() ); + getCOSStream().setItem(COSName.BBOX, bbox.getCOSArray()); } } /** - * This will get the optional Matrix of an XObjectForm. - * It maps the form space into the user space + * This will get the optional Matrix of an XObjectForm. It maps the form space into the user space + * * @return the form matrix */ public Matrix getMatrix() { Matrix retval = null; - COSArray array = (COSArray)getCOSStream().getDictionaryObject( COSName.MATRIX ); - if( array != null ) + COSArray array = (COSArray) getCOSStream().getDictionaryObject(COSName.MATRIX); + if (array != null) { retval = new Matrix(); retval.setValue(0, 0, ((COSNumber) array.get(0)).floatValue()); @@ -172,6 +170,7 @@ public Matrix getMatrix() /** * Sets the optional Matrix entry for the form XObject. + * * @param transform the transformation matrix */ public void setMatrix(AffineTransform transform) @@ -181,9 +180,30 @@ public void setMatrix(AffineTransform transform) transform.getMatrix(values); for (double v : values) { - matrix.add(new COSFloat((float)v)); + matrix.add(new COSFloat((float) v)); } getCOSStream().setItem(COSName.MATRIX, matrix); } + /** + * This will get the key of this XObjectForm in the structural parent tree. Required if the form XObject contains + * marked-content sequences that are structural content items. + * + * @return the integer key of the XObjectForm's entry in the structural parent tree + */ + public int getStructParents() + { + return getCOSStream().getInt(COSName.STRUCT_PARENTS, 0); + } + + /** + * This will set the key for this XObjectForm in the structural parent tree. + * + * @param structParent The new key for this XObjectForm. + */ + public void setStructParents(int structParent) + { + getCOSStream().setInt(COSName.STRUCT_PARENTS, structParent); + } + } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java index ca3b5c08240..9f5e51c6685 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java @@ -21,28 +21,27 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; - import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.COSObjectable; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.graphics.color.PDGamma; import org.apache.pdfbox.util.BitFlagHelper; -import org.apache.pdfbox.cos.COSBase; /** * This class represents a PDF annotation. - * + * * @author Ben Litchfield - * @version $Revision: 1.16 $ + * */ public abstract class PDAnnotation implements COSObjectable { /** * Log instance. */ - private static final Log log = LogFactory.getLog(PDAnnotation.class); + private static final Log LOG = LogFactory.getLog(PDAnnotation.class); /** * An annotation flag. @@ -81,88 +80,86 @@ public abstract class PDAnnotation implements COSObjectable */ public static final int FLAG_TOGGLE_NO_VIEW = 1 << 8; - - private COSDictionary dictionary; /** * Create the correct annotation from the base COS object. - * + * * @param base The COS object that is the annotation. * @return The correctly typed annotation object. * @throws IOException If there is an error while creating the annotation. */ - // TODO not yet implemented: - // Movie, Screen, PrinterMark, TrapNet, Watermark, 3D, Redact - public static PDAnnotation createAnnotation( COSBase base ) throws IOException + public static PDAnnotation createAnnotation(COSBase base) throws IOException { PDAnnotation annot = null; - if( base instanceof COSDictionary ) + if (base instanceof COSDictionary) { - COSDictionary annotDic = (COSDictionary)base; - String subtype = annotDic.getNameAsString( COSName.SUBTYPE ); - if( PDAnnotationFileAttachment.SUB_TYPE.equals(subtype) ) + COSDictionary annotDic = (COSDictionary) base; + String subtype = annotDic.getNameAsString(COSName.SUBTYPE); + if (PDAnnotationFileAttachment.SUB_TYPE.equals(subtype)) { - annot = new PDAnnotationFileAttachment( annotDic ); + annot = new PDAnnotationFileAttachment(annotDic); } - else if( PDAnnotationLine.SUB_TYPE.equals(subtype) ) + else if (PDAnnotationLine.SUB_TYPE.equals(subtype)) { - annot = new PDAnnotationLine( annotDic ); + annot = new PDAnnotationLine(annotDic); } - else if( PDAnnotationLink.SUB_TYPE.equals(subtype) ) + else if (PDAnnotationLink.SUB_TYPE.equals(subtype)) { annot = new PDAnnotationLink(annotDic); } - else if( PDAnnotationPopup.SUB_TYPE.equals(subtype) ) + else if (PDAnnotationPopup.SUB_TYPE.equals(subtype)) { annot = new PDAnnotationPopup(annotDic); } - else if( PDAnnotationRubberStamp.SUB_TYPE.equals(subtype) ) + else if (PDAnnotationRubberStamp.SUB_TYPE.equals(subtype)) { annot = new PDAnnotationRubberStamp(annotDic); } - else if( PDAnnotationSquareCircle.SUB_TYPE_SQUARE.equals(subtype) || - PDAnnotationSquareCircle.SUB_TYPE_CIRCLE.equals(subtype) ) + else if (PDAnnotationSquareCircle.SUB_TYPE_SQUARE.equals(subtype) + || PDAnnotationSquareCircle.SUB_TYPE_CIRCLE.equals(subtype)) { - annot = new PDAnnotationSquareCircle( annotDic ); + annot = new PDAnnotationSquareCircle(annotDic); } - else if( PDAnnotationText.SUB_TYPE.equals(subtype) ) + else if (PDAnnotationText.SUB_TYPE.equals(subtype)) { - annot = new PDAnnotationText( annotDic); + annot = new PDAnnotationText(annotDic); } - else if( PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT.equals(subtype) || - PDAnnotationTextMarkup.SUB_TYPE_UNDERLINE.equals(subtype) || - PDAnnotationTextMarkup.SUB_TYPE_SQUIGGLY.equals(subtype) || - PDAnnotationTextMarkup.SUB_TYPE_STRIKEOUT.equals(subtype) ) + else if (PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT.equals(subtype) + || PDAnnotationTextMarkup.SUB_TYPE_UNDERLINE.equals(subtype) + || PDAnnotationTextMarkup.SUB_TYPE_SQUIGGLY.equals(subtype) + || PDAnnotationTextMarkup.SUB_TYPE_STRIKEOUT.equals(subtype)) { - annot = new PDAnnotationTextMarkup( annotDic ); + annot = new PDAnnotationTextMarkup(annotDic); } - else if( PDAnnotationLink.SUB_TYPE.equals(subtype) ) + else if (PDAnnotationLink.SUB_TYPE.equals(subtype)) { - annot = new PDAnnotationLink( annotDic ); + annot = new PDAnnotationLink(annotDic); } - else if( PDAnnotationWidget.SUB_TYPE.equals(subtype) ) + else if (PDAnnotationWidget.SUB_TYPE.equals(subtype)) { - annot = new PDAnnotationWidget( annotDic ); + annot = new PDAnnotationWidget(annotDic); } - else if( PDAnnotationMarkup.SUB_TYPE_FREETEXT.equals(subtype) || - PDAnnotationMarkup.SUB_TYPE_POLYGON.equals(subtype) || - PDAnnotationMarkup.SUB_TYPE_POLYLINE.equals(subtype) || - PDAnnotationMarkup.SUB_TYPE_CARET.equals(subtype) || - PDAnnotationMarkup.SUB_TYPE_INK.equals(subtype) || - PDAnnotationMarkup.SUB_TYPE_SOUND.equals(subtype) ) + else if (PDAnnotationMarkup.SUB_TYPE_FREETEXT.equals(subtype) + || PDAnnotationMarkup.SUB_TYPE_POLYGON.equals(subtype) + || PDAnnotationMarkup.SUB_TYPE_POLYLINE.equals(subtype) + || PDAnnotationMarkup.SUB_TYPE_CARET.equals(subtype) + || PDAnnotationMarkup.SUB_TYPE_INK.equals(subtype) + || PDAnnotationMarkup.SUB_TYPE_SOUND.equals(subtype)) { - annot = new PDAnnotationMarkup( annotDic ); + annot = new PDAnnotationMarkup(annotDic); } else { - annot = new PDAnnotationUnknown( annotDic ); - log.debug("Unknown or unsupported annotation subtype "+subtype); + // TODO not yet implemented: + // Movie, Screen, PrinterMark, TrapNet, Watermark, 3D, Redact + annot = new PDAnnotationUnknown(annotDic); + LOG.debug("Unknown or unsupported annotation subtype " + subtype); } } else { - throw new IOException( "Error: Unknown annotation type " + base ); + throw new IOException("Error: Unknown annotation type " + base); } return annot; @@ -174,21 +171,22 @@ else if( PDAnnotationMarkup.SUB_TYPE_FREETEXT.equals(subtype) || public PDAnnotation() { dictionary = new COSDictionary(); - dictionary.setItem( COSName.TYPE, COSName.ANNOT ); + dictionary.setItem(COSName.TYPE, COSName.ANNOT); } /** * Constructor. - * + * * @param dict The annotations dictionary. */ - public PDAnnotation( COSDictionary dict ) + public PDAnnotation(COSDictionary dict) { dictionary = dict; } /** * returns the dictionary. + * * @return the dictionary */ public COSDictionary getDictionary() @@ -197,57 +195,56 @@ public COSDictionary getDictionary() } /** - * The annotation rectangle, defining the location of the annotation - * on the page in default user space units. This is usually required and should - * not return null on valid PDF documents. But where this is a parent form field - * with children, such as radio button collections then the rectangle will be null. - * + * The annotation rectangle, defining the location of the annotation on the page in default user space units. This + * is usually required and should not return null on valid PDF documents. But where this is a parent form field with + * children, such as radio button collections then the rectangle will be null. + * * @return The Rect value of this annotation. */ public PDRectangle getRectangle() { - COSArray rectArray = (COSArray)dictionary.getDictionaryObject( COSName.RECT ); + COSArray rectArray = (COSArray) dictionary.getDictionaryObject(COSName.RECT); PDRectangle rectangle = null; - if( rectArray != null ) + if (rectArray != null) { - rectangle = new PDRectangle( rectArray ); + rectangle = new PDRectangle(rectArray); } return rectangle; } /** * This will set the rectangle for this annotation. - * + * * @param rectangle The new rectangle values. */ - public void setRectangle( PDRectangle rectangle ) + public void setRectangle(PDRectangle rectangle) { - dictionary.setItem( COSName.RECT, rectangle.getCOSArray() ); + dictionary.setItem(COSName.RECT, rectangle.getCOSArray()); } - /** + /** * This will get the flags for this field. - * + * * @return flags The set of flags. */ public int getAnnotationFlags() { - return getDictionary().getInt( COSName.F, 0 ); + return getDictionary().getInt(COSName.F, 0); } /** * This will set the flags for this field. - * + * * @param flags The new flags. */ - public void setAnnotationFlags( int flags ) + public void setAnnotationFlags(int flags) { - getDictionary().setInt( COSName.F, flags ); + getDictionary().setInt(COSName.F, flags); } /** * Interface method for COSObjectable. - * + * * @return This object as a standard COS object. */ public COSBase getCOSObject() @@ -257,14 +254,14 @@ public COSBase getCOSObject() /** * This will get the name of the current appearance stream if any. - * + * * @return The name of the appearance stream. */ public String getAppearanceStream() { String retval = null; - COSName name = (COSName)getDictionary().getDictionaryObject( COSName.AS ); - if( name != null ) + COSName name = (COSName) getDictionary().getDictionaryObject(COSName.AS); + if (name != null) { retval = name.getName(); } @@ -273,236 +270,235 @@ public String getAppearanceStream() /** * This will set the annotations appearance stream name. - * + * * @param as The name of the appearance stream. */ - public void setAppearanceStream( String as ) + public void setAppearanceStream(String as) { - if( as == null ) + if (as == null) { - getDictionary().removeItem( COSName.AS ); + getDictionary().removeItem(COSName.AS); } else { - getDictionary().setItem( COSName.AS, COSName.getPDFName( as ) ); + getDictionary().setItem(COSName.AS, COSName.getPDFName(as)); } } /** - * This will get the appearance dictionary associated with this annotation. - * This may return null. - * + * This will get the appearance dictionary associated with this annotation. This may return null. + * * @return This annotations appearance. */ public PDAppearanceDictionary getAppearance() { PDAppearanceDictionary ap = null; - COSDictionary apDic = (COSDictionary)dictionary.getDictionaryObject( COSName.AP ); - if( apDic != null ) + COSDictionary apDic = (COSDictionary) dictionary.getDictionaryObject(COSName.AP); + if (apDic != null) { - ap = new PDAppearanceDictionary( apDic ); + ap = new PDAppearanceDictionary(apDic); } return ap; } /** * This will set the appearance associated with this annotation. - * + * * @param appearance The appearance dictionary for this annotation. */ - public void setAppearance( PDAppearanceDictionary appearance ) + public void setAppearance(PDAppearanceDictionary appearance) { COSDictionary ap = null; - if( appearance != null ) + if (appearance != null) { ap = appearance.getDictionary(); } - dictionary.setItem( COSName.AP, ap ); + dictionary.setItem(COSName.AP, ap); } /** * Get the invisible flag. - * + * * @return The invisible flag. */ public boolean isInvisible() { - return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_INVISIBLE ); + return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_INVISIBLE); } /** * Set the invisible flag. - * + * * @param invisible The new invisible flag. */ - public void setInvisible( boolean invisible ) + public void setInvisible(boolean invisible) { - BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_INVISIBLE, invisible ); + BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_INVISIBLE, invisible); } /** * Get the hidden flag. - * + * * @return The hidden flag. */ public boolean isHidden() { - return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_HIDDEN ); + return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_HIDDEN); } /** * Set the hidden flag. - * + * * @param hidden The new hidden flag. */ - public void setHidden( boolean hidden ) + public void setHidden(boolean hidden) { - BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_HIDDEN, hidden ); + BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_HIDDEN, hidden); } /** * Get the printed flag. - * + * * @return The printed flag. */ public boolean isPrinted() { - return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_PRINTED ); + return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_PRINTED); } /** * Set the printed flag. - * + * * @param printed The new printed flag. */ - public void setPrinted( boolean printed ) + public void setPrinted(boolean printed) { - BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_PRINTED, printed ); + BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_PRINTED, printed); } /** * Get the noZoom flag. - * + * * @return The noZoom flag. */ public boolean isNoZoom() { - return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_ZOOM ); + return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_NO_ZOOM); } /** * Set the noZoom flag. - * + * * @param noZoom The new noZoom flag. */ - public void setNoZoom( boolean noZoom ) + public void setNoZoom(boolean noZoom) { - BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_ZOOM, noZoom ); + BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_NO_ZOOM, noZoom); } /** * Get the noRotate flag. - * + * * @return The noRotate flag. */ public boolean isNoRotate() { - return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_ROTATE ); + return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_NO_ROTATE); } /** * Set the noRotate flag. - * + * * @param noRotate The new noRotate flag. */ - public void setNoRotate( boolean noRotate ) + public void setNoRotate(boolean noRotate) { - BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_ROTATE, noRotate ); + BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_NO_ROTATE, noRotate); } /** * Get the noView flag. - * + * * @return The noView flag. */ public boolean isNoView() { - return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_NO_VIEW ); + return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_NO_VIEW); } /** * Set the noView flag. - * + * * @param noView The new noView flag. */ - public void setNoView( boolean noView ) + public void setNoView(boolean noView) { - BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_NO_VIEW, noView ); + BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_NO_VIEW, noView); } /** * Get the readOnly flag. - * + * * @return The readOnly flag. */ public boolean isReadOnly() { - return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_READ_ONLY ); + return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_READ_ONLY); } /** * Set the readOnly flag. - * + * * @param readOnly The new readOnly flag. */ - public void setReadOnly( boolean readOnly ) + public void setReadOnly(boolean readOnly) { - BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_READ_ONLY, readOnly ); + BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_READ_ONLY, readOnly); } /** * Get the locked flag. - * + * * @return The locked flag. */ public boolean isLocked() { - return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_LOCKED ); + return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_LOCKED); } /** * Set the locked flag. - * + * * @param locked The new locked flag. */ - public void setLocked( boolean locked ) + public void setLocked(boolean locked) { - BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_LOCKED, locked ); + BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_LOCKED, locked); } /** * Get the toggleNoView flag. - * + * * @return The toggleNoView flag. */ public boolean isToggleNoView() { - return BitFlagHelper.getFlag( getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW ); + return BitFlagHelper.getFlag(getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW); } /** * Set the toggleNoView flag. - * + * * @param toggleNoView The new toggleNoView flag. */ - public void setToggleNoView( boolean toggleNoView ) + public void setToggleNoView(boolean toggleNoView) { - BitFlagHelper.setFlag( getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW, toggleNoView ); + BitFlagHelper.setFlag(getDictionary(), COSName.F, FLAG_TOGGLE_NO_VIEW, toggleNoView); } /** * Get the "contents" of the field. - * + * * @return the value of the contents. */ public String getContents() @@ -512,93 +508,105 @@ public String getContents() /** * Set the "contents" of the field. - * + * * @param value the value of the contents. */ - public void setContents( String value) + public void setContents(String value) { dictionary.setString(COSName.CONTENTS, value); } /** * This will retrieve the date and time the annotation was modified. - * + * * @return the modified date/time (often in date format, but can be an arbitary string). */ public String getModifiedDate() { - return getDictionary().getString( COSName.M ); + return getDictionary().getString(COSName.M); } /** * This will set the the date and time the annotation was modified. - * - * @param m - * the date and time the annotation was created. + * + * @param m the date and time the annotation was created. */ - public void setModifiedDate( String m ) + public void setModifiedDate(String m) { - getDictionary().setString( COSName.M, m ); + getDictionary().setString(COSName.M, m); } /** - * This will get the name, a string intended to uniquely identify each annotation - * within a page. Not to be confused with some annotations Name entry which - * impact the default image drawn for them. - * + * This will get the name, a string intended to uniquely identify each annotation within a page. Not to be confused + * with some annotations Name entry which impact the default image drawn for them. + * * @return The identifying name for the Annotation. */ public String getAnnotationName() { - return getDictionary().getString( COSName.NM ); + return getDictionary().getString(COSName.NM); } /** - * This will set the name, a string intended to uniquely identify each annotation - * within a page. Not to be confused with some annotations Name entry which - * impact the default image drawn for them. - * + * This will set the name, a string intended to uniquely identify each annotation within a page. Not to be confused + * with some annotations Name entry which impact the default image drawn for them. + * * @param nm The identifying name for the annotation. */ - public void setAnnotationName( String nm ) + public void setAnnotationName(String nm) + { + getDictionary().setString(COSName.NM, nm); + } + + /** + * This will get the key of this annotation in the structural parent tree. + * + * @return the integer key of the annotation's entry in the structural parent tree + */ + public int getStructParent() + { + return getDictionary().getInt(COSName.STRUCT_PARENT, 0); + } + + /** + * This will set the key for this annotation in the structural parent tree. + * + * @param structParent The new key for this annotation. + */ + public void setStructParent(int structParent) { - getDictionary().setString( COSName.NM, nm ); + getDictionary().setInt(COSName.STRUCT_PARENT, structParent); } /** - * This will set the colour used in drawing various elements. - * As of PDF 1.6 these are : Background of icon when closed - * Title bar of popup window - * Border of a link annotation - * + * This will set the colour used in drawing various elements. As of PDF 1.6 these are : Background of icon when + * closed Title bar of popup window Border of a link annotation + * * Colour is in DeviceRGB colourspace - * - * @param c - * colour in the DeviceRGB colourspace - * + * + * @param c colour in the DeviceRGB colourspace + * */ - public void setColour( PDGamma c ) + public void setColour(PDGamma c) { - getDictionary().setItem( COSName.C, c ); + getDictionary().setItem(COSName.C, c); } /** - * This will retrieve the colour used in drawing various elements. - * As of PDF 1.6 these are : Background of icon when closed - * Title bar of popup window - * Border of a link annotation - * + * This will retrieve the colour used in drawing various elements. As of PDF 1.6 these are : Background of icon when + * closed Title bar of popup window Border of a link annotation + * * Colour is in DeviceRGB colourspace - * + * * @return PDGamma object representing the colour - * + * */ public PDGamma getColour() { - COSArray c = (COSArray) getDictionary().getItem(COSName.C ); + COSArray c = (COSArray) getDictionary().getItem(COSName.C); if (c != null) { - return new PDGamma( c ); + return new PDGamma(c); } else { @@ -606,7 +614,7 @@ public PDGamma getColour() } } - /** + /** * This will retrieve the subtype of the annotation. * * @return the subtype @@ -625,7 +633,7 @@ public void setPage(PDPage page) { this.getDictionary().setItem(COSName.P, page); } - + /** * This will retrieve the corresponding page of this annotation. * diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java index 918ad53efa7..c305c424c3e 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java @@ -21,6 +21,7 @@ import org.apache.pdfbox.cos.COSDocument; import org.apache.pdfbox.pdfparser.VisualSignatureParser; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDVisibleSigProperties; public class SignatureOptions @@ -65,6 +66,20 @@ public void setVisualSignature(InputStream is) throws IOException visParser.parse(); visualSignature = visParser.getDocument(); } + + /** + * Reads the visual signature from the given visual signature properties + * + * @param is the PDVisibleSigProperties object containing the visual signature + * + * @throws IOException when something went wrong during parsing + * + * @since 1.8.3 + */ + public void setVisualSignature(PDVisibleSigProperties visSignatureProperties) throws IOException + { + setVisualSignature(visSignatureProperties.getVisibleSignature()); + } /** * Get the visual signature. diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateBuilder.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateBuilder.java new file mode 100644 index 00000000000..b4cfd7f961e --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateBuilder.java @@ -0,0 +1,249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.visible; + +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.pdfbox.cos.COSArray; +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.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; + +/** + * That class builds visible signature template + * which will be added in our pdf document + * @author Vakhtang koroghlishvili (Gogebashvili) + * + */ +public interface PDFTemplateBuilder { + + /** + * In order to create Affine Transform, using parameters + * @param params + */ + public void createAffineTransform(byte [] params); + + /** + * Creates specified size page + * @param properties + */ + public void createPage(PDVisibleSignDesigner properties); + + /** + * Creates template using page + * @param page + * @throws IOException + */ + public void createTemplate(PDPage page) throws IOException; + + /** + * Creates Acro forms in the template + * @param template + */ + public void createAcroForm(PDDocument template); + + /** + * Creates signature fields + * @param acroForm + * @throws IOException + */ + public void createSignatureField(PDAcroForm acroForm) throws IOException; + + /** + * Creates PDSignature + * @param pdSignatureField + * @param page + * @param signatureName + * @throws IOException + */ + public void createSignature(PDSignatureField pdSignatureField, PDPage page, String signatureName) throws IOException; + + /** + * Create AcroForm Dictionary + * @param acroForm + * @param signatureField + * @throws IOException + */ + public void createAcroFormDictionary(PDAcroForm acroForm, PDSignatureField signatureField) throws IOException; + + /** + * Creates SingatureRectangle + * @param signatureField + * @param properties + * @throws IOException + */ + public void createSignatureRectangle(PDSignatureField signatureField, PDVisibleSignDesigner properties) throws IOException; + + /** + * Creates procSetArray of PDF,Text,ImageB,ImageC,ImageI + */ + public void createProcSetArray(); + + /** + * Creates signature image + * @param template + * @param InputStream + * @throws IOException + */ + public void createSignatureImage(PDDocument template, InputStream InputStream) throws IOException; + + /** + * + * @param params + */ + public void createFormaterRectangle(byte [] params); + + /** + * + * @param template + */ + public void createHolderFormStream(PDDocument template); + + /** + * Creates resources of form + */ + public void createHolderFormResources(); + + /** + * Creates Form + * @param holderFormResources + * @param holderFormStream + * @param formrect + */ + public void createHolderForm(PDResources holderFormResources, PDStream holderFormStream, PDRectangle formrect); + + /** + * Creates appearance dictionary + * @param holderForml + * @param signatureField + * @throws IOException + */ + public void createAppearanceDictionary(PDXObjectForm holderForml, PDSignatureField signatureField) throws IOException; + + /** + * + * @param template + */ + public void createInnerFormStream(PDDocument template); + + + /** + * Creates InnerForm + */ + public void createInnerFormResource(); + + /** + * + * @param innerFormResources + * @param innerFormStream + * @param formrect + */ + public void createInnerForm(PDResources innerFormResources, PDStream innerFormStream, PDRectangle formrect); + + + /** + * + * @param innerForm + * @param holderFormResources + */ + public void insertInnerFormToHolerResources(PDXObjectForm innerForm, PDResources holderFormResources); + + /** + * + * @param template + */ + public void createImageFormStream(PDDocument template); + + /** + * Create resource of image form + */ + public void createImageFormResources(); + + /** + * Creates Image form + * @param imageFormResources + * @param innerFormResource + * @param imageFormStream + * @param formrect + * @param affineTransform + * @param img + * @throws IOException + */ + public void createImageForm(PDResources imageFormResources, PDResources innerFormResource, PDStream imageFormStream, PDRectangle formrect, + AffineTransform affineTransform, PDJpeg img) throws IOException; + + /** + * Inject procSetArray + * @param innerForm + * @param page + * @param innerFormResources + * @param imageFormResources + * @param holderFormResources + * @param procSet + */ + public void injectProcSetArray(PDXObjectForm innerForm, PDPage page, PDResources innerFormResources, PDResources imageFormResources, + PDResources holderFormResources, COSArray procSet); + + /** + * injects appearance streams + * @param holderFormStream + * @param innterFormStream + * @param imageFormStream + * @param imageObjectName + * @param imageName + * @param innerFormName + * @param properties + * @throws IOException + */ + public void injectAppearanceStreams(PDStream holderFormStream, PDStream innterFormStream, PDStream imageFormStream, String imageObjectName, + String imageName, String innerFormName, PDVisibleSignDesigner properties) throws IOException; + + /** + * just to create visible signature + * @param template + */ + public void createVisualSignature(PDDocument template); + + /** + * adds Widget Dictionary + * @param signatureField + * @param holderFormResources + * @throws IOException + */ + public void createWidgetDictionary(PDSignatureField signatureField, PDResources holderFormResources) throws IOException; + + /** + * + * @return - PDF template Structure + */ + public PDFTemplateStructure getStructure(); + + /** + * Closes template + * @param template + * @throws IOException + */ + public void closeTemplate(PDDocument template) throws IOException; +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateCreator.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateCreator.java new file mode 100644 index 00000000000..07b4ee711fc --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateCreator.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.visible; + +import java.awt.geom.AffineTransform; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.PDXObjectForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; + +/** + * Using that class, we build pdf template + * @author vakhtang koroghlishvili (gogebashvili) + */ +public class PDFTemplateCreator +{ + + PDFTemplateBuilder pdfBuilder; + private static final Log logger = LogFactory.getLog(PDFTemplateCreator.class); + + /** + * sets PDFBuilder + * + * @param bookBuilder + */ + public PDFTemplateCreator(PDFTemplateBuilder bookBuilder) + { + this.pdfBuilder = bookBuilder; + } + + /** + * that method returns object of PDFStructur + * + * @return PDFStructure + */ + public PDFTemplateStructure getPdfStructure() + { + return this.pdfBuilder.getStructure(); + } + + /** + * this method builds pdf step by step, and finally it returns stream of visible signature + * @param properties + * @return InputStream + * @throws IOException + * @throws COSVisitorException + */ + + public InputStream buildPDF(PDVisibleSignDesigner properties) throws IOException + { + logger.info("pdf building has been started"); + PDFTemplateStructure pdfStructure = pdfBuilder.getStructure(); + + // we create array of [Text, ImageB, ImageC, ImageI] + this.pdfBuilder.createProcSetArray(); + + //create page + this.pdfBuilder.createPage(properties); + PDPage page = pdfStructure.getPage(); + + //create template + this.pdfBuilder.createTemplate(page); + PDDocument template = pdfStructure.getTemplate(); + + //create /AcroForm + this.pdfBuilder.createAcroForm(template); + PDAcroForm acroForm = pdfStructure.getAcroForm(); + + // AcroForm contains singature fields + this.pdfBuilder.createSignatureField(acroForm); + PDSignatureField pdSignatureField = pdfStructure.getSignatureField(); + + // create signature + this.pdfBuilder.createSignature(pdSignatureField, page, properties.getSignatureFieldName()); + + // that is /AcroForm/DR entry + this.pdfBuilder.createAcroFormDictionary(acroForm, pdSignatureField); + + // create AffineTransform + this.pdfBuilder.createAffineTransform(properties.getAffineTransformParams()); + AffineTransform transform = pdfStructure.getAffineTransform(); + + // rectangle, formatter, image. /AcroForm/DR/XObject contains that form + this.pdfBuilder.createSignatureRectangle(pdSignatureField, properties); + this.pdfBuilder.createFormaterRectangle(properties.getFormaterRectangleParams()); + PDRectangle formater = pdfStructure.getFormaterRectangle(); + this.pdfBuilder.createSignatureImage(template, properties.getImageStream()); + + // create form stream, form and resource. + this.pdfBuilder.createHolderFormStream(template); + PDStream holderFormStream = pdfStructure.getHolderFormStream(); + this.pdfBuilder.createHolderFormResources(); + PDResources holderFormResources = pdfStructure.getHolderFormResources(); + this.pdfBuilder.createHolderForm(holderFormResources, holderFormStream, formater); + + // that is /AP entry the appearance dictionary. + this.pdfBuilder.createAppearanceDictionary(pdfStructure.getHolderForm(), pdSignatureField); + + // inner formstream, form and resource (hlder form containts inner form) + this.pdfBuilder.createInnerFormStream(template); + this.pdfBuilder.createInnerFormResource(); + PDResources innerFormResource = pdfStructure.getInnerFormResources(); + this.pdfBuilder.createInnerForm(innerFormResource, pdfStructure.getInnterFormStream(), formater); + PDXObjectForm innerForm = pdfStructure.getInnerForm(); + + // inner form must be in the holder form as we wrote + this.pdfBuilder.insertInnerFormToHolerResources(innerForm, holderFormResources); + + // Image form is in this structure: /AcroForm/DR/FRM0/Resources/XObject/n0 + this.pdfBuilder.createImageFormStream(template); + PDStream imageFormStream = pdfStructure.getImageFormStream(); + this.pdfBuilder.createImageFormResources(); + PDResources imageFormResources = pdfStructure.getImageFormResources(); + this.pdfBuilder.createImageForm(imageFormResources, innerFormResource, imageFormStream, formater, transform, + pdfStructure.getJpedImage()); + + // now inject procSetArray + this.pdfBuilder.injectProcSetArray(innerForm, page, innerFormResource, imageFormResources, holderFormResources, + pdfStructure.getProcSet()); + + String imgFormName = pdfStructure.getImageFormName(); + String imgName = pdfStructure.getImageName(); + String innerFormName = pdfStructure.getInnerFormName(); + + // now create Streams of AP + this.pdfBuilder.injectAppearanceStreams(holderFormStream, imageFormStream, imageFormStream, imgFormName, + imgName, innerFormName, properties); + this.pdfBuilder.createVisualSignature(template); + this.pdfBuilder.createWidgetDictionary(pdSignatureField, holderFormResources); + + ByteArrayInputStream in = null; + try + { + in = pdfStructure.getTemplateAppearanceStream(); + } + catch (COSVisitorException e) + { + logger.error("COSVisitorException: can't get apereance stream ", e); + } + logger.info("stream returning started, size= " + in.available()); + + // we must close the document + template.close(); + + // return result of the stream + return in; + + } +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateStructure.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateStructure.java new file mode 100644 index 00000000000..a49577f04fe --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateStructure.java @@ -0,0 +1,616 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.visible; + +import java.awt.geom.AffineTransform; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +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.COSDocument; +import org.apache.pdfbox.exceptions.COSVisitorException; +import org.apache.pdfbox.pdfwriter.COSWriter; +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.interactive.annotation.PDAppearanceDictionary; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; + +/** + * Structure of PDF document with visible signature + * + * @author vakhtang koroghlishvili (gogebashvili) + * + */ +public class PDFTemplateStructure +{ + + private PDPage page; + private PDDocument template; + private PDAcroForm acroForm; + private PDSignatureField signatureField; + private PDSignature pdSignature; + private COSDictionary acroFormDictionary; + private PDRectangle singatureRectangle; + private AffineTransform affineTransform; + private COSArray procSet; + private PDJpeg jpedImage; + private PDRectangle formaterRectangle; + private PDStream holderFormStream; + private PDResources holderFormResources; + private PDXObjectForm holderForm; + private PDAppearanceDictionary appearanceDictionary; + private PDStream innterFormStream; + private PDResources innerFormResources; + private PDXObjectForm innerForm; + private PDStream imageFormStream; + private PDResources imageFormResources; + private List acroFormFields; + private String innerFormName; + private String imageFormName; + private String imageName; + private COSDocument visualSignature; + private PDXObjectForm imageForm; + private COSDictionary widgetDictionary; + + /** + * Returns document page. + * @return + */ + public PDPage getPage() + { + return page; + } + + /** + * Sets document page + * @param page + */ + public void setPage(PDPage page) + { + this.page = page; + } + + /** + * Gets PDDocument template. + * This represents a digital signature + * that can be attached to a document + * @return + */ + public PDDocument getTemplate() + { + return template; + } + + /** + * Wets PDDocument template. + * This represents a digital signature + * that can be attached to a document + * @param template + */ + public void setTemplate(PDDocument template) + { + this.template = template; + } + + /** + * Gets Acroform + * @return + */ + public PDAcroForm getAcroForm() + { + return acroForm; + } + + /** + * Sets Acroform + * @param acroForm + */ + public void setAcroForm(PDAcroForm acroForm) + { + this.acroForm = acroForm; + } + + /** + * Gets Signature field + * @return + */ + public PDSignatureField getSignatureField() + { + return signatureField; + } + + /** + * Sets signature field + * @param signatureField + */ + public void setSignatureField(PDSignatureField signatureField) + { + this.signatureField = signatureField; + } + + /** + * Gets PDSignature + * @return + */ + public PDSignature getPdSignature() + { + return pdSignature; + } + + /** + * Sets PDSignature + * @param pdSignature + */ + public void setPdSignature(PDSignature pdSignature) + { + this.pdSignature = pdSignature; + } + + /** + * Gets Dictionary of AcroForm. Thats /DR + * entry in the AcroForm + * @return + */ + public COSDictionary getAcroFormDictionary() + { + return acroFormDictionary; + } + + /** + * Acroform have its Dictionary, so we here set + * the Dictionary which is in this location: + * AcroForm/DR + * @param acroFormDictionary + */ + public void setAcroFormDictionary(COSDictionary acroFormDictionary) + { + this.acroFormDictionary = acroFormDictionary; + } + + /** + * Gets SignatureRectangle + * @return + */ + public PDRectangle getSingatureRectangle() + { + return singatureRectangle; + } + + /** + * Sets SignatureRectangle + * @param singatureRectangle + */ + public void setSignatureRectangle(PDRectangle singatureRectangle) + { + this.singatureRectangle = singatureRectangle; + } + + /** + * Gets AffineTransform + * @return + */ + public AffineTransform getAffineTransform() + { + return affineTransform; + } + + /** + * Sets AffineTransform + * @param affineTransform + */ + public void setAffineTransform(AffineTransform affineTransform) + { + this.affineTransform = affineTransform; + } + + /** + * Gets ProcSet Array + * @return + */ + public COSArray getProcSet() + { + return procSet; + } + + /** + * Sets ProcSet Array + * @param procSet + */ + public void setProcSet(COSArray procSet) + { + this.procSet = procSet; + } + + /** + * Gets the image of visible signature + * @return + */ + public PDJpeg getJpedImage() + { + return jpedImage; + } + + /** + * Sets the image of visible signature + * @param jpedImage + */ + public void setJpedImage(PDJpeg jpedImage) + { + this.jpedImage = jpedImage; + } + + /** + * Gets formatter rectangle + * @return + */ + public PDRectangle getFormaterRectangle() + { + return formaterRectangle; + } + + /** + * Sets formatter rectangle + * @param formaterRectangle + */ + public void setFormaterRectangle(PDRectangle formaterRectangle) + { + this.formaterRectangle = formaterRectangle; + } + + /** + * Sets HolderFormStream + * @return + */ + public PDStream getHolderFormStream() + { + return holderFormStream; + } + + /** + * Sets stream of holder form Stream + * @param holderFormStream + */ + public void setHolderFormStream(PDStream holderFormStream) + { + this.holderFormStream = holderFormStream; + } + + /** + * Gets Holder form. + * That form is here AcroForm/DR/XObject/{holder form name} + * By default, name stars with FRM. We also add number of form + * to the name. + * @return + */ + public PDXObjectForm getHolderForm() + { + return holderForm; + } + + /** + * In the structure, form will be contained by XObject in the AcroForm/DR/ + * @param holderForm + */ + public void setHolderForm(PDXObjectForm holderForm) + { + this.holderForm = holderForm; + } + + /** + * Gets Holder form resources + * @return + */ + public PDResources getHolderFormResources() + { + return holderFormResources; + } + + /** + * Sets holder form resources + * @param holderFormResources + */ + public void setHolderFormResources(PDResources holderFormResources) + { + this.holderFormResources = holderFormResources; + } + + /** + * Gets AppearanceDictionary + * That is /AP entry the appearance dictionary. + * @return + */ + public PDAppearanceDictionary getAppearanceDictionary() + { + return appearanceDictionary; + } + + /** + * Sets AppearanceDictionary + * That is /AP entry the appearance dictionary. + * @param appearanceDictionary + */ + public void setAppearanceDictionary(PDAppearanceDictionary appearanceDictionary) + { + this.appearanceDictionary = appearanceDictionary; + } + + /** + * Gets Inner form Stream. + * @return + */ + public PDStream getInnterFormStream() + { + return innterFormStream; + } + + /** + * Sets inner form stream + * @param innterFormStream + */ + public void setInnterFormStream(PDStream innterFormStream) + { + this.innterFormStream = innterFormStream; + } + + /** + * Gets inner form Resource + * @return + */ + public PDResources getInnerFormResources() + { + return innerFormResources; + } + + /** + * Sets inner form resource + * @param innerFormResources + */ + public void setInnerFormResources(PDResources innerFormResources) + { + this.innerFormResources = innerFormResources; + } + + /** + * Gets inner form that is in this location: + * AcroForm/DR/XObject/{holder form name}/Resources/XObject/{inner name} + * By default inner form name starts with "n". Then we add number of form + * to the name. + * @return + */ + public PDXObjectForm getInnerForm() + { + return innerForm; + } + + /** + * sets inner form to this location: + * AcroForm/DR/XObject/{holder form name}/Resources/XObject/{destination} + * @param innerForm + */ + public void setInnerForm(PDXObjectForm innerForm) + { + this.innerForm = innerForm; + } + + /** + * Gets name of inner form + * @return + */ + public String getInnerFormName() + { + return innerFormName; + } + + /** + * Sets inner form name + * @param innerFormName + */ + public void setInnerFormName(String innerFormName) + { + this.innerFormName = innerFormName; + } + + /** + * Gets Image form stream + * @return + */ + public PDStream getImageFormStream() + { + return imageFormStream; + } + + /** + * Sets image form stream + * @param imageFormStream + */ + public void setImageFormStream(PDStream imageFormStream) + { + this.imageFormStream = imageFormStream; + } + + /** + * Gets image form resources + * @return + */ + public PDResources getImageFormResources() + { + return imageFormResources; + } + + /** + * Sets image form resource + * @param imageFormResources + */ + public void setImageFormResources(PDResources imageFormResources) + { + this.imageFormResources = imageFormResources; + } + + /** + * Gets Image form. Image form is in this structure: + * /AcroForm/DR/{holder form}/Resources/XObject /{inner form} + * /Resources/XObject/{image form name}. + * @return + */ + public PDXObjectForm getImageForm() + { + return imageForm; + } + + /** + * Sets Image form. Image form will be in this structure: + * /AcroForm/DR/{holder form}/Resources/XObject /{inner form} + * /Resources/XObject/{image form name}. By default we start + * image form name with "img". Then we add number of image + * form to the form name. + * Sets image form + * @param imageForm + */ + public void setImageForm(PDXObjectForm imageForm) + { + this.imageForm = imageForm; + } + + /** + * Gets image form name + * @return + */ + public String getImageFormName() + { + return imageFormName; + } + + /** + * Sets image form name + * @param imageFormName + */ + public void setImageFormName(String imageFormName) + { + this.imageFormName = imageFormName; + } + + /** + * Gets visible signature image name + * @return + */ + public String getImageName() + { + return imageName; + } + + /** + * Sets visible signature image name + * @param imageName + */ + public void setImageName(String imageName) + { + this.imageName = imageName; + } + + /** + * Gets COSDocument of visible Signature. + * @see org.apache.pdfbox.cos.COSDocument + * @return + */ + public COSDocument getVisualSignature() + { + return visualSignature; + } + + /** + * + * Sets COSDocument of visible Signature. + * @see org.apache.pdfbox.cos.COSDocument + * @param visualSignature + */ + public void setVisualSignature(COSDocument visualSignature) + { + this.visualSignature = visualSignature; + } + + /** + * Gets acroFormFields + * @return + */ + public List getAcroFormFields() + { + return acroFormFields; + } + + /** + * Sets acroFormFields + * @param acroFormFields + */ + public void setAcroFormFields(List acroFormFields) + { + this.acroFormFields = acroFormFields; + } + + /** + * Gets AP of the created template + * @return + * @throws IOException + * @throws COSVisitorException + */ + public ByteArrayInputStream getTemplateAppearanceStream() throws IOException, COSVisitorException + { + COSDocument visualSignature = getVisualSignature(); + ByteArrayOutputStream memoryOut = new ByteArrayOutputStream(); + COSWriter memoryWriter = new COSWriter(memoryOut); + memoryWriter.write(visualSignature); + + ByteArrayInputStream input = new ByteArrayInputStream(memoryOut.toByteArray()); + + getTemplate().close(); + + return input; + } + + /** + * Gets Widget Dictionary. + * {@link org.apache.pdfbox.pdmodel.interactive.form.PDField} + * @see org.apache.pdfbox.pdmodel.interactive.form.PDField.getWidget() + * @return + */ + public COSDictionary getWidgetDictionary() + { + return widgetDictionary; + } + + /** + * Sets Widget Dictionary. + * {@link org.apache.pdfbox.pdmodel.interactive.form.PDField} + * @see org.apache.pdfbox.pdmodel.interactive.form.PDField.getWidget() + * @param widgetDictionary + */ + public void setWidgetDictionary(COSDictionary widgetDictionary) + { + this.widgetDictionary = widgetDictionary; + } + +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigBuilder.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigBuilder.java new file mode 100644 index 00000000000..097fd96383a --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigBuilder.java @@ -0,0 +1,371 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.visible; + +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +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.COSDictionary; +import org.apache.pdfbox.cos.COSName; +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.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.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; + +/** + * That's implementation of PDFTemplateBuilder + * @see org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible.PDFTemplateBuilder + * @author vakhtang koroghlishvili (gogebashvili) + * + */ +public class PDVisibleSigBuilder implements PDFTemplateBuilder +{ + + private PDFTemplateStructure pdfStructure; + private static final Log logger = LogFactory.getLog(PDVisibleSigBuilder.class); + + public void createPage(PDVisibleSignDesigner properties) + { + PDPage page = new PDPage(); + page.setMediaBox(new PDRectangle(properties.getPageWidth(), properties.getPageHeight())); + pdfStructure.setPage(page); + logger.info("PDF page has been created"); + } + + public void createTemplate(PDPage page) throws IOException + { + PDDocument template = new PDDocument(); + template.addPage(page); + pdfStructure.setTemplate(template); + } + + public PDVisibleSigBuilder() + { + pdfStructure = new PDFTemplateStructure(); + logger.info("PDF Strucure has been Created"); + + } + + public void createAcroForm(PDDocument template) + { + PDAcroForm theAcroForm = new PDAcroForm(template); + template.getDocumentCatalog().setAcroForm(theAcroForm); + pdfStructure.setAcroForm(theAcroForm); + logger.info("Acro form page has been created"); + + } + + public PDFTemplateStructure getStructure() + { + return pdfStructure; + } + + public void createSignatureField(PDAcroForm acroForm) throws IOException + { + PDSignatureField sf = new PDSignatureField(acroForm); + pdfStructure.setSignatureField(sf); + logger.info("Signature field has been created"); + } + + public void createSignature(PDSignatureField pdSignatureField, PDPage page, String signatureName) + throws IOException + { + PDSignature pdSignature = new PDSignature(); + pdSignatureField.setSignature(pdSignature); + pdSignatureField.getWidget().setPage(page); + page.getAnnotations().add(pdSignatureField.getWidget()); + pdSignature.setName(signatureName); + pdSignature.setByteRange(new int[] { 0, 0, 0, 0 }); + pdSignature.setContents(new byte[4096]); + pdfStructure.setPdSignature(pdSignature); + logger.info("PDSignatur has been created"); + } + + public void createAcroFormDictionary(PDAcroForm acroForm, PDSignatureField signatureField) throws IOException + { + @SuppressWarnings("unchecked") + List acroFormFields = acroForm.getFields(); + COSDictionary acroFormDict = acroForm.getDictionary(); + acroFormDict.setDirect(true); + acroFormDict.setInt(COSName.SIG_FLAGS, 3); + acroFormFields.add(signatureField); + acroFormDict.setString(COSName.DA, "/sylfaen 0 Tf 0 g"); + pdfStructure.setAcroFormFields(acroFormFields); + pdfStructure.setAcroFormDictionary(acroFormDict); + logger.info("AcroForm dictionary has been created"); + } + + public void createSignatureRectangle(PDSignatureField signatureField, PDVisibleSignDesigner properties) + throws IOException + { + + PDRectangle rect = new PDRectangle(); + rect.setUpperRightX(properties.getxAxis() + properties.getWidth()); + rect.setUpperRightY(properties.getTemplateHeight() - properties.getyAxis()); + rect.setLowerLeftY(properties.getTemplateHeight() - properties.getyAxis() - properties.getHeight()); + rect.setLowerLeftX(properties.getxAxis()); + signatureField.getWidget().setRectangle(rect); + pdfStructure.setSignatureRectangle(rect); + logger.info("rectangle of signature has been created"); + } + + public void createAffineTransform(byte[] params) + { + AffineTransform transform = new AffineTransform(params[0], params[1], params[2], params[3], params[4], + params[5]); + pdfStructure.setAffineTransform(transform); + logger.info("Matrix has been added"); + } + + public void createProcSetArray() + { + COSArray procSetArr = new COSArray(); + procSetArr.add(COSName.getPDFName("PDF")); + procSetArr.add(COSName.getPDFName("Text")); + procSetArr.add(COSName.getPDFName("ImageB")); + procSetArr.add(COSName.getPDFName("ImageC")); + procSetArr.add(COSName.getPDFName("ImageI")); + pdfStructure.setProcSet(procSetArr); + logger.info("ProcSet array has been created"); + } + + public void createSignatureImage(PDDocument template, InputStream inputStream) throws IOException + { + PDJpeg img = new PDJpeg(template, inputStream); + pdfStructure.setJpedImage(img); + logger.info("Visible Signature Image has been created"); + // pdfStructure.setTemplate(template); + inputStream.close(); + + } + + public void createFormaterRectangle(byte[] params) + { + + PDRectangle formrect = new PDRectangle(); + formrect.setUpperRightX(params[0]); + formrect.setUpperRightY(params[1]); + formrect.setLowerLeftX(params[2]); + formrect.setLowerLeftY(params[3]); + + pdfStructure.setFormaterRectangle(formrect); + logger.info("Formater rectangle has been created"); + + } + + public void createHolderFormStream(PDDocument template) + { + PDStream holderForm = new PDStream(template); + pdfStructure.setHolderFormStream(holderForm); + logger.info("Holder form Stream has been created"); + } + + public void createHolderFormResources() + { + PDResources holderFormResources = new PDResources(); + pdfStructure.setHolderFormResources(holderFormResources); + logger.info("Holder form resources have been created"); + + } + + public void createHolderForm(PDResources holderFormResources, PDStream holderFormStream, PDRectangle formrect) + { + + PDXObjectForm holderForm = new PDXObjectForm(holderFormStream); + holderForm.setResources(holderFormResources); + holderForm.setBBox(formrect); + holderForm.setFormType(1); + pdfStructure.setHolderForm(holderForm); + logger.info("Holder form has been created"); + + } + + public void createAppearanceDictionary(PDXObjectForm holderForml, PDSignatureField signatureField) + throws IOException + { + + PDAppearanceDictionary appearance = new PDAppearanceDictionary(); + appearance.getCOSObject().setDirect(true); + + PDAppearanceStream appearanceStream = new PDAppearanceStream(holderForml.getCOSStream()); + + appearance.setNormalAppearance(appearanceStream); + signatureField.getWidget().setAppearance(appearance); + + pdfStructure.setAppearanceDictionary(appearance); + logger.info("PDF appereance Dictionary has been created"); + + } + + public void createInnerFormStream(PDDocument template) + { + PDStream innterFormStream = new PDStream(template); + pdfStructure.setInnterFormStream(innterFormStream); + logger.info("Strean of another form (inner form - it would be inside holder form) has been created"); + } + + public void createInnerFormResource() + { + PDResources innerFormResources = new PDResources(); + pdfStructure.setInnerFormResources(innerFormResources); + logger.info("Resources of another form (inner form - it would be inside holder form) have been created"); + } + + public void createInnerForm(PDResources innerFormResources, PDStream innerFormStream, PDRectangle formrect) + { + PDXObjectForm innerForm = new PDXObjectForm(innerFormStream); + innerForm.setResources(innerFormResources); + innerForm.setBBox(formrect); + innerForm.setFormType(1); + pdfStructure.setInnerForm(innerForm); + logger.info("Another form (inner form - it would be inside holder form) have been created"); + + } + + public void insertInnerFormToHolerResources(PDXObjectForm innerForm, PDResources holderFormResources) + { + String name = holderFormResources.addXObject(innerForm, "FRM"); + pdfStructure.setInnerFormName(name); + logger.info("Alerady inserted inner form inside holder form"); + } + + public void createImageFormStream(PDDocument template) + { + PDStream imageFormStream = new PDStream(template); + pdfStructure.setImageFormStream(imageFormStream); + logger.info("Created image form Stream"); + + } + + public void createImageFormResources() + { + PDResources imageFormResources = new PDResources(); + pdfStructure.setImageFormResources(imageFormResources); + logger.info("Created image form Resources"); + } + + public void createImageForm(PDResources imageFormResources, PDResources innerFormResource, + PDStream imageFormStream, PDRectangle formrect, AffineTransform affineTransform, PDJpeg img) + throws IOException + { + + /* + * if you need text on the visible signature + * + * PDFont font = PDTrueTypeFont.loadTTF(this.pdfStructure.getTemplate(), new File("D:\\arial.ttf")); + * font.setFontEncoding(new WinAnsiEncoding()); + * + * Map fonts = new HashMap(); fonts.put("arial", font); + */ + PDXObjectForm imageForm = new PDXObjectForm(imageFormStream); + imageForm.setBBox(formrect); + imageForm.setMatrix(affineTransform); + imageForm.setResources(imageFormResources); + imageForm.setFormType(1); + /* + * imageForm.getResources().addFont(font); + * imageForm.getResources().setFonts(fonts); + */ + + imageFormResources.getCOSObject().setDirect(true); + String imageFormName = innerFormResource.addXObject(imageForm, "n"); + String imageName = imageFormResources.addXObject(img, "img"); + this.pdfStructure.setImageForm(imageForm); + this.pdfStructure.setImageFormName(imageFormName); + this.pdfStructure.setImageName(imageName); + logger.info("Created image form"); + } + + public void injectProcSetArray(PDXObjectForm innerForm, PDPage page, PDResources innerFormResources, + PDResources imageFormResources, PDResources holderFormResources, COSArray procSet) + { + + innerForm.getResources().getCOSDictionary().setItem(COSName.PROC_SET, procSet); // + page.getCOSDictionary().setItem(COSName.PROC_SET, procSet); + innerFormResources.getCOSDictionary().setItem(COSName.PROC_SET, procSet); + imageFormResources.getCOSDictionary().setItem(COSName.PROC_SET, procSet); + holderFormResources.getCOSDictionary().setItem(COSName.PROC_SET, procSet); + logger.info("inserted ProcSet to PDF"); + } + + public void injectAppearanceStreams(PDStream holderFormStream, PDStream innterFormStream, PDStream imageFormStream, + String imageObjectName, String imageName, String innerFormName, PDVisibleSignDesigner properties) + throws IOException + { + + // 100 means that document width is 100% via the rectangle. if rectangle + // is 500px, images 100% is 500px. + // String imgFormComment = "q "+imageWidthSize+ " 0 0 50 0 0 cm /" + + // imageName + " Do Q\n" + builder.toString(); + String imgFormComment = "q " + 100 + " 0 0 50 0 0 cm /" + imageName + " Do Q\n"; + String holderFormComment = "q 1 0 0 1 0 0 cm /" + innerFormName + " Do Q \n"; + String innerFormComment = "q 1 0 0 1 0 0 cm /" + imageObjectName + " Do Q\n"; + + appendRawCommands(pdfStructure.getHolderFormStream().createOutputStream(), holderFormComment); + appendRawCommands(pdfStructure.getInnterFormStream().createOutputStream(), innerFormComment); + appendRawCommands(pdfStructure.getImageFormStream().createOutputStream(), imgFormComment); + logger.info("Injected apereance stream to pdf"); + + } + + public void appendRawCommands(OutputStream os, String commands) throws IOException + { + os.write(commands.getBytes("UTF-8")); + os.close(); + } + + public void createVisualSignature(PDDocument template) + { + this.pdfStructure.setVisualSignature(template.getDocument()); + logger.info("Visible signature has been created"); + + } + + public void createWidgetDictionary(PDSignatureField signatureField, PDResources holderFormResources) + throws IOException + { + + COSDictionary widgetDict = signatureField.getWidget().getDictionary(); + widgetDict.setNeedToBeUpdate(true); + widgetDict.setItem(COSName.DR, holderFormResources.getCOSObject()); + + pdfStructure.setWidgetDictionary(widgetDict); + logger.info("WidgetDictionary has been crated"); + } + + public void closeTemplate(PDDocument template) throws IOException + { + template.close(); + this.pdfStructure.getTemplate().close(); + + } +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigProperties.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigProperties.java new file mode 100644 index 00000000000..3fe63e3e911 --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigProperties.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.visible; + +import java.io.IOException; +import java.io.InputStream; + +/** + * This builder class is in order to create visible signature properties. + * + * @author vakhtang koroghlishvili (gogebashvili) + * + */ +public class PDVisibleSigProperties +{ + private String signerName; + private String signerLocation; + private String signatureReason; + private boolean visualSignEnabled; + private int page; + private int preferredSize; + + private InputStream visibleSignature; + private PDVisibleSignDesigner pdVisibleSignature; + + /** + * start building of visible signature + * + * @throws IOException + */ + public void buildSignature() throws IOException + { + PDFTemplateBuilder builder = new PDVisibleSigBuilder(); + PDFTemplateCreator creator = new PDFTemplateCreator(builder); + setVisibleSignature(creator.buildPDF(getPdVisibleSignature())); + } + + /** + * + * @return - signer name + */ + public String getSignerName() + { + return signerName; + } + + /** + * Sets signer name + * @param signerName + * @return + */ + public PDVisibleSigProperties signerName(String signerName) + { + this.signerName = signerName; + return this; + } + + /** + * Gets signer locations + * @return - location + */ + public String getSignerLocation() + { + return signerLocation; + } + + /** + * Sets location + * @param signerLocation + * @return + */ + public PDVisibleSigProperties signerLocation(String signerLocation) + { + this.signerLocation = signerLocation; + return this; + } + + /** + * gets reason of signing + * @return + */ + public String getSignatureReason() + { + return signatureReason; + } + + /** + * sets reason of signing + * @param signatureReason + * @return + */ + public PDVisibleSigProperties signatureReason(String signatureReason) + { + this.signatureReason = signatureReason; + return this; + } + + /** + * returns your page + * @return + */ + public int getPage() + { + return page; + } + + /** + * sets page number + * @param page + * @return + */ + public PDVisibleSigProperties page(int page) + { + this.page = page; + return this; + } + + /** + * gets our preferred size + * @return + */ + public int getPreferredSize() + { + return preferredSize; + } + + /** + * sets our preferred size + * @param preferredSize + * @return + */ + public PDVisibleSigProperties preferredSize(int preferredSize) + { + this.preferredSize = preferredSize; + return this; + } + + /** + * checks if we need to add visible signature + * @return + */ + public boolean isVisualSignEnabled() + { + return visualSignEnabled; + } + + /** + * sets visible signature to be added or not + * @param visualSignEnabled + * @return + */ + public PDVisibleSigProperties visualSignEnabled(boolean visualSignEnabled) + { + this.visualSignEnabled = visualSignEnabled; + return this; + } + + /** + * this method gets visible signature configuration object + * @return + */ + public PDVisibleSignDesigner getPdVisibleSignature() + { + return pdVisibleSignature; + } + + /** + * Sets visible signature configuration Object + * @param pdVisibleSignature + * @return + */ + public PDVisibleSigProperties setPdVisibleSignature(PDVisibleSignDesigner pdVisibleSignature) + { + this.pdVisibleSignature = pdVisibleSignature; + return this; + } + + /** + * returns visible signature configuration object + * @return + */ + public InputStream getVisibleSignature() + { + return visibleSignature; + } + + /** + * sets configuration object of visible signature + * @param visibleSignature + */ + public void setVisibleSignature(InputStream visibleSignature) + { + this.visibleSignature = visibleSignature; + } + +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSignDesigner.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSignDesigner.java new file mode 100644 index 00000000000..71f50fcf773 --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSignDesigner.java @@ -0,0 +1,464 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.visible; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import javax.imageio.ImageIO; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.bouncycastle.util.Arrays; + +/** + * + * That class is in order to build your + * visible signature design. Because of + * this is builder, instead of setParam() + * we use param() methods. + * @author vakhtang koroghlishvili (gogebashvili) + */ +public class PDVisibleSignDesigner +{ + + private Float sigImgWidth; + private Float sigImgHeight; + private float xAxis; + private float yAxis; + private float pageHeight; + private float pageWidth; + private InputStream imgageStream; + private String signatureFieldName = "sig"; // default + private byte[] formaterRectangleParams = { 0, 0, 100, 50 }; // default + private byte[] AffineTransformParams = { 1, 0, 0, 1, 0, 0 }; // default + private float imageSizeInPercents; + private PDDocument document = null; + + + + /** + * + * @param originalDocumenStream + * @param imageStream + * @param page-which page are you going to add visible signature + * @throws IOException + */ + public PDVisibleSignDesigner(InputStream originalDocumenStream, InputStream imageStream, int page) throws IOException + { + signatureImageStream(imageStream); + document = PDDocument.load(originalDocumenStream); + calculatePageSize(document, page); + } + + /** + * + * @param documentPath - path of your pdf document + * @param imageStream - stream of image + * @param page -which page are you going to add visible signature + * @throws IOException + */ + public PDVisibleSignDesigner(String documentPath, InputStream imageStream, int page) throws IOException + { + + // set visible singature image Input stream + signatureImageStream(imageStream); + + // create PD document + document = PDDocument.load(documentPath); + + // calculate height an width of document + calculatePageSize(document, page); + + document.close(); + } + + /** + * + * @param doc - Already created PDDocument of your PDF document + * @param imageStream + * @param page + * @throws IOException - If we can't read, flush, or can't close stream + */ + public PDVisibleSignDesigner(PDDocument doc, InputStream imageStream, int page) throws IOException + { + signatureImageStream(imageStream); + calculatePageSize(doc, page); + } + + /** + * Each page of document can be different sizes. + * + * @param document + * @param page + */ + private void calculatePageSize(PDDocument document, int page) + { + + if (page < 1) + { + throw new IllegalArgumentException("First page of pdf is 1, not " + page); + } + + List pages = document.getDocumentCatalog().getAllPages(); + PDPage firstPage =(PDPage) pages.get(page - 1); + PDRectangle mediaBox = firstPage.findMediaBox(); + this.pageHeight(mediaBox.getHeight()); + this.pageWidth = mediaBox.getWidth(); + + float x = this.pageWidth; + float y = 0; + this.pageWidth = this.pageWidth + y; + float tPercent = (100 * y / (x + y)); + this.imageSizeInPercents = 100 - tPercent; + + } + + /** + * + * @param path of image location + * @return image Stream + * @throws IOException + */ + public PDVisibleSignDesigner signatureImage(String path) throws IOException + { + InputStream fin = new FileInputStream(path); + return signatureImageStream(fin); + } + + /** + * zoom signature image with some percent. + * + * @param percent- x % increase image with x percent. + * @return Visible Signature Configuration Object + */ + public PDVisibleSignDesigner zoom(float percent) + { + sigImgHeight = sigImgHeight + (sigImgHeight * percent) / 100; + sigImgWidth = sigImgWidth + (sigImgWidth * percent) / 100; + return this; + } + + /** + * + * @param xAxis - x coordinate + * @param yAxis - y coordinate + * @return Visible Signature Configuration Object + */ + public PDVisibleSignDesigner coordinates(float x, float y) + { + xAxis(x); + yAxis(y); + return this; + } + + /** + * + * @return xAxis - gets x coordinates + */ + public float getxAxis() + { + return xAxis; + } + + /** + * + * @param xAxis - x coordinate + * @return Visible Signature Configuration Object + */ + public PDVisibleSignDesigner xAxis(float xAxis) + { + this.xAxis = xAxis; + return this; + } + + /** + * + * @return yAxis + */ + public float getyAxis() + { + return yAxis; + } + + /** + * + * @param yAxis + * @return Visible Signature Configuration Object + */ + public PDVisibleSignDesigner yAxis(float yAxis) + { + this.yAxis = yAxis; + return this; + } + + /** + * + * @return signature image width + */ + public float getWidth() + { + return sigImgWidth; + } + + /** + * + * @param sets signature image width + * @return Visible Signature Configuration Object + */ + public PDVisibleSignDesigner width(float signatureImgWidth) + { + this.sigImgWidth = signatureImgWidth; + return this; + } + + /** + * + * @return signature image height + */ + public float getHeight() + { + return sigImgHeight; + } + + /** + * + * @param set signature image Height + * @return Visible Signature Configuration Object + */ + public PDVisibleSignDesigner height(float signatureImgHeight) + { + this.sigImgHeight = signatureImgHeight; + return this; + } + + /** + * + * @return template height + */ + protected float getTemplateHeight() + { + return getPageHeight(); + } + + /** + * + * @param templateHeight + * @return Visible Signature Configuration Object + */ + private PDVisibleSignDesigner pageHeight(float templateHeight) + { + this.pageHeight = templateHeight; + return this; + } + + /** + * + * @return signature field name + */ + public String getSignatureFieldName() + { + return signatureFieldName; + } + + /** + * + * @param signatureFieldName + * @return Visible Signature Configuration Object + */ + public PDVisibleSignDesigner signatureFieldName(String signatureFieldName) + { + this.signatureFieldName = signatureFieldName; + return this; + } + + /** + * + * @return image Stream + */ + public InputStream getImageStream() + { + return imgageStream; + } + + /** + * + * @param imgageStream- stream of your visible signature image + * @return Visible Signature Configuration Object + * @throws IOException - If we can't read, flush, or close stream of image + */ + private PDVisibleSignDesigner signatureImageStream(InputStream imageStream) throws IOException + { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = imageStream.read(buffer)) > -1) + { + baos.write(buffer, 0, len); + } + baos.flush(); + baos.close(); + + byte[] byteArray = baos.toByteArray(); + byte[] byteArraySecond = Arrays.clone(byteArray); + + InputStream inputForBufferedImage = new ByteArrayInputStream(byteArray); + InputStream revertInputStream = new ByteArrayInputStream(byteArraySecond); + + if (sigImgHeight == null || sigImgWidth == null) + { + calcualteImageSize(inputForBufferedImage); + } + + this.imgageStream = revertInputStream; + + return this; + } + + /** + * calculates image width and height. sported formats: all + * + * @param fis - input stream of image + * @throws IOException - if can't read input stream + */ + private void calcualteImageSize(InputStream fis) throws IOException + { + + BufferedImage bimg = ImageIO.read(fis); + int width = bimg.getWidth(); + int height = bimg.getHeight(); + + sigImgHeight = (float) height; + sigImgWidth = (float) width; + + } + + /** + * + * @return Affine Transform parameters of for PDF Matrix + */ + public byte[] getAffineTransformParams() + { + return AffineTransformParams; + } + + /** + * + * @param affineTransformParams + * @return Visible Signature Configuration Object + */ + public PDVisibleSignDesigner affineTransformParams(byte[] affineTransformParams) + { + AffineTransformParams = affineTransformParams; + return this; + } + + /** + * + * @return formatter PDRectanle parameters + */ + public byte[] getFormaterRectangleParams() + { + return formaterRectangleParams; + } + + /** + * sets formatter PDRectangle; + * + * @param formaterRectangleParams + * @return Visible Signature Configuration Object + */ + public PDVisibleSignDesigner formaterRectangleParams(byte[] formaterRectangleParams) + { + this.formaterRectangleParams = formaterRectangleParams; + return this; + } + + /** + * + * @return page width + */ + public float getPageWidth() + { + return pageWidth; + } + + /** + * + * @param sets pageWidth + * @return Visible Signature Configuration Object + */ + public PDVisibleSignDesigner pageWidth(float pageWidth) + { + this.pageWidth = pageWidth; + return this; + } + + /** + * + * @return page height + */ + public float getPageHeight() + { + return pageHeight; + } + + /** + * get image size in percents + * @return + */ + public float getImageSizeInPercents() + { + return imageSizeInPercents; + } + + /** + * + * @param imageSizeInPercents + */ + public void imageSizeInPercents(float imageSizeInPercents) + { + this.imageSizeInPercents = imageSizeInPercents; + } + + /** + * returns visible signature text + * @return + */ + public String getSignatureText() + { + throw new UnsupportedOperationException("That method is not yet implemented"); + } + + /** + * + * @param signatureText - adds the text on visible signature + * @return + */ + public PDVisibleSignDesigner signatureText(String signatureText) + { + throw new UnsupportedOperationException("That method is not yet implemented"); + } + +} diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/package.html b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/package.html new file mode 100644 index 00000000000..cabf72c6a9a --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/digitalsignature/visible/package.html @@ -0,0 +1,25 @@ + + + + + + + +This is the visual signature part that help creating the visual representation for the digital signature. + + diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDField.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDField.java index 8f8475ec22a..c9742b067a4 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDField.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDField.java @@ -16,34 +16,28 @@ */ 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 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.COSInteger; import org.apache.pdfbox.cos.COSName; - +import org.apache.pdfbox.pdmodel.common.COSArrayList; +import org.apache.pdfbox.pdmodel.common.COSObjectable; import org.apache.pdfbox.pdmodel.common.PDTextStream; - import org.apache.pdfbox.pdmodel.fdf.FDFField; +import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; 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. - * + * 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 { @@ -60,28 +54,25 @@ public abstract class PDField implements COSObjectable */ 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 ) + public PDField(PDAcroForm theAcroForm) { acroForm = theAcroForm; dictionary = new COSDictionary(); - //no required fields in base field class + // 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. - * + * 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. */ @@ -93,42 +84,41 @@ public PDField(PDAcroForm theAcroForm, COSDictionary field) /** * Returns the partial name of the field. - * + * * @return the name of the field */ public String getPartialName() { - return getDictionary().getString( COSName.T ); + 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 ) + public void setPartialName(String name) { - getDictionary().setString( COSName.T, 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. - * + * 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 ) + if (parent != null) { parentName = parent.getFullyQualifiedName(); } String finalName = getPartialName(); - if( parentName != null ) + if (parentName != null) { finalName = parentName + "." + finalName; } @@ -156,135 +146,132 @@ public void setAlternateFieldName(String 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. - * + * 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 ); + 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 + * 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() ); + return findFieldType(getDictionary()); } - private String findFieldType( COSDictionary dic ) + private String findFieldType(COSDictionary dic) { - String retval = dic.getNameAsString( COSName.FT ); - if( retval == null ) + String retval = dic.getNameAsString(COSName.FT); + if (retval == null) { - COSDictionary parent = (COSDictionary)dic.getDictionaryObject( COSName.PARENT, COSName.P ); - if( parent != null ) + COSDictionary parent = (COSDictionary) dic.getDictionaryObject(COSName.PARENT, COSName.P); + if (parent != null) { - retval = findFieldType( parent ); + 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 ); + 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 ); + 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 ); + 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 ); + 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 ); + 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 ); + 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 ) + COSInteger ff = (COSInteger) getDictionary().getDictionaryObject(COSName.FF); + if (ff != null) { retval = ff.intValue(); } @@ -293,128 +280,128 @@ public int getFieldFlags() /** * This will set the flags for this field. - * + * * @param flags The new flags. */ - public void setFieldFlags( int flags ) + public void setFieldFlags(int flags) { - getDictionary().setInt( COSName.FF, 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 + public void importFDF(FDFField fdfField) throws IOException { Object fieldValue = fdfField.getValue(); int fieldFlags = getFieldFlags(); - if( fieldValue != null ) + if (fieldValue != null) { - if( fieldValue instanceof String ) + if (fieldValue instanceof String) { - setValue( (String)fieldValue ); + setValue((String) fieldValue); } - else if( fieldValue instanceof PDTextStream ) + else if (fieldValue instanceof PDTextStream) { - setValue( ((PDTextStream)fieldValue).getAsString() ); + setValue(((PDTextStream) fieldValue).getAsString()); } else { - throw new IOException( "Unknown field type:" + fieldValue.getClass().getName() ); + throw new IOException("Unknown field type:" + fieldValue.getClass().getName()); } } Integer ff = fdfField.getFieldFlags(); - if( ff != null ) + if (ff != null) { - setFieldFlags( ff.intValue() ); + setFieldFlags(ff.intValue()); } else { - //these are suppose to be ignored if the Ff is set. + // these are suppose to be ignored if the Ff is set. Integer setFf = fdfField.getSetFieldFlags(); - if( setFf != null ) + if (setFf != null) { int setFfInt = setFf.intValue(); fieldFlags = fieldFlags | setFfInt; - setFieldFlags( fieldFlags ); + setFieldFlags(fieldFlags); } Integer clrFf = fdfField.getClearFieldFlags(); - if( clrFf != null ) + if (clrFf != null) { - //we have to clear the bits of the document fields for every bit that is - //set in this field. + // 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 + // Example: + // docFf = 1011 + // clrFf = 1101 + // clrFfValue = 0010; + // newValue = 1011 & 0010 which is 0010 int clrFfValue = clrFf.intValue(); clrFfValue ^= 0xFFFFFFFF; fieldFlags = fieldFlags & clrFfValue; - setFieldFlags( fieldFlags ); + setFieldFlags(fieldFlags); } } PDAnnotationWidget widget = getWidget(); - if( widget != null ) + if (widget != null) { int annotFlags = widget.getAnnotationFlags(); Integer f = fdfField.getWidgetFieldFlags(); - if( f != null && widget != null ) + if (f != null && widget != null) { - widget.setAnnotationFlags( f.intValue() ); + widget.setAnnotationFlags(f.intValue()); } else { - //these are suppose to be ignored if the F is set. + // these are suppose to be ignored if the F is set. Integer setF = fdfField.getSetWidgetFieldFlags(); - if( setF != null ) + if (setF != null) { annotFlags = annotFlags | setF.intValue(); - widget.setAnnotationFlags( annotFlags ); + widget.setAnnotationFlags(annotFlags); } Integer clrF = fdfField.getClearWidgetFieldFlags(); - if( clrF != null ) + if (clrF != null) { - //we have to clear the bits of the document fields for every bit that is - //set in this field. + // 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 + // 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 ); + widget.setAnnotationFlags(annotFlags); } } } List fdfKids = fdfField.getKids(); List pdKids = getKids(); - for( int i=0; fdfKids != null && i kids = getKids(); - if( kids == null ) + if (kids == null) { - retval = new PDAnnotationWidget( getDictionary() ); + retval = new PDAnnotationWidget(getDictionary()); } - else if( kids.size() > 0 ) + else if (kids.size() > 0) { - Object firstKid = kids.get( 0 ); - if( firstKid instanceof PDAnnotationWidget ) + Object firstKid = kids.get(0); + if (firstKid instanceof PDAnnotationWidget) { - retval = (PDAnnotationWidget)firstKid; + retval = (PDAnnotationWidget) firstKid; } else { - retval = ((PDField)firstKid).getWidget(); + retval = ((PDField) firstKid).getWidget(); } } else @@ -460,58 +445,57 @@ else if( kids.size() > 0 ) /** * 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 ) + COSDictionary parentDic = (COSDictionary) getDictionary().getDictionaryObject(COSName.PARENT, COSName.P); + if (parentDic != null) { - parent = PDFieldFactory.createField( getAcroForm(), parentDic ); + 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 ) + public void setParent(PDField parent) { - getDictionary().setItem( "Parent", 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. - * + * 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 + public PDField findKid(String[] name, int nameIndex) throws IOException { PDField retval = null; - COSArray kids = (COSArray)getDictionary().getDictionaryObject( COSName.KIDS ); - if( kids != 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" ) ) ) + COSDictionary kidDictionary = (COSDictionary) kids.getObject(i); + if (name[nameIndex].equals(kidDictionary.getString("T"))) { - retval = PDFieldFactory.createField( acroForm, kidDictionary ); - if( name.length > nameIndex+1 ) + retval = PDFieldFactory.createField(acroForm, kidDictionary); + if (name.length > nameIndex + 1) { - retval = retval.findKid( name, nameIndex+1 ); + retval = retval.findKid(name, nameIndex + 1); } } } @@ -520,69 +504,72 @@ public PDField findKid( String[] name, int nameIndex ) throws IOException } /** - * 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. - * + * 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 getKids() throws IOException { List retval = null; - COSArray kids = (COSArray)getDictionary().getDictionaryObject(COSName.KIDS); - if( kids != null ) + COSArray kids = (COSArray) getDictionary().getDictionaryObject(COSName.KIDS); + if (kids != null) { List kidsList = new ArrayList(); 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 ) ) + COSDictionary kidDictionary = (COSDictionary) kids.getObject(i); + if (kidDictionary == null) + { + continue; + } + 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 )); + kidsList.add(PDFieldFactory.createField(acroForm, kidDictionary)); } - else if( "Widget".equals( kidDictionary.getNameAsString( COSName.SUBTYPE ) ) ) + else if ("Widget".equals(kidDictionary.getNameAsString(COSName.SUBTYPE))) { - kidsList.add( new PDAnnotationWidget( kidDictionary ) ); + kidsList.add(new PDAnnotationWidget(kidDictionary)); } else { // - kidsList.add( PDFieldFactory.createField( acroForm, kidDictionary )); + kidsList.add(PDFieldFactory.createField(acroForm, kidDictionary)); } } - retval = new COSArrayList( kidsList, kids ); + 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 kids ) + public void setKids(List kids) { - COSArray kidsArray = COSArrayList.converterToCOSArray( kids ); - getDictionary().setItem( COSName.KIDS, kidsArray ); + 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 ); + 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() @@ -592,7 +579,7 @@ public PDAcroForm getAcroForm() /** * This will set the form this field is on. - * + * * @param value The new form to use. */ public void setAcroForm(PDAcroForm value) @@ -602,7 +589,7 @@ public void setAcroForm(PDAcroForm value) /** * This will get the dictionary associated with this field. - * + * * @return The dictionary that this class wraps. */ public COSDictionary getDictionary() @@ -612,7 +599,7 @@ public COSDictionary getDictionary() /** * Convert this standard java object to a COS object. - * + * * @return The cos object that matches this Java object. */ public COSBase getCOSObject() @@ -621,29 +608,29 @@ public COSBase getCOSObject() } /** - * Get the additional actions for this field. This will return null - * if there are no additional actions for this field. - * + * 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 ); + COSDictionary aa = (COSDictionary) dictionary.getDictionaryObject(COSName.AA); PDFormFieldAdditionalActions retval = null; - if( aa != null ) + if (aa != null) { - retval = new PDFormFieldAdditionalActions( aa ); + retval = new PDFormFieldAdditionalActions(aa); } return retval; } /** * Set the actions of the field. - * + * * @param actions The field actions. */ - public void setActions( PDFormFieldAdditionalActions actions ) + public void setActions(PDFormFieldAdditionalActions actions) { - dictionary.setItem( COSName.AA, actions ); + dictionary.setItem(COSName.AA, actions); } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/DateConverter.java b/pdfbox/src/main/java/org/apache/pdfbox/util/DateConverter.java index afc5f2940fb..64474788158 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/DateConverter.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/DateConverter.java @@ -16,11 +16,9 @@ */ package org.apache.pdfbox.util; -import java.text.ParseException; -import java.text.SimpleDateFormat; - import java.io.IOException; - +import java.text.ParsePosition; +import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; @@ -30,330 +28,790 @@ import org.apache.pdfbox.cos.COSString; +/** + * Date format is described in PDF Reference 1.7 section 3.8.2 + * (www.adobe.com/devnet/acrobat/pdfs/pdf_reference_1-7.pdf) + * and also in PDF 32000-1:2008 + * (http://www.adobe.com/devnet/acrobat/pdfs/PDF32000_2008.pdf)) + * although the latter inexplicably omits the trailing apostrophe. + * + * The interpretation of dates without timezones is unclear. + * The code below assumes that such dates are in UTC+00 (aka GMT). + * This is in keeping with the PDF Reference's assertion that: + * numerical fields default to zero values. + * However, the Reference does go on to make the cryptic remark: + * If no UT information is specified, the relationship of the specified + * time to UT is considered to be unknown. Whether or not the time + * zone is known, the rest of the date should be specified in local time. + * I understand this to refer to _creating_ a pdf date value. That is, + * code that can get the wall clock time and cannot get the timezone + * should write the wall clock time with a time zone of zero. + * When _parsing_ a PDF date, the statement talks about "the rest of the date" + * being local time, thus explicitly excluding the use of the local time + * for the time zone. +*/ + /** * 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 + * date standard in section 3.8.2 of PDF Reference 1.7. * * @author Ben Litchfield - * @version $Revision: 1.14 $ + * @author Fred Hansen + * + * TODO Move members of this class elsewhere for shared use in pdfbox, xmpbox, and jempbox. */ 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 + // milliseconds/1000 = seconds; seconds / 60 = minutes; minutes/60 = hours + private static final int MINUTES_PER_HOUR = 60; + private static final int SECONDS_PER_MINUTE = 60; + private static final int MILLIS_PER_MINUTE = SECONDS_PER_MINUTE*1000; + private static final int MILLIS_PER_HOUR = MINUTES_PER_HOUR * MILLIS_PER_MINUTE; + private static final int + HALF_DAY = 12 * MINUTES_PER_HOUR * MILLIS_PER_MINUTE, + DAY = 2*HALF_DAY; + + /** + * Error value if date is invalid. Parsing is done with + * GregorianCalendar.setLenient(false), so every date field value + * must be within bounds. If an attempt is made to parse an invalid date + * field, toCalendar(String, String[]) returns Jan 1 in year INVALID_YEAR. + */ + public static final int INVALID_YEAR = 999; + + + /** + * The Date format is supposed to be the PDF_DATE_FORMAT, but other + * forms appear. These lists offer alternatives to be tried + * if parseBigEndianDate fails. + * + * The time zone offset generally trails the date string, so it is processed + * separately with parseTZoffset. (This does not preclude having time + * zones in the elements below; one does.) + * + * Alas, SimpleDateFormat is badly non-reentrant -- it modifies its + * calendar field (PDFBox-402), so these lists are strings to create + * SimpleDate format as needed. + * + * Some past entries have been elided because they duplicate existing + * entries. See the API for SimpleDateFormat, which says + * "For parsing, the number of pattern letters is ignored + * unless it's needed to separate two adjacent fields." + * + * toCalendar(String, String[]) tests to see that the entire input text + * has been consumed. Therefore the ordering of formats is important. + * If one format begins with the entirety of another, the longer + * must precede the other in the list. + * + * HH is for 0-23 hours and hh for 1-12 hours; an "a" field must follow "hh" + * Where year is yy, four digit years are accepted + * and two digit years are converted to four digits in the range + * [thisyear-79...thisyear+20] + */ + private static final String[] ALPHA_START_FORMATS = + { + "EEEE, dd MMM yy hh:mm:ss a", + "EEEE, MMM dd, yy hh:mm:ss a", + "EEEE, MMM dd, yy 'at' hh:mma", // Acrobat Net Distiller 1.0 for Windows + "EEEE, MMM dd, yy", // Acrobat Distiller 1.0.2 for Macintosh && PDFBOX-465 + "EEEE MMM dd, yy HH:mm:ss", // ECMP5 + "EEEE MMM dd HH:mm:ss z yy", // GNU Ghostscript 7.0.7 + "EEEE MMM dd HH:mm:ss yy", // GNU Ghostscript 7.0.7 variant + }; + + private static final String[] DIGIT_START_FORMATS = + { + "dd MMM yy HH:mm:ss", // for 26 May 2000 11:25:00 + "dd MMM yy HH:mm", // for 26 May 2000 11:25 + "yyyy MMM d", // ambiguity resolved only by omitting time + "yyyymmddhh:mm:ss", // test case "200712172:2:3" + "H:m M/d/yy", // test case "9:47 5/12/2008" + "M/d/yy HH:mm:ss", + "M/d/yy HH:mm", + "M/d/yy", + + // proposed rule that is unreachable due to "dd MMM yy HH:mm:ss" + // "yyyy MMM d HH:mm:ss", + + // rules made unreachable by "M/d/yy HH:mm:ss" "M/d/yy HH:mm" "M/d/yy", + // (incoming digit strings do not mark themselves as y, m, or d!) + // "d/MM/yyyy HH:mm:ss", // PDFBOX-164 and PDFBOX-170 + // "M/dd/yyyy hh:mm:ss", + // "MM/d/yyyy hh:mm:ss", + // "M/d/yyyy HH:mm:ss", + // "M/dd/yyyy", + // "MM/d/yyyy", + // "M/d/yyyy", + // "M/d/yyyy HH:mm:ss", + // "M/d/yy HH:mm:ss", + // subsumed by big-endian parse + // "yyyy-MM-dd'T'HH:mm:ss", + // "yyyy-MM-dd'T'HH:mm:ss", + // "yyyymmdd hh:mm:ss", + // "yyyymmdd", + // "yyyymmddX''00''", // covers 24 cases + // (orignally the above ended with '+00''00'''; + // the first apostrophe quoted the plus, + // '' mapped to a single ', and the ''' was invalid) }; + private DateConverter() { //utility class should not be constructed. } + //////////////////////////////////////////// + // C o n v e r t t o S t r i n g Methods + /** - * This will convert the calendar to a string. + * Get all know formats. + * + * @return an array containig all known formats + */ + public static String[] getFormats() + { + String[] val = new String[ALPHA_START_FORMATS.length+DIGIT_START_FORMATS.length]; + System.arraycopy(ALPHA_START_FORMATS, 0, val, 0, ALPHA_START_FORMATS.length); + System.arraycopy(DIGIT_START_FORMATS, 0, val,ALPHA_START_FORMATS.length, DIGIT_START_FORMATS.length); + return val; + } + + /** + * Converts a Calendar to a string formatted as: + * D:yyyyMMddHHmmss#hh'mm' where # is Z, +, or -. + * + * @param cal The date to convert to a string. May be null. + * The DST_OFFSET is included when computing the output time zone. * - * @param date The date to convert to a string. + * @return The date as a String to be used in a PDF document, + * or null if the cal value is null + */ + public static String toString(Calendar cal) + { + if (cal == null) + { + return null; + } + String offset = formatTZoffset(cal.get(Calendar.ZONE_OFFSET) + + cal.get(Calendar.DST_OFFSET), "'"); + return String.format("D:" + + "%1$4tY%1$2tm%1$2td" // yyyyMMdd + + "%1$2tH%1$2tM%1$2tS" // HHmmss + + "%2$s" // time zone + + "'", // trailing apostrophe + cal, offset); + } + + /** + * Converts the date to ISO 8601 string format: + * yyyy-mm-ddThh:MM:ss#hh:mm (where '#" is '+' or '-'). * - * @return The date as a String to be used in a PDF document. + * @param cal The date to convert. Must not be null. + * The DST_OFFSET is included in the output value. + * + * @return The date represented as an ISO 8601 string. */ - public static String toString( Calendar date ) + public static String toISO8601(Calendar cal) { - String retval = null; - if( date != null ) + String offset = formatTZoffset(cal.get(Calendar.ZONE_OFFSET) + + cal.get(Calendar.DST_OFFSET), ":"); + return String.format( + "%1$4tY" // yyyy + + "-%1$2tm" // -mm (%tm adds one to cal month value) + + "-%1$2td" // -dd (%tm adds one to cal month value) + + "T" // T + + "%1$2tH:%1$2tM:%1$2tS" // HHmmss + + "%2$s", // time zone + cal, offset); + } + + /** + * Constrain a timezone offset to the range [-11:59 thru +11:59]. + * @param proposedOffset A value intended to be a timezone offset. + * @return The corresponding value reduced to the above noted range + * by adding or subtracting multiples of a full day. + */ + public static int restrainTZoffset(long proposedOffset) + { + proposedOffset = ((proposedOffset+HALF_DAY)%DAY+DAY)%DAY; + // 0 <= proposedOffset < DAY + proposedOffset = (proposedOffset-HALF_DAY)%HALF_DAY; + // -HALF_DAY < proposedOffset < HALF_DAY + return (int)proposedOffset; + } + + /** + * Formats a time zone offset as #hh^mm + * where # is + or -, hh is hours, ^ is a separator, and mm is minutes. + * Any separator may be specified by the second argument; + * the usual values are ":" (ISO 8601), "" (RFC 822), and "'" (PDF). + * The returned value is constrained to the range -11:59 ... 11:59. + * For offset of 0 millis, the String returned is "+00^00", never "Z". + * To get a "general" offset in form GMT#hh:mm, write + * "GMT"+DateConverter.formatTZoffset(offset, ":"); + *

+ * Take thought in choosing the source for the millis value. + * It can come from calendarValue.getTimeZone() or from + * calendarValue.get(Calendar.ZONE_OFFSET). If a TimeZone was created + * from a valid time zone ID, then it may have a daylight savings rule. + * (As of July 4, 2013, the data base at http://www.iana.org/time-zones + * recognized 629 time zone regions. But a TimeZone created as + * new SimpleTimeZone(millisOffset, "ID"), + * will not have a daylight savings rule. (Not even if there is a + * known time zone with the given ID. To get the TimeZone named "xDT" + * with its DST rule, use an ID of EST5EDT, CST6CDT, MST7MDT, or PST8PDT. + *

+ * When parsing PDF dates, the incoming values DOES NOT have a TIMEZONE value. + * At most it has an OFFSET value like -04'00'. It is generally impossible to + * determine what TIMEZONE corresponds to a given OFFSET. If the date is + * in the summer when daylight savings is in effect, an offset of -0400 + * might correspond to any one of the 38 regions (of 53) with standard time + * offset -0400 and no daylight saving. Or it might correspond to + * any one of the 31 regions (out of 43) that observe daylight savings + * and have standard time offset of -0500. + *

+ * If a Calendar has not been assigned a TimeZone with setTimeZone(), + * it will have by default the local TIMEZONE, not just the OFFSET. In the + * USA, this TimeZone will have a daylight savings rule. + *

+ * The offset assigned with calVal.set(Calendar.ZONE_OFFSET) differs + * from the offset in the TimeZone set by Calendar.setTimeZone(). Example: + * Suppose my local TimeZone is America/New_York. It has an offset of -05'00'. + * And suppose I set a GregorianCalendar's ZONE_OFFSET to -07'00' + * calVal = new GregorianCalendar(); // TimeZone is the local default + * calVal.set(Calendar.ZONE_OFFSET, -7* MILLIS_PER_HOUR); + * Four different offsets can be computed from calVal: + * calVal.get(Calendar.ZONE_OFFSET) => -07:00 + * calVal.get(Calendar.ZONE_OFFSET) + calVal.get(Calendar.DST_OFFSET) => -06:00 + * calVal.getTimeZone().getRawOffset() => -05:00 + * calVal.getTimeZone().getOffset(calVal.getTimeInMillis()) => -04:00 + *

+ * Which is correct??? I dunno, though setTimeZone() does seem to affect + * ZONE_OFFSET, and not vice versa. One cannot even test whether TimeZone + * or ZONE_OFFSET has been set; both have been set by initialization code. + * TimeZone is initialized to the local default time zone + * and ZONE_OFFSET is set from it. + * + * My choice in this DateConverter class has been to set the + * initial TimeZone of a GregorianCalendar to GMT. Thereafter + * the TimeZone is modified with {@link #adjustTimeZoneNicely}. + * + * @param millis a time zone offset expressed in milliseconds + * Any value is accepted; it is normalized to [-11:59 ... +11:59] + * @param sep a String to insert between hh and mm. May be empty. + * @return the formatted String for the offset + */ + public static String formatTZoffset(long millis, String sep) + { + SimpleDateFormat sdf = new SimpleDateFormat("Z"); // #hhmm + sdf.setTimeZone(new SimpleTimeZone(restrainTZoffset(millis),"unknown")); + String tz = sdf.format(new Date()); + return tz.substring(0,3)+sep+tz.substring(3); + } + + ////////////////////////////////////////////// + // P A R S E Methods + + /** + * Parses an integer from a string, starting at and advancing a ParsePosition. + * + * @param text The string being parsed. If null, the remedy value is returned. + * @param where The ParsePosition to start the search. This value + * will be incremented by the number of digits found, but no + * more than maxlen. That is, the ParsePosition will + * advance across at most maxlen initial digits in text. + * The error index is ignored and unchanged. + * @param maxlen The maximum length of the integer to parse. + * Usually 2, but 4 for year fields. + * If the field of length maxlen begins with a digit, + * but contains a non-digit, no error is signaled + * and the integer value is returned. + * @param remedy Value to be assigned if no digit is found at the + * initial parse position; that is, if the field is empty. + * @return The integer that was at the given parse position. Or + * the remedy value if no digits were found. + */ + public static int parseTimeField(String text, ParsePosition where, + int maxlen, int remedy) + { + if (text == 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 ) + return remedy; + } + // (it would seem that DecimalFormat.parse() would be simpler; + // but that class blithely ignores setMaximumIntegerDigits) + int retval = 0; + int index = where.getIndex(); + int limit = index + Math.min(maxlen, text.length()-index); + for (; index < limit; index++) + { + int cval = text.charAt(index) - '0'; // convert digit to integer + if (cval <0 || cval > 9) // test to see if we got a digit { - buffer.append( "Z" ); + break; // no digit at index } - else if( offsetInMinutes < 0 ) - { - buffer.append( "-" ); + retval = retval*10 + cval; // append the digit to the return value + } + if (index == where.getIndex()) + { + return remedy; + } + where.setIndex(index); + return retval; + } + + /** + * Advances the ParsePosition past any and all the characters + * that match those in the optionals list. + * In particular, a space will skip all spaces. + * @param text The text to examine + * @param where index to start looking. + * The value is incremented by the number of optionals found. + * The error index is ignored and unchanged. + * @param optionals A String listing all the optional characters + * to be skipped. + * @return The last non-space character passed over. + * Returns a space if no non-space character was found + * (even if space is not in the optionals list.) + */ + public static char skipOptionals(String text, ParsePosition where, + String optionals) + { + char retval = ' ', currch; + while (text != null && where.getIndex() < text.length() + && optionals.indexOf( + (currch=text.charAt(where.getIndex())) + ) >= 0) + { + retval = (currch != ' ') ? currch : retval; + where.setIndex(where.getIndex() + 1); + } + return retval; + } + + /** + * If the victim string is at the given position in the text, + * this method advances the position past that string. + * + * @param text The text to examine + * @param victim The string to look for + * @param where The initial position to look at. After return, this will + * have been incremented by the length of the victim if it was found. + * The error index is ignored and unchanged. + * @return true if victim was found; otherwise false. + */ + public static boolean skipString(String text, String victim, ParsePosition where) + { + if (text.startsWith(victim, where.getIndex())) + { + where.setIndex(where.getIndex()+victim.length()); + return true; + } + return false; + } + + /** + * Construct a new GregorianCalendar and set defaults. + * Locale is ENGLISH. + * TimeZone is "UTC" (zero offset and no DST). + * Parsing is NOT lenient. Milliseconds are zero. + * + * @return a new gregorian calendar + */ + public static GregorianCalendar newGreg() + { + GregorianCalendar retCal = new GregorianCalendar(Locale.ENGLISH); + retCal.setTimeZone(new SimpleTimeZone(0, "UTC")); + retCal.setLenient(false); + retCal.set(Calendar.MILLISECOND, 0); + return retCal; + } + + /** + * Install a TimeZone on a GregorianCalendar without changing the + * hours value. A plain GregorianCalendat.setTimeZone() + * adjusts the Calendar.HOUR value to compensate. This is *BAD* + * (not to say *EVIL*) when we have already set the time. + * @param cal The GregorianCalendar whose TimeZone to change. + * @param tz The new TimeZone. + */ + public static void adjustTimeZoneNicely(GregorianCalendar cal, TimeZone tz) + { + cal.setTimeZone(tz); + int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) + / MILLIS_PER_HOUR; + cal.add(Calendar.HOUR, -offset); + } + + /** + * Parses the end of a date string for a time zone and, if one is found, + * sets the time zone of the GregorianCalendar. Otherwise the calendar + * time zone is unchanged. + * + * The text is parsed as + * (Z|GMT|UTC)? [+- ]* h [': ]? m '? + * where the leading String is optional, h is two digits by default, + * but may be a single digit if followed by one of space, apostrophe, + * colon, or the end of string. Similarly, m is one or two digits. + * This scheme accepts the format of PDF, RFC 822, and ISO8601. + * If none of these applies (as for a time zone name), we try + * TimeZone.getTimeZone(). + * + * @param text The text expected to begin with a time zone value, + * possibly with leading or trailing spaces. + * @param cal The Calendar whose TimeZone to set. + * @param initialWhere where Scanning begins at where.index. After success, the returned + * index is that of the next character after the recognized string. + * The error index is ignored and unchanged. + * @return true if parsed a time zone value; otherwise the + * time zone is unchanged and the return value is false. + */ + public static boolean parseTZoffset(String text, GregorianCalendar cal, + ParsePosition initialWhere) + { + ParsePosition where = new ParsePosition(initialWhere.getIndex()); + TimeZone tz = new SimpleTimeZone(0, "GMT"); + int tzHours, tzMin; + char sign = skipOptionals(text, where, "Z+- "); + boolean hadGMT = (sign == 'Z' || skipString(text, "GMT", where) + || skipString(text, "UTC", where)); + sign = ( ! hadGMT) ? sign : skipOptionals(text, where, "+- "); + + tzHours = parseTimeField(text, where, 2, -999); + skipOptionals(text, where, "\': "); + tzMin = parseTimeField(text, where, 2, 0); + skipOptionals(text, where, "\' "); + + if (tzHours != -999) + { // we parsed a time zone in default format + int hrSign = (sign == '-' ? -1 :+1); + tz.setRawOffset(restrainTZoffset(hrSign*(tzHours*MILLIS_PER_HOUR + tzMin*MILLIS_PER_MINUTE))); + tz.setID("unknown"); + } + else if ( ! hadGMT) + { // try to process as a name; "GMT" or "UTC" has already been processed + String tzText = text.substring(initialWhere.getIndex()).trim(); + tz = TimeZone.getTimeZone(tzText); + // getTimeZone returns "GMT" for unknown ids + if ("GMT".equals(tz.getID())) + { // no timezone in text + // cal amd initialWhere are unchanged + return false; } else - { - buffer.append( "+" ); - } - if( hours < 10 ) - { - buffer.append( "0" ); + { // we got a tz by name; use it + where.setIndex(text.length()); } - buffer.append( hours ); - buffer.append( "'" ); - if( minutes < 10 ) - { - buffer.append( "0" ); - } - buffer.append( minutes ); - buffer.append( "'" ); - retval = buffer.toString(); - } - return retval; + adjustTimeZoneNicely(cal, tz); + initialWhere.setIndex(where.getIndex()); + return true; } - + /** - * 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. + * Parses a big-endian date: year month day hour min sec. + * The year must be four digits. Other fields may be adjacent + * and delimited by length or they may follow appropriate delimiters. + * year [ -/]* month [ -/]* dayofmonth [ T]* hour [:] min [:] sec [.secFraction] + * If any numeric field is omitted, all following fields must also be omitted. + * No time zone is processed. + * + * Ambiguous dates can produce unexpected results. For example: + * 1970 12 23:08 will parse as 1970 December 23 00:08:00 + * + * @param text The string to parse. + * + * @param initialWhere Where to begin the parse. On return the index + * is advanced to just beyond the last character processed. + * The error index is ignored and unchanged. + * + * @return a GregorianCalendar representing the parsed date. + * Or null if the text did not begin with at least four digits. */ - public static Calendar toCalendar( COSString date ) throws IOException + public static GregorianCalendar parseBigEndianDate(String text, + ParsePosition initialWhere) { - Calendar retval = null; - if( date != null ) + ParsePosition where = new ParsePosition(initialWhere.getIndex()); + int year = parseTimeField(text, where, 4, 0); + if (where.getIndex() != 4 + initialWhere.getIndex()) + { + return null; + } + skipOptionals(text, where, "/- "); + int month = parseTimeField(text, where, 2, 1) - 1; // Calendar months are 0...11 + skipOptionals(text, where, "/- "); + int day = parseTimeField(text, where, 2, 1); + skipOptionals(text, where, " T"); + int hour = parseTimeField(text, where, 2, 0); + skipOptionals(text, where, ": "); + int minute = parseTimeField(text, where, 2, 0); + skipOptionals(text, where, ": "); + int second = parseTimeField(text, where, 2, 0); + char nextC = skipOptionals(text, where, "."); + if (nextC == '.') { - retval = toCalendar( date.getString() ); + // fractions of a second: skip upto 19 digits + parseTimeField(text, where, 19, 0); } - return retval; + GregorianCalendar dest = newGreg(); + try + { + dest.set(year, month, day, hour, minute, second); + dest.getTimeInMillis(); // trigger limit tests + } + catch (IllegalArgumentException ill) + { + return null; + } + initialWhere.setIndex(where.getIndex()); + skipOptionals(text, initialWhere, " "); + return dest; // dest has at least a year value } /** - * 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. + * See if text can be parsed as a date according to any of a list of + * formats. The time zone may be included as part of the format, or + * omitted in favor of later testing for a trailing time zone. + * + * @param text The text to be parsed. + * + * @param fmts A list of formats to be tried. The syntax is that for + * {@link #java.text.SimpleDateFormat} + * + * @param initialWhere At start this is the position to begin + * examining the text. Upon return it will have been + * incremented to refer to the next non-space character after the date. + * If no date was found, the value is unchanged. + * The error index is ignored and unchanged. + * + * @return null for failure to find a date, or the GregorianCalendar + * for the date that was found. Unless a time zone was + * part of the format, the time zone will be GMT+0 */ - public static Calendar toCalendar( String date ) throws IOException + public static GregorianCalendar parseSimpleDate(String text, String[] fmts, + ParsePosition initialWhere) { - Calendar retval = null; - if( date != null && date.trim().length() > 0 ) + for(String fmt : fmts) { - //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 + ParsePosition where = new ParsePosition(initialWhere.getIndex()); + SimpleDateFormat sdf = new SimpleDateFormat(fmt, Locale.ENGLISH); + GregorianCalendar retCal = newGreg(); + sdf.setCalendar(retCal); + if (sdf.parse(text, where) != null) { - 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 ) ); - } + initialWhere.setIndex(where.getIndex()); + skipOptionals(text, initialWhere, " "); + return retCal; + } + } + return null; + } - 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(); - } + + /** + * Parses a String to see if it begins with a date, and if so, + * returns that date. The date must be strictly correct--no + * field may exceed the appropriate limit. + * (That is, the Calendar has setLenient(false).) + * Skips initial spaces, but does NOT check for "D:" + * + * The scan first tries parseBigEndianDate and parseTZoffset + * and then tries parseSimpleDate with appropriate formats, + * again followed by parseTZoffset. If at any stage the entire + * text is consumed, that date value is returned immediately. + * Otherwise the date that consumes the longest initial part + * of the text is returned. + * + * - PDF format dates are among those recognized by parseBigEndianDate. + * - The formats tried are alphaStartFormats or digitStartFormat and + * any listed in the value of moreFmts. + * + * @param text The String that may begin with a date. Must not be null. + * Initial spaces and "D:" are skipped over. + * @param moreFmts Additional formats to be tried after trying the + * built-in formats. + * @param initialWhere where Parsing begins at the given position in text. If the + * parse succeeds, the index of where is advanced to point + * to the first unrecognized character. + * The error index is ignored and unchanged. + * @return A GregorianCalendar for the date. If no date is found, + * returns null. The time zone will be GMT+0 unless parsing + * succeeded with a format containing a time zone. (Only one + * builtin format contains a time zone.) + * + */ + public static Calendar parseDate(String text, String[] moreFmts, + ParsePosition initialWhere) + { + // place to remember longestr date string + int longestLen = -999999; // theorem: this value will never be used + // proof: longestLen is only used if longestDate is not null + GregorianCalendar longestDate = null; // null says no date found yet + int whereLen; // tempcopy of where.getIndex() + + ParsePosition where = new ParsePosition(initialWhere.getIndex()); + // check for null (throws exception) and trim off surrounding spaces + skipOptionals(text, where, " "); + int startPosition = where.getIndex(); - 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); + // try big-endian parse + GregorianCalendar retCal = parseBigEndianDate(text, where); + // check for success and a timezone + if (retCal != null && + (where.getIndex() == text.length() + || parseTZoffset(text, retCal, where))) + { + // if text is fully consumed, return the date + // else remember it and its length + whereLen = where.getIndex(); + if (whereLen == text.length()) + { + initialWhere.setIndex(whereLen); + return retCal; } - catch( NumberFormatException e ) + longestLen = whereLen; + longestDate = retCal; + } + + // try one of the sets of standard formats + where.setIndex(startPosition); + String [] formats + = Character.isDigit(text.charAt(startPosition)) + ? DIGIT_START_FORMATS + : ALPHA_START_FORMATS; + retCal = parseSimpleDate(text, formats, where); + // check for success and a timezone + if (retCal != null && + (where.getIndex() == text.length() + || parseTZoffset(text, retCal, where))) + { + // if text is fully consumed, return the date + // else remember it and its length + whereLen = where.getIndex(); + if (whereLen == text.length()) { - for( int i=0; retval == null && i longestLen) + { + longestLen = whereLen; + longestDate = retCal; + } + } + + // try the supplied formats + if (moreFmts != null) + { + where.setIndex(startPosition); + retCal = parseSimpleDate(text, moreFmts, where); + if (retCal != null && + (where.getIndex() == text.length() + || parseTZoffset(text, retCal, where))) + { + whereLen = where.getIndex(); + // if text is fully consumed, return the date + // else remember it and its length + if (whereLen == text.length() || + (longestDate != null && whereLen > longestLen)) { - //we didn't find a valid date format so throw an exception - throw new IOException( "Error converting date:" + date ); + initialWhere.setIndex(whereLen); + return retCal; } } } - return retval; - } - - private static final void zeroAppend( StringBuffer out, int number ) - { - if( number < 10 ) + if (longestDate != null) { - out.append( "0" ); + initialWhere.setIndex(longestLen); + return longestDate; } - out.append( number ); + return retCal; } - + /** - * Convert the date to iso 8601 string format. + * Converts a string to a Calendar by parsing the String for a date. + * @see toCalendar(String). * - * @param cal The date to convert. - * @return The date represented as an ISO 8601 string. + * The returned value will have 0 for DST_OFFSET. + * + * @param text The COSString representation of a date. + * @return The Calendar that the text string represents. + * Or null if text was null. + * @throws IOException If the date string is not in the correct format. + * @deprecated This method throws an IOException for failure. Replace + * calls to it with {@link #toCalendar(text.getString(), null)} + * and test for failure with + * (value == null || value.get(Calendar.YEAR) == INVALID_YEAR) */ - public static String toISO8601( Calendar cal ) + public static Calendar toCalendar(COSString text) throws IOException { - 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 ) + if (text == null) { - retval.append( "-" ); + return null; } - else + return toCalendar(text.getString()); + } + + /** + * Converts a string date to a Calendar date value; equivalent to + * {@link #toCalendar(String, null)}, + * but throws an IOException for failure. + * + * The returned value will have 0 for DST_OFFSET. + * + * @param text The string representation of the calendar. + * @return The Calendar that this string represents + * or null if the incoming text is null. + * @throws IOException If the date string is non-null + * and not a parseable date. + * @deprecated This method throws an IOException for failure. Replace + * calls to it with {@link #toCalendar(text, null)} + * and test for failure with + * (value == null || value.get(Calendar.YEAR) == INVALID_YEAR) + */ + public static Calendar toCalendar(String text) throws IOException + { + if (text == null) { - retval.append( "+" ); + return null; } - 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 ) + Calendar val = toCalendar(text, null); + if (val != null && val.get(Calendar.YEAR) == INVALID_YEAR) { - retval.append( "0" ); + throw new IOException("Error converting date: " + text); } - retval.append( Integer.toString( hours ) ); - retval.append( ":" ); - if( minutes < 10 ) + return val; + } + + /** + * Converts a string to a calendar. The entire string must be consumed. + * The date must be strictly correct; that is, no field may exceed + * the appropriate limit. Uses {@link #parseDate} to do the actual parsing. + * + * The returned value will have 0 for DST_OFFSET. + * + * @param text The text to parse. Initial spaces and "D:" are skipped over. + * @param moreFmts An Array of formats (as Strings) to try + * in addition to the standard list. + * @return the Calendar value corresponding to the date text. + * If text does not represent a valid date, + * the value is January 1 on year INVALID_YEAR at 0:0:0 GMT. + * + */ + public static Calendar toCalendar(String text, String[] moreFmts) + { + ParsePosition where = new ParsePosition(0); + skipOptionals(text, where, " "); + skipString(text, "D:", where); + Calendar retCal = parseDate(text, moreFmts, where); // PARSE THE TEXT + if (retCal == null || where.getIndex() != text.length()) { - retval.append( "0" ); + // the date string is invalid for all formats we tried, + retCal = newGreg(); + retCal.set(INVALID_YEAR, 0, 1, 0, 0, 0); } - retval.append( Integer.toString( minutes ) ); - - return retval.toString(); + return retCal; } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java index c11fd8179ca..d06d1654444 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java @@ -22,15 +22,18 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSInteger; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSNumber; import org.apache.pdfbox.cos.COSStream; +import org.apache.pdfbox.cos.COSString; import org.apache.pdfbox.exceptions.COSVisitorException; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentCatalog; @@ -38,7 +41,11 @@ import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.COSArrayList; +import org.apache.pdfbox.pdmodel.common.PDNumberTreeNode; import org.apache.pdfbox.pdmodel.common.PDStream; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline; import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; @@ -46,14 +53,14 @@ import org.apache.pdfbox.pdmodel.interactive.form.PDFieldFactory; /** - * This class will take a list of pdf documents and merge them, saving the result - * in a new document. - * + * This class will take a list of pdf documents and merge them, saving the result in a new document. + * * @author Ben Litchfield - * @version $Revision: 1.3 $ + * */ public class PDFMergerUtility { + private static final String STRUCTURETYPE_DOCUMENT = "Document"; private List sources; private String destinationFileName; @@ -70,6 +77,7 @@ public PDFMergerUtility() /** * Get the name of the destination file. + * * @return Returns the destination. */ public String getDestinationFileName() @@ -79,8 +87,8 @@ public String getDestinationFileName() /** * Set the name of the destination file. - * @param destination - * The destination to set. + * + * @param destination The destination to set. */ public void setDestinationFileName(String destination) { @@ -89,6 +97,7 @@ public void setDestinationFileName(String destination) /** * Get the destination OutputStream. + * * @return Returns the destination OutputStream. */ public OutputStream getDestinationStream() @@ -98,6 +107,7 @@ public OutputStream getDestinationStream() /** * Set the destination OutputStream. + * * @param destStream The destination to set. */ public void setDestinationStream(OutputStream destStream) @@ -107,7 +117,7 @@ public void setDestinationStream(OutputStream destStream) /** * Add a source file to the list of files to merge. - * + * * @param source Full path and file name of source document. */ public void addSource(String source) @@ -116,7 +126,7 @@ public void addSource(String source) { sources.add(new FileInputStream(new File(source))); } - catch(Exception e) + catch (Exception e) { throw new RuntimeException(e); } @@ -124,7 +134,7 @@ public void addSource(String source) /** * Add a source file to the list of files to merge. - * + * * @param source File representing source document */ public void addSource(File source) @@ -133,7 +143,7 @@ public void addSource(File source) { sources.add(new FileInputStream(source)); } - catch(Exception e) + catch (Exception e) { throw new RuntimeException(e); } @@ -141,7 +151,7 @@ public void addSource(File source) /** * Add a source to the list of documents to merge. - * + * * @param source InputStream representing source document */ public void addSource(InputStream source) @@ -151,17 +161,17 @@ public void addSource(InputStream source) /** * Add a list of sources to the list of documents to merge. - * + * * @param sourcesList List of InputStream objects representing source documents */ public void addSources(List sourcesList) { - this.sources.addAll(sourcesList); + sources.addAll(sourcesList); } /** * Merge the list of source documents, saving the result in the destination file. - * + * * @throws IOException If there is an error saving the document. * @throws COSVisitorException If an error occurs while saving the destination file. */ @@ -187,7 +197,7 @@ public void mergeDocuments() throws IOException, COSVisitorException tobeclosed.add(source); appendDocument(destination, source); } - if(destinationStream == null) + if (destinationStream == null) { destination.save(destinationFileName); } @@ -210,52 +220,51 @@ public void mergeDocuments() throws IOException, COSVisitorException } } - /** * append all pages from source to destination. - * + * * @param destination the document to receive the pages * @param source the document originating the new pages - * + * * @throws IOException If there is an error accessing data from either document. */ public void appendDocument(PDDocument destination, PDDocument source) throws IOException { - if( destination.isEncrypted() ) + if (destination.isEncrypted()) { - throw new IOException( "Error: destination PDF is encrypted, can't append encrypted PDF documents." ); + throw new IOException("Error: destination PDF is encrypted, can't append encrypted PDF documents."); } - if( source.isEncrypted() ) + if (source.isEncrypted()) { - throw new IOException( "Error: source PDF is encrypted, can't append encrypted PDF documents." ); + throw new IOException("Error: source PDF is encrypted, can't append encrypted PDF documents."); } PDDocumentInformation destInfo = destination.getDocumentInformation(); PDDocumentInformation srcInfo = source.getDocumentInformation(); - destInfo.getDictionary().mergeInto( srcInfo.getDictionary() ); + destInfo.getDictionary().mergeInto(srcInfo.getDictionary()); PDDocumentCatalog destCatalog = destination.getDocumentCatalog(); PDDocumentCatalog srcCatalog = source.getDocumentCatalog(); // use the highest version number for the resulting pdf - float destVersion = destination.getDocument().getVersion(); - float srcVersion = source.getDocument().getVersion(); + float destVersion = destination.getDocument().getVersion(); + float srcVersion = source.getDocument().getVersion(); if (destVersion < srcVersion) { destination.getDocument().setVersion(srcVersion); } - - if( destCatalog.getOpenAction() == null ) + + if (destCatalog.getOpenAction() == null) { - destCatalog.setOpenAction( srcCatalog.getOpenAction() ); + destCatalog.setOpenAction(srcCatalog.getOpenAction()); } - // maybe there are some shared resources for all pages - COSDictionary srcPages = (COSDictionary)srcCatalog.getCOSDictionary().getDictionaryObject( COSName.PAGES ); - COSDictionary srcResources = (COSDictionary)srcPages.getDictionaryObject( COSName.RESOURCES ); - COSDictionary destPages = (COSDictionary)destCatalog.getCOSDictionary().getDictionaryObject( COSName.PAGES ); - COSDictionary destResources = (COSDictionary)destPages.getDictionaryObject( COSName.RESOURCES ); - if (srcResources != null) + // maybe there are some shared resources for all pages + COSDictionary srcPages = (COSDictionary) srcCatalog.getCOSDictionary().getDictionaryObject(COSName.PAGES); + COSDictionary srcResources = (COSDictionary) srcPages.getDictionaryObject(COSName.RESOURCES); + COSDictionary destPages = (COSDictionary) destCatalog.getCOSDictionary().getDictionaryObject(COSName.PAGES); + COSDictionary destResources = (COSDictionary) destPages.getDictionaryObject(COSName.RESOURCES); + if (srcResources != null) { if (destResources != null) { @@ -266,56 +275,54 @@ public void appendDocument(PDDocument destination, PDDocument source) throws IOE destPages.setItem(COSName.RESOURCES, srcResources); } } - + PDFCloneUtility cloner = new PDFCloneUtility(destination); try { PDAcroForm destAcroForm = destCatalog.getAcroForm(); PDAcroForm srcAcroForm = srcCatalog.getAcroForm(); - if( destAcroForm == null ) + if (destAcroForm == null) { - cloner.cloneForNewDocument( srcAcroForm ); - destCatalog.setAcroForm( srcAcroForm ); + cloner.cloneForNewDocument(srcAcroForm); + destCatalog.setAcroForm(srcAcroForm); } else { - if( srcAcroForm != null ) + if (srcAcroForm != null) { mergeAcroForm(cloner, destAcroForm, srcAcroForm); } } } - catch(Exception e) + catch (Exception e) { // if we are not ignoring exceptions, we'll re-throw this - if(!ignoreAcroFormErrors) + if (!ignoreAcroFormErrors) { - throw (IOException)e; + throw (IOException) e; } } - COSArray destThreads = (COSArray)destCatalog.getCOSDictionary().getDictionaryObject( - COSName.THREADS); - COSArray srcThreads = (COSArray)cloner.cloneForNewDocument( - destCatalog.getCOSDictionary().getDictionaryObject( COSName.THREADS )); - if( destThreads == null ) + COSArray destThreads = (COSArray) destCatalog.getCOSDictionary().getDictionaryObject(COSName.THREADS); + COSArray srcThreads = (COSArray) cloner.cloneForNewDocument(destCatalog.getCOSDictionary().getDictionaryObject( + COSName.THREADS)); + if (destThreads == null) { - destCatalog.getCOSDictionary().setItem( COSName.THREADS, srcThreads ); + destCatalog.getCOSDictionary().setItem(COSName.THREADS, srcThreads); } else { - destThreads.addAll( srcThreads ); + destThreads.addAll(srcThreads); } PDDocumentNameDictionary destNames = destCatalog.getNames(); PDDocumentNameDictionary srcNames = srcCatalog.getNames(); - if( srcNames != null ) + if (srcNames != null) { - if( destNames == null ) + if (destNames == null) { - destCatalog.getCOSDictionary().setItem( COSName.NAMES, - cloner.cloneForNewDocument( srcNames ) ); + destCatalog.getCOSDictionary().setItem(COSName.NAMES, cloner.cloneForNewDocument(srcNames)); } else { @@ -326,142 +333,336 @@ public void appendDocument(PDDocument destination, PDDocument source) throws IOE PDDocumentOutline destOutline = destCatalog.getDocumentOutline(); PDDocumentOutline srcOutline = srcCatalog.getDocumentOutline(); - if( srcOutline != null ) + if (srcOutline != null) { - if( destOutline == null ) + if (destOutline == null) { - PDDocumentOutline cloned = - new PDDocumentOutline( (COSDictionary)cloner.cloneForNewDocument( srcOutline ) ); - destCatalog.setDocumentOutline( cloned ); + PDDocumentOutline cloned = new PDDocumentOutline((COSDictionary) cloner.cloneForNewDocument(srcOutline)); + destCatalog.setDocumentOutline(cloned); } else { PDOutlineItem first = srcOutline.getFirstChild(); - if(first != null) + if (first != null) { - PDOutlineItem clonedFirst = new PDOutlineItem( - (COSDictionary)cloner.cloneForNewDocument( first )); - destOutline.appendChild( clonedFirst ); + PDOutlineItem clonedFirst = new PDOutlineItem((COSDictionary) cloner.cloneForNewDocument(first)); + destOutline.appendChild(clonedFirst); } } } String destPageMode = destCatalog.getPageMode(); String srcPageMode = srcCatalog.getPageMode(); - if( destPageMode == null ) + if (destPageMode == null) { - destCatalog.setPageMode( srcPageMode ); + destCatalog.setPageMode(srcPageMode); } - COSDictionary destLabels = (COSDictionary)destCatalog.getCOSDictionary().getDictionaryObject( - COSName.PAGE_LABELS); - COSDictionary srcLabels = (COSDictionary)srcCatalog.getCOSDictionary().getDictionaryObject( + COSDictionary destLabels = (COSDictionary) destCatalog.getCOSDictionary().getDictionaryObject( COSName.PAGE_LABELS); - if( srcLabels != null ) + COSDictionary srcLabels = (COSDictionary) srcCatalog.getCOSDictionary() + .getDictionaryObject(COSName.PAGE_LABELS); + if (srcLabels != null) { int destPageCount = destination.getNumberOfPages(); COSArray destNums = null; - if( destLabels == null ) + if (destLabels == null) { destLabels = new COSDictionary(); destNums = new COSArray(); - destLabels.setItem( COSName.NUMS, destNums ); - destCatalog.getCOSDictionary().setItem( COSName.PAGE_LABELS, destLabels ); + destLabels.setItem(COSName.NUMS, destNums); + destCatalog.getCOSDictionary().setItem(COSName.PAGE_LABELS, destLabels); } else { - destNums = (COSArray)destLabels.getDictionaryObject( COSName.NUMS ); + destNums = (COSArray) destLabels.getDictionaryObject(COSName.NUMS); } - COSArray srcNums = (COSArray)srcLabels.getDictionaryObject( COSName.NUMS ); + COSArray srcNums = (COSArray) srcLabels.getDictionaryObject(COSName.NUMS); if (srcNums != null) { - for( int i=0; i 0) + { + if (srcStructTree != null) + { + PDNumberTreeNode srcParentTree = srcStructTree.getParentTree(); + if (srcParentTree != null) + { + srcParentTreeDict = srcParentTree.getCOSDictionary(); + srcNumbersArray = (COSArray) srcParentTreeDict.getDictionaryObject(COSName.NUMS); + if (srcNumbersArray != null) + { + mergeStructTree = true; + } + } + } + } + } + } + if (destMark != null && destMark.isMarked() && !mergeStructTree) + { + destMark.setMarked(false); + } + if (!mergeStructTree) + { + destCatalog.setStructureTreeRoot(null); + } + } + + // finally append the pages List pages = srcCatalog.getAllPages(); Iterator pageIter = pages.iterator(); - while( pageIter.hasNext() ) + HashMap objMapping = new HashMap(); + while (pageIter.hasNext()) { PDPage page = pageIter.next(); - PDPage newPage = - new PDPage( (COSDictionary)cloner.cloneForNewDocument( page.getCOSDictionary() ) ); - newPage.setCropBox( page.findCropBox() ); - newPage.setMediaBox( page.findMediaBox() ); - newPage.setRotation( page.findRotation() ); - destination.addPage( newPage ); + PDPage newPage = new PDPage((COSDictionary) cloner.cloneForNewDocument(page.getCOSDictionary())); + newPage.setCropBox(page.findCropBox()); + newPage.setMediaBox(page.findMediaBox()); + newPage.setRotation(page.findRotation()); + if (mergeStructTree) + { + updateStructParentEntries(newPage, destParentTreeNextKey); + objMapping.put(page.getCOSDictionary(), newPage.getCOSDictionary()); + List oldAnnots = page.getAnnotations(); + List newAnnots = newPage.getAnnotations(); + for (int i = 0; i < oldAnnots.size(); i++) + { + objMapping.put(oldAnnots.get(i).getDictionary(), newAnnots.get(i).getDictionary()); + } + // TODO update mapping for XObjects + } + destination.addPage(newPage); + } + if (mergeStructTree) + { + updatePageReferences(srcNumbersArray, objMapping); + for (int i = 0; i < srcNumbersArray.size() / 2; i++) + { + destNumbersArray.add(COSInteger.get(destParentTreeNextKey + i)); + destNumbersArray.add(srcNumbersArray.getObject(i * 2 + 1)); + } + destParentTreeNextKey += srcNumbersArray.size() / 2; + destParentTreeDict.setItem(COSName.NUMS, destNumbersArray); + PDNumberTreeNode newParentTreeNode = new PDNumberTreeNode(destParentTreeDict, COSBase.class); + destStructTree.setParentTree(newParentTreeNode); + destStructTree.setParentTreeNextKey(destParentTreeNextKey); + + COSDictionary kDictLevel0 = new COSDictionary(); + COSArray newKArray = new COSArray(); + COSArray destKArray = destStructTree.getKArray(); + COSArray srcKArray = srcStructTree.getKArray(); + if (destKArray != null && srcKArray != null) + { + updateParentEntry(destKArray, kDictLevel0); + newKArray.addAll(destKArray); + if (mergeStructTree) + { + updateParentEntry(srcKArray, kDictLevel0); + } + newKArray.addAll(srcKArray); + } + kDictLevel0.setItem(COSName.K, newKArray); + kDictLevel0.setItem(COSName.P, destStructTree); + kDictLevel0.setItem(COSName.S, new COSString(STRUCTURETYPE_DOCUMENT)); + destStructTree.setK(kDictLevel0); } } - private int nextFieldNum = 1; /** - * Merge the contents of the source form into the destination form - * for the destination file. - * + * Merge the contents of the source form into the destination form for the destination file. + * * @param cloner the object cloner for the destination document * @param destAcroForm the destination form * @param srcAcroForm the source form * @throws IOException If an error occurs while adding the field. */ private void mergeAcroForm(PDFCloneUtility cloner, PDAcroForm destAcroForm, PDAcroForm srcAcroForm) - throws IOException + throws IOException { List destFields = destAcroForm.getFields(); List srcFields = srcAcroForm.getFields(); - if( srcFields != null ) + if (srcFields != null) { - if( destFields == null ) + if (destFields == null) { destFields = new COSArrayList(); - destAcroForm.setFields( destFields ); + destAcroForm.setFields(destFields); } Iterator srcFieldsIterator = srcFields.iterator(); while (srcFieldsIterator.hasNext()) { - PDField srcField = (PDField)srcFieldsIterator.next(); - PDField destField = - PDFieldFactory.createField( - destAcroForm, - (COSDictionary)cloner.cloneForNewDocument(srcField.getDictionary() )); + PDField srcField = (PDField) srcFieldsIterator.next(); + PDField destField = PDFieldFactory.createField(destAcroForm, + (COSDictionary) cloner.cloneForNewDocument(srcField.getDictionary())); // if the form already has a field with this name then we need to rename this field // to prevent merge conflicts. - if ( destAcroForm.getField(destField.getFullyQualifiedName()) != null ) + if (destAcroForm.getField(destField.getFullyQualifiedName()) != null) { - destField.setPartialName("dummyFieldName"+(nextFieldNum++)); + destField.setPartialName("dummyFieldName" + (nextFieldNum++)); } destFields.add(destField); } } } + /** + * Indicates if acroform errors are ignored or not. + * + * @return true if acroform errors are ignored + */ public boolean isIgnoreAcroFormErrors() { return ignoreAcroFormErrors; } - public void setIgnoreAcroFormErrors(boolean ignoreAcroFormErrors) + /** + * Set to true to ignore acroform errors. + * + * @param ignoreAcroFormErrorsValue true if acroform errors should be ignored + */ + public void setIgnoreAcroFormErrors(boolean ignoreAcroFormErrorsValue) + { + ignoreAcroFormErrors = ignoreAcroFormErrorsValue; + } + + /** + * Update the Pg and Obj references to the new (merged) page. + * + * @param parentTreeEntry + * @param objMapping mapping between old and new references + */ + private void updatePageReferences(COSDictionary parentTreeEntry, HashMap objMapping) + { + COSBase page = parentTreeEntry.getDictionaryObject(COSName.PG); + if (page instanceof COSDictionary) + { + if (objMapping.containsKey(page)) + { + parentTreeEntry.setItem(COSName.PG, objMapping.get(page)); + } + } + COSBase obj = parentTreeEntry.getDictionaryObject(COSName.OBJ); + if (obj instanceof COSDictionary) + { + if (objMapping.containsKey(obj)) + { + parentTreeEntry.setItem(COSName.OBJ, objMapping.get(obj)); + } + } + COSBase kSubEntry = parentTreeEntry.getDictionaryObject(COSName.K); + if (kSubEntry instanceof COSArray) + { + updatePageReferences((COSArray) kSubEntry, objMapping); + } + else if (kSubEntry instanceof COSDictionary) + { + updatePageReferences((COSDictionary) kSubEntry, objMapping); + } + } + + private void updatePageReferences(COSArray parentTreeEntry, HashMap objMapping) + { + for (int i = 0; i < parentTreeEntry.size(); i++) + { + COSBase subEntry = parentTreeEntry.getObject(i); + if (subEntry instanceof COSArray) + { + updatePageReferences((COSArray) subEntry, objMapping); + } + else if (subEntry instanceof COSDictionary) + { + updatePageReferences((COSDictionary) subEntry, objMapping); + } + } + } + + /** + * Update the P reference to the new parent dictionary. + * + * @param kArray the kids array + * @param newParent the new parent + */ + private void updateParentEntry(COSArray kArray, COSDictionary newParent) { - this.ignoreAcroFormErrors = ignoreAcroFormErrors; + for (int i = 0; i < kArray.size(); i++) + { + COSBase subEntry = kArray.getObject(i); + if (subEntry instanceof COSDictionary) + { + COSDictionary dictEntry = (COSDictionary) subEntry; + if (dictEntry.getDictionaryObject(COSName.P) != null) + { + dictEntry.setItem(COSName.P, newParent); + } + } + } } + /** + * Update the StructParents and StructParent values in a PDPage. + * + * @param page the new page + * @param structParentOffset the offset which should be applied + */ + private void updateStructParentEntries(PDPage page, int structParentOffset) throws IOException + { + page.setStructParents(page.getStructParents() + structParentOffset); + List annots = page.getAnnotations(); + List newannots = new ArrayList(); + for (PDAnnotation annot : annots) + { + annot.setStructParent(annot.getStructParent() + structParentOffset); + newannots.add(annot); + } + page.setAnnotations(newannots); + } } diff --git a/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java b/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java index 58a3bfae872..a66bee8b751 100644 --- a/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java +++ b/pdfbox/src/test/java/org/apache/pdfbox/util/TestDateUtil.java @@ -17,22 +17,32 @@ package org.apache.pdfbox.util; import java.io.IOException; +import java.text.ParsePosition; import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.SimpleTimeZone; import java.util.TimeZone; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; +import org.apache.pdfbox.cos.COSString; + /** * Test the date conversion utility. * * @author Ben Litchfield - * @version $Revision: 1.2 $ + * @author Fred Hansen + * */ public class TestDateUtil extends TestCase { + private static final int MINS = 60*1000, HRS = 60*MINS; + // expect parse fail + private static final int BAD = -666; + /** * Test class constructor. * @@ -45,29 +55,68 @@ public TestDateUtil( String name ) throws IOException super( name ); } + + //////////////////////////////////////////////////// + // Test body follows + + /** * Test common date formats. * * @throws Exception when there is an exception */ - public void testExtract() - throws Exception + public void testExtract() throws Exception { TimeZone timezone = TimeZone.getDefault(); TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - try { - assertEquals( DateConverter.toCalendar( "D:05/12/2005" ), new GregorianCalendar( 2005, 4, 12 ) ); - assertEquals( DateConverter.toCalendar( "5/12/2005 15:57:16" ), new GregorianCalendar( 2005, 4,12,15,57,16 ) ); - } finally { + try + { + assertCalendarEquals( new GregorianCalendar( 2005, 4, 12 ), + DateConverter.toCalendar( "D:05/12/2005" ) ); + assertCalendarEquals( new GregorianCalendar( 2005, 4,12,15,57,16 ), + DateConverter.toCalendar( "5/12/2005 15:57:16" ) ); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + finally + { TimeZone.setDefault(timezone); } + // check that new toCalendar gives NullPointer for a null arg + try + { + DateConverter.toCalendar(null, null); + assertNotNull(null); // failed to have expected exception + } + catch (NullPointerException ex) + { + // expected outcome + } } - + + /** + * Calendar.equals test case. + * + * @param expect the expected calendar value + * @param was the calendar value to be checked + */ + public void assertCalendarEquals(Calendar expect, Calendar was) + { + assertEquals( expect.getTimeInMillis(), was.getTimeInMillis() ); + assertEquals( expect.getTimeZone().getRawOffset(), + was.getTimeZone().getRawOffset() ); + } + /** * Test case for - * PDFBOX-598 + * PDFBOX-598. + * + * @throws IOException if something went wrong. */ - public void testDateConversion() throws Exception { + public void testDateConversion() throws IOException + { Calendar c = DateConverter.toCalendar("D:20050526205258+01'00'"); assertEquals(2005, c.get(Calendar.YEAR)); assertEquals(05-1, c.get(Calendar.MONTH)); @@ -78,41 +127,322 @@ public void testDateConversion() throws Exception { assertEquals(0, c.get(Calendar.MILLISECOND)); } - public void testDateConverter() throws Exception { - TimeZone timezone = TimeZone.getDefault(); - TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - try { - assertDate("2010-01-01T00:00:00+00:00", "D:2010"); - assertDate("2010-01-01T00:00:00+00:00", "2010"); - assertDate("2010-04-23T00:00:00+00:00", "D:20100423"); - assertDate("2010-04-23T00:00:00+00:00", "20100423"); + /** + * Check toCalendar. + * @param yr expected year value + * If an IOException is the expected result, yr should be null + * @param mon expected month value + * @param day expected dayofmonth value + * @param hr expected hour value + * @param min expected minute value + * @param sec expected second value + * @param tz represents expected timezone offset + * @param orig A date to be parsed. + * @throws Exception If an unexpected error occurs. + */ + private static void checkParse(int yr, int mon, int day, + int hr, int min, int sec, int offset, + String orig) throws Exception + { + String pdfDate = String.format("D:%04d%02d%02d%02d%02d%02d%+03d'00'", + yr,mon,day,hr,min,sec,offset); + String iso8601Date = String.format("%04d-%02d-%02d" + + "T%02d:%02d:%02d%+03d:00", + yr,mon,day,hr,min,sec,offset); + Calendar cal = null; + try + { + cal = DateConverter.toCalendar(orig); + } + catch (IOException ex) + { + assertEquals(yr, BAD); + } + if (cal != null) + { + assertEquals(iso8601Date, DateConverter.toISO8601(cal)); + assertEquals(pdfDate, DateConverter.toString(cal)); + } + // new toCalendar() + cal = DateConverter.toCalendar(orig, null); + if (yr == BAD) + { + assertEquals(cal.get(Calendar.YEAR), DateConverter.INVALID_YEAR); + } + else + { + assertEquals(pdfDate, DateConverter.toString(cal)); + } + } - // assertDate("2007-04-30T19:36:47+????", "20070430193647+713'00'"); - // assertDate("2007-08-21T10:35:22+00:00", "Tue Aug 21 10:35:22 2007"); - assertDate("2008-11-04T00:00:00+00:00", "Tuesday, November 04, 2008"); - // assertDate("2007-12-17T02:02:03+00:00", "200712172:2:3"); - // assertDate("????", "Unknown"); - // assertDate("2009-03-19T20:01:22+00:00", "20090319 200122"); - // assertDate("2008-05-12T09:47:00+00:00", "9:47 5/12/2008"); + /** + * Test dates in various formats. + * Years differ to make it easier to find failures. + * @throws Exception none expected + */ + public void testDateConverter() throws Exception + { + int year = Calendar.getInstance().get(Calendar.YEAR); + checkParse(2010, 4,23, 0, 0, 0, 0, "D:20100423"); + checkParse(2011, 4,23, 0, 0, 0, 0, "20110423"); + checkParse(2012, 1, 1, 0, 0, 0, 0, "D:2012"); + checkParse(2013, 1, 1, 0, 0, 0, 0, "2013"); - // assertDate("2009-04-01T00:00:00+02:00", "20090401+0200"); - assertDate("2008-01-11T00:00:00+00:00", "Friday, January 11, 2008"); - // assertDate("2009-04-01T00:00:00+04:00", "20090401+04'00'"); - // assertDate("2009-04-01T00:00:00+09:00", "20090401+09'00'"); - // assertDate("2009-04-01T00:00:00-02:00", "20090401-02'00'"); - // assertDate("2009-04-01T06:01:01+00:00", "20090401 01:01:01 -0500"); - // assertDate("2000-05-26T11:25:10+00:00", "26 May 2000 11:25:10"); - // assertDate("2000-05-26T11:25:00+00:00", "26 May 2000 11:25"); - } finally { - TimeZone.setDefault(timezone); - } + // PDFBOX-1219 + checkParse(2001, 1,31,10,33, 0, +1, "2001-01-31T10:33+01:00 "); + // PDFBOX-465 + checkParse(2002, 5,12, 9,47, 0, 0, "9:47 5/12/2002"); + // PDFBOX-465 + checkParse(2003,12,17, 2, 2, 3, 0, "200312172:2:3"); + // PDFBOX-465 + checkParse(2009, 3,19,20, 1,22, 0, " 20090319 200122"); + + checkParse(2014, 4, 1, 0, 0, 0, +2, "20140401+0200"); + // "EEEE, MMM dd, yy", + checkParse(2115, 1,11, 0, 0, 0, 0, "Friday, January 11, 2115"); + // "EEEE, MMM dd, yy", + checkParse(1915, 1,11, 0, 0, 0, 0, "Monday, Jan 11, 1915"); + // "EEEE, MMM dd, yy", + checkParse(2215, 1,11, 0, 0, 0, 0, "Wed, January 11, 2215"); + // "EEEE, MMM dd, yy", + checkParse(2015, 1,11, 0, 0, 0, 0, " Sun, January 11, 2015 "); + checkParse(2016, 4, 1, 0, 0, 0, +4, "20160401+04'00'"); + checkParse(2017, 4, 1, 0, 0, 0, +9, "20170401+09'00'"); + checkParse(2018, 4, 1, 0, 0, 0, -2, "20180401-02'00'"); + checkParse(2019, 4, 1, 6, 1, 1, -11, "20190401 6:1:1 -1100"); + checkParse(2020, 5,26,11,25,10, 0, "26 May 2020 11:25:10"); + checkParse(2021, 5,26,11,23, 0, 0, "26 May 2021 11:23"); + + // try dates invalid due to out of limit values + checkParse(BAD, 0, 0, 0, 0, 0, 0, "Tuesday, May 32 2000 11:27 UCT"); + checkParse(BAD, 0, 0, 0, 0, 0, 0, "32 May 2000 11:25"); + checkParse(BAD, 0, 0, 0, 0, 0, 0, "Tuesday, May 32 2000 11:25"); + checkParse(BAD, 0, 0, 0, 0, 0, 0, "19921301 11:25"); + checkParse(BAD, 0, 0, 0, 0, 0, 0, "19921232 11:25"); + checkParse(BAD, 0, 0, 0, 0, 0, 0, "19921001 11:60"); + checkParse(BAD, 0, 0, 0, 0, 0, 0, "19920401 24:25"); + + checkParse(BAD, 0, 0, 0, 0, 0, 0, + "20070430193647+713'00' illegal tz hr"); // PDFBOX-465 + checkParse(BAD, 0, 0, 0, 0, 0, 0, "nodigits"); + checkParse(BAD, 0, 0, 0, 0, 0, 0, "Unknown"); // PDFBOX-465 + checkParse(BAD, 0, 0, 0, 0, 0, 0, "333three digit year"); + + checkParse(2000, 2,29, 0, 0, 0, 0, "2000 Feb 29"); // valid date + checkParse(2000, 2,29, 0, 0, 0,+11, " 2000 Feb 29 GMT + 11:00"); // valid date + checkParse(BAD, 0, 0, 0, 0, 0, 0, "2100 Feb 29 GMT+11"); // invalid date + checkParse(2012, 2,29, 0, 0, 0,+11, "2012 Feb 29 GMT+11"); // valid date + checkParse(BAD, 0, 0, 0, 0, 0, 0, "2012 Feb 30 GMT+11"); // invalid date + + checkParse(1970,12,23, 0, 8, 0, 0, "1970 12 23:08"); // test ambiguous date + + // cannot have P for PM + // cannot have Sat. instead of Sat + // EST works, but EDT does not; EST is a special kludge in Java + + // test cases for all entries on old formats list + // "E, dd MMM yyyy hh:mm:ss a" + checkParse(1971, 7, 6, 17, 22, 1, 0, "Tuesday, 6 Jul 1971 5:22:1 PM"); + // "EE, MMM dd, yyyy hh:mm:ss a" + checkParse(1972, 7, 6, 17, 22, 1, 0, "Thu, July 6, 1972 5:22:1 pm"); + // "MM/dd/yyyy hh:mm:ss" + checkParse(1973, 7, 6, 17, 22, 1, 0, "7/6/1973 17:22:1"); + // "MM/dd/yyyy" + checkParse(1974, 7, 6, 0, 0, 0, 0, "7/6/1974"); + // "yyyy-MM-dd'T'HH:mm:ss'Z'" + checkParse(1975, 7, 6, 17, 22, 1, -10, "1975-7-6T17:22:1-1000"); + // "yyyy-MM-dd'T'HH:mm:ssz" + checkParse(1976, 7, 6, 17, 22, 1, -4, "1976-7-6T17:22:1GMT-4"); + // "yyyy-MM-dd'T'HH:mm:ssz" + checkParse(BAD, 7, 6, 17, 22, 1, -4, "2076-7-6T17:22:1EDT"); // "EDT" is not a known tz ID + // "yyyy-MM-dd'T'HH:mm:ssz" + // ATTENTION **************************************** + // changed from "EST" to "GMT-5", as this doesn't work on java5 + // ATTENTION **************************************** + checkParse(1960, 7, 6, 17, 22, 1, -5, "1960-7-6T17:22:1GMT-5"); // "EST" does not have a DST rule + // "EEEE, MMM dd, yyyy" + checkParse(1977, 7, 6, 0, 0, 0, 0, "Wednesday, Jul 6, 1977"); + // "EEEE MMM dd, yyyy HH:mm:ss" + checkParse(1978, 7, 6, 17, 22, 1, 0, "Thu Jul 6, 1978 17:22:1"); + // "EEEE MMM dd HH:mm:ss z yyyy" + checkParse(1979, 7, 6, 17, 22, 1, +8, "Friday July 6 17:22:1 GMT+08:00 1979"); + // "EEEE, MMM dd, yyyy 'at' hh:mma" + checkParse(1980, 7, 6, 16, 23, 0, 0, "Sun, Jul 6, 1980 at 4:23pm"); + // "EEEEEEEEEE, MMMMMMMMMMMM dd, yyyy" + checkParse(1981, 7, 6, 0, 0, 0, 0, "Monday, July 6, 1981"); + // "dd MMM yyyy hh:mm:ss" + checkParse(1982, 7, 6, 17, 22, 1, 0, "6 Jul 1982 17:22:1"); + // "M/dd/yyyy hh:mm:ss" + checkParse(1983, 7, 6, 17, 22, 1, 0, "7/6/1983 17:22:1"); + // "MM/d/yyyy hh:mm:ss" + checkParse(1984, 7, 6, 17, 22, 1, 0, "7/6/1984 17:22:01"); + // "M/dd/yyyy" + checkParse(1985, 7, 6, 0, 0, 0, 0, "7/6/1985"); + // "MM/d/yyyy" + checkParse(1986, 7, 6, 0, 0, 0, 0, "07/06/1986"); + // "M/d/yyyy hh:mm:ss" + checkParse(1987, 7, 6, 17, 22, 1, 0, "7/6/1987 17:22:1"); + // "M/d/yyyy" + checkParse(1988, 7, 6, 0, 0, 0, 0, "7/6/1988"); + + // test ends of range of two digit years + checkParse(year-79, 1, 1, 0, 0, 0, 0, "1/1/" + ((year-79)%100) + + " 00:00:00"); // "M/d/yy hh:mm:ss" + // "M/d/yy" + checkParse(year+19, 1, 1, 0, 0, 0, 0, "1/1/" + ((year+19)%100)); + + // "yyyyMMdd hh:mm:ss Z" + checkParse(1991, 7, 6, 17, 7, 1, +6, "19910706 17:7:1 Z+0600"); + // "yyyyMMdd hh:mm:ss" + checkParse(1992, 7, 6, 17, 7, 1, 0, "19920706 17:07:01"); + // "yyyyMMdd'+00''00'''" + checkParse(1993, 7, 6, 0, 0, 0, 0, "19930706+00'00'"); + // "yyyyMMdd'+01''00'''" + checkParse(1994, 7, 6, 0, 0, 0, 1, "19940706+01'00'"); + // "yyyyMMdd'+02''00'''" + checkParse(1995, 7, 6, 0, 0, 0, 2, "19950706+02'00'"); + // "yyyyMMdd'+03''00'''" + checkParse(1996, 7, 6, 0, 0, 0, 3, "19960706+03'00'"); + // . . . + // "yyyyMMdd'-10''00'''" + checkParse(1997, 7, 6, 0, 0, 0, -10, "19970706-10'00'"); + // "yyyyMMdd'-11''00'''" + checkParse(1998, 7, 6, 0, 0, 0, -11, "19980706-11'00'"); + // "yyyyMMdd" + checkParse(1999, 7, 6, 0, 0, 0, 0, "19990706"); + // ambiguous big-endian date + checkParse(2073,12,25, 0, 8, 0, 0, "2073 12 25:08"); + } - private void assertDate(String expected, String date) throws Exception { - Calendar calendar = DateConverter.toCalendar(date); - assertEquals(expected, DateConverter.toISO8601(calendar)); + private static void checkToString(int yr, int mon, int day, + int hr, int min, int sec, TimeZone tz, int off) throws Exception + { + // construct a GregoreanCalendar from args + GregorianCalendar cal = new GregorianCalendar(tz, Locale.ENGLISH); + cal.set(yr, mon-1, day, hr, min, sec); + // create expected strings + String pdfDate = String.format("D:%04d%02d%02d%02d%02d%02d%+03d'00'", + yr,mon,day,hr,min,sec,off); + String iso8601Date = String.format("%04d-%02d-%02d" + + "T%02d:%02d:%02d%+03d:00", + yr,mon,day,hr,min,sec,off); + // compare outputs from toString and toISO8601 with expected values + assertEquals(pdfDate, DateConverter.toString(cal)); + assertEquals(iso8601Date, DateConverter.toISO8601(cal)); + } + + /** + * Test toString() and toISO8601() for various dates. + * + * @throws Exception if something went wrong. + */ + public void testToString() throws Exception + { // std DST + TimeZone tzPgh = TimeZone.getTimeZone("America/New_York"); // -5 -4 + TimeZone tzBerlin = TimeZone.getTimeZone("Europe/Berlin"); // +1 +2 + TimeZone tzMaputo = TimeZone.getTimeZone("Africa/Maputo"); // +2 +2 + TimeZone tzAruba = TimeZone.getTimeZone("America/Aruba"); // -4 -4 + TimeZone tzJamaica = TimeZone.getTimeZone("America/Jamaica");// -5 -5 + TimeZone tzMcMurdo = TimeZone.getTimeZone("Antartica/McMurdo");// +12 +13 + + assertNull(DateConverter.toCalendar((COSString) null)); + assertNull(DateConverter.toCalendar((String) null)); + + checkToString(2013, 8, 28, 3, 14, 15, tzPgh, -4); + checkToString(2014, 2, 28, 3, 14, 15, tzPgh, -5); + checkToString(2015, 8, 28, 3, 14, 15, tzBerlin, +2); + checkToString(2016, 2, 28, 3, 14, 15, tzBerlin, +1); + checkToString(2017, 8, 28, 3, 14, 15, tzAruba, -4); + checkToString(2018, 1, 1, 1, 14, 15, tzJamaica, -5); + checkToString(2019, 12, 31, 12, 59, 59, tzJamaica, -5); + checkToString(2020, 2, 29, 0, 0, 0, tzMaputo, +2); + // McMurdo has a daylightsavings rule, but it seems never to apply + checkToString(1981, 1, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1982, 2, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1983, 3, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1984, 4, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1985, 5, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1986, 6, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1987, 7, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1988, 8, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1989, 9, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1990, 10, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1991, 11, 1, 1, 14, 15, tzMcMurdo, +0); + checkToString(1992, 12, 1, 1, 14, 15, tzMcMurdo, +0); + } + + private static void checkParseTZ(int expect, String src) + { + GregorianCalendar dest = DateConverter.newGreg(); + DateConverter.parseTZoffset(src, dest, new ParsePosition(0)); + assertEquals(expect, dest.get(Calendar.ZONE_OFFSET)); } + /** + * Timezone testcase. + */ + public void testParseTZ() + { + checkParseTZ(0*HRS+0*MINS, "+00:00"); + checkParseTZ(0*HRS+0*MINS, "-0000"); + checkParseTZ(1*HRS+0*MINS, "+1:00"); + checkParseTZ(-(1*HRS+0*MINS), "-1:00"); + checkParseTZ(-(1*HRS+30*MINS), "-0130"); + checkParseTZ(11*HRS+59*MINS, "1159"); + checkParseTZ(-(11*HRS+30*MINS), "1230"); + checkParseTZ(11*HRS+30*MINS, "-12:30"); + checkParseTZ(0*HRS+0*MINS, "Z"); + checkParseTZ(-(8*HRS+0*MINS), "PST"); + checkParseTZ(0*HRS+0*MINS, "EDT"); // EDT does not parse + checkParseTZ(-(3*HRS+0*MINS), "GMT-0300"); + checkParseTZ(+(11*HRS+0*MINS), "GMT+11:00"); + checkParseTZ(-(6*HRS+0*MINS), "America/Chicago"); + // ATTENTION **************************************** + // changed from "Europe/Moscow" to "Europe/Berlin", as this doesn't work on java5 + // ATTENTION **************************************** + checkParseTZ(+(1*HRS+0*MINS), "Europe/Berlin"); + checkParseTZ((5*HRS+0*MINS), "0500"); + checkParseTZ((5*HRS+0*MINS), "+0500"); + checkParseTZ((11*HRS+0*MINS), "+11'00'"); + checkParseTZ(0, "Z"); + } + + private static void checkFormatOffset(double off, String expect) + { + TimeZone tz = new SimpleTimeZone((int)(off*60*60*1000), "junkID"); + String got = DateConverter.formatTZoffset(tz.getRawOffset(), ":"); + assertEquals(expect, got); + } + + /** + * Timezone offset testcase. + * + * @throws Exception + */ + public void testFormatTZoffset() + { + checkFormatOffset(-12.1, "+11:54"); + checkFormatOffset(12.1, "-11:54"); + checkFormatOffset(0, "+00:00"); + checkFormatOffset(-1, "-01:00"); + checkFormatOffset(.5, "+00:30"); + checkFormatOffset(-0.5, "-00:30"); + checkFormatOffset(.1, "+00:06"); + checkFormatOffset(-0.1, "-00:06"); + checkFormatOffset(-12, "+00:00"); + checkFormatOffset(12, "+00:00"); + checkFormatOffset(-11.5, "-11:30"); + checkFormatOffset(11.5, "+11:30"); + checkFormatOffset(11.9, "+11:54"); + checkFormatOffset(11.1, "+11:06"); + checkFormatOffset(-11.9, "-11:54"); + checkFormatOffset(-11.1, "-11:06"); + } + + // testbody precedes + //////////////////////////////////////////////////// + /** * Set the tests in the suite for this test class. * @@ -130,7 +460,10 @@ public static Test suite() */ public static void main( String[] args ) { - String[] arg = {TestDateUtil.class.getName() }; + String[] arg = + { + TestDateUtil.class.getName() + }; junit.textui.TestRunner.main( arg ); } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/FileSpecificationValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/FileSpecificationValidationProcess.java index be0ced8f849..a52ae05dfb3 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/FileSpecificationValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/FileSpecificationValidationProcess.java @@ -58,7 +58,7 @@ public void validate(PreflightContext ctx) throws ValidationException { COSDictionary dic = (COSDictionary) cBase; String type = dic.getNameAsString(COSName.TYPE); - if (FILE_SPECIFICATION_VALUE_TYPE.equals(type)) + if (FILE_SPECIFICATION_VALUE_TYPE.equals(type) || COSName.F.getName().equals(type)) { // ---- It is a file specification validateFileSpecification(ctx, dic); From 2c2a23a76f013be6da330c0635a30c03ffd7477d Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 17 Nov 2013 17:02:03 +0000 Subject: [PATCH 0027/1017] PDFBOX-1730: use the correct image type as default value git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1542762 13f79535-47bb-0310-9956-ffa450edef68 --- pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java index 65fae1f299f..6d8665cc71b 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java @@ -729,7 +729,7 @@ public BufferedImage convertToImage() throws IOException //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); + return convertToImage(BufferedImage.TYPE_INT_RGB, 2 * DEFAULT_USER_SPACE_UNIT_DPI); } /** From d50ec339caee42d543653b46939896e341919e9f Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 17 Nov 2013 17:27:41 +0000 Subject: [PATCH 0028/1017] PDFBOX-1687: added some graphics.dispose calls and a new dispose method in PageDrawer to release used resources git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1542768 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/pdfbox/filter/JBIG2Filter.java | 5 ++++- .../java/org/apache/pdfbox/pdfviewer/PageDrawer.java | 11 +++++++++++ .../main/java/org/apache/pdfbox/pdmodel/PDPage.java | 4 +++- .../java/org/apache/pdfbox/pdmodel/PDPageable.java | 1 + .../pdmodel/graphics/xobject/PDXObjectImage.java | 2 ++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java b/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java index dba00e38beb..52722efd8a1 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/filter/JBIG2Filter.java @@ -16,6 +16,7 @@ */ package org.apache.pdfbox.filter; +import java.awt.Graphics; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; @@ -98,7 +99,9 @@ public void decode( InputStream compressedData, OutputStream result, COSDictiona } BufferedImage packedImage = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_BYTE_BINARY); - packedImage.getGraphics().drawImage(bi, 0, 0, null); + Graphics graphics = packedImage.getGraphics(); + graphics.drawImage(bi, 0, 0, null); + graphics.dispose(); bi = packedImage; } DataBuffer dBuf = bi.getData().getDataBuffer(); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java index 3b79134959e..4c975b5ac2b 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java @@ -162,6 +162,17 @@ public void drawPage( Graphics g, PDPage p, Dimension pageDimension ) throws IOE } + /** + * Remove all cached resources. + */ + public void dispose() + { + graphics = null; + linePath = null; + page = null; + pageSize = null; + } + /** * You should override this method if you want to perform an action when a * text is being processed. diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java index 6d8665cc71b..542a6e8a0e0 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPage.java @@ -799,7 +799,8 @@ else if (rotationAngle >= 360) graphics.scale( scaling, scaling ); PageDrawer drawer = new PageDrawer(); drawer.drawPage( graphics, this, pageDimension ); - + drawer.dispose(); + graphics.dispose(); return retval; } @@ -882,6 +883,7 @@ public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) PageDrawer drawer = new PageDrawer(); PDRectangle cropBox = findCropBox(); drawer.drawPage( graphics, this, cropBox.createDimension() ); + drawer.dispose(); return PAGE_EXISTS; } catch( IOException io ) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageable.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageable.java index bffda307ee9..ecf44feafcd 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageable.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageable.java @@ -195,6 +195,7 @@ public int print(Graphics graphics, PageFormat format, int i) throws PrinterExce PDRectangle cropBox = page.findCropBox(); PageDrawer drawer = new PageDrawer(); drawer.drawPage( graphics, page, cropBox.createDimension() ); + drawer.dispose(); return PAGE_EXISTS; } catch( IOException io ) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java index 5b121938188..c1ade624523 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java @@ -183,6 +183,7 @@ public BufferedImage imageMask(BufferedImage baseImage) throws IOException // TODO DecodeArray == [1,0] graphics.setComposite(AlphaComposite.DstIn); graphics.drawImage(baseImage, null, 0, 0); + graphics.dispose(); return stencilMask; } @@ -205,6 +206,7 @@ public BufferedImage mask(BufferedImage baseImage) graphics.drawImage(baseImage, 0, 0, maskImage.getWidth(), maskImage.getHeight(), 0, 0, baseImage.getWidth(), baseImage.getHeight(), null); graphics.setComposite(AlphaComposite.DstIn); graphics.drawImage(maskImage, null, 0, 0); + graphics.dispose(); return newImage; } else From 1eb7fa9dcc992cb6095783d52f6db2032dd7dc66 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 17 Nov 2013 18:33:35 +0000 Subject: [PATCH 0029/1017] PDFBOX-1778: use a BigDecimal as internal representation for a COSFloat and save the value as String as well git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1542789 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/pdfbox/cos/COSFloat.java | 43 ++++++++----------- .../org/apache/pdfbox/cos/TestCOSFloat.java | 22 +++------- 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSFloat.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSFloat.java index cc7d03c9e6b..060b630840d 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSFloat.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSFloat.java @@ -18,10 +18,7 @@ import java.io.IOException; import java.io.OutputStream; - -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.NumberFormat; +import java.math.BigDecimal; import org.apache.pdfbox.exceptions.COSVisitorException; @@ -29,11 +26,12 @@ * This class represents a floating point number in a PDF document. * * @author Ben Litchfield - * @version $Revision: 1.17 $ + * */ public class COSFloat extends COSNumber { - private float value; + private BigDecimal value; + private String valueAsString; /** * Constructor. @@ -42,7 +40,8 @@ public class COSFloat extends COSNumber */ public COSFloat( float aFloat ) { - value = aFloat; + valueAsString = String.valueOf(aFloat); + value = new BigDecimal(valueAsString); } /** @@ -56,7 +55,8 @@ public COSFloat( String aFloat ) throws IOException { try { - value = Float.parseFloat( aFloat ); + valueAsString = aFloat; + value = new BigDecimal( valueAsString ); } catch( NumberFormatException e ) { @@ -71,7 +71,8 @@ public COSFloat( String aFloat ) throws IOException */ public void setValue( float floatValue ) { - value = floatValue; + valueAsString = String.valueOf(floatValue); + value = new BigDecimal(valueAsString); } /** @@ -81,7 +82,7 @@ public void setValue( float floatValue ) */ public float floatValue() { - return value; + return value.floatValue(); } /** @@ -91,17 +92,17 @@ public float floatValue() */ public double doubleValue() { - return value; + return value.doubleValue(); } /** - * This will get the integer value of this object. + * This will get the long value of this object. * - * @return The int value of this object, + * @return The long value of this object, */ public long longValue() { - return (long)value; + return value.longValue(); } /** @@ -111,7 +112,7 @@ public long longValue() */ public int intValue() { - return (int)value; + return value.intValue(); } /** @@ -119,7 +120,7 @@ public int intValue() */ public boolean equals( Object o ) { - return o instanceof COSFloat && Float.floatToIntBits(((COSFloat)o).value) == Float.floatToIntBits(value); + return o instanceof COSFloat && Float.floatToIntBits(((COSFloat)o).value.floatValue()) == Float.floatToIntBits(value.floatValue()); } /** @@ -127,7 +128,7 @@ public boolean equals( Object o ) */ public int hashCode() { - return Float.floatToIntBits(value); + return value.hashCode(); } /** @@ -158,12 +159,6 @@ public Object accept(ICOSVisitor visitor) throws COSVisitorException */ 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")); + output.write(valueAsString.getBytes("ISO-8859-1")); } } diff --git a/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSFloat.java b/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSFloat.java index 12348b4ca85..075f005a767 100644 --- a/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSFloat.java +++ b/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSFloat.java @@ -20,9 +20,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.NumberFormat; import java.util.Random; import junit.framework.Test; @@ -37,15 +34,6 @@ public class TestCOSFloat extends TestCOSNumber { // Use random number to ensure various float values are expressed in the test private Random rnd; - private DecimalFormat formatDecimal; - { - formatDecimal = (DecimalFormat) NumberFormat.getNumberInstance(); - formatDecimal.setMaximumFractionDigits(10); - formatDecimal.setGroupingUsed(false); - DecimalFormatSymbols symbols = formatDecimal.getDecimalFormatSymbols(); - symbols.setDecimalSeparator('.'); - formatDecimal.setDecimalFormatSymbols(symbols); - } public void setUp() { @@ -131,7 +119,9 @@ public void testDoubleValue() { float num = i * rnd.nextFloat(); COSFloat testFloat = new COSFloat(num); - assertEquals((double) num, testFloat.doubleValue()); + // compare the string representation instead of the numeric values + // as the cast from float to double adds some more fraction digits + assertEquals(Float.toString(num), Double.toString(testFloat.doubleValue())); } } @@ -170,7 +160,8 @@ public void testAccept() num = i * rnd.nextFloat(); COSFloat cosFloat = new COSFloat(num); cosFloat.accept(visitor); - testByteArrays(formatDecimal.format(num).getBytes("ISO-8859-1"), + assertEquals(Float.toString(cosFloat.floatValue()), outStream.toString("ISO-8859-1")); + testByteArrays(Float.toString(num).getBytes("ISO-8859-1"), outStream.toByteArray()); outStream.reset(); } @@ -195,7 +186,8 @@ public void testWritePDF() num = i * rnd.nextFloat(); COSFloat cosFloat = new COSFloat(num); cosFloat.writePDF(outStream); - testByteArrays(formatDecimal.format(num).getBytes("ISO-8859-1"), + assertEquals(Float.toString(cosFloat.floatValue()), outStream.toString("ISO-8859-1")); + testByteArrays(Float.toString(num).getBytes("ISO-8859-1"), outStream.toByteArray()); outStream.reset(); } From 52579c0170a1a01af943170af2cc08114b427396 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sat, 23 Nov 2013 13:37:07 +0000 Subject: [PATCH 0030/1017] PDFBOX-1778: avoid a floating point string representation in some corner cases git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1544791 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/pdfbox/cos/COSFloat.java | 24 ++++++++++--- .../org/apache/pdfbox/cos/TestCOSFloat.java | 35 ++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSFloat.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSFloat.java index 060b630840d..a71662e3d1f 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSFloat.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSFloat.java @@ -40,8 +40,7 @@ public class COSFloat extends COSNumber */ public COSFloat( float aFloat ) { - valueAsString = String.valueOf(aFloat); - value = new BigDecimal(valueAsString); + setValue(aFloat); } /** @@ -71,8 +70,23 @@ public COSFloat( String aFloat ) throws IOException */ public void setValue( float floatValue ) { - valueAsString = String.valueOf(floatValue); - value = new BigDecimal(valueAsString); + // use a BigDecimal as intermediate state to avoid + // a floating point string representation of the float value + value = new BigDecimal(String.valueOf(floatValue)); + valueAsString = removeNullDigits(value.toPlainString()); + } + + private String removeNullDigits(String value) + { + // remove fraction digit "0" only + if (value.indexOf(".") > -1 && !value.endsWith(".0")) + { + while (value.endsWith("0") && !value.endsWith(".0")) + { + value = value.substring(0,value.length()-1); + } + } + return value; } /** @@ -136,7 +150,7 @@ public int hashCode() */ public String toString() { - return "COSFloat{" + value + "}"; + return "COSFloat{" + valueAsString + "}"; } /** diff --git a/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSFloat.java b/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSFloat.java index 075f005a767..7e1ad6d3c82 100644 --- a/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSFloat.java +++ b/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSFloat.java @@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.math.BigDecimal; import java.util.Random; import junit.framework.Test; @@ -160,8 +161,8 @@ public void testAccept() num = i * rnd.nextFloat(); COSFloat cosFloat = new COSFloat(num); cosFloat.accept(visitor); - assertEquals(Float.toString(cosFloat.floatValue()), outStream.toString("ISO-8859-1")); - testByteArrays(Float.toString(num).getBytes("ISO-8859-1"), + assertEquals(floatToString(cosFloat.floatValue()), outStream.toString("ISO-8859-1")); + testByteArrays(floatToString(num).getBytes("ISO-8859-1"), outStream.toByteArray()); outStream.reset(); } @@ -186,11 +187,17 @@ public void testWritePDF() num = i * rnd.nextFloat(); COSFloat cosFloat = new COSFloat(num); cosFloat.writePDF(outStream); - assertEquals(Float.toString(cosFloat.floatValue()), outStream.toString("ISO-8859-1")); - testByteArrays(Float.toString(num).getBytes("ISO-8859-1"), + assertEquals(floatToString(cosFloat.floatValue()), outStream.toString("ISO-8859-1")); + testByteArrays(floatToString(num).getBytes("ISO-8859-1"), outStream.toByteArray()); outStream.reset(); } + // test a corner case as described in PDFBOX-1778 + num = 0.000000000000000000000000000000001f; + COSFloat test = new COSFloat(num); + test.writePDF(outStream); + assertEquals(floatToString(num), outStream.toString("ISO-8859-1")); + outStream.reset(); } catch (IOException e) { @@ -198,6 +205,26 @@ public void testWritePDF() } } + private String floatToString(float value) + { + // use a BigDecimal as intermediate state to avoid + // a floating point string representation of the float value + return removeTrailingNull(new BigDecimal(String.valueOf(value)).toPlainString()); + } + + private String removeTrailingNull(String value) + { + // remove fraction digit "0" only + if (value.indexOf(".") > -1 && !value.endsWith(".0")) + { + while (value.endsWith("0") && !value.endsWith(".0")) + { + value = value.substring(0,value.length()-1); + } + } + return value; + } + /** * This will get the suite of test that this class holds. * From b93af3c91e76184f6ec881a5bf1e379829661c32 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sat, 23 Nov 2013 14:21:02 +0000 Subject: [PATCH 0031/1017] PDFBOX-1768: fixed some dependecy issues git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1544807 13f79535-47bb-0310-9956-ffa450edef68 --- pdfbox/build.xml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pdfbox/build.xml b/pdfbox/build.xml index 0a417a573b1..b41b160589a 100644 --- a/pdfbox/build.xml +++ b/pdfbox/build.xml @@ -116,9 +116,12 @@ - - - + + + + + + @@ -126,19 +129,17 @@ - - - + + - - - - - + + + + From 7c4ff0f64ef9c30e1bacac513db9e22bf949ca8e Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 24 Nov 2013 13:46:27 +0000 Subject: [PATCH 0032/1017] PDFBOX-1564: added PDOutputIntent support including a simple sample on how to create a PDF/A git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1544973 13f79535-47bb-0310-9956-ffa450edef68 --- .../main/appended-resources/META-INF/LICENSE | 183 ++++++++++++++++++ .../main/appended-resources/META-INF/NOTICE | 4 + .../pdfbox/examples/pdfa/CreatePDFA.java | 154 +++++++++++++++ .../pdfa/sRGB Color Space Profile.icm | Bin 0 -> 3144 bytes .../sRGB Color Space Profile.icm.LICENSE.txt | 14 ++ .../pdfbox/pdmodel/PDDocumentCatalog.java | 51 +++++ .../graphics/color/PDOutputIntent.java | 98 ++++++++++ 7 files changed, 504 insertions(+) create mode 100644 examples/src/main/appended-resources/META-INF/LICENSE create mode 100644 examples/src/main/appended-resources/META-INF/NOTICE create mode 100644 examples/src/main/java/org/apache/pdfbox/examples/pdfa/CreatePDFA.java create mode 100644 examples/src/main/resources/org/apache/pdfbox/resources/pdfa/sRGB Color Space Profile.icm create mode 100644 examples/src/main/resources/org/apache/pdfbox/resources/pdfa/sRGB Color Space Profile.icm.LICENSE.txt create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDOutputIntent.java diff --git a/examples/src/main/appended-resources/META-INF/LICENSE b/examples/src/main/appended-resources/META-INF/LICENSE new file mode 100644 index 00000000000..4d80c76e6fb --- /dev/null +++ b/examples/src/main/appended-resources/META-INF/LICENSE @@ -0,0 +1,183 @@ + +EXTERNAL COMPONENTS + +Apache PDFBox includes a number of components with separate copyright notices +and license terms. Your use of these components is subject to the terms and +conditions of the following licenses. + +Contributions made to the original PDFBox, JempBox and FontBox projects: + + Copyright (c) 2002-2007, www.pdfbox.org + Copyright (c) 2006-2007, www.jempbox.org + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of pdfbox; nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +Bouncy Castle encryption libraries + + Copyright (c) 2000-2006 The Legion Of The Bouncy Castle + (http://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +Adobe Font Metrics (AFM) for PDF Core 14 Fonts + + This file and the 14 PostScript(R) AFM files it accompanies may be used, + copied, and distributed for any purpose and without charge, with or without + modification, provided that all copyright notices are retained; that the + AFM files are not distributed without this file; that all modifications + to this file or any of the AFM files are prominently noted in the modified + file(s); and that this paragraph is not modified. Adobe Systems has no + responsibility or obligation to support the use of the AFM files. + +CMaps for PDF Fonts (http://opensource.adobe.com/wiki/display/cmap/Downloads) + + Copyright 1990-2009 Adobe Systems Incorporated. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + Neither the name of Adobe Systems Incorporated nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. + +Glyphlist (http://www.adobe.com/devnet/opentype/archives/glyph.html) + + Copyright (c) 1997,1998,2002,2007 Adobe Systems Incorporated + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this documentation file to use, copy, publish, distribute, + sublicense, and/or sell copies of the documentation, and to permit + others to do the same, provided that: + - No modification, editing or other alteration of this document is + allowed; and + - The above copyright notice and this permission notice shall be + included in all copies of the documentation. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this documentation file, to create their own derivative works + from the content of this document to use, copy, publish, distribute, + sublicense, and/or sell the derivative works, and to permit others to do + the same, provided that the derived work is not represented as being a + copy or version of this document. + + Adobe shall not be liable to any party for any loss of revenue or profit + or for indirect, incidental, special, consequential, or other similar + damages, whether based on tort (including without limitation negligence + or strict liability), contract or other legal or equitable grounds even + if Adobe has been advised or had reason to know of the possibility of + such damages. The Adobe materials are provided on an "AS IS" basis. + Adobe specifically disclaims all express, statutory, or implied + warranties relating to the Adobe materials, including but not limited to + those concerning merchantability or fitness for a particular purpose or + non-infringement of any third party rights regarding the Adobe + materials. + +The International Components for Unicode library (http://site.icu-project.org/) + + ICU License - ICU 1.8.1 and later + + COPYRIGHT AND PERMISSION NOTICE + + Copyright (c) 1995-2009 International Business Machines Corporation and others + + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, provided that the above copyright notice(s) and this + permission notice appear in all copies of the Software and that both the + above copyright notice(s) and this permission notice appear in supporting + documentation. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE + BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, + OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + Except as contained in this notice, the name of a copyright holder shall + not be used in advertising or otherwise to promote the sale, use or other + dealings in this Software without prior written authorization of the + copyright holder. + +The file "sRGB Color Space Profile.icm" is: +Copyright (c) 1998 Hewlett-Packard Company + +To anyone who acknowledges that the file "sRGB Color Space Profile.icm" +is provided "AS IS" WITH NO EXPRESS OR IMPLIED WARRANTY: +permission to use, copy and distribute this file for any purpose is hereby +granted without fee, provided that the file is not changed including the HP +copyright notice tag, and that the name of Hewlett-Packard Company not be +used in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. Hewlett-Packard Company makes +no representations about the suitability of this software for any purpose. + diff --git a/examples/src/main/appended-resources/META-INF/NOTICE b/examples/src/main/appended-resources/META-INF/NOTICE new file mode 100644 index 00000000000..9a162ab7212 --- /dev/null +++ b/examples/src/main/appended-resources/META-INF/NOTICE @@ -0,0 +1,4 @@ +Based on source code originally developed in the PDFBox, JempBox and +FontBox projects. +Copyright (c) 2002-2007, www.pdfbox.org +Copyright (c) 2006-2007, www.jempbox.org diff --git a/examples/src/main/java/org/apache/pdfbox/examples/pdfa/CreatePDFA.java b/examples/src/main/java/org/apache/pdfbox/examples/pdfa/CreatePDFA.java new file mode 100644 index 00000000000..bedf823f508 --- /dev/null +++ b/examples/src/main/java/org/apache/pdfbox/examples/pdfa/CreatePDFA.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.pdfa; + +import java.io.InputStream; + +import org.apache.jempbox.xmp.XMPMetadata; +import org.apache.jempbox.xmp.pdfa.XMPSchemaPDFAId; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.pdfbox.pdmodel.edit.PDPageContentStream; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont; +import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent; + +/** + * This is an example that creates a simple PDF/A document. + * + */ +public class CreatePDFA +{ + /** + * Constructor. + */ + public CreatePDFA() + { + super(); + } + + /** + * Create a simple PDF/A document. + * + * This example is based on HelloWorld example. + * + * As it is a simple case, to conform the PDF/A norm, are added : + * - the font used in the document + * - a light xmp block with only PDF identification schema (the only mandatory) + * - an output intent + * + * @param file The file to write the PDF to. + * @param message The message to write in the file. + * + * @throws Exception If something bad occurs + */ + public void doIt( String file, String message) throws Exception + { + // the document + PDDocument doc = null; + try + { + doc = new PDDocument(); + + PDPage page = new PDPage(); + doc.addPage( page ); + + // load the font from pdfbox.jar + InputStream fontStream = CreatePDFA.class.getResourceAsStream("/org/apache/pdfbox/resources/ttf/ArialMT.ttf"); + PDFont font = PDTrueTypeFont.loadTTF(doc, fontStream); + + // create a page with the message where needed + PDPageContentStream contentStream = new PDPageContentStream(doc, page); + contentStream.beginText(); + contentStream.setFont( font, 12 ); + contentStream.moveTextPositionByAmount( 100, 700 ); + contentStream.drawString( message ); + contentStream.endText(); + contentStream.saveGraphicsState(); + contentStream.close(); + + PDDocumentCatalog cat = doc.getDocumentCatalog(); + PDMetadata metadata = new PDMetadata(doc); + cat.setMetadata(metadata); + + // jempbox version + XMPMetadata xmp = new XMPMetadata(); + XMPSchemaPDFAId pdfaid = new XMPSchemaPDFAId(xmp); + xmp.addSchema(pdfaid); + pdfaid.setConformance("B"); + pdfaid.setPart(1); + pdfaid.setAbout(""); + metadata.importXMPMetadata(xmp); + + InputStream colorProfile = CreatePDFA.class.getResourceAsStream("/org/apache/pdfbox/resources/pdfa/sRGB Color Space Profile.icm"); + // create output intent + PDOutputIntent oi = new PDOutputIntent(doc, colorProfile); + oi.setInfo("sRGB IEC61966-2.1"); + oi.setOutputCondition("sRGB IEC61966-2.1"); + oi.setOutputConditionIdentifier("sRGB IEC61966-2.1"); + oi.setRegistryName("http://www.color.org"); + cat.addOutputIntent(oi); + + doc.save( file ); + + } + finally + { + if( doc != null ) + { + doc.close(); + } + } + } + + /** + * This will create a hello world PDF/A document. + *
+ * see usage() for commandline + * + * @param args Command line arguments. + */ + public static void main(String[] args) + { + CreatePDFA app = new CreatePDFA(); + 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() + " " ); + } +} diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/pdfa/sRGB Color Space Profile.icm b/examples/src/main/resources/org/apache/pdfbox/resources/pdfa/sRGB Color Space Profile.icm new file mode 100644 index 0000000000000000000000000000000000000000..7f9d18d097d1bcccb32e6d5743ac4af593170b6f GIT binary patch literal 3144 zcmbW3cT`i^7KhKhH@(mjA|NI78hQyJ(mO~M1W}1efKUR4geG=G1x6GRDOO}uzyU{x zB4b4q3xk4U*9r0vP{zSgL`CJ@jB5$+tu^!Bn*GOF-`VH4*V$+9eb>4GQ2c@f!gN>x zfHa|46z=Q6ToMz@#P0Oj{6)6@8zOaL$xnP1H3 zCZTMJGDQ>_?uqgO00@-CIlOWXi}^Wdo&b2JXXJ_miAiFn5!aY$<><&}`th?<`C>6E zl*3KohJ5ztS`M50QMwWn;o;hl~n+=Z3aN);jB;ZAOP|O0JPqm^B#t5UXsM(3?~bV z?CflF0iSCwE9f`-pMr17zlJ&Ynal3`Ry(E_KY=4j6*FZ;<)){mOGM1f3=WsiWc~LL z|Kq?pY0b&QES#Uf7x9JYRZ-}a351DgyM;V~SdcDc3WR^v;eQx5CkGjRoof_mbzcB| zg*i~TNe9SpJpe-^10-%gdIEjxTM#h{$iBR2t&y*Dk1~4x=lUNDT#9btOhF=3=JpJa zWO5~-11?AcX+Q+BK|a_3 z3PCa00d|9GZ~)YSde8(;f;MmlTm)CZ4R8w#fJZ5%ns2D1Ps-Z(rJ@g}V8oCHwgZiKc&>%Dh{Q-T1 z5tstgVGY;-wt(5NI~)i{!pq=9com!n7r@)$O1Kt24!6P=;T!M(EQMdflL$b_2m{eZ zSO^>OL_&~QBmog2Ymp+P0;xrskPf69xs6DXapVIAhoNDZ7#7AEHf9hrf%$@^V%4#xSSPGMb_q5Sn~g2RR$z}{Td>%$GT@D&7?Df)BxS@M3%+z6yUFe;(hDAI85YkO^7@ zYl06UmLMc-BvcR@2gb~6tQGuvWbRvcjdBj{|DY1^&LA*&ECQg$SNrogBQY0yv zw1HGfY9d`GJtn;&lgT<{M{*chKweL-B%dH(B@dE6P?RVp6i-SVMMBw5IZC-ec|dtX zrBV&3Zq!(+n7W->PrXEaN_{U!lQWm|m*dH;ldF+ymFt&#B~O&smv@(6E}th~A%9B# zru?`9QNcjLQ-PzfR-s1Ww8A}wNky8Xm12lunqrA!gW@&CQ6;>Rfs(fpPpLrZu+k-^ zAsRx{rFqi0v`w@++GW}>9Y;5$`_q%@#q=NOz4QrXC1qRXDCHdG8s)RfQU<~>VE8jq z8KsPqjJu4F^EBqU&*RVAHm_-3|GX)c`6_NIe3c@VW|aYzkE%>nZ`BmlovLlBPt-7K zCTd}7*=qaME~|~tr_Xnu&z)a1|K$9K>aeXRCp8onAC8dVyXG{!X< znr@oOn&q13G{>}PS_`!VTIE{jwO%llneNO~W+k(WIiant?WZl#KBV2NJ*{J?vsh=N zPP5JvU6L+aH$k^tw^MgQPfIUYFJG@w@1Z_XpRJ#yzem4Yf6BnnAlhJyL7Ty-p^Blu zVV+^5;bSAJk*m=vqgtao#yDfPafahC5 z+Qgb`U1NR6hHB$uv(Bd7=C!S z*mQO%yM%q!5$ovTxX$s6;|C{uC$ZB>r{A4f&MD3f&Z7$q7w{L-x-1*Nx|P#BIdg*ge_(xce&)OAnDpi^r6wljj=G3totquUC;* zueXx-V()72r#?D9i9U@!6TWu7t9>u{Vf_OAcKY4(*YM~1*ZWTdI0UQ-=nA9+Mg&#| zJ_|Al5(RYx!@&W;yMiAt(qAN8)D{9l{6ltyJP9=nT^0Is7%nU{tS0PvxK((5cy|OX zVnswl#Jfn3$kND1i;Wgb7I#L;N5w_eM@>b0MVCcOmsl)Wv*bpMT8tp3Jr*Au6?-Ih za;f*y-Ajk#?BWXJ?k+Q2mb2{Ia<%2D%g?TmU%^>%Djpjj9p4cDi4(%93l#rC8 zDKn|jsm*DGwD`2sLM35}uq$0NJtw^{!!)BXL%Pao)$UajqCnA+Oek|%X1kb<{!H~q zj3k>SgINo+YO>yEM`fSNQOptM^yHf4Zp$6X^U3>uHD)z;b!WbI{)YU)HSTK;t%cUE zT-&)$cU{4{;q|`jk8L1rNZIhqM$3(58{ck<-qcZ`QLw&XXtVF;#zMJ5QQ^HU&RY&_ z#cUO9?cHXxt!mqsB3{w;V$0%+;!h>qlI!1DeOLM2%=U!sy`}c0`*+}W2zT7w>ALe+ znPOR9+0d@wU9IKJ@}ly$yI1VKR$*IlU=L}JWRJ8msIt9EuWCotr|P8Yff|pR=Dq5B zx9y$U$J^Jx-)(=>0gVI22Rfw1Ho}dP#Y+O@16`i z*?Ee6s_7^FpZ2!UT8dhs*4);Kwxl*`drW)Z>44Ln9nKvsKb!x2^o;hInzPDhOV5$c z6`Y$npLc%pLdJ#hi-{M9I#+Z)y0qldovyI1-phfPuU_%Fa;e+B`}|dxt7on`T|0f9 zeZB35{f*WhyPlR`?0#wKweM}c>2S0CmgB9Cz6E{f`d#}k-uAkE<&OWI8v~05`tB~i zd;ebCJ?VYk{m}=h4_-gadN}=P{bTIoq9+PZDxRu8t^L*b*Ji1`^z5M5V9&FNXOD)s zLoff8`L9pI1<$F^D@L?N>PM|d&y4wu-FmU~#qi6Fm($~gzbXE<_m$DBpMH1yy=P*{ z#PDm;>zO}F-l)Dg`quVs=Va*Q(|2j_rl*SDtG++>f&HQTWAw+7>FiJVPnDmIKX-fy L{PK7vZD!`*_k{EN literal 0 HcmV?d00001 diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/pdfa/sRGB Color Space Profile.icm.LICENSE.txt b/examples/src/main/resources/org/apache/pdfbox/resources/pdfa/sRGB Color Space Profile.icm.LICENSE.txt new file mode 100644 index 00000000000..9b817e33924 --- /dev/null +++ b/examples/src/main/resources/org/apache/pdfbox/resources/pdfa/sRGB Color Space Profile.icm.LICENSE.txt @@ -0,0 +1,14 @@ +Obtained from: http://www.srgb.com/usingsrgb.html + +The file "sRGB Color Space Profile.icm" is: +Copyright (c) 1998 Hewlett-Packard Company + +To anyone who acknowledges that the file "sRGB Color Space Profile.icm" +is provided "AS IS" WITH NO EXPRESS OR IMPLIED WARRANTY: +permission to use, copy and distribute this file for any purpose is hereby +granted without fee, provided that the file is not changed including the HP +copyright notice tag, and that the name of Hewlett-Packard Company not be +used in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. Hewlett-Packard Company makes +no representations about the suitability of this software for any purpose. + diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocumentCatalog.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocumentCatalog.java index 14a6cf3382c..d354f57d06d 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocumentCatalog.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocumentCatalog.java @@ -32,6 +32,7 @@ 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.color.PDOutputIntent; import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties; import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory; import org.apache.pdfbox.pdmodel.interactive.action.PDDocumentCatalogAdditionalActions; @@ -443,6 +444,56 @@ public void setMarkInfo( PDMarkInfo markInfo ) root.setItem( COSName.MARK_INFO, markInfo ); } + /** + * Get the list of OutputIntents defined in the document. + * + * @return The list of PDOoutputIntent + */ + public List getOutputIntent () { + List retval = new ArrayList(); + COSArray array = (COSArray)root.getItem(COSName.OUTPUT_INTENTS); + if (array!=null) { + for (COSBase cosBase : array) + { + PDOutputIntent oi = new PDOutputIntent((COSStream)cosBase); + retval.add(oi); + } + } + return retval; + } + + /** + * Add an OutputIntent to the list. + * + * If there is not OutputIntent, the list is created and the first + * element added. + * + * @param outputIntent the OutputIntent to add. + */ + public void addOutputIntent (PDOutputIntent outputIntent) { + COSArray array = (COSArray)root.getItem(COSName.OUTPUT_INTENTS); + if (array==null) { + array = new COSArray(); + root.setItem(COSName.OUTPUT_INTENTS, array); + } + array.add(outputIntent.getCOSObject()); + } + + /** + * Replace the list of OutputIntents of the document. + * + * @param outputIntents the list of OutputIntents, if the list is empty all + * OutputIntents are removed. + */ + public void setOutputIntents (List outputIntents) { + COSArray array = new COSArray(); + for (PDOutputIntent intent : outputIntents) + { + array.add(intent.getCOSObject()); + } + root.setItem(COSName.OUTPUT_INTENTS, array); + } + /** * Set the page display mode, see the PAGE_MODE_XXX constants. * @return A string representing the page mode. diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDOutputIntent.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDOutputIntent.java new file mode 100644 index 00000000000..38cbd226d23 --- /dev/null +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDOutputIntent.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.io.IOException; +import java.io.InputStream; + +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.common.COSObjectable; +import org.apache.pdfbox.pdmodel.common.PDStream; + +public class PDOutputIntent implements COSObjectable { + + private COSDictionary dictionary; + + public PDOutputIntent(PDDocument doc, InputStream colorProfile) throws Exception{ + dictionary = new COSDictionary(); + dictionary.setItem(COSName.TYPE, COSName.OUTPUT_INTENT); + dictionary.setItem(COSName.S, COSName.GTS_PDFA1); + PDStream destOutputIntent = configureOutputProfile(doc, colorProfile); + dictionary.setItem(COSName.DEST_OUTPUT_PROFILE, destOutputIntent); + } + + public PDOutputIntent (COSDictionary dictionary) { + this.dictionary = dictionary; + } + + public COSBase getCOSObject() { + return dictionary; + } + + public COSStream getDestOutputIntent () { + return (COSStream)dictionary.getItem(COSName.DEST_OUTPUT_PROFILE); + } + + public String getInfo () { + return dictionary.getString(COSName.INFO); + } + + public void setInfo( String value ) + { + dictionary.setString(COSName.INFO, value); + } + + public String getOutputCondition () { + return dictionary.getString(COSName.OUTPUT_CONDITION); + } + + public void setOutputCondition( String value ) + { + dictionary.setString(COSName.OUTPUT_CONDITION, value); + } + + public String getOutputConditionIdentifier () { + return dictionary.getString(COSName.OUTPUT_CONDITION_IDENTIFIER); + } + + public void setOutputConditionIdentifier( String value ) + { + dictionary.setString(COSName.OUTPUT_CONDITION_IDENTIFIER, value); + } + + public String getRegistryName () { + return dictionary.getString(COSName.REGISTRY_NAME); + } + + public void setRegistryName( String value ) + { + dictionary.setString(COSName.REGISTRY_NAME, value); + } + + private PDStream configureOutputProfile (PDDocument doc, InputStream colorProfile) throws IOException { + PDStream stream = new PDStream(doc,colorProfile, false); + stream.getStream().setFilters(COSName.FLATE_DECODE); + stream.getStream().setInt( COSName.LENGTH, stream.getByteArray().length ); + stream.getStream().setInt(COSName.N, 3); + stream.addCompression(); + return stream; + } +} From eea967082c1fb9f4f978df71ee2fdb4bde71c6b9 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 24 Nov 2013 13:55:24 +0000 Subject: [PATCH 0033/1017] =?UTF-8?q?PDFBOX-1213:=20added=20some=20style?= =?UTF-8?q?=20imformation=20to=20the=20PDF2HTML=20converter=20as=20propose?= =?UTF-8?q?d=20by=20Axel=20D=C3=B6rfler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1544975 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/pdfbox/util/PDFText2HTML.java | 239 ++++++++++++++++-- .../apache/pdfbox/util/PDFTextStripper.java | 159 +++++++----- 2 files changed, 304 insertions(+), 94 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java index ea7e81406ea..08caf174c34 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java @@ -17,25 +17,28 @@ package org.apache.pdfbox.util; import java.io.IOException; - +import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.font.PDFontDescriptor; /** * 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; + private FontState fontState = new FontState(); /** * Constructor. @@ -179,6 +182,19 @@ protected void endArticle() throws IOException super.writeString(""); } + /** + * Write a string to the output stream, maintain font state, and escape some HTML characters. + * The font state is only preserved per word. + * + * @param text The text to write to the stream. + * @param textPositions the corresponding text positions + * @throws IOException If there is an error writing to the stream. + */ + protected void writeString(String text, List textPositions) throws IOException + { + super.writeString(fontState.push(text, textPositions)); + } + /** * Write a string to the output stream and escape some HTML characters. * @@ -191,45 +207,216 @@ protected void writeString(String chars) throws IOException super.writeString(escape(chars)); } + /** + * Writes the paragraph end "

" to the output. Furthermore, it will also clear the font state. + * + * {@inheritDoc} + */ + @Override + protected void writeParagraphEnd() throws IOException + { + writeString(fontState.clear()); + super.writeParagraphEnd(); + } + /** * Escape some HTML characters. * * @param chars String to be escaped * @return returns escaped String. */ - private String escape(String chars) + private static 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)) + appendEscaped(builder, chars.charAt(i)); + } + return builder.toString(); + } + + private static void appendEscaped(StringBuilder builder, char character) + { + // write non-ASCII as named entities + if ((character < 32) || (character > 126)) + { + int charAsInt = character; + builder.append("&#").append(charAsInt).append(";"); + } + else + { + switch (character) { - int charAsInt = c; - builder.append("&#").append(charAsInt).append(";"); + 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(character)); } - else + } + } + + /** + * A helper class to maintain the current font state. It's public methods will emit opening and + * closing tags as needed, and in the correct order. + * + * @author Axel Dörfler + */ + private static class FontState + { + protected List stateList = new ArrayList(); + protected Set stateSet = new HashSet(); + + /** + * Pushes new {@link TextPosition TextPositions} into the font state. The state is only + * preserved correctly for each letter if the number of letters in text matches + * the number of {@link TextPosition} objects. Otherwise, it's done once for the complete + * array (just by looking at its first entry). + * + * @return A string that contains the text including tag changes caused by its font state. + */ + public String push(String text, List textPositions) + { + StringBuilder buffer = new StringBuilder(); + + if (text.length() == textPositions.size()) { - switch (c) + // There is a 1:1 mapping, and we can use the TextPositions directly + for (int i = 0; i < text.length(); i++) { - 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)); + push(buffer, text.charAt(i), textPositions.get(i)); } } + else if (!text.isEmpty()) + { + // The normalized text does not match the number of TextPositions, so we'll just + // have a look at its first entry. + // TODO change PDFTextStripper.normalize() such that it maintains the 1:1 relation + if (textPositions.isEmpty()) + { + return text; + } + push(buffer, text.charAt(0), textPositions.get(0)); + buffer.append(escape(text.substring(1))); + } + return buffer.toString(); + } + + /** + * Closes all open states. + * @return A string that contains the closing tags of all currently open states. + */ + public String clear() + { + StringBuilder buffer = new StringBuilder(); + closeUntil(buffer, null); + stateList.clear(); + stateSet.clear(); + return buffer.toString(); + } + + protected String push(StringBuilder buffer, char character, TextPosition textPosition) + { + boolean bold = false; + boolean italics = false; + + PDFontDescriptor descriptor = textPosition.getFont().getFontDescriptor(); + if (descriptor != null) + { + bold = isBold(descriptor); + italics = isItalic(descriptor); + } + + buffer.append(bold ? open("b") : close("b")); + buffer.append(italics ? open("i") : close("i")); + appendEscaped(buffer, character); + + return buffer.toString(); + } + + private String open(String tag) + { + if (stateSet.contains(tag)) + { + return ""; + } + stateList.add(tag); + stateSet.add(tag); + + return openTag(tag); + } + + private String close(String tag) + { + if (!stateSet.contains(tag)) + { + return ""; + } + // Close all tags until (but including) the one we should close + StringBuilder tagsBuilder = new StringBuilder(); + int index = closeUntil(tagsBuilder, tag); + + // Remove from state + stateList.remove(index); + stateSet.remove(tag); + + // Now open the states that were closed but should remain open again + for (; index < stateList.size(); index++) + { + tagsBuilder.append(openTag(stateList.get(index))); + } + return tagsBuilder.toString(); + } + + private int closeUntil(StringBuilder tagsBuilder, String endTag) + { + for (int i = stateList.size(); i-- > 0;) + { + String tag = stateList.get(i); + tagsBuilder.append(closeTag(tag)); + if (endTag != null && tag.equals(endTag)) + { + return i; + } + } + return -1; + } + + private String openTag(String tag) + { + return "<" + tag + ">"; + } + + private String closeTag(String tag) + { + return ""; + } + + private boolean isBold(PDFontDescriptor descriptor) + { + if (descriptor.isForceBold()) + { + return true; + } + return descriptor.getFontName().contains("Bold"); + } + + private boolean isItalic(PDFontDescriptor descriptor) + { + if (descriptor.isItalic()) + { + return true; + } + return descriptor.getFontName().contains("Italic"); } - return builder.toString(); } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java index 38d737af629..a8940621423 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java @@ -59,7 +59,7 @@ * Eventually, we fully process each page and then print it. * * @author Ben Litchfield - * @version $Revision: 1.70 $ + * */ public class PDFTextStripper extends PDFStreamEngine { @@ -120,7 +120,6 @@ public class PDFTextStripper extends PDFStreamEngine private String articleStart = ""; private String articleEnd = ""; - private int currentPageNo = 0; private int startPage = 1; private int endPage = Integer.MAX_VALUE; @@ -188,7 +187,6 @@ public class PDFTextStripper extends PDFStreamEngine */ private boolean inParagraph; - /** * Instantiate a new PDFTextStripper object. This object will load * properties from PDFTextStripper.properties and will not do @@ -205,7 +203,6 @@ public PDFTextStripper() throws IOException 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 @@ -243,9 +240,7 @@ public PDFTextStripper( String encoding ) throws IOException * 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 @@ -331,7 +326,6 @@ public void writeText( PDDocument doc, Writer outputStream ) throws IOException throw new WrappedIOException("Error: document is encrypted", e); } } - processPages( document.getDocumentCatalog().getAllPages() ); endDocument(document); } @@ -349,7 +343,6 @@ protected void processPages( List pages ) throws IOException { startBookmarkPageNumber = getPageNumber( startBookmark, pages ); } - if( endBookmark != null ) { endBookmarkPageNumber = getPageNumber( endBookmark, pages ); @@ -365,8 +358,6 @@ protected void processPages( List pages ) throws IOException startBookmarkPageNumber = 0; endBookmarkPageNumber = 0; } - - Iterator pageIter = pages.iterator(); while( pageIter.hasNext() ) { @@ -450,13 +441,11 @@ protected void processPage( PDPage page, COSStream content ) throws IOException charactersByArticle.set( i, new ArrayList() ); } } - characterListMapping.clear(); processStream( page, page.findResources(), content ); writePage(); endPage( page ); } - } /** @@ -564,9 +553,7 @@ protected void writePage() throws IOException TextPositionComparator comparator = new TextPositionComparator(); Collections.sort( textList, comparator ); } - Iterator 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 @@ -607,7 +594,6 @@ else if ((dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT ) || } } } - // choose the dominant direction boolean isRtlDominant = rtlCnt > ltrCnt; @@ -742,17 +728,14 @@ else if ((dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT ) || { 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 && @@ -763,12 +746,10 @@ else if ((dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT ) || 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; @@ -795,14 +776,12 @@ else if ((dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT ) || lastWordSpacing = wordSpacing; previousAveCharWidth = averageCharWidth; } - // print the final line if (line.size() > 0) { writeLine(normalize(line,isRtlDominant,hasRtl),isRtlDominant); writeParagraphEnd(); } - endArticle(); } writePageEnd(); @@ -823,7 +802,6 @@ 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(); } @@ -860,6 +838,19 @@ protected void writeCharacters( TextPosition text ) throws IOException output.write( text.getCharacter() ); } + /** + * Write a Java string to the output stream. The default implementation will ignore the textPositions + * and just calls {@link #writeString(String)}. + * + * @param text The text to write to the stream. + * @param textPositions The TextPositions belonging to the text. + * @throws IOException If there is an error when writing the text. + */ + protected void writeString(String text, List textPositions) throws IOException + { + writeString(text); + } + /** * Write a Java string to the output stream. * @@ -905,7 +896,6 @@ protected void processTextPosition( TextPosition text ) sameTextCharacters = new TreeMap>(); 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. @@ -932,7 +922,6 @@ protected void processTextPosition( TextPosition text ) break; } } - if( !suppressCharacter ) { TreeSet ySet = sameTextCharacters.get(textX); @@ -945,7 +934,6 @@ protected void processTextPosition( TextPosition text ) showCharacter = true; } } - if( showCharacter ) { //if we are showing the character then we need to determine which @@ -1220,7 +1208,7 @@ protected Vector> getCharactersByArticle() public void setSuppressDuplicateOverlappingText( boolean suppressDuplicateOverlappingTextValue) { - this.suppressDuplicateOverlappingText = suppressDuplicateOverlappingTextValue; + suppressDuplicateOverlappingText = suppressDuplicateOverlappingTextValue; } /** @@ -1240,7 +1228,7 @@ public boolean getSeparateByBeads() */ public void setShouldSeparateByBeads(boolean aShouldSeparateByBeads) { - this.shouldSeparateByBeads = aShouldSeparateByBeads; + shouldSeparateByBeads = aShouldSeparateByBeads; } /** @@ -1353,7 +1341,7 @@ public float getSpacingTolerance() */ public void setSpacingTolerance(float spacingToleranceValue) { - this.spacingTolerance = spacingToleranceValue; + spacingTolerance = spacingToleranceValue; } /** @@ -1378,7 +1366,7 @@ public float getAverageCharTolerance() */ public void setAverageCharTolerance(float averageCharToleranceValue) { - this.averageCharTolerance = averageCharToleranceValue; + averageCharTolerance = averageCharToleranceValue; } @@ -1455,7 +1443,7 @@ public String getParagraphStart() */ public void setParagraphStart(String s) { - this.paragraphStart = s; + paragraphStart = s; } /** @@ -1473,7 +1461,7 @@ public String getParagraphEnd() */ public void setParagraphEnd(String s) { - this.paragraphEnd = s; + paragraphEnd = s; } @@ -1576,7 +1564,6 @@ public String inspectFontEncoding(String str) return str; } } - StringBuilder reversed = new StringBuilder(str.length()); for (int i = str.length() - 1; i >= 0; --i) { @@ -1784,8 +1771,7 @@ protected Pattern matchListItemPattern(PositionWrapper pw) { TextPosition tp = pw.getTextPosition(); String txt = tp.getCharacter(); - Pattern p = matchPattern(txt,getListItemPatterns()); - return p; + return matchPattern(txt,getListItemPatterns()); } /** @@ -1820,7 +1806,6 @@ protected void setListItemPatterns(List patterns) listOfPatterns = patterns; } - /** * returns a list of regular expression Patterns representing * different common list item formats. For example @@ -1887,12 +1872,13 @@ protected static final Pattern matchPattern(String string, List pattern * @param isRtlDominant determines if rtl or ltl is dominant * @throws IOException if something went wrong */ - private void writeLine(List line, boolean isRtlDominant)throws IOException + private void writeLine(List line, boolean isRtlDominant) throws IOException { int numberOfStrings = line.size(); for(int i=0; i line, boolean isRtlDominant)throws IOExcepti * @param hasRtl determines if lines contains rtl formatted text(parts) * @return a list of strings, one string for every word */ - private List normalize(List line, boolean isRtlDominant, boolean hasRtl) + private List normalize(List line, boolean isRtlDominant, boolean hasRtl) { - LinkedList normalized = new LinkedList(); + LinkedList normalized = new LinkedList(); StringBuilder lineBuilder = new StringBuilder(); + List wordPositions = new ArrayList(); // concatenate the pieces of text in opposite order if RTL is dominant if (isRtlDominant) { int numberOfPositions = line.size(); for(int i = numberOfPositions-1;i>=0;i--) { - TextPosition text = line.get(i); - if (text instanceof WordSeparator) - { - normalized.add(normalize.normalizePres(lineBuilder.toString())); - lineBuilder = new StringBuilder(); - } - else - { - lineBuilder.append(text.getCharacter()); - } - } - if (lineBuilder.length() > 0) - { - normalized.add(normalize.normalizePres(lineBuilder.toString())); + lineBuilder = normalizeAdd(normalized, lineBuilder, wordPositions, line.get(i)); } } else { for(TextPosition text : line) { - if (text instanceof WordSeparator) - { - normalized.add(normalize.normalizePres(lineBuilder.toString())); - lineBuilder = new StringBuilder(); - } - else - { - lineBuilder.append(text.getCharacter()); - } - } - if (lineBuilder.length() > 0) - { - normalized.add(normalize.normalizePres(lineBuilder.toString())); + lineBuilder = normalizeAdd(normalized, lineBuilder, wordPositions, text); } } + if (lineBuilder.length() > 0) + { + normalized.add(createWord(lineBuilder.toString(), wordPositions)); + } return normalized; } + /** + * Used within {@link #normalize(List, boolean, boolean)} to create a single {@link WordWithTextPositions} + * entry. + */ + private WordWithTextPositions createWord(String word, List wordPositions) + { + return new WordWithTextPositions(normalize.normalizePres(word), wordPositions); + } + + /** + * Used within {@link #normalize(List, boolean, boolean)} to handle a {@link TextPosition}. + * @return The StringBuilder that must be used when calling this method. + */ + private StringBuilder normalizeAdd(LinkedList normalized, + StringBuilder lineBuilder, List wordPositions, TextPosition text) + { + if (text instanceof WordSeparator) + { + normalized.add(createWord(lineBuilder.toString(), wordPositions)); + lineBuilder = new StringBuilder(); + wordPositions.clear(); + } + else + { + lineBuilder.append(text.getCharacter()); + wordPositions.add(text); + } + return lineBuilder; + } + /** * internal marker class. Used as a place holder in * a line of TextPositions. @@ -1973,7 +1969,34 @@ public static final WordSeparator getSeparator() { return separator; } - } + /** + * Internal class that maps strings to lists of {@link TextPosition} arrays. + * Note that the number of entries in that list may differ from the number of characters in the + * string due to normalization. + * + * @author Axel Dörfler + */ + private static final class WordWithTextPositions + { + protected String text; + protected List textPositions; + + public WordWithTextPositions(String word, List positions) + { + text = word; + textPositions = positions; + } + + public String getText() + { + return text; + } + + public List getTextPositions() + { + return textPositions; + } + } } From 0250f773e291e3590c4e5f485efaacdcd87f23e6 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 24 Nov 2013 14:34:00 +0000 Subject: [PATCH 0034/1017] PDFBOX-1782: added getter/setter for the MaxLen value of a textfield git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1544978 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/pdfbox/cos/COSName.java | 4 ++++ .../pdmodel/interactive/form/PDTextbox.java | 24 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java index c282aefb872..0fcae1f8c7c 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java @@ -915,6 +915,10 @@ public final class COSName extends COSBase implements Comparable * A common COSName value. */ public static final COSName MATRIX = new COSName( "Matrix" ); + /** + * A common COSName value. + */ + public static final COSName MAX_LEN = new COSName("MaxLen"); /** * A common COSName value. */ diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDTextbox.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDTextbox.java index eff83c28d53..20f74765c5f 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDTextbox.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PDTextbox.java @@ -17,12 +17,13 @@ package org.apache.pdfbox.pdmodel.interactive.form; import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; /** * A class for handling the PDF field as a textbox. * * @author sug - * @version $Revision: 1.9 $ + * */ public class PDTextbox extends PDVariableText { @@ -47,4 +48,25 @@ public PDTextbox( PDAcroForm theAcroForm, COSDictionary field) { super( theAcroForm, field); } + + /** + * Returns the maximum number of characters of the text field. + * + * @return the maximum number of characters, returns -1 if the value isn't present + */ + public int getMaxLen() + { + return getDictionary().getInt(COSName.MAX_LEN); + } + + /** + * Sets the maximum number of characters of the text field. + * + * @param maxLen the maximum number of characters + */ + public void setMaxLen(int maxLen) + { + getDictionary().setInt(COSName.MAX_LEN, maxLen); + } + } From d54ab5d264f186d0a096286c4292c9636fe0b252 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 24 Nov 2013 14:44:07 +0000 Subject: [PATCH 0035/1017] PDFBOX-1213: reestablish java5 conformance git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1544979 13f79535-47bb-0310-9956-ffa450edef68 --- pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java index 08caf174c34..4e8aeea52de 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java @@ -296,7 +296,7 @@ public String push(String text, List textPositions) push(buffer, text.charAt(i), textPositions.get(i)); } } - else if (!text.isEmpty()) + else if (text.length() > 0) { // The normalized text does not match the number of TextPositions, so we'll just // have a look at its first entry. From 2cfc7b7a44a6cab6065aea54904fb33117c1004e Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 24 Nov 2013 15:25:30 +0000 Subject: [PATCH 0036/1017] prepare release 1.8.3 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1544997 13f79535-47bb-0310-9956-ffa450edef68 --- pdfbox/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdfbox/build.xml b/pdfbox/build.xml index b41b160589a..13faf2863d1 100644 --- a/pdfbox/build.xml +++ b/pdfbox/build.xml @@ -28,7 +28,7 @@ - + From b716352b3cb5065896f3ba186e5f2f058c8b3e8c Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 24 Nov 2013 15:35:22 +0000 Subject: [PATCH 0037/1017] updated release notes for the 1.8.3 version git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1545000 13f79535-47bb-0310-9956-ffa450edef68 --- RELEASE-NOTES.txt | 93 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index d3fa54b5e2d..8e24a9123fc 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,11 +1,11 @@ -Release Notes -- Apache PDFBox -- Version 1.8.2 +Release Notes -- Apache PDFBox -- Version 1.8.3 Introduction ------------ The Apache PDFBox library is an open source Java tool for working with PDF documents. -This is an incremental bugfix release based on the earlier 1.8.1 release. It +This is an incremental bugfix release based on the earlier 1.8.2 release. It contains a couple of fixes and small improvements. For more details on all fixes included in this release, please refer to the following @@ -13,36 +13,77 @@ issues on the PDFBox issue tracker at https://issues.apache.org/jira/browse/PDFB Bug Fixes -[PDFBOX-1093] - Copy Page from one Document to another: Page Content Stream Linked - to Original Document -[PDFBOX-1561] - PDFBox throws exception with PDFTextStripper.getText -[PDFBOX-1580] - Oracle JVM crashes because of embedded fonts. -[PDFBOX-1583] - wasted work in PDDocument.addSignature(...) -[PDFBOX-1586] - IndexOutOfBoundsException when saving a document (at random) -[PDFBOX-1592] - Addition of 'null' in COSString when extracting form fields -[PDFBOX-1599] - ConvertToImage gets ClassCastException with Checkboxes in PDFForm -[PDFBOX-1601] - java.lang.IllegalArgumentException: Width (4032) and height (-2880) - cannot be <= 0 -[PDFBOX-1602] - Erroneous values for TextPosition get{X,Y}[DirAdj] -[PDFBOX-1603] - Regression in PDDocument.loadNonSeq ? -[PDFBOX-1605] - PDPropBuildDataDict: use COSName instead of COSString as name value -[PDFBOX-1609] - EXCEPTION_ACCESS_VIOLATION with PDF file and image conversion -[PDFBOX-1610] - Corrupted result pdf when overlay one document with another one -[PDFBOX-1611] - Avoid IndexOutOfBoundsException when extracting the font matrix of - a Type1 font -[PDFBOX-1612] - Avoid ArrayOutOfBoundsException when creating a PDPixelMap using an - indexed colorspace -[PDFBOX-1615] - Color map not correctly copied when PDF file is split +[PDFBOX-465] - invalid date formats +[PDFBOX-823] - NullPointerException in DateConverter.toISO8601(DateConverter.java:221) +[PDFBOX-837] - Wrong RevisionNumber when disabling all permissions and using 128bit + encryption +[PDFBOX-1219] - org.apache.jempbox.impl.DateConverter unable to parse correct date + value +[PDFBOX-1342] - Tags not fully preserved when merging PDFs. +[PDFBOX-1412] - NullPointerException when getting fields from a PDF file +[PDFBOX-1564] - Extending COSName to produce PDF/A with correct OutputIntents +[PDFBOX-1576] - StackOverflowError [COSDictionary.toString(COSDictionary.java:1418)] +[PDFBOX-1606] - NonSequentialPDFParser produces garbage text in document info +[PDFBOX-1607] - StringIndexOutOfBoundsException in PDFParser +[PDFBOX-1617] - Null pointer exception +[PDFBOX-1622] - TextNormalize init not thread-safe, may lead to infinite loop +[PDFBOX-1627] - Exception in thread "main" java.lang.NullPointerException +[PDFBOX-1629] - Null PointerException +[PDFBOX-1630] - An interesting Exception error +[PDFBOX-1631] - Group Exception +[PDFBOX-1632] - Exception with validation +[PDFBOX-1633] - DateConverter needs to work +[PDFBOX-1638] - PDCcitt doesn't use color space +[PDFBOX-1639] - Infinite loop with PDFParser used by tika. +[PDFBOX-1643] - Check for missing validation processes does not work properly in + Preflight +[PDFBOX-1653] - Fix pdfbox eating up big chunks of memory for identical CID mappings +[PDFBOX-1655] - Wasted work (or incorrect behavior) in + PDCIDFontType2Font.readCIDToGIDMapping +[PDFBOX-1657] - glyph contours missing +[PDFBOX-1663] - Hello World using a TrueType font ArrayIndexOutOfBoundsException +[PDFBOX-1674] - Preflight doesn't correctly parse PDF if obj identifier not followed by + line terminator +[PDFBOX-1681] - java.lang.IllegalArgumentException: Color parameter outside of expected + range: Red +[PDFBOX-1692] - java.lang.OutOfMemoryError: Java heap space +[PDFBOX-1694] - Bug in org.apache.pdfbox.io.Ascii85InputStream +[PDFBOX-1696] - Bug in org.apache.pdfbox.io.Ascii85OutputStream +[PDFBOX-1714] - Merging PDFs results in java.io.IOException: expected='R' actual='0' +[PDFBOX-1719] - NPE while signing PDF - acroform without fields +[PDFBOX-1730] - Image in PDF has extremely different colors when rendered +[PDFBOX-1735] - Convert page pdf to image +[PDFBOX-1737] - Skip whitespaces when resolving a XRef +[PDFBOX-1743] - OutOfMemoryError in fontbox +[PDFBOX-1749] - Out of memory exception when parsing TTF file +[PDFBOX-1753] - The font gets gibbrish when adding a line of text to an existing PDF with + a table +[PDFBOX-1758] - Preflight doesn't report Filespec dictionary that refers (indirectly) to an + EmbeddedFile entry in some cases +[PDFBOX-1764] - PDFBox takes ages to render page 2 of the attached PDF +[PDFBOX-1768] - cannot build last source code +[PDFBOX-1778] - Rounding issue in generated PDF file +[PDFBOX-1780] - previous revision is damaged after signing Improvement -[PDFBOX-1293] - PDFImageWriter should use logging instead of System.out.println -[PDFBOX-1581] - Add PDDocument.save(File) and PDDocument.loadNonSeq(InputStream, ...) +[PDFBOX-1213] - Adding style information to the PDF to HTML converter +[PDFBOX-1564] - Extending COSName to produce PDF/A with correct OutputIntents +[PDFBOX-1613] - The ability to inject the time/random component into the COSWriter process + to write a PDF document allows some advanced signature creation scenarios + where the signature is generated on a separate server that does not hold the + full PDF document. +[PDFBOX-1687] - add dispose() in pdfbox\pdmodel\PDPage.convertToImage() +[PDFBOX-1690] - Add description to embedded file +[PDFBOX-1702] - Performance improvement in PDPageContentStream.drawString +[PDFBOX-1744] - Be resilient to PDFs with missing version info +[PDFBOX-1782] - Add getMaxLength() and setMaxLength() methods to PDTextbox New Feature -[PDFBOX-1565] - Add support for Lab color spaces - +[PDFBOX-1054] - DateConverter: allow for external adding of potential date parsing formats +[PDFBOX-1766] - [PATCH] Visible Signature using PDFbox +[PDFBOX-1540] - Add XML output option to preflight Release Contents ---------------- From 7d8a01c5c40675c37e5ab4ccd10dfae750b73dc7 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 25 Nov 2013 17:43:31 +0000 Subject: [PATCH 0038/1017] PDFBOX-1663: changed data type to avoid broken API git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1545349 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/pdfbox/pdmodel/font/PDFont.java | 18 +++++------------- .../pdfbox/pdmodel/font/PDTrueTypeFont.java | 8 ++++---- .../pdfbox/pdmodel/font/PDType1AfmPfbFont.java | 8 ++++---- .../preflight/font/Type3FontValidator.java | 2 +- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java index ac1d5a4dafc..fbc5e35abc9 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java @@ -49,15 +49,6 @@ 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. * @@ -106,7 +97,7 @@ public abstract class PDFont implements COSObjectable /** * A list a floats representing the widths. */ - private List widths = null; + private List widths = null; /** * The static map of the default Adobe font metrics. @@ -790,15 +781,16 @@ public void setLastChar( int lastChar ) * The widths of the characters. This will be null for the standard 14 fonts. * * @return The widths of the characters. + * */ - public List getWidths() + public List getWidths() { if (widths == null) { COSArray array = (COSArray)font.getDictionaryObject( COSName.WIDTHS ); if (array != null) { - widths = COSArrayList.convertIntegerCOSArrayToList(array); + widths = COSArrayList.convertFloatCOSArrayToList(array); } } return widths; @@ -809,7 +801,7 @@ public List getWidths() * * @param widthsList The widths of the character codes. */ - public void setWidths(List widthsList) + public void setWidths(List widthsList) { widths = widthsList; font.setItem( COSName.WIDTHS, COSArrayList.converterToCOSArray( widths ) ); diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java index e01fda437eb..4bd09d2d039 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDTrueTypeFont.java @@ -383,14 +383,14 @@ else if( nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME ) // instead of an array containing the same value for every glyphid boolean isMonospaced = fd.isFixedPitch(); int nWidths=lastChar-firstChar+1; - List widths = new ArrayList(nWidths); + List widths = new ArrayList(nWidths); // use the first width as default // proportional fonts -> width of the .notdef character // monospaced-fonts -> the first width int defaultWidth = Math.round(widthValues[0] * scaling); for (int i = 0; i < nWidths; i++) { - widths.add(defaultWidth); + widths.add((float)defaultWidth); } // Encoding singleton to have acces to the chglyph name to // unicode cpoint point mapping of Adobe's glyphlist.txt @@ -412,11 +412,11 @@ else if( nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME ) { if (isMonospaced) { - widths.set(e.getKey().intValue() - firstChar, defaultWidth); + widths.set(e.getKey().intValue() - firstChar, (float)defaultWidth); } else { - widths.set(e.getKey().intValue() - firstChar, Math.round(widthValues[gid] * scaling)); + widths.set(e.getKey().intValue() - firstChar, (float)Math.round(widthValues[gid] * scaling)); } } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java index 81e8c2ce2b8..60ca94f174c 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1AfmPfbFont.java @@ -143,12 +143,12 @@ private void load(final PDDocument doc, final InputStream afm, final InputStream List listmetric = metric.getCharMetrics(); Encoding encoding = getFontEncoding(); int maxWidths = 256; - List widths = new ArrayList(maxWidths); + List widths = new ArrayList(maxWidths); int zero = 250; Iterator iter = listmetric.iterator(); for (int i = 0; i < maxWidths; i++) { - widths.add(zero); + widths.add((float)zero); } while (iter.hasNext()) { @@ -161,13 +161,13 @@ private void load(final PDDocument doc, final InputStream afm, final InputStream if (m.getWx() > 0) { int width = Math.round(m.getWx()); - widths.set(n, width); + widths.set(n, (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, width); + widths.set(0337, (float)width); } } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/font/Type3FontValidator.java b/preflight/src/main/java/org/apache/pdfbox/preflight/font/Type3FontValidator.java index a231046274f..e3b1355cf23 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/font/Type3FontValidator.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/font/Type3FontValidator.java @@ -276,7 +276,7 @@ private void checkEncodingAsDictionary(COSBase fontEncoding) */ private void checkCharProcsAndMetrics() throws ValidationException { - List widths = font.getWidths(); + List widths = font.getWidths(); if (widths == null || widths.isEmpty()) { this.fontContainer.push(new ValidationError(ERROR_FONTS_DICTIONARY_INVALID, From 9af94514fcf83cfaea862fe0394727fb0aa89c16 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 25 Nov 2013 17:58:15 +0000 Subject: [PATCH 0039/1017] [maven-release-plugin] prepare release 1.8.3 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1545352 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index 86935e437ff..775db3edcc0 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index c630251ba22..b9660f92693 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 210bd882fd0..9c2bceedbfb 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 641a889682a..7cb39e98763 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index 8102e935408..cf02ab59885 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 4ab2cb619de..fdbff6e406f 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 50281fb6acc..388d1a0ed61 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.3/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.3/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/tags/1.8.3/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index aeb04c1543a..de296b2ffe1 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml diff --git a/pom.xml b/pom.xml index bdc3386e989..8bc039433ec 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.3 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.3 - http://svn.apache.org/viewvc/pdfbox/branches/1.8 + http://svn.apache.org/viewvc/pdfbox/tags/1.8.3 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 9f0f1b06bfa..183b48b6c21 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index a19b1b0f3c9..ee2219f1dd8 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index 8b4c00899d7..9f2ae35ebe3 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index c636d1a7541..83972932082 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3-SNAPSHOT + 1.8.3 ../parent/pom.xml From 80eb7762fc5df153be9f30d3739d2ccc82240202 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 25 Nov 2013 17:58:26 +0000 Subject: [PATCH 0040/1017] [maven-release-plugin] prepare for next development iteration git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1545354 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index 775db3edcc0..8c7e001cb70 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index b9660f92693..f5c703be89b 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 9c2bceedbfb..e044c99d2e3 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 7cb39e98763..845bbcc0bbf 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index cf02ab59885..9d3504ea3dd 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index fdbff6e406f..59a305981a8 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 388d1a0ed61..8e61b5280c3 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.3/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.3/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/tags/1.8.3/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index de296b2ffe1..a5c5d39d2bf 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 8bc039433ec..1364ae63054 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.3 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.3 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 - http://svn.apache.org/viewvc/pdfbox/tags/1.8.3 + http://svn.apache.org/viewvc/pdfbox/branches/1.8 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 183b48b6c21..2e654bf6ca8 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index ee2219f1dd8..2c25caa46eb 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index 9f2ae35ebe3..c861c62085d 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 83972932082..27afb68a66c 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.3 + 1.8.4-SNAPSHOT ../parent/pom.xml From 62af69d25047d7be819c0bbaea948fb3497c45bf Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Tue, 3 Dec 2013 19:29:41 +0000 Subject: [PATCH 0041/1017] PDFBOX-1789: avoid NPE after reseting the content stream git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1547541 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdmodel/edit/PDPageContentStream.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java index 1f2e6f045f1..2129e52d1ad 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/edit/PDPageContentStream.java @@ -66,7 +66,6 @@ public class PDPageContentStream */ private static final Log LOG = LogFactory.getLog(PDPageContentStream.class); - private PDPage page; private OutputStream output; private boolean inTextMode = false; private PDResources resources; @@ -186,15 +185,6 @@ public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appen 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); - } - // Get the pdstream from the source page instead of creating a new one PDStream contents = sourcePage.getContents(); boolean hasContent = contents != null; @@ -276,6 +266,14 @@ public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appen } formatDecimal.setMaximumFractionDigits(10); formatDecimal.setGroupingUsed(false); + // this has to be done here, as the resources will be set to null when reseting the content stream + resources = sourcePage.getResources(); + if (resources == null) + { + resources = new PDResources(); + sourcePage.setResources(resources); + } + } /** @@ -1513,7 +1511,6 @@ public void close() throws IOException output.close(); currentNonStrokingColorSpace = null; currentStrokingColorSpace = null; - page = null; resources = null; } } From 6fdff9d26236fa58cea8dafaaa9728390633b3d4 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 8 Dec 2013 13:57:07 +0000 Subject: [PATCH 0042/1017] PDFBOX-1769: fix invalid xref offsets automatically, identify corrupt stream length and use workaround if necessary git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1549029 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdfparser/NonSequentialPDFParser.java | 287 +++++++++++++++--- 1 file changed, 252 insertions(+), 35 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java index 3e270323345..a29646da57a 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java @@ -25,12 +25,14 @@ import java.io.OutputStream; import java.security.KeyStore; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.Set; @@ -82,8 +84,11 @@ public class NonSequentialPDFParser extends PDFParser { - private static final int E = 'e'; + private static final byte[] XREF = new byte[] { 'x', 'r', 'e', 'f' }; + + private static final int E = 'e'; private static final int N = 'n'; + private static final int X = 'x'; public static final String SYSPROP_PARSEMINIMAL = "org.apache.pdfbox.pdfparser.nonSequentialPDFParser.parseMinimal"; public static final String SYSPROP_EOFLOOKUPRANGE = "org.apache.pdfbox.pdfparser.nonSequentialPDFParser.eofLookupRange"; @@ -105,6 +110,7 @@ public class NonSequentialPDFParser extends PDFParser protected static final char[] OBJ_MARKER = new char[] { 'o', 'b', 'j' }; private final File pdfFile; + private long fileLen; private final RandomAccessBufferedFileInputStream raStream; /** @@ -319,15 +325,15 @@ public void setEOFLookupRange(int byteCount) */ protected void initialParse() throws IOException { - final long startxrefOff = getStartxrefOffset(); - // ---- parse startxref - setPdfSource(startxrefOff); + setPdfSource(getStartxrefOffset()); parseStartXref(); - final long xrefOffset = document.getStartXref(); - long prev = xrefOffset; - + long startXrefOffset = document.getStartXref(); + // check the startxref offset + startXrefOffset -= calculateFixingOffset(startXrefOffset); + document.setStartXref(startXrefOffset); + long prev = startXrefOffset; // ---- parse whole chain of xref tables/object streams using PREV // reference while (prev > -1) @@ -338,12 +344,11 @@ protected void initialParse() throws IOException // skip white spaces skipSpaces(); // -- parse xref - if (pdfSource.peek() == 'x') + if (pdfSource.peek() == X) { // xref table and trailer // use existing parser to parse xref table parseXrefTable(prev); - // parse the last trailer. if (!parseTrailer()) { @@ -351,19 +356,43 @@ protected void initialParse() throws IOException } COSDictionary trailer = xrefTrailerResolver.getCurrentTrailer(); prev = trailer.getInt(COSName.PREV); + if (prev > -1) + { + // check the xref table reference + long fixingOffset = calculateFixingOffset(prev); + if (fixingOffset != 0) + { + prev -= fixingOffset; + trailer.setLong(COSName.PREV, prev); + } + } } else { - // xref stream + // parse xref stream prev = parseXrefObjStream(prev); + if (prev > -1) + { + // check the xref table reference + long fixingOffset = calculateFixingOffset(prev); + if (fixingOffset != 0) + { + prev -= fixingOffset; + COSDictionary trailer = xrefTrailerResolver.getCurrentTrailer(); + trailer.setLong(COSName.PREV, prev); + } + } } } // ---- build valid xrefs out of the xref chain - xrefTrailerResolver.setStartxref(xrefOffset); + xrefTrailerResolver.setStartxref(startXrefOffset); COSDictionary trailer = xrefTrailerResolver.getTrailer(); document.setTrailer(trailer); + // check the offsets of all referenced objects + checkXrefOffsets(); + // ---- prepare encryption if necessary COSBase trailerEncryptItem = document.getTrailer().getItem(COSName.ENCRYPT); if (trailerEncryptItem != null) @@ -530,7 +559,7 @@ protected final long getStartxrefOffset() throws IOException long skipBytes; // ---- read trailing bytes into buffer - final long fileLen = pdfFile.length(); + fileLen = pdfFile.length(); FileInputStream fIn = null; try @@ -1490,36 +1519,34 @@ else if (whitespace != 0x0A) throw new IOException("Missing length for stream."); } + boolean useReadUntilEnd = false; // ---- get output stream to copy data to out = stream.createFilteredStream(streamLengthObj); - - long remainBytes = streamLengthObj.longValue(); - int bytesRead = 0; - boolean unexpectedEndOfStream = false; - if (remainBytes == 35090) + if (validateStreamLength(streamLengthObj.longValue())) { - // TODO debug system out, to be removed?? - System.out.println(); + long remainBytes = streamLengthObj.longValue(); + int bytesRead = 0; + while (remainBytes > 0) + { + final int readBytes = pdfSource.read(streamCopyBuf, 0, + (remainBytes > streamCopyBufLen) ? streamCopyBufLen : (int) remainBytes); + if (readBytes <= 0) + { + useReadUntilEnd = true; + pdfSource.unread(bytesRead); + break; + } + out.write(streamCopyBuf, 0, readBytes); + remainBytes -= readBytes; + bytesRead += readBytes; + } } - while (remainBytes > 0) + else { - final int readBytes = pdfSource.read(streamCopyBuf, 0, - (remainBytes > streamCopyBufLen) ? streamCopyBufLen : (int) remainBytes); - if (readBytes <= 0) - { - // throw new IOException( - // "No more bytes from stream but expected: " + remainBytes - // ); - unexpectedEndOfStream = true; - break; - } - out.write(streamCopyBuf, 0, readBytes); - remainBytes -= readBytes; - bytesRead += readBytes; + useReadUntilEnd = true; } - if (unexpectedEndOfStream) + if (useReadUntilEnd) { - pdfSource.unread(bytesRead); out = stream.createFilteredStream(streamLengthObj); readUntilEndStream(out); } @@ -1540,6 +1567,29 @@ else if (whitespace != 0x0A) return stream; } + private boolean validateStreamLength(long streamLength) throws IOException + { + boolean streamLengthIsValid = true; + long originOffset = pdfSource.getOffset(); + long expectedEndOfStream = originOffset + streamLength; + if (expectedEndOfStream > fileLen) + { + streamLengthIsValid = false; + LOG.error("The end of the stream is out of range, using workaround to read the stream"); + } + else + { + pdfSource.seek(expectedEndOfStream); + skipSpaces(); + if (!checkBytesAtOffset("endstream".getBytes("ISO-8859-1"))) + { + streamLengthIsValid = false; + LOG.error("The end of the stream doesn't point to the correct offset, using workaround to read the stream"); + } + pdfSource.seek(originOffset); + } + return streamLengthIsValid; + } private void readUntilEndStream(final OutputStream out) throws IOException { @@ -1648,5 +1698,172 @@ private void readUntilEndStream(final OutputStream out) throws IOException } // while } + + /** + * + * @param startXRefOffset + * @return + * @throws IOException + */ + private long calculateFixingOffset(long startXRefOffset) throws IOException + { + // TODO check offset for XRef stream objects + setPdfSource(startXRefOffset); + if (pdfSource.peek() == X && calculateFixingOffset(startXRefOffset, XREF) == 0) + { + return 0; + } + long fixingOffset = calculateFixingOffset(startXRefOffset, XREF); + return fixingOffset; + } + + /** + * Try to dereference the given object at the given offset and calculate a new + * offset if necessary. + * + * @param objectOffset the offset where to look at + * @param objectID the object ID + * @param genID the generation number + * @return the difference to the origin offset + * @throws IOException if something went wrong + */ + private long calculateFixingOffset(long objectOffset, long objectID, long genID) throws IOException + { + String objString = Long.toString(objectID) + " " + Long.toString(genID)+ " obj"; + return calculateFixingOffset(objectOffset, objString.getBytes("ISO-8859-1")); + } + + /** + * Check if the given bytes can be found at the current offset. + * + * @param string the bytes to look for + * @return true if the bytes are in place, false if not + * @throws IOException if something went wrong + */ + private boolean checkBytesAtOffset(byte[] string) throws IOException + { + byte[] bytesRead = new byte[string.length]; + boolean bytesMatching = false; + if (pdfSource.peek() == string[0]) + { + pdfSource.read(bytesRead, 0, string.length); + if (Arrays.equals(string, bytesRead)) + { + bytesMatching = true; + } + pdfSource.unread(bytesRead); + } + return bytesMatching; + } + + /** + * Check if the given bytes can be found at the given offset. + * The method seeks 200 bytes backward/forward if the given string + * can't be found at the given offset and returns the difference + * of the new offset to the origin one. + * + * @param objectOffset the given offset where to look at + * @param string the bytes to look for + * @return the difference to the origin one + * @throws IOException if something went wrong + */ + private long calculateFixingOffset(long objectOffset, byte[] string) throws IOException + { + if (objectOffset < 0) + { + LOG.error("Invalid object offset " + objectOffset + " for object " + new String(string)); + return 0; + } + long originOffset = pdfSource.getOffset(); + pdfSource.seek(objectOffset); + // most likely the object can be found at the given offset + if (checkBytesAtOffset(string)) + { + pdfSource.seek(originOffset); + return 0; + } + // the offset seems to be wrong -> seek backward to find the object we are looking for + long currentOffset = objectOffset; + for (int i=1; i<20;i++) + { + currentOffset = objectOffset - (i*10); + if (currentOffset > 0) + { + pdfSource.seek(currentOffset); + for (int j=0; j<10;j++) + { + if (checkBytesAtOffset(string)) + { + pdfSource.seek(originOffset); + LOG.debug("Fixed reference for object "+new String(string)+" "+objectOffset + " -> "+(objectOffset - currentOffset)); + return objectOffset - currentOffset; + } + else + { + currentOffset++; + pdfSource.read(); + } + } + } + } + // no luck by seeking backward -> seek forward to find the object we are looking for + pdfSource.seek(objectOffset); + currentOffset = objectOffset; + do + { + if (checkBytesAtOffset(string)) + { + pdfSource.seek(originOffset); + if (currentOffset != 0) + { + LOG.debug("Fixed reference for object "+new String(string)+" "+objectOffset + " -> "+(objectOffset - currentOffset)); + } + return objectOffset - currentOffset; + } + else + { + // next byte + currentOffset++; + if (pdfSource.read() == -1) + { + throw new IOException("Premature end of file while dereferencing object "+ new String(string) + " at offset " + objectOffset); + } + } + } + while(currentOffset < objectOffset+200); + pdfSource.seek(originOffset); + LOG.error("Can't find the object " + new String(string) + " at offset " + objectOffset); + return 0; + } + + /** + * Check the XRef table by dereferencing all objects and fixing + * the offset if necessary. + * + * @throws IOException if something went wrong. + */ + private void checkXrefOffsets() throws IOException + { + MapxrefOffset = xrefTrailerResolver.getXrefTable(); + if (xrefOffset != null) + { + for (COSObjectKey objectKey : xrefOffset.keySet()) + { + Long objectOffset = xrefOffset.get(objectKey); + if (objectOffset != null) + { + long objectNr = objectKey.getNumber(); + long objectGen = objectKey.getGeneration(); + long fixingOffset = calculateFixingOffset(objectOffset, objectNr, objectGen); + if (fixingOffset != 0) + { + long newOffset = objectOffset - fixingOffset; + xrefOffset.put(objectKey, newOffset); + LOG.debug("Fixed reference for object "+objectNr+" "+objectGen+" "+objectOffset + " -> "+newOffset); + } + } + } + } + } } From 27b5a2c77c0c06fcf0a54a369594e838142fdafe Mon Sep 17 00:00:00 2001 From: Thomas Chojecki Date: Mon, 9 Dec 2013 22:36:03 +0000 Subject: [PATCH 0043/1017] PDFBOX-1792: Some parser tests. Ignored at the moment, because we have no fix yet. git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1549697 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdmodel/TestPDDocumentInformation.java | 79 +++++++++++++------ 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocumentInformation.java b/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocumentInformation.java index 6aadf05391a..d7135a200dd 100644 --- a/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocumentInformation.java +++ b/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocumentInformation.java @@ -14,56 +14,87 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.pdfbox.pdmodel; +import java.io.File; import java.util.Arrays; import java.util.List; import junit.framework.TestCase; +import org.apache.pdfbox.io.RandomAccessBuffer; + /** * This class tests the extraction of document-level metadata. * @author Neil McErlean * @since 1.3.0 */ -public class TestPDDocumentInformation extends TestCase { +public class TestPDDocumentInformation extends TestCase +{ + + // skipping this test, we have no fix yet. + public void XXXtestMetadataSequentialNonSequentialEquality() throws Exception + { + File dir = new File("src/test/resources/input"); + for (File f : dir.listFiles()) { + if (f.getName().toLowerCase().endsWith(".pdf")) { + testSingleFileEquality(f); + } + } + } + + private void testSingleFileEquality(File f) throws Exception + { + PDDocument seqDoc = PDDocument.load(f); + PDDocument nonSeqDoc = PDDocument.loadNonSeq(f, new RandomAccessBuffer()); + PDDocumentInformation seqInfo = seqDoc.getDocumentInformation(); + PDDocumentInformation nonSeqInfo = nonSeqDoc.getDocumentInformation(); + assertEquals("Metadata item count", seqInfo.getMetadataKeys().size(), nonSeqInfo.getMetadataKeys().size()); + for (String name : seqInfo.getMetadataKeys()) { + assertEquals(f.getName() + " :: " + name, seqInfo.getCustomMetadataValue(name), + nonSeqInfo.getCustomMetadataValue(name)); + } + seqDoc.close(); + nonSeqDoc.close(); + } public void testMetadataExtraction() throws Exception { - PDDocument doc = null; + PDDocument doc = null; try { - // This document has been selected for this test as it contains custom metadata. - doc = PDDocument.load( "src/test/resources/input/hello3.pdf"); - PDDocumentInformation info = doc.getDocumentInformation(); - - assertEquals("Wrong author", "Brian Carrier", info.getAuthor()); - assertNotNull("Wrong creationDate", info.getCreationDate()); - assertEquals("Wrong creator", "Acrobat PDFMaker 8.1 for Word", info.getCreator()); - assertNull ("Wrong keywords", info.getKeywords()); - assertNotNull("Wrong modificationDate", info.getModificationDate()); - assertEquals("Wrong producer", "Acrobat Distiller 8.1.0 (Windows)", info.getProducer()); - assertNull ("Wrong subject", info.getSubject()); - assertNull ("Wrong trapped", info.getTrapped()); + // This document has been selected for this test as it contains custom metadata. + doc = PDDocument.load("src/test/resources/input/hello3.pdf"); + PDDocumentInformation info = doc.getDocumentInformation(); + + assertEquals("Wrong author", "Brian Carrier", info.getAuthor()); + assertNotNull("Wrong creationDate", info.getCreationDate()); + assertEquals("Wrong creator", "Acrobat PDFMaker 8.1 for Word", info.getCreator()); + assertNull("Wrong keywords", info.getKeywords()); + assertNotNull("Wrong modificationDate", info.getModificationDate()); + assertEquals("Wrong producer", "Acrobat Distiller 8.1.0 (Windows)", info.getProducer()); + assertNull("Wrong subject", info.getSubject()); + assertNull("Wrong trapped", info.getTrapped()); List expectedMetadataKeys = Arrays.asList(new String[] {"CreationDate", "Author", "Creator", "Producer", "ModDate", "Company", "SourceModified", "Title"}); assertEquals("Wrong metadata key count", expectedMetadataKeys.size(), info.getMetadataKeys().size()); - for (String key : expectedMetadataKeys) { - assertTrue("Missing metadata key:" + key, info.getMetadataKeys().contains(key)); - } - - // Custom metadata fields. - assertEquals("Wrong company", "Basis Technology Corp.", info.getCustomMetadataValue("Company")); - assertEquals("Wrong sourceModified", "D:20080819181502", info.getCustomMetadataValue("SourceModified")); + for (String key : expectedMetadataKeys) { + assertTrue("Missing metadata key:" + key, info.getMetadataKeys().contains(key)); + } + + // Custom metadata fields. + assertEquals("Wrong company", "Basis Technology Corp.", info.getCustomMetadataValue("Company")); + assertEquals("Wrong sourceModified", "D:20080819181502", info.getCustomMetadataValue("SourceModified")); } finally { if( doc != null ) { - doc.close(); - } - } + doc.close(); + } } + } } From 78bce2a781993ce23ffb849be32190828cd0ae4b Mon Sep 17 00:00:00 2001 From: Thomas Chojecki Date: Wed, 11 Dec 2013 20:43:20 +0000 Subject: [PATCH 0044/1017] PDFBOX-1792: Revert last commit git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1550253 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdmodel/TestPDDocumentInformation.java | 79 ++++++------------- 1 file changed, 24 insertions(+), 55 deletions(-) diff --git a/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocumentInformation.java b/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocumentInformation.java index d7135a200dd..6aadf05391a 100644 --- a/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocumentInformation.java +++ b/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/TestPDDocumentInformation.java @@ -14,87 +14,56 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.pdfbox.pdmodel; -import java.io.File; import java.util.Arrays; import java.util.List; import junit.framework.TestCase; -import org.apache.pdfbox.io.RandomAccessBuffer; - /** * This class tests the extraction of document-level metadata. * @author Neil McErlean * @since 1.3.0 */ -public class TestPDDocumentInformation extends TestCase -{ - - // skipping this test, we have no fix yet. - public void XXXtestMetadataSequentialNonSequentialEquality() throws Exception - { - File dir = new File("src/test/resources/input"); - for (File f : dir.listFiles()) { - if (f.getName().toLowerCase().endsWith(".pdf")) { - testSingleFileEquality(f); - } - } - } - - private void testSingleFileEquality(File f) throws Exception - { - PDDocument seqDoc = PDDocument.load(f); - PDDocument nonSeqDoc = PDDocument.loadNonSeq(f, new RandomAccessBuffer()); - PDDocumentInformation seqInfo = seqDoc.getDocumentInformation(); - PDDocumentInformation nonSeqInfo = nonSeqDoc.getDocumentInformation(); - assertEquals("Metadata item count", seqInfo.getMetadataKeys().size(), nonSeqInfo.getMetadataKeys().size()); - for (String name : seqInfo.getMetadataKeys()) { - assertEquals(f.getName() + " :: " + name, seqInfo.getCustomMetadataValue(name), - nonSeqInfo.getCustomMetadataValue(name)); - } - seqDoc.close(); - nonSeqDoc.close(); - } +public class TestPDDocumentInformation extends TestCase { public void testMetadataExtraction() throws Exception { - PDDocument doc = null; + PDDocument doc = null; try { - // This document has been selected for this test as it contains custom metadata. - doc = PDDocument.load("src/test/resources/input/hello3.pdf"); - PDDocumentInformation info = doc.getDocumentInformation(); - - assertEquals("Wrong author", "Brian Carrier", info.getAuthor()); - assertNotNull("Wrong creationDate", info.getCreationDate()); - assertEquals("Wrong creator", "Acrobat PDFMaker 8.1 for Word", info.getCreator()); - assertNull("Wrong keywords", info.getKeywords()); - assertNotNull("Wrong modificationDate", info.getModificationDate()); - assertEquals("Wrong producer", "Acrobat Distiller 8.1.0 (Windows)", info.getProducer()); - assertNull("Wrong subject", info.getSubject()); - assertNull("Wrong trapped", info.getTrapped()); + // This document has been selected for this test as it contains custom metadata. + doc = PDDocument.load( "src/test/resources/input/hello3.pdf"); + PDDocumentInformation info = doc.getDocumentInformation(); + + assertEquals("Wrong author", "Brian Carrier", info.getAuthor()); + assertNotNull("Wrong creationDate", info.getCreationDate()); + assertEquals("Wrong creator", "Acrobat PDFMaker 8.1 for Word", info.getCreator()); + assertNull ("Wrong keywords", info.getKeywords()); + assertNotNull("Wrong modificationDate", info.getModificationDate()); + assertEquals("Wrong producer", "Acrobat Distiller 8.1.0 (Windows)", info.getProducer()); + assertNull ("Wrong subject", info.getSubject()); + assertNull ("Wrong trapped", info.getTrapped()); List expectedMetadataKeys = Arrays.asList(new String[] {"CreationDate", "Author", "Creator", "Producer", "ModDate", "Company", "SourceModified", "Title"}); assertEquals("Wrong metadata key count", expectedMetadataKeys.size(), info.getMetadataKeys().size()); - for (String key : expectedMetadataKeys) { - assertTrue("Missing metadata key:" + key, info.getMetadataKeys().contains(key)); - } - - // Custom metadata fields. - assertEquals("Wrong company", "Basis Technology Corp.", info.getCustomMetadataValue("Company")); - assertEquals("Wrong sourceModified", "D:20080819181502", info.getCustomMetadataValue("SourceModified")); + for (String key : expectedMetadataKeys) { + assertTrue("Missing metadata key:" + key, info.getMetadataKeys().contains(key)); + } + + // Custom metadata fields. + assertEquals("Wrong company", "Basis Technology Corp.", info.getCustomMetadataValue("Company")); + assertEquals("Wrong sourceModified", "D:20080819181502", info.getCustomMetadataValue("SourceModified")); } finally { if( doc != null ) { - doc.close(); - } + doc.close(); + } + } } - } } From 5101416629749d15450607e4030801cd2cb4a66d Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Tue, 17 Dec 2013 11:19:14 +0000 Subject: [PATCH 0045/1017] PDFBOX-1769: avoid false error messages when checking xref tables, calculate the correct number of bytes to be unread git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1551514 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdfparser/NonSequentialPDFParser.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java index a29646da57a..a2bed154123 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java @@ -1521,9 +1521,9 @@ else if (whitespace != 0x0A) boolean useReadUntilEnd = false; // ---- get output stream to copy data to - out = stream.createFilteredStream(streamLengthObj); if (validateStreamLength(streamLengthObj.longValue())) { + out = stream.createFilteredStream(streamLengthObj); long remainBytes = streamLengthObj.longValue(); int bytesRead = 0; while (remainBytes > 0) @@ -1533,6 +1533,7 @@ else if (whitespace != 0x0A) if (readBytes <= 0) { useReadUntilEnd = true; + out.close(); pdfSource.unread(bytesRead); break; } @@ -1604,8 +1605,8 @@ private void readUntilEndStream(final OutputStream out) throws IOException // beginning of buffer while ((bufSize = pdfSource.read(streamCopyBuf, charMatchCount, streamCopyBufLen - charMatchCount)) > 0) { - bufSize += charMatchCount; - + // number of already matching chars + int startingMatchCount = charMatchCount; int bIdx = charMatchCount; int quickTestIdx; @@ -1684,9 +1685,9 @@ private void readUntilEndStream(final OutputStream out) throws IOException } if (charMatchCount == keyw.length) { - // keyword matched; unread matched keyword (endstream/endobj) - // and following buffered content - pdfSource.unread(streamCopyBuf, contentBytes, bufSize - contentBytes); + // keyword matched; + // unread matched keyword (endstream/endobj) and following buffered content + pdfSource.unread(streamCopyBuf, contentBytes, bufSize - contentBytes - keyw.length + startingMatchCount); break; } @@ -1850,7 +1851,9 @@ private void checkXrefOffsets() throws IOException for (COSObjectKey objectKey : xrefOffset.keySet()) { Long objectOffset = xrefOffset.get(objectKey); - if (objectOffset != null) + // a negative offset number represents a object number itself + // see type 2 entry in xref stream + if (objectOffset != null && objectOffset > 0) { long objectNr = objectKey.getNumber(); long objectGen = objectKey.getGeneration(); From 0835df9d6fb7290e1bdf92ca12242f121d216423 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Fri, 20 Dec 2013 08:06:12 +0000 Subject: [PATCH 0046/1017] PDFBOX-1769: loop to read all needed bytes git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1552522 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdfbox/pdfparser/NonSequentialPDFParser.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java index a2bed154123..1604eab64a6 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java @@ -1743,16 +1743,26 @@ private long calculateFixingOffset(long objectOffset, long objectID, long genID) */ private boolean checkBytesAtOffset(byte[] string) throws IOException { - byte[] bytesRead = new byte[string.length]; boolean bytesMatching = false; if (pdfSource.peek() == string[0]) { - pdfSource.read(bytesRead, 0, string.length); + int length = string.length; + byte[] bytesRead = new byte[length]; + int numberOfBytes = pdfSource.read(bytesRead, 0, length); + while (numberOfBytes < length) + { + int readMore = pdfSource.read(bytesRead, numberOfBytes, length-numberOfBytes); + if (readMore < 0) + { + break; + } + numberOfBytes += readMore; + } if (Arrays.equals(string, bytesRead)) { bytesMatching = true; } - pdfSource.unread(bytesRead); + pdfSource.unread(bytesRead, 0, numberOfBytes); } return bytesMatching; } From 0ae991d7654142bd4472dd06b44e50a3b667eb9d Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Fri, 20 Dec 2013 08:35:20 +0000 Subject: [PATCH 0047/1017] PDFBOX-1815: close streams to release resources as proposed by Tilman Hausherr git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1552535 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/pdfbox/cos/COSStream.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java index c4b6055b433..517a7bb33c4 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java @@ -22,16 +22,13 @@ 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.IOUtils; import org.apache.pdfbox.io.RandomAccess; import org.apache.pdfbox.io.RandomAccessFileInputStream; import org.apache.pdfbox.io.RandomAccessFileOutputStream; @@ -40,7 +37,7 @@ * This class represents a stream object in a PDF document. * * @author Ben Litchfield - * @version $Revision: 1.41 $ + * */ public class COSStream extends COSDictionary { @@ -263,7 +260,6 @@ 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; @@ -287,6 +283,7 @@ private void doDecode( COSName filterName, int filterIndex ) throws IOException //try again with one less byte. for( int tryCount=0; !done && tryCount<5; tryCount++ ) { + InputStream input = null; try { input = new BufferedInputStream( @@ -300,6 +297,10 @@ private void doDecode( COSName filterName, int filterIndex ) throws IOException length--; exception = io; } + finally + { + IOUtils.closeQuietly(input); + } } if( !done ) { @@ -309,6 +310,7 @@ private void doDecode( COSName filterName, int filterIndex ) throws IOException length = writtenLength; for( int tryCount=0; !done && tryCount<5; tryCount++ ) { + InputStream input = null; try { input = new BufferedInputStream( @@ -322,6 +324,10 @@ private void doDecode( COSName filterName, int filterIndex ) throws IOException length--; exception = io; } + finally + { + IOUtils.closeQuietly(input); + } } } } @@ -373,13 +379,13 @@ private void doEncode( COSName filterName, int filterIndex ) throws IOException { FilterManager manager = getFilterManager(); Filter filter = manager.getFilter( filterName ); - InputStream input; - input = new BufferedInputStream( + InputStream input = new BufferedInputStream( new RandomAccessFileInputStream( file, filteredStream.getPosition(), filteredStream.getLength() ), BUFFER_SIZE ); filteredStream = new RandomAccessFileOutputStream( file ); filter.encode( input, filteredStream, this, filterIndex ); + IOUtils.closeQuietly(input); } /** From 9991182bf8a5d41439700fa7eff984c90d0c2aef Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Fri, 20 Dec 2013 09:17:59 +0000 Subject: [PATCH 0048/1017] PDFBOX-1810: fixed the handling of bitsPerComponent when creating a color model for DeviceGray based on a proposal by Tilman Hausherr git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1552553 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdmodel/graphics/color/PDDeviceGray.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java index bd63ab3932c..0a7368723d6 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDDeviceGray.java @@ -17,19 +17,18 @@ 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.image.IndexColorModel; import java.awt.Transparency; - import java.io.IOException; /** * This class represents a Gray color space. * * @author Ben Litchfield - * @version $Revision: 1.6 $ + * */ public class PDDeviceGray extends PDColorSpace { @@ -86,11 +85,24 @@ protected ColorSpace createColorSpace() */ 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); + ColorModel colorModel = null; + if (bpc == 8) + { + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); + int[] nBits = {bpc}; + colorModel = new ComponentColorModel(cs, nBits, false, false, Transparency.OPAQUE,DataBuffer.TYPE_BYTE); + } + else + { + int numEntries = 1 << bpc; + // calculate all possible values + byte[] indexedValues = new byte[numEntries]; + for (int i = 0; i < numEntries; i++) + { + indexedValues[i] = (byte)(i*255/(numEntries - 1)); + } + colorModel = new IndexColorModel(bpc, numEntries, indexedValues, indexedValues, indexedValues); + } return colorModel; - } } From ac11f99316d26684adbb42e6c35879f935170281 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 22 Dec 2013 18:53:34 +0000 Subject: [PATCH 0049/1017] PDFBOX-1777: release/close all resources based on a proposal by Robert Scharpf git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1553018 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/pdfbox/cos/COSDocument.java | 31 +++++++++++- .../java/org/apache/pdfbox/cos/COSStream.java | 49 +++++++++++++++++-- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java index 7e3f5aeb91a..aa8cc1cea46 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java @@ -40,7 +40,7 @@ * close() on this object when you are done using it!! * * @author Ben Litchfield - * @version $Revision: 1.28 $ + * */ public class COSDocument extends COSBase { @@ -566,6 +566,35 @@ public void close() throws IOException { tmpFile.delete(); } + if (trailer != null) + { + trailer.clear(); + trailer = null; + } + // Clear object pool + List list = getObjects(); + if (list != null && !list.isEmpty()) + { + for (COSObject object : list) + { + COSBase cosObject = object.getObject(); + // clear the resources of the pooled objects + if (cosObject instanceof COSStream) + { + ((COSStream)cosObject).close(); + } + else if (cosObject instanceof COSDictionary) + { + ((COSDictionary)cosObject).clear(); + } + else if (cosObject instanceof COSArray) + { + ((COSArray)cosObject).clear(); + } + // TODO are there other kind of COSObjects to be cleared? + } + list.clear(); + } closed = true; } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java index 517a7bb33c4..22735a27f58 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java @@ -24,6 +24,8 @@ import java.io.OutputStream; import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.filter.Filter; import org.apache.pdfbox.filter.FilterManager; import org.apache.pdfbox.pdfparser.PDFStreamParser; @@ -41,6 +43,11 @@ */ public class COSStream extends COSDictionary { + /** + * Log instance. + */ + private static final Log LOG = LogFactory.getLog(COSStream.class); + private static final int BUFFER_SIZE=16384; private RandomAccess file; @@ -273,6 +280,7 @@ private void doDecode( COSName filterName, int filterIndex ) throws IOException //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 + IOUtils.closeQuietly(unFilteredStream); unFilteredStream = new RandomAccessFileOutputStream( file ); done = true; } @@ -288,6 +296,7 @@ private void doDecode( COSName filterName, int filterIndex ) throws IOException { input = new BufferedInputStream( new RandomAccessFileInputStream( file, position, length ), BUFFER_SIZE ); + IOUtils.closeQuietly(unFilteredStream); unFilteredStream = new RandomAccessFileOutputStream( file ); filter.decode( input, unFilteredStream, this, filterIndex ); done = true; @@ -315,6 +324,7 @@ private void doDecode( COSName filterName, int filterIndex ) throws IOException { input = new BufferedInputStream( new RandomAccessFileInputStream( file, position, length ), BUFFER_SIZE ); + IOUtils.closeQuietly(unFilteredStream); unFilteredStream = new RandomAccessFileOutputStream( file ); filter.decode( input, unFilteredStream, this, filterIndex ); done = true; @@ -383,6 +393,7 @@ private void doEncode( COSName filterName, int filterIndex ) throws IOException InputStream input = new BufferedInputStream( new RandomAccessFileInputStream( file, filteredStream.getPosition(), filteredStream.getLength() ), BUFFER_SIZE ); + IOUtils.closeQuietly(filteredStream); filteredStream = new RandomAccessFileOutputStream( file ); filter.encode( input, filteredStream, this, filterIndex ); IOUtils.closeQuietly(input); @@ -413,8 +424,10 @@ public COSBase getFilters() */ public OutputStream createFilteredStream() throws IOException { + IOUtils.closeQuietly(unFilteredStream); + unFilteredStream = null; + IOUtils.closeQuietly(filteredStream); filteredStream = new RandomAccessFileOutputStream( file ); - unFilteredStream = null; return new BufferedOutputStream( filteredStream, BUFFER_SIZE ); } @@ -431,9 +444,11 @@ public OutputStream createFilteredStream() throws IOException */ public OutputStream createFilteredStream( COSBase expectedLength ) throws IOException { + IOUtils.closeQuietly(unFilteredStream); + unFilteredStream = null; + IOUtils.closeQuietly(filteredStream); filteredStream = new RandomAccessFileOutputStream( file ); filteredStream.setExpectedLength( expectedLength ); - unFilteredStream = null; return new BufferedOutputStream( filteredStream, BUFFER_SIZE ); } @@ -448,6 +463,7 @@ public void setFilters(COSBase filters) throws IOException { setItem(COSName.FILTER, filters); // kill cached filtered streams + IOUtils.closeQuietly(filteredStream); filteredStream = null; } @@ -460,8 +476,35 @@ public void setFilters(COSBase filters) throws IOException */ public OutputStream createUnfilteredStream() throws IOException { - unFilteredStream = new RandomAccessFileOutputStream( file ); + IOUtils.closeQuietly(filteredStream); filteredStream = null; + IOUtils.closeQuietly(unFilteredStream); + unFilteredStream = new RandomAccessFileOutputStream( file ); return new BufferedOutputStream( unFilteredStream, BUFFER_SIZE ); } + + public void close() + { + try + { + if (file != null) + { + file.close(); + file = null; + } + } + catch (IOException exception) + { + LOG.error("Exception occured when closing the file.", exception); + } + if (filteredStream != null) + { + IOUtils.closeQuietly(filteredStream); + } + if (unFilteredStream != null) + { + IOUtils.closeQuietly(unFilteredStream); + } + clear(); + } } From f2ecccb9331cd749d7bcd879b9f9d202c930721d Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Tue, 31 Dec 2013 15:07:25 +0000 Subject: [PATCH 0050/1017] PDFBOX-1302: backported a fix for the CMap format 2 parser from PDFBOX-490 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1554530 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/fontbox/ttf/CMAPEncodingEntry.java | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/CMAPEncodingEntry.java b/fontbox/src/main/java/org/apache/fontbox/ttf/CMAPEncodingEntry.java index 3b530d4eee3..1a4612a30b3 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/CMAPEncodingEntry.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/CMAPEncodingEntry.java @@ -415,7 +415,7 @@ protected void processSubtype2( TrueTypeFont ttf, TTFDataStream data, int numGly int firstCode = data.readUnsignedShort(); int entryCount = data.readUnsignedShort(); short idDelta = data.readSignedShort(); - int idRangeOffset = data.readUnsignedShort(); + int idRangeOffset = data.readUnsignedShort() - (maxSubHeaderIndex + 1 - i - 1) * 8 - 2; subHeaders[i] = new SubHeader(firstCode, entryCount, idDelta, idRangeOffset); } long startGlyphIndexOffset = data.getCurrentPosition(); @@ -424,26 +424,31 @@ protected void processSubtype2( TrueTypeFont ttf, TTFDataStream data, int numGly { SubHeader sh = subHeaders[i]; int firstCode = sh.getFirstCode(); - for ( int j = 0 ; j < sh.getEntryCount() ; ++j) + int idRangeOffset = sh.getIdRangeOffset(); + int idDelta = sh.getIdDelta(); + int entryCount = sh.getEntryCount(); + data.seek(startGlyphIndexOffset + idRangeOffset); + for (int j = 0; j < entryCount; ++j) { // ---- compute the Character Code - int charCode = ( i * 8 ); - charCode = (charCode << 8 ) + (firstCode + j); - - // ---- Go to the CharacterCOde position in the Sub Array - // of the glyphIndexArray - // glyphIndexArray contains Unsigned Short so add (j * 2) bytes - // at the index position - data.seek(startGlyphIndexOffset + sh.getIdRangeOffset() + (j*2)); + int charCode = i; + charCode = (charCode << 8) + (firstCode + j); + + // ---- Go to the CharacterCOde position in the Sub Array + // of the glyphIndexArray + // glyphIndexArray contains Unsigned Short so add (j * 2) bytes + // at the index position int p = data.readUnsignedShort(); - // ---- compute the glyphIndex - p = p + sh.getIdDelta() % 65536; - + // ---- compute the glyphIndex + if (p > 0) + { + p = (p + idDelta) % 65536; + } glyphIdToCharacterCode[p] = charCode; characterCodeToGlyphId.put(charCode, p); } } - } + } /** * Initialize the CMapEntry when it is a subtype 0 From c41f8da463afde2a4da028b8ebb3e0199426248d Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 1 Jan 2014 17:10:13 +0000 Subject: [PATCH 0051/1017] PDFBOX-940: avoid false error messages when reading a CMap based on a proposal by Wolfgang Kronberg git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1554645 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/pdfbox/pdmodel/font/PDCIDFont.java | 28 ++++++++++++++----- .../pdfbox/pdmodel/font/PDSimpleFont.java | 23 ++++++++++++--- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java index afbc96386fa..32ebc76d72f 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java @@ -17,7 +17,7 @@ package org.apache.pdfbox.pdmodel.font; import java.io.IOException; - +import java.io.InputStream; import java.util.HashMap; import java.util.Map; @@ -28,7 +28,7 @@ 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.io.IOUtils; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.util.ResourceLoader; @@ -320,18 +320,32 @@ else if (cidSystemInfo.startsWith("Adobe-UCS-")) cmap = cmapObjects.get( cidSystemInfo ); if (cmap == null) { - String resourceName = resourceRootCMAP + cidSystemInfo; - try { - cmap = parseCmap( resourceRootCMAP, ResourceLoader.loadResource( resourceName )); - if( cmap == null) + InputStream cmapStream = null; + try + { + // look for a predefined CMap with the given name + cmapStream = ResourceLoader.loadResource(resourceRootCMAP + cidSystemInfo); + if (cmapStream != null) { - log.error("Error: Could not parse predefined CMAP file for '" + cidSystemInfo + "'" ); + cmap = parseCmap(resourceRootCMAP, cmapStream); + if (cmap == null) + { + log.error("Error: Could not parse predefined CMAP file for '" + cidSystemInfo + "'"); + } + } + else + { + log.debug("Debug: '" + cidSystemInfo + "' isn't a predefined CMap, most likely it's embedded in the pdf itself."); } } catch(IOException exception) { log.error("Error: Could not find predefined CMAP file for '" + cidSystemInfo + "'" ); } + finally + { + IOUtils.closeQuietly(cmapStream); + } } } else diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDSimpleFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDSimpleFont.java index d15c591a26f..dae5c679706 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDSimpleFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDSimpleFont.java @@ -27,6 +27,7 @@ import java.awt.geom.NoninvertibleTransformException; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import org.apache.fontbox.afm.FontMetric; @@ -44,6 +45,7 @@ import org.apache.pdfbox.encoding.Encoding; import org.apache.pdfbox.encoding.EncodingManager; +import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.util.ResourceLoader; @@ -427,19 +429,32 @@ else if (encoding instanceof COSDictionary) if (cmap == null && cmapName != null) { - String resourceName = resourceRootCMAP + cmapName; + InputStream cmapStream = null; try { - cmap = parseCmap( resourceRootCMAP, ResourceLoader.loadResource( resourceName ) ); - if( cmap == null && encodingName == null) + // look for a predefined CMap with the given name + cmapStream = ResourceLoader.loadResource(resourceRootCMAP + cmapName); + if (cmapStream != null) { - LOG.error("Error: Could not parse predefined CMAP file for '" + cmapName + "'" ); + cmap = parseCmap(resourceRootCMAP, cmapStream); + if (cmap == null && encodingName == null) + { + LOG.error("Error: Could not parse predefined CMAP file for '" + cmapName + "'"); + } + } + else + { + LOG.debug("Debug: '" + cmapName + "' isn't a predefined map, most likely it's embedded in the pdf itself."); } } catch(IOException exception) { LOG.error("Error: Could not find predefined CMAP file for '" + cmapName + "'" ); } + finally + { + IOUtils.closeQuietly(cmapStream); + } } } From 6ffb51edc2b4554d1487f679d3e19eb5629475dd Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Thu, 2 Jan 2014 09:23:14 +0000 Subject: [PATCH 0052/1017] PDFBOX-1804: fixed handling of textpositions within normalizeAdd as proposed by Andy Phillips git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1554775 13f79535-47bb-0310-9956-ffa450edef68 --- .../src/main/java/org/apache/pdfbox/util/PDFTextStripper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java index a8940621423..9f263e20662 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java @@ -1939,7 +1939,7 @@ private StringBuilder normalizeAdd(LinkedList normalized, { if (text instanceof WordSeparator) { - normalized.add(createWord(lineBuilder.toString(), wordPositions)); + normalized.add(createWord(lineBuilder.toString(), new ArrayList(wordPositions))); lineBuilder = new StringBuilder(); wordPositions.clear(); } From 0a94ca68da5885306309e12b8dc83d6c874fc906 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Thu, 2 Jan 2014 12:23:24 +0000 Subject: [PATCH 0053/1017] PDFBOX-1733: close subpath to avoid the usage of line cap style git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1554793 13f79535-47bb-0310-9956-ffa450edef68 --- .../util/operator/pagedrawer/AppendRectangleToPath.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/AppendRectangleToPath.java b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/AppendRectangleToPath.java index 0b04318da66..ac320ef1697 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/AppendRectangleToPath.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/AppendRectangleToPath.java @@ -30,7 +30,7 @@ * Implementation of content stream operator for page drawer. * * @author Ben Litchfield - * @version $Revision: 1.3 $ + * */ public class AppendRectangleToPath extends OperatorProcessor { @@ -72,6 +72,9 @@ public void process(PDFOperator operator, List arguments) path.lineTo(xStart+width, yStart); path.lineTo(xStart+width, yStart+height); path.lineTo(xStart, yStart+height); - path.lineTo(xStart, yStart); + // close the subpath instead of adding the last line + // so that a possible set line cap style isn't taken into account + // at the "beginning" of the rectangle + path.closePath(); } } From 74f4b8e9a67a4c8987318d393864ebcdc70606c6 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Thu, 2 Jan 2014 17:00:02 +0000 Subject: [PATCH 0054/1017] PDFBOX-1794: fixed a problem with the parsing of inline images based on a proposal by Tilman Hausherr git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1554847 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdfbox/pdfparser/PDFStreamParser.java | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFStreamParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFStreamParser.java index 439ca22c58e..9f42ca24c53 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFStreamParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFStreamParser.java @@ -41,13 +41,12 @@ * This will parse a PDF byte stream and extract operands and such. * * @author Ben Litchfield - * @version $Revision: 1.32 $ + * */ public class PDFStreamParser extends BaseParser { private List streamObjects = new ArrayList( 100 ); private RandomAccess file; - private PDFOperator lastBIToken = null; /** * Constructor that takes a stream to parse. @@ -355,12 +354,11 @@ else if( next.equals( "false" ) ) { String next = readString(); retval = PDFOperator.getOperator( next ); - if( next.equals( "BI" ) ) { - lastBIToken = (PDFOperator)retval; + PDFOperator beginImageOP = (PDFOperator)retval; COSDictionary imageParams = new COSDictionary(); - lastBIToken.setImageParameters( new ImageParameters( imageParams ) ); + beginImageOP.setImageParameters( new ImageParameters( imageParams ) ); Object nextToken = null; while( (nextToken = parseNextToken()) instanceof COSName ) { @@ -369,16 +367,12 @@ else if( next.equals( "false" ) ) } //final token will be the image data, maybe?? PDFOperator imageData = (PDFOperator)nextToken; - lastBIToken.setImageData( imageData.getImageData() ); + beginImageOP.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" ) ) @@ -386,40 +380,29 @@ else if( next.equals( "false" ) ) throw new IOException( "Error: Expected operator 'ID' actual='" + id + "'" ); } ByteArrayOutputStream imageData = new ByteArrayOutputStream(); - //boolean foundEnd = false; - if( this.isWhitespace() ) + if( 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 EI. - while( !(isWhitespace( twoBytesAgo ) && - lastByte == 'E' && + // PDF spec is kinda unclear about this. Should a whitespace + // always appear before EI? Not sure, so that we just read + // until EI. + // Be aware not all kind of whitespaces are allowed here. see PDFBOX1561 + while( !(lastByte == 'E' && currentByte == 'I' && - isWhitespace() //&& - //amyuni2_05d__pdf1_3_acro4x.pdf has image data that - //is compressed, so expectedBytes is useless here. - //count >= expectedBytes - ) && + isSpaceOrReturn()) && !pdfSource.isEOF() ) { imageData.write( lastByte ); - twoBytesAgo = lastByte; lastByte = currentByte; currentByte = pdfSource.read(); - count++; } - pdfSource.unread( 'I' ); //unread the EI operator - pdfSource.unread( 'E' ); + // the EI operator isn't unread, as it won't be processed anyway retval = PDFOperator.getOperator( "ID" ); + // save the image data to the operator, so that it can be accessed it later ((PDFOperator)retval).setImageData( imageData.toByteArray() ); break; } @@ -489,4 +472,21 @@ protected String readOperator() throws IOException } return buffer.toString(); } + + private boolean isSpaceOrReturn( int c ) + { + return c == 10 || c == 13 || c == 32; + } + + /** + * Checks if the next char is a space or a return. + * + * @return true if the next char is a space or a return + * @throws IOException if something went wrong + */ + private boolean isSpaceOrReturn() throws IOException + { + return isSpaceOrReturn( pdfSource.peek() ); + } + } From ba73997d22276a857af212f4e686b18d31caaa8d Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sat, 4 Jan 2014 13:29:16 +0000 Subject: [PATCH 0055/1017] PDFBOX-1830: use the correct default cap-style for lines git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1555347 13f79535-47bb-0310-9956-ffa450edef68 --- .../src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java index 4c975b5ac2b..a92e36e2e2a 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfviewer/PageDrawer.java @@ -117,6 +117,8 @@ public void drawPage( Graphics g, PDPage p, Dimension pageDimension ) throws IOE pageSize = pageDimension; graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); graphics.setRenderingHint( RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON ); + // initialize the used stroke with CAP_BUTT instead of CAP_SQUARE + graphics.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); // 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) From 35364fe09dc457510ad263af8cb1092b9db53a98 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sat, 4 Jan 2014 17:34:29 +0000 Subject: [PATCH 0056/1017] PDFBOX-1770: don't try to encode the given string using an embedded Type1C font git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1555384 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdfbox/pdmodel/font/PDType1CFont.java | 24 ------------------- .../pdfbox/pdmodel/font/PDType1Font.java | 16 ------------- 2 files changed, 40 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1CFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1CFont.java index 17d507f766e..daf86261678 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1CFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1CFont.java @@ -104,21 +104,6 @@ public PDType1CFont( COSDictionary fontDictionary ) throws IOException 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; - } - public int encodeToCID( byte[] bytes, int offset, int length ) { if (length > 2) @@ -133,15 +118,6 @@ public int encodeToCID( byte[] bytes, int offset, int length ) return code; } - private String getCharacter( byte[] bytes, int offset, int length ) - { - int code = encodeToCID(bytes, offset, length); - if (code == -1) { - return null; - } - return (String)this.codeToCharacter.get(code); - } - /** * {@inheritDoc} */ diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java index c87245ab7b1..35fc2032246 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType1Font.java @@ -451,22 +451,6 @@ else if (line.startsWith("dup")) { } } - /** - * {@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); - } - } - public int encodeToCID( byte[] c, int offset, int length ) throws IOException { if (type1CFont != null && getFontEncoding() == null) { From 337293a011e738ec03801faf897ff1164c3c99ab Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 5 Jan 2014 16:15:31 +0000 Subject: [PATCH 0057/1017] PDFBOX-1820: close streams as proposed by Tilman Hausherr git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1555553 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdfbox/pdmodel/graphics/color/PDIndexed.java | 2 ++ .../pdfbox/pdmodel/graphics/xobject/PDJpeg.java | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java index 7dfc0562557..0708a10f1a4 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/color/PDIndexed.java @@ -31,6 +31,7 @@ import org.apache.pdfbox.cos.COSNumber; import org.apache.pdfbox.cos.COSStream; import org.apache.pdfbox.cos.COSString; +import org.apache.pdfbox.io.IOUtils; /** * This class represents an Indexed color space. @@ -260,6 +261,7 @@ else if (lookupTable instanceof COSStream) output.write(buffer, 0, amountRead); } lookupData = output.toByteArray(); + IOUtils.closeQuietly(input); } else if (lookupTable == null) { diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDJpeg.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDJpeg.java index 59874ea2bfc..45e43e9dbc4 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDJpeg.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDJpeg.java @@ -32,7 +32,6 @@ import java.io.OutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; - import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -45,7 +44,6 @@ 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.common.function.PDFunction; @@ -65,7 +63,7 @@ * An image class for JPegs. * * @author mathiak - * @version $Revision: 1.5 $ + * */ public class PDJpeg extends PDXObjectImage { @@ -351,7 +349,6 @@ private int getApp14AdobeTransform(byte[] bytes) Iterator readers = ImageIO.getImageReaders(input); if (readers == null || !readers.hasNext()) { - input.close(); throw new RuntimeException("No ImageReaders found"); } reader = (ImageReader) readers.next(); @@ -388,6 +385,17 @@ private int getApp14AdobeTransform(byte[] bytes) } finally { + try + { + if (input != null) + { + input.close(); + } + } + catch (IOException exception) + { + // swallow the exception + } if (reader != null) { reader.dispose(); From 7e5511a34fd157bd1de39732cd194bbdadeb28c5 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sat, 11 Jan 2014 10:59:17 +0000 Subject: [PATCH 0058/1017] PDFBOX-1839: use TYPE_INT_RGB as default image type as proposed by Tilman Hausherr git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1557341 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/pdfbox/util/PDFImageWriter.java | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFImageWriter.java b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFImageWriter.java index d20a45f8093..3ac758d5f00 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFImageWriter.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFImageWriter.java @@ -30,9 +30,9 @@ /** * This class writes single pages of a pdf to a file. - * + * * @author Daniel Wilson - * @version $Revision: 1.1 $ + * */ public class PDFImageWriter extends PDFStreamEngine { @@ -65,25 +65,22 @@ public PDFImageWriter(Properties props) throws IOException } /** - * 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 + * Converts a given page range of a PDF document to bitmap images by calling + * {@link writeImage(PDDocument document, String imageFormat, String password, int startPage, int endPage, + * String outputPrefix, int imageType, int resolution)} with imageType {@link BufferedImage}.TYPE_INT_RGB + * and screen resolution, or 96dpi if screen resolution is unavailable. + * + * @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 * @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, + public boolean writeImage(PDDocument document, String imageFormat, String password, int startPage, int endPage, String outputPrefix) throws IOException { int resolution; @@ -95,7 +92,7 @@ public boolean writeImage(PDDocument document, String imageType, String password { resolution = 96; } - return writeImage(document, imageType, password, startPage, endPage, outputPrefix, 8, resolution); + return writeImage(document, imageFormat, password, startPage, endPage, outputPrefix, BufferedImage.TYPE_INT_RGB, resolution); } /** From d85535490f9f820a3441995702bde0c21ad3027c Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sat, 11 Jan 2014 13:45:18 +0000 Subject: [PATCH 0059/1017] PDFBOX-161: avoid EmptyStackException if the number of q/Q-operators isn't well balanced git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1557395 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/pdfbox/util/operator/GRestore.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/GRestore.java b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/GRestore.java index 896ace9aa6c..f42162bcd4c 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/GRestore.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/GRestore.java @@ -18,24 +18,40 @@ 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.PDGraphicsState; import org.apache.pdfbox.util.PDFOperator; /** - * + * Process the Q operator. + * * @author Huault : huault@free.fr - * @version $Revision: 1.4 $ + * */ public class GRestore extends OperatorProcessor { + + /** + * Log instance. + */ + private static final Log LOG = LogFactory.getLog(GRestore.class); + /** - * process : Q : Restore graphics state. - * @param operator The operator that is being executed. - * @param arguments List + * {@inheritDoc} */ public void process(PDFOperator operator, List arguments) { - context.setGraphicsState( (PDGraphicsState)context.getGraphicsStack().pop() ); + if (context.getGraphicsStack().size() > 0) + { + context.setGraphicsState( (PDGraphicsState)context.getGraphicsStack().pop() ); + } + else + { + // this shouldn't happen but it does, see PDFBOX-161 + // TODO make this self healing mechanism optional for preflight?? + LOG.debug("GRestore: no graphics state left to be restored."); + } } } From 2c0401f71fd12ea26f3c8c443d5d96436b27e3b8 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 12 Jan 2014 16:28:50 +0000 Subject: [PATCH 0060/1017] write COSDictionary direct if set to direct git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1557554 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/pdfbox/pdfwriter/COSWriter.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java index f17c0a3ca72..f0c10a55754 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java @@ -918,8 +918,15 @@ public Object visitFromArray( COSArray obj ) throws COSVisitorException COSBase current = i.next(); if( current instanceof COSDictionary ) { - addObjectToWrite( current ); - writeReference( current ); + if (current.isDirect()) + { + visitFromDictionary((COSDictionary)current); + } + else + { + addObjectToWrite( current ); + writeReference( current ); + } } else if( current instanceof COSObject ) { From 4a0019506bbafff68540ab8548cc8e3499b6550b Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 12 Jan 2014 17:12:19 +0000 Subject: [PATCH 0061/1017] PDFBOX-1841: removed the distinction of cases for ICC-based colorspaces with 1 component git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1557562 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdfbox/pdmodel/graphics/xobject/PDPixelMap.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java index a9e18456a3b..82c54b55fa2 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDPixelMap.java @@ -269,17 +269,6 @@ else if (bpc == 1) 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}; From 6ae275cae756d7ef6accec7632c7cb2bf648e764 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 13 Jan 2014 17:55:54 +0000 Subject: [PATCH 0062/1017] PDFBOX-1799: avoid NPE based on a proposal by Puhong You git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1557794 13f79535-47bb-0310-9956-ffa450edef68 --- .../main/java/org/apache/pdfbox/util/ImageIOUtil.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/ImageIOUtil.java b/pdfbox/src/main/java/org/apache/pdfbox/util/ImageIOUtil.java index 0fe7a9ffc6e..24508a3e315 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/ImageIOUtil.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/ImageIOUtil.java @@ -156,9 +156,12 @@ public static boolean writeImage(BufferedImage image, String imageFormat, Object writerParams.setCompressionQuality(quality); } IIOMetadata meta = createMetadata( image, imageWriter, writerParams, resolution); - imageWriter.setOutput( output ); - imageWriter.write( null, new IIOImage( image, null, meta ), writerParams ); - foundWriter = true; + if (meta != null) + { + imageWriter.setOutput( output ); + imageWriter.write( null, new IIOImage( image, null, meta ), writerParams ); + foundWriter = true; + } } catch( IIOException io ) { @@ -208,7 +211,7 @@ private static IIOMetadata createMetadata(RenderedImage image, ImageWriter image private static boolean addResolution(IIOMetadata meta, int resolution) { - if (!meta.isReadOnly() && meta.isStandardMetadataFormatSupported()) + if (meta != null && !meta.isReadOnly() && meta.isStandardMetadataFormatSupported()) { IIOMetadataNode root = (IIOMetadataNode)meta.getAsTree(STANDARD_METADATA_FORMAT); IIOMetadataNode dim = getChildNode(root, "Dimension"); From 9b00c31db2447e104d15c3f8329af22e9b2b8b8c Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 15 Jan 2014 06:45:40 +0000 Subject: [PATCH 0063/1017] PDFBOX-1812: merged trunk revisions 1557546 and 1558205 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1558309 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/pdfbox/pdfparser/BaseParser.java | 4 +- .../pdfparser/NonSequentialPDFParser.java | 53 +++++++++++++++---- .../preflight/parser/PreflightParser.java | 2 + 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java index 0130d7111e3..0dab47a08b6 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java @@ -1639,7 +1639,7 @@ protected int readInt() throws IOException catch( NumberFormatException e ) { pdfSource.unread(intBuffer.toString().getBytes("ISO-8859-1")); - throw new IOException( "Error: Expected an integer type, actual='" + intBuffer + "'" ); + throw new IOException( "Error: Expected an integer type at offset "+pdfSource.getOffset()); } return retval; } @@ -1666,7 +1666,7 @@ protected long readLong() throws IOException catch( NumberFormatException e ) { pdfSource.unread(longBuffer.toString().getBytes("ISO-8859-1")); - throw new IOException( "Error: Expected a long type, actual='" + longBuffer + "'" ); + throw new IOException( "Error: Expected a long type at offset "+pdfSource.getOffset()); } return retval; } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java index 1604eab64a6..006bab5f70a 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java @@ -113,6 +113,11 @@ public class NonSequentialPDFParser extends PDFParser private long fileLen; private final RandomAccessBufferedFileInputStream raStream; + /** + * is parser using auto healing capacity ? + */ + private boolean isLenient = true; + /** * The security handler. */ @@ -331,8 +336,10 @@ protected void initialParse() throws IOException long startXrefOffset = document.getStartXref(); // check the startxref offset - startXrefOffset -= calculateFixingOffset(startXrefOffset); - document.setStartXref(startXrefOffset); + if (isLenient) { + startXrefOffset -= calculateFixingOffset(startXrefOffset); + document.setStartXref(startXrefOffset); + } long prev = startXrefOffset; // ---- parse whole chain of xref tables/object streams using PREV // reference @@ -356,7 +363,7 @@ protected void initialParse() throws IOException } COSDictionary trailer = xrefTrailerResolver.getCurrentTrailer(); prev = trailer.getInt(COSName.PREV); - if (prev > -1) + if (isLenient && prev > -1) { // check the xref table reference long fixingOffset = calculateFixingOffset(prev); @@ -364,14 +371,14 @@ protected void initialParse() throws IOException { prev -= fixingOffset; trailer.setLong(COSName.PREV, prev); - } - } + } + } } else { // parse xref stream prev = parseXrefObjStream(prev); - if (prev > -1) + if (isLenient && prev > -1) { // check the xref table reference long fixingOffset = calculateFixingOffset(prev); @@ -380,8 +387,8 @@ protected void initialParse() throws IOException prev -= fixingOffset; COSDictionary trailer = xrefTrailerResolver.getCurrentTrailer(); trailer.setLong(COSName.PREV, prev); - } - } + } + } } } @@ -390,8 +397,10 @@ protected void initialParse() throws IOException COSDictionary trailer = xrefTrailerResolver.getTrailer(); document.setTrailer(trailer); - // check the offsets of all referenced objects - checkXrefOffsets(); + // check the offsets of all referenced objects + if (isLenient) { + checkXrefOffsets(); + } // ---- prepare encryption if necessary COSBase trailerEncryptItem = document.getTrailer().getItem(COSName.ENCRYPT); @@ -781,6 +790,30 @@ protected File getPdfFile() return this.pdfFile; } + /** + * Return true if parser is lenient. Meaning auto healing capacity of the parser are used. + * + * @return true if parser is lenient + */ + public boolean isLenient () { + return isLenient; + } + + /** + * Change the parser leniency flag. + * + * This method can only be called before the parsing of the file. + * + * @param lenient + * + * @throws IllegalArgumentException if the method is called after parsing. + */ + public void setLenient (boolean lenient) throws IllegalArgumentException { + if (initialParseDone) { + throw new IllegalArgumentException("Cannot change leniency after parsing"); + } + this.isLenient = lenient; + } /** * Remove the temporary file. A temporary file is created if this class is * instantiated with an InputStream diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java b/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java index 281f37bd335..6dedc72c51c 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/parser/PreflightParser.java @@ -106,6 +106,7 @@ public class PreflightParser extends NonSequentialPDFParser public PreflightParser(File file, RandomAccess rafi) throws IOException { super(file, rafi); + this.setLenient(false); this.originalDocument = new FileDataSource(file); } @@ -122,6 +123,7 @@ public PreflightParser(String filename) throws IOException public PreflightParser(DataSource input) throws IOException { super(input.getInputStream()); + this.setLenient(false); this.originalDocument = input; } From 40b00b401c8c1f56b4f5882bbdfa96b95b3b0114 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 15 Jan 2014 19:03:13 +0000 Subject: [PATCH 0064/1017] PDFBOX-1796, PDFBOX-1585: Fix for infinite loop in parseCOSHexString git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1558517 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/pdfbox/pdfparser/BaseParser.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java index 0dab47a08b6..c9c1d1d4c9d 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java @@ -1008,7 +1008,15 @@ else if ( ( c == ' ' ) || ( c == '\n' ) || do { c = pdfSource.read(); - } while ( c != '>' ); + } while ( c != '>' && c >= 0 ); + + // might have reached EOF while looking for the closing bracket + // this can happen for malformed PDFs only. Make sure that there is + // no endless loop. + if ( c < 0 ) + { + throw new IOException( "Missing closing bracket for hex string. Reached EOS." ); + } // exit loop break; From b62d9537fefa35067636ab2617217f9e49a8d30e Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 15 Jan 2014 19:08:21 +0000 Subject: [PATCH 0065/1017] PDFBOX-1685: Update the preflight validation following the compatibility requirement exposed in the JIRA issue. RDF:About must be the same in all rdf:description (or empty) git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1558521 13f79535-47bb-0310-9956-ffa450edef68 --- .../test/java/org/apache/xmpbox/schema/XMPSchemaTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xmpbox/src/test/java/org/apache/xmpbox/schema/XMPSchemaTest.java b/xmpbox/src/test/java/org/apache/xmpbox/schema/XMPSchemaTest.java index c391fd111cb..056a56f3f69 100644 --- a/xmpbox/src/test/java/org/apache/xmpbox/schema/XMPSchemaTest.java +++ b/xmpbox/src/test/java/org/apache/xmpbox/schema/XMPSchemaTest.java @@ -139,12 +139,14 @@ public void testSeqManagement() throws Exception @Test public void rdfAboutTest() { - Assert.assertNull(schem.getAboutValue()); + Assert.assertEquals("",schem.getAboutValue()); String about = "about"; schem.setAboutAsSimple(about); Assert.assertEquals(about, schem.getAboutValue()); + schem.setAboutAsSimple(""); + Assert.assertEquals("",schem.getAboutValue()); schem.setAboutAsSimple(null); - Assert.assertNull(schem.getAboutValue()); + Assert.assertEquals("",schem.getAboutValue()); } @Test(expected = BadFieldValueException.class) From 299c657fb5bc8691a2e68bd688b2453e4e42ba1d Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 15 Jan 2014 19:20:14 +0000 Subject: [PATCH 0066/1017] PDFBOX-1725: use the DW as default width git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1558531 13f79535-47bb-0310-9956-ffa450edef68 --- .../src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java index 32ebc76d72f..b3f238b3399 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java @@ -273,7 +273,7 @@ public float getAverageFontWidth() throws IOException */ public float getFontWidth( int charCode ) { - float width = -1; + float width = getDefaultWidth(); if (widthCache.containsKey(charCode)) { width = widthCache.get(charCode); From d35843b53ea3db682f3eeb3495e8e84f2a212fda Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 15 Jan 2014 19:23:38 +0000 Subject: [PATCH 0067/1017] PDFBOX-1763: Convert IllegalArgumentException in ValidationError if the ICCProfile is invalid git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1558537 13f79535-47bb-0310-9956-ffa450edef68 --- .../pdfbox/preflight/graphic/ICCProfileWrapper.java | 12 ++++++++---- .../preflight/graphic/StandardColorSpaceHelper.java | 8 +++++++- .../preflight/process/CatalogValidationProcess.java | 4 ++-- .../process/reflect/ExtGStateValidationProcess.java | 4 ++-- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/graphic/ICCProfileWrapper.java b/preflight/src/main/java/org/apache/pdfbox/preflight/graphic/ICCProfileWrapper.java index ff59ae00d99..dc4571ca9f1 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/graphic/ICCProfileWrapper.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/graphic/ICCProfileWrapper.java @@ -22,7 +22,8 @@ package org.apache.pdfbox.preflight.graphic; import static org.apache.pdfbox.preflight.PreflightConstants.DOCUMENT_DICTIONARY_KEY_OUTPUT_INTENTS; -import static org.apache.pdfbox.preflight.PreflightConstants.OUTPUT_INTENT_DICTIONARY_KEY_DEST_OUTPUT_PROFILE; +import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_GRAPHIC_OUTPUT_INTENT_ICC_PROFILE_INVALID; +import static org.apache.pdfbox.preflight.PreflightConstants.*; import java.awt.color.ICC_ColorSpace; import java.awt.color.ICC_Profile; @@ -36,6 +37,7 @@ import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.preflight.PreflightContext; import org.apache.pdfbox.preflight.PreflightDocument; +import org.apache.pdfbox.preflight.ValidationResult.ValidationError; import org.apache.pdfbox.preflight.exception.ValidationException; import org.apache.pdfbox.preflight.utils.COSUtils; @@ -143,11 +145,13 @@ private static ICCProfileWrapper searchFirstICCProfile(PreflightContext context) } catch (IllegalArgumentException e) { - throw new ValidationException("DestOutputProfile isn't a ICCProfile", e); + context.addValidationError(new ValidationError(ERROR_GRAPHIC_OUTPUT_INTENT_ICC_PROFILE_INVALID, + "DestOutputProfile isn't a valid ICCProfile. Caused by : " + e.getMessage())); } catch (IOException e) - { - throw new ValidationException("Unable to parse the ICCProfile", e); + { + context.addValidationError(new ValidationError(ERROR_GRAPHIC_OUTPUT_INTENT_ICC_PROFILE_INVALID, + "Unable to parse the ICCProfile. Caused by : " + e.getMessage())); } } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/graphic/StandardColorSpaceHelper.java b/preflight/src/main/java/org/apache/pdfbox/preflight/graphic/StandardColorSpaceHelper.java index 826e843fff5..f354ecb7282 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/graphic/StandardColorSpaceHelper.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/graphic/StandardColorSpaceHelper.java @@ -266,11 +266,17 @@ protected void processICCBasedColorSpace(PDColorSpace pdcs) */ } } + } + catch (IllegalArgumentException e) + { + // this is not a ICC_Profile + context.addValidationError(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE_ICCBASED, + "ICCBase color space is invalid. Caused By: " + e.getMessage())); } catch (IOException e) { context.addValidationError(new ValidationError(ERROR_GRAPHIC_INVALID_COLOR_SPACE, - "Unable to read ICCBase color space : " + e.getMessage())); + "Unable to read ICCBase color space. Caused by : " + e.getMessage())); } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/CatalogValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/CatalogValidationProcess.java index f892d406baa..0ddf2dfe566 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/CatalogValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/CatalogValidationProcess.java @@ -456,11 +456,11 @@ else if (iccp.getMajorVersion() > 2) { // this is not a ICC_Profile addValidationError(ctx, new ValidationError(ERROR_GRAPHIC_OUTPUT_INTENT_ICC_PROFILE_INVALID, - "DestOutputProfile isn't a ICCProfile")); + "DestOutputProfile isn't a valid ICCProfile. Caused by : " + e.getMessage())); } catch (IOException e) { - throw new ValidationException("Unable to parse the ICC Profile", e); + throw new ValidationException("Unable to parse the ICC Profile.", e); } } } diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ExtGStateValidationProcess.java b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ExtGStateValidationProcess.java index 422e78242d4..919fff2c48b 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ExtGStateValidationProcess.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/process/reflect/ExtGStateValidationProcess.java @@ -231,10 +231,10 @@ protected void checkTR2Key(PreflightContext context, COSDictionary egs) if (egs.getItem("TR2") != null) { String s = egs.getNameAsString("TR2"); - if (!"default".equals(s)) + if (!"Default".equals(s)) { context.addValidationError(new ValidationError(ERROR_GRAPHIC_UNEXPECTED_VALUE_FOR_KEY, - "TR2 key only expect 'default' value, not '" + s + "'")); + "TR2 key only expect 'Default' value, not '" + s + "'")); } } } From f4ebdc593fbb9d471c71816d263562d6f87fd700 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 15 Jan 2014 21:59:30 +0000 Subject: [PATCH 0068/1017] PDFBOX-1685: Update the preflight validation following the compatibility requirement exposed in the JIRA issue. RDF:About must be the same in all rdf:description (or empty) git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1558581 13f79535-47bb-0310-9956-ffa450edef68 --- ...DFAboutAttributeConcordanceValidation.java | 21 +++++++++++-------- .../org/apache/xmpbox/schema/XMPSchema.java | 4 ++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/preflight/src/main/java/org/apache/pdfbox/preflight/metadata/RDFAboutAttributeConcordanceValidation.java b/preflight/src/main/java/org/apache/pdfbox/preflight/metadata/RDFAboutAttributeConcordanceValidation.java index 546dd536a23..8221145fc89 100644 --- a/preflight/src/main/java/org/apache/pdfbox/preflight/metadata/RDFAboutAttributeConcordanceValidation.java +++ b/preflight/src/main/java/org/apache/pdfbox/preflight/metadata/RDFAboutAttributeConcordanceValidation.java @@ -51,23 +51,26 @@ public void validateRDFAboutAttributes(XMPMetadata metadata) throws ValidationEx { throw new ValidationException("Schemas not found in the given metadata representation"); } + String about = schemas.get(0).getAboutValue(); + // rdf:description must have an rdf:about attribute for (XMPSchema xmpSchema : schemas) { - checkRdfAbout(about, xmpSchema); + // each rdf:Description must have the same rdf:about (or an empty one) + String schemaAboutValue = xmpSchema.getAboutValue(); + if (!("".equals(schemaAboutValue) || "".equals(about) || about.equals(schemaAboutValue))) + { + throw new DifferentRDFAboutException(); + } + + if ("".equals(about)) { + about = schemaAboutValue; + } } } - private void checkRdfAbout(String about, XMPSchema xmpSchema) throws DifferentRDFAboutException - { - if (!about.equals(xmpSchema.getAboutValue())) - { - throw new DifferentRDFAboutException(); - } - } - public static class DifferentRDFAboutException extends Exception { diff --git a/xmpbox/src/main/java/org/apache/xmpbox/schema/XMPSchema.java b/xmpbox/src/main/java/org/apache/xmpbox/schema/XMPSchema.java index b7b4f3be012..717c350f010 100644 --- a/xmpbox/src/main/java/org/apache/xmpbox/schema/XMPSchema.java +++ b/xmpbox/src/main/java/org/apache/xmpbox/schema/XMPSchema.java @@ -122,7 +122,7 @@ public Attribute getAboutAttribute() /** * Get the RDF about value. * - * @return The RDF 'about' value. + * @return The RDF 'about' value. If there are not rdf:about attribute, an empty string is returned. */ public String getAboutValue() { @@ -131,7 +131,7 @@ public String getAboutValue() { return prop.getValue(); } - return null; + return ""; // PDFBOX-1685 : if missing rdf:about should be considered as empty string } /** From 98c8343ebb7fb60961039f5084a56c7ee5a84f31 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 15 Jan 2014 22:02:58 +0000 Subject: [PATCH 0069/1017] merged the changes of PDFBOX-1625 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1558585 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/pdfbox/cos/COSStream.java | 19 +++++--- .../apache/pdfbox/io/RandomAccessBuffer.java | 45 ++++++++++++++++++- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java index 22735a27f58..69104388d3b 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java @@ -28,13 +28,10 @@ import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.filter.Filter; import org.apache.pdfbox.filter.FilterManager; +import org.apache.pdfbox.io.*; import org.apache.pdfbox.pdfparser.PDFStreamParser; import org.apache.pdfbox.exceptions.COSVisitorException; import org.apache.pdfbox.io.IOUtils; -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. * @@ -61,6 +58,16 @@ public class COSStream extends COSDictionary */ private RandomAccessFileOutputStream unFilteredStream; + private RandomAccess clone (RandomAccess file) { + if (file == null) { + return null; + } else if (file instanceof RandomAccessFile) { + return file; + } else { + return ((RandomAccessBuffer)file).clone(); + } + } + /** * Constructor. Creates a new stream with an empty dictionary. * @@ -69,7 +76,7 @@ public class COSStream extends COSDictionary public COSStream( RandomAccess storage ) { super(); - file = storage; + file = clone(storage); } /** @@ -81,7 +88,7 @@ public COSStream( RandomAccess storage ) public COSStream( COSDictionary dictionary, RandomAccess storage ) { super( dictionary ); - file = storage; + file = clone(storage); } /** diff --git a/pdfbox/src/main/java/org/apache/pdfbox/io/RandomAccessBuffer.java b/pdfbox/src/main/java/org/apache/pdfbox/io/RandomAccessBuffer.java index 263fd0ffff2..53c5bcc8b4b 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/io/RandomAccessBuffer.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/io/RandomAccessBuffer.java @@ -59,6 +59,30 @@ public RandomAccessBuffer() bufferListMaxIndex = 0; } + @Override + public RandomAccessBuffer clone() { + RandomAccessBuffer copy = new RandomAccessBuffer(); + + copy.bufferList = new ArrayList(bufferList.size()); + for (byte [] buffer : bufferList) { + byte [] newBuffer = new byte [buffer.length]; + System.arraycopy(buffer,0,newBuffer,0,buffer.length); + copy.bufferList.add(newBuffer); + } + if (currentBuffer!=null) { + copy.currentBuffer = copy.bufferList.get(copy.bufferList.size()-1); + } else { + copy.currentBuffer = null; + } + copy.pointer = pointer; + copy.currentBufferPointer = currentBufferPointer; + copy.size = size; + copy.bufferListIndex = bufferListIndex; + copy.bufferListMaxIndex = bufferListMaxIndex; + + return copy; + } + /** * {@inheritDoc} */ @@ -77,6 +101,7 @@ public void close() throws IOException */ public void seek(long position) throws IOException { + checkClosed(); pointer = position; // calculate the chunk list index bufferListIndex = (int)(position / BUFFER_SIZE); @@ -88,7 +113,8 @@ public void seek(long position) throws IOException * {@inheritDoc} */ public long getPosition() throws IOException { - return pointer; + checkClosed(); + return pointer; } /** @@ -96,6 +122,7 @@ public long getPosition() throws IOException { */ public int read() throws IOException { + checkClosed(); if (pointer >= this.size) { return -1; @@ -121,6 +148,7 @@ public int read() throws IOException */ public int read(byte[] b, int offset, int length) throws IOException { + checkClosed(); if (pointer >= this.size) { return 0; @@ -164,6 +192,7 @@ public int read(byte[] b, int offset, int length) throws IOException */ public long length() throws IOException { + checkClosed(); return size; } @@ -172,6 +201,7 @@ public long length() throws IOException */ public void write(int b) throws IOException { + checkClosed(); // end of buffer reached? if (currentBufferPointer >= BUFFER_SIZE) { @@ -203,6 +233,7 @@ public void write(int b) throws IOException */ public void write(byte[] b, int offset, int length) throws IOException { + checkClosed(); long newSize = pointer + length; long remainingBytes = BUFFER_SIZE - currentBufferPointer; if (length >= remainingBytes) @@ -276,4 +307,16 @@ private void nextBuffer() currentBufferPointer = 0; currentBuffer = bufferList.get(++bufferListIndex); } + + /** + * Ensure that the RandomAccessBuffer is not closed + * @throws IOException + */ + private void checkClosed () throws IOException { + if (currentBuffer==null) { + // consider that the rab is closed if there is no current buffer + throw new IOException("RandomAccessBuffer already closed"); + } + + } } From 08cef7ac04d3d09fbf5edf26d57e445d08898ee3 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Thu, 16 Jan 2014 06:33:38 +0000 Subject: [PATCH 0070/1017] PDFBOX-1829: avoid NPE when creating a smasked image git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1558702 13f79535-47bb-0310-9956-ffa450edef68 --- .../graphics/xobject/PDXObjectImage.java | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java index c1ade624523..382935f8799 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/xobject/PDXObjectImage.java @@ -137,24 +137,32 @@ public PDXObjectImage getSMaskImage() throws IOException public BufferedImage applyMasks(BufferedImage baseImage) throws IOException { - if (getImageMask()) - { - return imageMask(baseImage); - } - if(getMask() != null) - { - return mask(baseImage); - } - PDXObjectImage smask = getSMaskImage(); - if(smask != null) - { - BufferedImage smaskBI = smask.getRGBImage(); - COSArray decodeArray = smask.getDecode(); - CompositeImage compositeImage = new CompositeImage(baseImage, smaskBI); - BufferedImage rgbImage = compositeImage.createMaskedImage(decodeArray); - return rgbImage; - } - return baseImage; + if (getImageMask()) + { + return imageMask(baseImage); + } + if (getMask() != null) + { + return mask(baseImage); + } + PDXObjectImage smask = getSMaskImage(); + if (smask != null) + { + BufferedImage smaskBI = smask.getRGBImage(); + if (smaskBI != null) + { + COSArray decodeArray = smask.getDecode(); + CompositeImage compositeImage = new CompositeImage(baseImage, smaskBI); + BufferedImage rgbImage = compositeImage.createMaskedImage(decodeArray); + return rgbImage; + } + else + { + // this may happen if the smask is somehow broken, e.g. unsupported filter + LOG.warn("masking getRGBImage returned NULL"); + } + } + return baseImage; } public boolean hasMask() throws IOException From 0ead6704a6e8b73f0f73a863565b455997391151 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Thu, 16 Jan 2014 07:11:30 +0000 Subject: [PATCH 0071/1017] PDFBOX-1808: release used resources git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1558705 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/pdfbox/pdfparser/BaseParser.java | 17 ++++++++ .../apache/pdfbox/pdfparser/PDFParser.java | 20 +++++++++- .../pdfbox/pdfparser/PDFStreamParser.java | 12 ++++++ .../pdfbox/pdfparser/XrefTrailerResolver.java | 40 ++++++++++++++++++- .../org/apache/pdfbox/pdmodel/PDDocument.java | 36 ++++++++++++++++- .../apache/pdfbox/util/PDFTextStripper.java | 13 +++++- .../java/org/apache/pdfbox/TestTextToPdf.java | 2 +- 7 files changed, 134 insertions(+), 6 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java index c9c1d1d4c9d..c8836a74dcf 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java @@ -38,6 +38,7 @@ import org.apache.pdfbox.cos.COSStream; import org.apache.pdfbox.cos.COSString; import org.apache.pdfbox.exceptions.WrappedIOException; +import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.io.PushBackInputStream; import org.apache.pdfbox.io.RandomAccess; import org.apache.pdfbox.persistence.util.COSObjectKey; @@ -556,6 +557,10 @@ else if ( ( nextEndstreamCIdx > 0 ) || ( ! isWhitespace( ch ) ) ) PROP_PUSHBACK_SIZE, ioe ); } // create new filtered stream + if (out != null) + { + IOUtils.closeQuietly(out); + } out = stream.createFilteredStream( streamLength ); // scan until we find endstream: readUntilEndStream( out ); @@ -1705,4 +1710,16 @@ protected final StringBuilder readStringNumber() throws IOException return buffer; } + /** + * Release all used resources. + */ + public void clearResources() + { + document = null; + if (pdfSource != null) + { + IOUtils.closeQuietly(pdfSource); + pdfSource = null; + } + } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java index d3f1b5da7a1..c7f88c21619 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java @@ -442,7 +442,7 @@ public COSDocument getDocument() throws IOException */ public PDDocument getPDDocument() throws IOException { - return new PDDocument( getDocument() ); + return new PDDocument( getDocument(), this ); } /** @@ -981,4 +981,22 @@ private static boolean tolerantConflicResolver(Collection values, long off } return false; } + + /** + * {@inheritDoc} + */ + @Override + public void clearResources() { + super.clearResources(); + if (conflictList != null) + { + conflictList.clear(); + conflictList = null; + } + if (xrefTrailerResolver != null) + { + xrefTrailerResolver.clearResources(); + xrefTrailerResolver = null; + } + } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFStreamParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFStreamParser.java index 9f42ca24c53..e16ea4d89dc 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFStreamParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFStreamParser.java @@ -489,4 +489,16 @@ private boolean isSpaceOrReturn() throws IOException return isSpaceOrReturn( pdfSource.peek() ); } + /** + * {@inheritDoc} + */ + @Override + public void clearResources() { + super.clearResources(); + if (streamObjects != null) + { + streamObjects.clear(); + streamObjects = null; + } + } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java index 8637a8c9f45..2c20d9041c7 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java @@ -65,11 +65,27 @@ private class XrefTrailerObj private final Map xrefTable = new HashMap(); /** - * Default cosntructor. + * Default constructor. */ private XrefTrailerObj() { } + + /** + * Release all used resources. + */ + public void clearResources() + { + if (trailer != null) + { + trailer.clear(); + trailer = null; + } + if (xrefTable != null) + { + xrefTable.clear(); + } + } } private final Map bytePosToXrefMap = new HashMap(); @@ -283,4 +299,26 @@ public Set getContainedObjectNumbers( final int objstmObjNr ) } return refObjNrs; } + + /** + * Release all used resources. + */ + public void clearResources() + { + if (curXrefTrailerObj != null) + { + curXrefTrailerObj.clearResources(); + curXrefTrailerObj = null; + } + if (resolvedXrefTrailer != null) + { + resolvedXrefTrailer.clearResources(); + resolvedXrefTrailer = null; + } + if (bytePosToXrefMap != null) + { + bytePosToXrefMap.clear(); + } + } + } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java index e4e2270dd55..e0fcdb92be7 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java @@ -48,6 +48,7 @@ import org.apache.pdfbox.exceptions.InvalidPasswordException; import org.apache.pdfbox.exceptions.SignatureException; import org.apache.pdfbox.io.RandomAccess; +import org.apache.pdfbox.pdfparser.BaseParser; import org.apache.pdfbox.pdfparser.NonSequentialPDFParser; import org.apache.pdfbox.pdfparser.PDFParser; import org.apache.pdfbox.pdfwriter.COSWriter; @@ -124,7 +125,8 @@ public class PDDocument implements Pageable */ private Long documentId; - + private BaseParser parser; + /** * Constructor, creates a new PDF Document with no pages. You need to add * at least one page for the document to be valid. @@ -693,8 +695,20 @@ public PDPage importPage( PDPage page ) throws IOException * @param doc The COSDocument that this document wraps. */ public PDDocument( COSDocument doc ) + { + this(doc, null); + } + + /** + * Constructor that uses an existing document. The COSDocument that is passed in must be valid. + * + * @param doc The COSDocument that this document wraps. + * @param usedParser the parser which is used to read the pdf + */ + public PDDocument(COSDocument doc, BaseParser usedParser) { document = doc; + parser = usedParser; } /** @@ -1525,7 +1539,25 @@ private void print(PrinterJob job, boolean silent) throws PrinterException */ public void close() throws IOException { - document.close(); + documentCatalog = null; + documentInformation = null; + encParameters = null; + if (pageMap != null) + { + pageMap.clear(); + pageMap = null; + } + securityHandler = null; + if (document != null) + { + document.close(); + document = null; + } + if (parser != null) + { + parser.clearResources(); + parser = null; + } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java index 9f263e20662..14a58713e5e 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFTextStripper.java @@ -281,6 +281,17 @@ public void resetEngine() { super.resetEngine(); currentPageNo = 0; + document = null; + if (charactersByArticle != null) + { + charactersByArticle.clear(); + } + if (characterListMapping != null) + { + characterListMapping.clear(); + } + startBookmark = null; + endBookmark = null; } /** @@ -1803,7 +1814,7 @@ protected Pattern matchListItemPattern(PositionWrapper pw) */ protected void setListItemPatterns(List patterns) { - listOfPatterns = patterns; + listOfPatterns = patterns; } /** diff --git a/pdfbox/src/test/java/org/apache/pdfbox/TestTextToPdf.java b/pdfbox/src/test/java/org/apache/pdfbox/TestTextToPdf.java index 189af93d53f..2842f716436 100644 --- a/pdfbox/src/test/java/org/apache/pdfbox/TestTextToPdf.java +++ b/pdfbox/src/test/java/org/apache/pdfbox/TestTextToPdf.java @@ -54,7 +54,6 @@ public void testCreateEmptyPdf() throws Exception StringReader reader = new StringReader(""); PDDocument pdfDoc = pdfCreator.createPDFFromText(reader); reader.close(); - pdfDoc.close(); // In order for the PDF document to be openable by Adobe Reader, it needs // to have some pages in it. So we'll check that. @@ -62,6 +61,7 @@ public void testCreateEmptyPdf() throws Exception List allPages = docCatalog.getAllPages(); assertNotNull("All Pages was unexpectedly null.", allPages); assertEquals("Wrong number of pages.", 1, allPages.size()); + pdfDoc.close(); } /** From 3d301684d9b2574bed41c0db1ce3aedaffe33c90 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Wed, 22 Jan 2014 20:21:57 +0000 Subject: [PATCH 0072/1017] PDFBOX-1822: don't rely on the trailer dictionary to determine if the pdf has a xref table or a xref stream git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1560498 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/pdfbox/cos/COSDocument.java | 20 +++++++++++++------ .../pdfparser/NonSequentialPDFParser.java | 2 ++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java index aa8cc1cea46..5a70692f6f7 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java @@ -93,6 +93,8 @@ public class COSDocument extends COSBase private boolean closed = false; + private boolean isXRefStream; + /** * Flag to skip malformed or otherwise unparseable input where possible. */ @@ -755,16 +757,22 @@ public long getStartXref() } /** - * Determines it the trailer is a XRef stream or not. + * Determines if the trailer is a XRef stream or not. * * @return true if the trailer is a XRef stream */ public boolean isXRefStream() { - if (trailer != null) - { - return COSName.XREF.equals(trailer.getItem(COSName.TYPE)); - } - return false; + return isXRefStream; + } + + /** + * Sets isXRefStream to the given value. + * + * @param isXRefStreamValue the new value for isXRefStream + */ + public void setIsXRefStream(boolean isXRefStreamValue) + { + isXRefStream = isXRefStreamValue; } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java index 006bab5f70a..84a8031497a 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java @@ -353,6 +353,7 @@ protected void initialParse() throws IOException // -- parse xref if (pdfSource.peek() == X) { + document.setIsXRefStream(false); // xref table and trailer // use existing parser to parse xref table parseXrefTable(prev); @@ -376,6 +377,7 @@ protected void initialParse() throws IOException } else { + document.setIsXRefStream(true); // parse xref stream prev = parseXrefObjStream(prev); if (isLenient && prev > -1) From a286a66406c52faf37f772128e07ddbb2168f872 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Fri, 24 Jan 2014 18:07:56 +0000 Subject: [PATCH 0073/1017] PDFBOX-1862: reverted the changes from PDFBOX-1780 due to some regression git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1561096 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/pdfbox/pdfwriter/COSWriter.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java index f0c10a55754..d74c967ebef 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java @@ -1061,15 +1061,7 @@ else if( value instanceof COSObject ) } else { - if (subValue.isDirect()) - { - subValue.accept( this ); - } - else - { - addObjectToWrite( subValue ); - writeReference( subValue ); - } + subValue.accept( this ); } } else From 3cfba430e1763370e33c186dd12c19c7eac6653e Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Fri, 24 Jan 2014 21:26:47 +0000 Subject: [PATCH 0074/1017] PDFBOX-1861: add the transformation to the dash before painting as proposed by Tilman Hausherr and John Hewson git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1561193 13f79535-47bb-0310-9956-ffa450edef68 --- .../operator/pagedrawer/SetLineWidth.java | 5 --- .../util/operator/pagedrawer/StrokePath.java | 34 ++++++++++++++----- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/SetLineWidth.java b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/SetLineWidth.java index a7444f97b4f..e254caec019 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/SetLineWidth.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/SetLineWidth.java @@ -23,7 +23,6 @@ import org.apache.pdfbox.util.PDFOperator; import java.awt.BasicStroke; -import java.awt.Graphics2D; import java.io.IOException; /** @@ -45,10 +44,6 @@ public void process(PDFOperator operator, List arguments) throws IOExce { 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) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/StrokePath.java b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/StrokePath.java index 0baeaae3b92..50c38c88d79 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/StrokePath.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/operator/pagedrawer/StrokePath.java @@ -27,7 +27,6 @@ import org.apache.pdfbox.util.operator.OperatorProcessor; import java.awt.BasicStroke; -import java.awt.Graphics2D; import java.io.IOException; /** @@ -46,6 +45,7 @@ public class StrokePath extends OperatorProcessor /** * S stroke the path. + * * @param operator The operator that is being executed. * @param arguments List * @@ -56,24 +56,42 @@ public void process(PDFOperator operator, List arguments) throws IOExce ///dwilson 3/19/07 refactor try { - PageDrawer drawer = (PageDrawer)context; + PageDrawer drawer = (PageDrawer) context; - float lineWidth = (float)context.getGraphicsState().getLineWidth(); + float lineWidth = (float) context.getGraphicsState().getLineWidth(); Matrix ctm = context.getGraphicsState().getCurrentTransformationMatrix(); - if ( ctm != null && ctm.getXScale() > 0) + if (ctm != null && ctm.getXScale() > 0) { lineWidth = lineWidth * ctm.getXScale(); } - BasicStroke stroke = (BasicStroke)drawer.getStroke(); + if (lineWidth < 0.25) + { + lineWidth = 0.25f; + } + + BasicStroke stroke = (BasicStroke) drawer.getStroke(); if (stroke == null) { - drawer.setStroke( new BasicStroke( lineWidth ) ); + drawer.setStroke(new BasicStroke(lineWidth)); } else { - drawer.setStroke( new BasicStroke(lineWidth, stroke.getEndCap(), stroke.getLineJoin(), - stroke.getMiterLimit(), stroke.getDashArray(), stroke.getDashPhase()) ); + float phaseStart = stroke.getDashPhase(); + float[] dashArray = stroke.getDashArray(); + if (dashArray != null) + { + if (ctm != null && ctm.getXScale() > 0) + { + for (int i = 0; i < dashArray.length; ++i) + { + dashArray[i] *= ctm.getXScale(); + } + } + phaseStart *= ctm.getXScale(); + } + drawer.setStroke(new BasicStroke(lineWidth, stroke.getEndCap(), stroke.getLineJoin(), + stroke.getMiterLimit(), dashArray, phaseStart)); } drawer.strokePath(); } From 8bd22f665bc26d8fde100a773c4b0d52f70bd829 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Sun, 26 Jan 2014 17:45:06 +0000 Subject: [PATCH 0075/1017] PDFBOX-1822: revert changes commited in r1560498 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1561523 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/pdfbox/cos/COSDocument.java | 20 ++++++------------- .../pdfparser/NonSequentialPDFParser.java | 2 -- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java index 5a70692f6f7..aa8cc1cea46 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java @@ -93,8 +93,6 @@ public class COSDocument extends COSBase private boolean closed = false; - private boolean isXRefStream; - /** * Flag to skip malformed or otherwise unparseable input where possible. */ @@ -757,22 +755,16 @@ public long getStartXref() } /** - * Determines if the trailer is a XRef stream or not. + * Determines it the trailer is a XRef stream or not. * * @return true if the trailer is a XRef stream */ public boolean isXRefStream() { - return isXRefStream; - } - - /** - * Sets isXRefStream to the given value. - * - * @param isXRefStreamValue the new value for isXRefStream - */ - public void setIsXRefStream(boolean isXRefStreamValue) - { - isXRefStream = isXRefStreamValue; + if (trailer != null) + { + return COSName.XREF.equals(trailer.getItem(COSName.TYPE)); + } + return false; } } diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java index 84a8031497a..006bab5f70a 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/NonSequentialPDFParser.java @@ -353,7 +353,6 @@ protected void initialParse() throws IOException // -- parse xref if (pdfSource.peek() == X) { - document.setIsXRefStream(false); // xref table and trailer // use existing parser to parse xref table parseXrefTable(prev); @@ -377,7 +376,6 @@ protected void initialParse() throws IOException } else { - document.setIsXRefStream(true); // parse xref stream prev = parseXrefObjStream(prev); if (isLenient && prev > -1) From c493d2d73309ee2ad8383415492bb94930d79cad Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 Jan 2014 17:44:52 +0000 Subject: [PATCH 0076/1017] prepare 1.8.3 release git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1561764 13f79535-47bb-0310-9956-ffa450edef68 --- RELEASE-NOTES.txt | 106 ++++++++++++++++------------------------------ pdfbox/build.xml | 2 +- 2 files changed, 38 insertions(+), 70 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 8e24a9123fc..5fadcbb6c27 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -5,7 +5,7 @@ Introduction The Apache PDFBox library is an open source Java tool for working with PDF documents. -This is an incremental bugfix release based on the earlier 1.8.2 release. It +This is an incremental bugfix release based on the earlier 1.8.3 release. It contains a couple of fixes and small improvements. For more details on all fixes included in this release, please refer to the following @@ -13,77 +13,45 @@ issues on the PDFBox issue tracker at https://issues.apache.org/jira/browse/PDFB Bug Fixes -[PDFBOX-465] - invalid date formats -[PDFBOX-823] - NullPointerException in DateConverter.toISO8601(DateConverter.java:221) -[PDFBOX-837] - Wrong RevisionNumber when disabling all permissions and using 128bit - encryption -[PDFBOX-1219] - org.apache.jempbox.impl.DateConverter unable to parse correct date - value -[PDFBOX-1342] - Tags not fully preserved when merging PDFs. -[PDFBOX-1412] - NullPointerException when getting fields from a PDF file -[PDFBOX-1564] - Extending COSName to produce PDF/A with correct OutputIntents -[PDFBOX-1576] - StackOverflowError [COSDictionary.toString(COSDictionary.java:1418)] -[PDFBOX-1606] - NonSequentialPDFParser produces garbage text in document info -[PDFBOX-1607] - StringIndexOutOfBoundsException in PDFParser -[PDFBOX-1617] - Null pointer exception -[PDFBOX-1622] - TextNormalize init not thread-safe, may lead to infinite loop -[PDFBOX-1627] - Exception in thread "main" java.lang.NullPointerException -[PDFBOX-1629] - Null PointerException -[PDFBOX-1630] - An interesting Exception error -[PDFBOX-1631] - Group Exception -[PDFBOX-1632] - Exception with validation -[PDFBOX-1633] - DateConverter needs to work -[PDFBOX-1638] - PDCcitt doesn't use color space -[PDFBOX-1639] - Infinite loop with PDFParser used by tika. -[PDFBOX-1643] - Check for missing validation processes does not work properly in - Preflight -[PDFBOX-1653] - Fix pdfbox eating up big chunks of memory for identical CID mappings -[PDFBOX-1655] - Wasted work (or incorrect behavior) in - PDCIDFontType2Font.readCIDToGIDMapping -[PDFBOX-1657] - glyph contours missing -[PDFBOX-1663] - Hello World using a TrueType font ArrayIndexOutOfBoundsException -[PDFBOX-1674] - Preflight doesn't correctly parse PDF if obj identifier not followed by - line terminator -[PDFBOX-1681] - java.lang.IllegalArgumentException: Color parameter outside of expected - range: Red -[PDFBOX-1692] - java.lang.OutOfMemoryError: Java heap space -[PDFBOX-1694] - Bug in org.apache.pdfbox.io.Ascii85InputStream -[PDFBOX-1696] - Bug in org.apache.pdfbox.io.Ascii85OutputStream -[PDFBOX-1714] - Merging PDFs results in java.io.IOException: expected='R' actual='0' -[PDFBOX-1719] - NPE while signing PDF - acroform without fields -[PDFBOX-1730] - Image in PDF has extremely different colors when rendered -[PDFBOX-1735] - Convert page pdf to image -[PDFBOX-1737] - Skip whitespaces when resolving a XRef -[PDFBOX-1743] - OutOfMemoryError in fontbox -[PDFBOX-1749] - Out of memory exception when parsing TTF file -[PDFBOX-1753] - The font gets gibbrish when adding a line of text to an existing PDF with - a table -[PDFBOX-1758] - Preflight doesn't report Filespec dictionary that refers (indirectly) to an - EmbeddedFile entry in some cases -[PDFBOX-1764] - PDFBox takes ages to render page 2 of the attached PDF -[PDFBOX-1768] - cannot build last source code -[PDFBOX-1778] - Rounding issue in generated PDF file -[PDFBOX-1780] - previous revision is damaged after signing +[PDFBOX-161] - java.util.EmptyStackException from PDFTextStripper.writeText +[PDFBOX-940] - [pdmodel.font.PDFont] Error: Could not parse predefined CMAP file for + 'PDFXC-Indentity0-0' +[PDFBOX-1302] - Got ArrayIndexOutOfBoundsException in parsing a Chinese ttf file. +[PDFBOX-1541] - expected='endstream' actual='' failure to parse +[PDFBOX-1585] - org.apache.pdfbox.util.PDFTextStripper.getText() causes thread to block + indefinitely +[PDFBOX-1625] - java.lang.IndexOutOfBoundsException at writing PDF file +[PDFBOX-1668] - Loading a Russian PDF never finishes +[PDFBOX-1679] - java.io.IOException: Error: Expected an integer type, actual='f' +[PDFBOX-1725] - Character rendered at wrong position +[PDFBOX-1733] - Rectangles have one rounded edge in rendered image only +[PDFBOX-1763] - Exception caused by "Invalid ICC Profile Data" +[PDFBOX-1770] - ExtractText gets all "?" when pdf 's font is instance of PDType1Font +[PDFBOX-1777] - memory leak in org.apache.pdfbox.cos.COSDocument +[PDFBOX-1789] - NullPointerException at PDPageContentStream.setFont +[PDFBOX-1794] - Rendering Problem with Type 3 Fonts +[PDFBOX-1796] - Infiniteloop BaseParser.java:1010 +[PDFBOX-1799] - NullPointerException when constructing a PDJPeg using a BufferedImage +[PDFBOX-1802] - COSDictionary in COSArray setDirect(true) but dic written indirect +[PDFBOX-1804] - PDFTextStripper Issue related to word positions not correctly being parsed +[PDFBOX-1808] - PDFTextStripper.getText - hight memory usage +[PDFBOX-1810] - PDFToImage: Image of pdf is resized and drawn multiple times at top of + output image +[PDFBOX-1812] - Illegal characters in XML output +[PDFBOX-1818] - Push back buffer is full error +[PDFBOX-1829] - PDF Extract Image Pixelmap Issue +[PDFBOX-1830] - Grey background rectangle rendered at different position +[PDFBOX-1861] - Line is incorrectly dashed +[PDFBOX-1862] - Incomplete signature creation (regression in 1.8.3 with PDFBOX-1780) Improvement -[PDFBOX-1213] - Adding style information to the PDF to HTML converter -[PDFBOX-1564] - Extending COSName to produce PDF/A with correct OutputIntents -[PDFBOX-1613] - The ability to inject the time/random component into the COSWriter process - to write a PDF document allows some advanced signature creation scenarios - where the signature is generated on a separate server that does not hold the - full PDF document. -[PDFBOX-1687] - add dispose() in pdfbox\pdmodel\PDPage.convertToImage() -[PDFBOX-1690] - Add description to embedded file -[PDFBOX-1702] - Performance improvement in PDPageContentStream.drawString -[PDFBOX-1744] - Be resilient to PDFs with missing version info -[PDFBOX-1782] - Add getMaxLength() and setMaxLength() methods to PDTextbox - -New Feature - -[PDFBOX-1054] - DateConverter: allow for external adding of potential date parsing formats -[PDFBOX-1766] - [PATCH] Visible Signature using PDFbox -[PDFBOX-1540] - Add XML output option to preflight +[PDFBOX-1685] - Verify interpretation of rdf:about for PDF/A +[PDFBOX-1707] - Add dispose() when done with graphics +[PDFBOX-1769] - Fix crash on invalid xref +[PDFBOX-1815] - Suggestion: close files in COSStream +[PDFBOX-1820] - Suggestion: close streams in PDIndex and PDJpeg +[PDFBOX-1839] - PDFImageWriter default BufferedImage type makes output colors look poor Release Contents ---------------- diff --git a/pdfbox/build.xml b/pdfbox/build.xml index 13faf2863d1..ed11bbb1584 100644 --- a/pdfbox/build.xml +++ b/pdfbox/build.xml @@ -28,7 +28,7 @@ - + From c5f49d4c0e2372ff5323b101f6f4500d7daac775 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 Jan 2014 17:57:56 +0000 Subject: [PATCH 0077/1017] [maven-release-plugin] prepare release 1.8.4 git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1561766 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index 8c7e001cb70..9b6b54240ed 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index f5c703be89b..3d74f33ad6c 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index e044c99d2e3..504d17045fa 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 845bbcc0bbf..55da8e52153 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index 9d3504ea3dd..5de5e59a15c 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 59a305981a8..3c44fdf6584 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 8e61b5280c3..c14e986020d 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.4/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.4/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/tags/1.8.4/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index a5c5d39d2bf..0d829218834 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 1364ae63054..00e4647705b 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.4 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.4 - http://svn.apache.org/viewvc/pdfbox/branches/1.8 + http://svn.apache.org/viewvc/pdfbox/tags/1.8.4 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 2e654bf6ca8..64fad1bdcdf 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index 2c25caa46eb..e2be6385473 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index c861c62085d..ffa74266464 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 27afb68a66c..8450bec58a2 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4-SNAPSHOT + 1.8.4 ../parent/pom.xml From 781e52872bc151f75840bf578f1fd34e5a603cf3 Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Mon, 27 Jan 2014 17:58:05 +0000 Subject: [PATCH 0078/1017] [maven-release-plugin] prepare for next development iteration git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1561768 13f79535-47bb-0310-9956-ffa450edef68 --- ant/pom.xml | 2 +- app/pom.xml | 2 +- examples/pom.xml | 2 +- fontbox/pom.xml | 2 +- jempbox/pom.xml | 2 +- lucene/pom.xml | 2 +- parent/pom.xml | 8 ++++---- pdfbox/pom.xml | 2 +- pom.xml | 8 ++++---- preflight-app/pom.xml | 2 +- preflight/pom.xml | 2 +- war/pom.xml | 2 +- xmpbox/pom.xml | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ant/pom.xml b/ant/pom.xml index 9b6b54240ed..2afa0ac80e9 100644 --- a/ant/pom.xml +++ b/ant/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml diff --git a/app/pom.xml b/app/pom.xml index 3d74f33ad6c..e1cbbc1b7c9 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml diff --git a/examples/pom.xml b/examples/pom.xml index 504d17045fa..5edd5978399 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml diff --git a/fontbox/pom.xml b/fontbox/pom.xml index 55da8e52153..5f6a1564a37 100644 --- a/fontbox/pom.xml +++ b/fontbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml diff --git a/jempbox/pom.xml b/jempbox/pom.xml index 5de5e59a15c..5d5a76943af 100644 --- a/jempbox/pom.xml +++ b/jempbox/pom.xml @@ -21,7 +21,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml diff --git a/lucene/pom.xml b/lucene/pom.xml index 3c44fdf6584..a681425c849 100644 --- a/lucene/pom.xml +++ b/lucene/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index c14e986020d..69d4706278a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -29,7 +29,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT pom PDFBox parent @@ -266,8 +266,8 @@ - scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/1.8.4/pdfbox-parent - scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/1.8.4/pdfbox-parent - http://svn.apache.org/viewvc/maven/pom/tags/1.8.4/pdfbox-parent + scm:svn:http://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + scm:svn:https://svn.apache.org/repos/asf/maven/pom/branches/1.8/pdfbox-parent + http://svn.apache.org/viewvc/maven/pom/branches/1.8/pdfbox-parent diff --git a/pdfbox/pom.xml b/pdfbox/pom.xml index 0d829218834..db1db8e312d 100644 --- a/pdfbox/pom.xml +++ b/pdfbox/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml diff --git a/pom.xml b/pom.xml index 00e4647705b..3202bb3b858 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT parent/pom.xml @@ -34,12 +34,12 @@ - scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/1.8.4 + scm:svn:http://svn.apache.org/repos/asf/pdfbox/branches/1.8 - scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/1.8.4 + scm:svn:https://svn.apache.org/repos/asf/pdfbox/branches/1.8 - http://svn.apache.org/viewvc/pdfbox/tags/1.8.4 + http://svn.apache.org/viewvc/pdfbox/branches/1.8 diff --git a/preflight-app/pom.xml b/preflight-app/pom.xml index 64fad1bdcdf..0878ec48733 100644 --- a/preflight-app/pom.xml +++ b/preflight-app/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml diff --git a/preflight/pom.xml b/preflight/pom.xml index e2be6385473..3a8def755d3 100644 --- a/preflight/pom.xml +++ b/preflight/pom.xml @@ -26,7 +26,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml diff --git a/war/pom.xml b/war/pom.xml index ffa74266464..b0cf3ef4c38 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -23,7 +23,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml diff --git a/xmpbox/pom.xml b/xmpbox/pom.xml index 8450bec58a2..3deeff1f56f 100644 --- a/xmpbox/pom.xml +++ b/xmpbox/pom.xml @@ -27,7 +27,7 @@ org.apache.pdfbox pdfbox-parent - 1.8.4 + 1.8.5-SNAPSHOT ../parent/pom.xml From cc68e5246a9d3a12aeb8df5fa81406e52edc203e Mon Sep 17 00:00:00 2001 From: Andreas Lehmkuhler Date: Fri, 31 Jan 2014 18:49:51 +0000 Subject: [PATCH 0079/1017] PDFBOX-1860: don't escape formatting close tags as proposed by Cheng Leong git-svn-id: https://svn.apache.org/repos/asf/pdfbox/branches/1.8@1563216 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/pdfbox/util/PDFText2HTML.java | 2 +- .../apache/pdfbox/util/TestPDFText2HTML.java | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java index 4e8aeea52de..fe7adcdff72 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/util/PDFText2HTML.java @@ -215,7 +215,7 @@ protected void writeString(String chars) throws IOException @Override protected void writeParagraphEnd() throws IOException { - writeString(fontState.clear()); + super.writeString(fontState.clear()); // do not escape HTML super.writeParagraphEnd(); } diff --git a/pdfbox/src/test/java/org/apache/pdfbox/util/TestPDFText2HTML.java b/pdfbox/src/test/java/org/apache/pdfbox/util/TestPDFText2HTML.java index 77257cd90eb..13e11e19543 100644 --- a/pdfbox/src/test/java/org/apache/pdfbox/util/TestPDFText2HTML.java +++ b/pdfbox/src/test/java/org/apache/pdfbox/util/TestPDFText2HTML.java @@ -30,16 +30,16 @@ public class TestPDFText2HTML extends TestCase { - private PDDocument createDocument() throws IOException { + private PDDocument createDocument(String title, PDFont font, String text) throws IOException { PDDocument doc = new PDDocument(); + doc.getDocumentInformation().setTitle(title); PDPage page = new PDPage(); doc.addPage(page); - PDFont font = PDType1Font.HELVETICA; PDPageContentStream contentStream = new PDPageContentStream(doc, page); contentStream.beginText(); contentStream.setFont(font, 12); contentStream.moveTextPositionByAmount(100, 700); - contentStream.drawString(""); + contentStream.drawString(text); contentStream.endText(); contentStream.close(); return doc; @@ -47,15 +47,23 @@ private PDDocument createDocument() throws IOException { public void testEscapeTitle() throws IOException { PDFTextStripper stripper = new PDFText2HTML("UTF-8"); - PDDocument doc = createDocument(); - doc.getDocumentInformation().setTitle("