From 4272abd4ee7dff41c98bf5741ff9e9da1ac99927 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Mon, 16 Nov 2020 09:22:25 +1100 Subject: [PATCH 001/165] Fix integer overflow in COSParser (#201) --- .../src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java index f3f367546..9945e904f 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java @@ -1968,7 +1968,7 @@ protected boolean parseXrefTable( long startByteOffset ) throws IOException { try { - int currOffset = Integer.parseInt(splitString[0]); + long currOffset = Long.parseLong(splitString[0]); int currGenID = Integer.parseInt(splitString[1]); COSObjectKey objKey = new COSObjectKey(currObjID, currGenID); xrefTrailerResolver.setXRef(objKey, currOffset); From 02b00957b8c52c1a37f58d1a535166e9c96c6308 Mon Sep 17 00:00:00 2001 From: ngochai84 Date: Thu, 3 Dec 2020 12:53:05 +0700 Subject: [PATCH 002/165] #203 Reduce seek file when parsing document (#204) --- .../tom_roush/pdfbox/pdfparser/COSParser.java | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java index 9945e904f..7dcb307c5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java @@ -1364,7 +1364,16 @@ private void bfSearchForObjects() throws IOException do { source.seek(currentOffset); - if (isString(string)) + long findOffset = findString(string); + if (findOffset >= 0) + { + currentOffset = findOffset - string.length; + } + else + { + break; + } + if (findOffset >= 0) { long tempOffset = currentOffset - 1; source.seek(tempOffset); @@ -1410,7 +1419,7 @@ private void bfSearchForObjects() throws IOException } } } - currentOffset++; + currentOffset = findOffset + 1; } while (!source.isEOF()); // reestablish origin position @@ -1743,17 +1752,55 @@ private boolean isString(byte[] string) throws IOException */ private boolean isString(char[] string) throws IOException { - boolean bytesMatching = true; long originOffset = source.getPosition(); for (char c : string) { if (source.read() != c) { - bytesMatching = false; + source.seek(originOffset); + return false; } } source.seek(originOffset); - return bytesMatching; + return true; + } + + /** + * find matched string, use this function to reduce seek file + * @param string the bytes of the string to look for + * @return last position of string found + * @throws IOException + */ + private long findString(char[] string) throws IOException + { + int i = 0; + char c = string[0]; + do + { + while (source.read() != c && !source.isEOF()) + { + continue; + } + i++; + c = string[i]; + while (!source.isEOF() && source.read() == c && i < string.length - 1) + { + i++; + c = string[i]; + } + if (i == string.length - 1) + { + return source.getPosition(); + } + else + { + i = 0; + c = string[0]; + } + } + while (!source.isEOF()); + + return -1; } /** From 72e629302cc74071f4630e2840ae7dd0560d4b41 Mon Sep 17 00:00:00 2001 From: Tom Roush Date: Thu, 3 Dec 2020 21:38:46 -0600 Subject: [PATCH 003/165] Remove permissions from library manifest (#234) --- library/src/androidTest/AndroidManifest.xml | 6 ++++++ library/src/main/AndroidManifest.xml | 7 ++----- 2 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 library/src/androidTest/AndroidManifest.xml diff --git a/library/src/androidTest/AndroidManifest.xml b/library/src/androidTest/AndroidManifest.xml new file mode 100644 index 000000000..dd12fc016 --- /dev/null +++ b/library/src/androidTest/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml index 4a3b6b3ce..105d19d96 100644 --- a/library/src/main/AndroidManifest.xml +++ b/library/src/main/AndroidManifest.xml @@ -1,6 +1,3 @@ - - - - + + \ No newline at end of file From d5e4009ae351c591ec20f467d7ad793b24694068 Mon Sep 17 00:00:00 2001 From: Tom Roush Date: Sun, 17 Jan 2021 21:27:51 -0600 Subject: [PATCH 004/165] Bump Android and Gradle versions (#240) --- .travis.yml | 4 ++-- build.gradle | 2 +- gradle.properties | 7 +++---- gradle/wrapper/gradle-wrapper.properties | 4 ++-- library/build.gradle | 3 +-- sample/build.gradle | 1 - 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index c27a2fb9b..cd9433905 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,9 @@ android: - tools - platform-tools - - build-tools-28.0.3 + - build-tools-29.0.2 - android-21 - - android-28 + - android-29 - sys-img-armeabi-v7a-android-21 - extra-android-support diff --git a/build.gradle b/build.gradle index 4f7f969cc..abc7341b1 100755 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.1' + classpath 'com.android.tools.build:gradle:4.1.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' } } diff --git a/gradle.properties b/gradle.properties index ab8a9e37f..594c73c33 100755 --- a/gradle.properties +++ b/gradle.properties @@ -14,10 +14,9 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m org.gradle.parallel=true -VERSION_NAME=2.0.0.0-SNAPSHOT +VERSION_NAME=1.8.10.2-SNAPSHOT VERSION_CODE=1 ANDROID_BUILD_MIN_SDK_VERSION=9 -ANDROID_BUILD_TARGET_SDK_VERSION=28 -ANDROID_BUILD_SDK_VERSION=28 -ANDROID_BUILD_TOOLS_VERSION=28.0.3 \ No newline at end of file +ANDROID_BUILD_TARGET_SDK_VERSION=29 +ANDROID_BUILD_SDK_VERSION=29 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index feaaf6d87..dbc6c61aa 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jan 17 20:41:09 CST 2019 +#Sat Jan 02 14:34:58 CST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/library/build.gradle b/library/build.gradle index c52492558..f25521741 100755 --- a/library/build.gradle +++ b/library/build.gradle @@ -10,7 +10,6 @@ ext { android { compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) - buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION compileOptions.encoding = 'UTF-8' defaultConfig { @@ -89,7 +88,7 @@ dependencies { api 'com.madgag.spongycastle:prov:1.58.0.0' // Test dependencies - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13.1' androidTestImplementation 'com.android.support.test:runner:1.0.2' } diff --git a/sample/build.gradle b/sample/build.gradle index 67d83d679..61a3a2f41 100755 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -2,7 +2,6 @@ apply plugin: 'com.android.application' android { compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) - buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION defaultConfig { applicationId 'com.tom_roush.pdfbox.sample' From 070819c9eea28316535b9ce1386a66f711298fc8 Mon Sep 17 00:00:00 2001 From: Bruno Coelho <4brunu@users.noreply.github.com> Date: Fri, 5 Mar 2021 01:56:15 +0000 Subject: [PATCH 005/165] Migrate from spongycastle to bouncycastle (#244) --- gradle.properties | 2 +- library/build.gradle | 5 +- .../encryption/PublicKeySecurityHandler.java | 57 ++++++++++--------- .../encryption/SecurityHandlerFactory.java | 3 +- .../tom_roush/pdfbox/sample/MainActivity.java | 2 +- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/gradle.properties b/gradle.properties index 594c73c33..18ea50285 100755 --- a/gradle.properties +++ b/gradle.properties @@ -17,6 +17,6 @@ org.gradle.parallel=true VERSION_NAME=1.8.10.2-SNAPSHOT VERSION_CODE=1 -ANDROID_BUILD_MIN_SDK_VERSION=9 +ANDROID_BUILD_MIN_SDK_VERSION=14 ANDROID_BUILD_TARGET_SDK_VERSION=29 ANDROID_BUILD_SDK_VERSION=29 \ No newline at end of file diff --git a/library/build.gradle b/library/build.gradle index f25521741..fad006bec 100755 --- a/library/build.gradle +++ b/library/build.gradle @@ -83,9 +83,8 @@ tasks.withType(Test) { } dependencies { - api 'com.madgag.spongycastle:core:1.58.0.0' - api 'com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0' - api 'com.madgag.spongycastle:prov:1.58.0.0' + api "org.bouncycastle:bcprov-jdk15on:1.58" + api "org.bouncycastle:bcpkix-jdk15on:1.58" // Test dependencies testImplementation 'junit:junit:4.13.1' diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java index abc283853..c96d1c4f0 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java @@ -44,31 +44,31 @@ import com.tom_roush.pdfbox.cos.COSString; import com.tom_roush.pdfbox.pdmodel.PDDocument; -import org.spongycastle.asn1.ASN1InputStream; -import org.spongycastle.asn1.ASN1ObjectIdentifier; -import org.spongycastle.asn1.ASN1Primitive; -import org.spongycastle.asn1.ASN1Set; -import org.spongycastle.asn1.DEROctetString; -import org.spongycastle.asn1.DEROutputStream; -import org.spongycastle.asn1.DERSet; -import org.spongycastle.asn1.cms.ContentInfo; -import org.spongycastle.asn1.cms.EncryptedContentInfo; -import org.spongycastle.asn1.cms.EnvelopedData; -import org.spongycastle.asn1.cms.IssuerAndSerialNumber; -import org.spongycastle.asn1.cms.KeyTransRecipientInfo; -import org.spongycastle.asn1.cms.RecipientIdentifier; -import org.spongycastle.asn1.cms.RecipientInfo; -import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers; -import org.spongycastle.asn1.x509.AlgorithmIdentifier; -import org.spongycastle.asn1.x509.TBSCertificateStructure; -import org.spongycastle.cert.X509CertificateHolder; -import org.spongycastle.cms.CMSEnvelopedData; -import org.spongycastle.cms.CMSException; -import org.spongycastle.cms.KeyTransRecipientId; -import org.spongycastle.cms.RecipientId; -import org.spongycastle.cms.RecipientInformation; -import org.spongycastle.cms.jcajce.JceKeyTransEnvelopedRecipient; -import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Set; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DEROutputStream; +import org.bouncycastle.asn1.DERSet; +import org.bouncycastle.asn1.cms.ContentInfo; +import org.bouncycastle.asn1.cms.EncryptedContentInfo; +import org.bouncycastle.asn1.cms.EnvelopedData; +import org.bouncycastle.asn1.cms.IssuerAndSerialNumber; +import org.bouncycastle.asn1.cms.KeyTransRecipientInfo; +import org.bouncycastle.asn1.cms.RecipientIdentifier; +import org.bouncycastle.asn1.cms.RecipientInfo; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.TBSCertificateStructure; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cms.CMSEnvelopedData; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.KeyTransRecipientId; +import org.bouncycastle.cms.RecipientId; +import org.bouncycastle.cms.RecipientInformation; +import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient; +import org.bouncycastle.jce.provider.BouncyCastleProvider; /** * This class implements the public key security handler described in the PDF specification. @@ -173,7 +173,7 @@ public void prepareForDecryption(PDEncryption encryption, COSArray documentIDArr { foundRecipient = true; PrivateKey privateKey = (PrivateKey) material.getPrivateKey(); - envelopedData = ri.getContent(new JceKeyTransEnvelopedRecipient(privateKey).setProvider("SC")); + envelopedData = ri.getContent(new JceKeyTransEnvelopedRecipient(privateKey).setProvider(BouncyCastleProvider.PROVIDER_NAME)); break; } j++; @@ -287,6 +287,7 @@ public void prepareDocumentForEncryption(PDDocument doc) throws IOException } try { + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); Security.addProvider(new BouncyCastleProvider()); PDEncryption dictionary = doc.getEncryption(); @@ -416,8 +417,8 @@ private ASN1Primitive createDERForRecipient(byte[] in, X509Certificate cert) try { apg = AlgorithmParameterGenerator.getInstance(algorithm); - keygen = KeyGenerator.getInstance(algorithm, "SC"); - cipher = Cipher.getInstance(algorithm, "SC"); + keygen = KeyGenerator.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME); + cipher = Cipher.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME); } catch (NoSuchAlgorithmException e) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandlerFactory.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandlerFactory.java index 2c40faeb1..128b64f40 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandlerFactory.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandlerFactory.java @@ -22,7 +22,7 @@ import java.util.HashMap; import java.util.Map; -import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.provider.BouncyCastleProvider; /** * Manages security handlers for the application. @@ -40,6 +40,7 @@ public final class SecurityHandlerFactory static { + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME); Security.addProvider(new BouncyCastleProvider()); } diff --git a/sample/src/main/java/com/tom_roush/pdfbox/sample/MainActivity.java b/sample/src/main/java/com/tom_roush/pdfbox/sample/MainActivity.java index 0a5fd717e..c4c9f0a1d 100644 --- a/sample/src/main/java/com/tom_roush/pdfbox/sample/MainActivity.java +++ b/sample/src/main/java/com/tom_roush/pdfbox/sample/MainActivity.java @@ -46,7 +46,7 @@ import com.tom_roush.pdfbox.text.PDFTextStripper; import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; -import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.provider.BouncyCastleProvider; public class MainActivity extends Activity { File root; From 85ef5aba36476d8f0d894f9437feb23738551123 Mon Sep 17 00:00:00 2001 From: Bruno Coelho <4brunu@users.noreply.github.com> Date: Sat, 6 Mar 2021 03:04:49 +0000 Subject: [PATCH 006/165] Update bouncycastle to the last version (#245) --- library/build.gradle | 4 ++-- .../tom_roush/pdfbox/exceptions/COSVisitorException.java | 2 +- .../main/java/com/tom_roush/pdfbox/multipdf/Overlay.java | 2 +- .../pdfbox/pdfparser/PDFObjectStreamParser.java | 2 +- .../tom_roush/pdfbox/pdfparser/PDFXrefStreamParser.java | 2 +- .../java/com/tom_roush/pdfbox/pdmodel/PDDocument.java | 2 +- .../main/java/com/tom_roush/pdfbox/pdmodel/PDPage.java | 4 ++-- .../tom_roush/pdfbox/pdmodel/common/PDTextStream.java | 4 ++-- .../pdmodel/encryption/PublicKeySecurityHandler.java | 9 +++++---- .../pdfbox/pdmodel/encryption/SecurityHandler.java | 2 +- .../com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotation.java | 2 +- .../java/com/tom_roush/pdfbox/pdmodel/fdf/FDFField.java | 4 ++-- .../com/tom_roush/pdfbox/pdmodel/fdf/FDFJavaScript.java | 4 ++-- .../tom_roush/pdfbox/pdmodel/font/PDCIDFontType2.java | 2 +- .../java/com/tom_roush/pdfbox/pdmodel/font/PDFont.java | 2 +- .../tom_roush/pdfbox/pdmodel/font/PDType3CharProc.java | 2 +- .../pdfbox/pdmodel/graphics/form/PDFormXObject.java | 2 +- .../pdfbox/pdmodel/graphics/pattern/PDTilingPattern.java | 2 +- .../pdmodel/interactive/action/PDActionJavaScript.java | 2 +- .../interactive/annotation/PDAnnotationMarkup.java | 2 +- .../pdfbox/pdmodel/interactive/form/PDVariableText.java | 2 +- .../tom_roush/pdfbox/pdmodel/interactive/form/PDXFA.java | 4 ++-- .../pdfbox/pdmodel/interactive/form/PDXFAResource.java | 4 ++-- 23 files changed, 34 insertions(+), 33 deletions(-) diff --git a/library/build.gradle b/library/build.gradle index fad006bec..e2b4c9978 100755 --- a/library/build.gradle +++ b/library/build.gradle @@ -83,8 +83,8 @@ tasks.withType(Test) { } dependencies { - api "org.bouncycastle:bcprov-jdk15on:1.58" - api "org.bouncycastle:bcpkix-jdk15on:1.58" + api "org.bouncycastle:bcprov-jdk15on:1.68" + api "org.bouncycastle:bcpkix-jdk15on:1.68" // Test dependencies testImplementation 'junit:junit:4.13.1' diff --git a/library/src/main/java/com/tom_roush/pdfbox/exceptions/COSVisitorException.java b/library/src/main/java/com/tom_roush/pdfbox/exceptions/COSVisitorException.java index 1f6b7e914..7bcd38e11 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/exceptions/COSVisitorException.java +++ b/library/src/main/java/com/tom_roush/pdfbox/exceptions/COSVisitorException.java @@ -22,7 +22,7 @@ * @author Michael Traut * @version $Revision: 1.6 $ */ -public class COSVisitorException extends WrappedException +public class COSVisitorException extends Exception { /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/multipdf/Overlay.java b/library/src/main/java/com/tom_roush/pdfbox/multipdf/Overlay.java index 3fb783f9f..ee77ede84 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/multipdf/Overlay.java +++ b/library/src/main/java/com/tom_roush/pdfbox/multipdf/Overlay.java @@ -280,7 +280,7 @@ private COSStream createContentStream(COSBase contents) throws IOException OutputStream out = concatStream.createOutputStream(COSName.FLATE_DECODE); for (COSStream contentStream : contentStreams) { - InputStream in = contentStream.getUnfilteredStream(); + InputStream in = contentStream.createInputStream(); byte[] buf = new byte[2048]; int n; while ((n = in.read(buf)) > 0) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFObjectStreamParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFObjectStreamParser.java index b144a2ddf..cef71b870 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFObjectStreamParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFObjectStreamParser.java @@ -46,7 +46,7 @@ public class PDFObjectStreamParser extends BaseParser */ public PDFObjectStreamParser(COSStream stream, COSDocument document) throws IOException { - super(new InputStreamSource(stream.getUnfilteredStream())); + super(new InputStreamSource(stream.createInputStream())); this.document = document; this.stream = stream; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXrefStreamParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXrefStreamParser.java index 8676d848a..209cd04be 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXrefStreamParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXrefStreamParser.java @@ -52,7 +52,7 @@ public class PDFXrefStreamParser extends BaseParser public PDFXrefStreamParser(COSStream stream, COSDocument document, XrefTrailerResolver resolver) throws IOException { - super(new InputStreamSource(stream.getUnfilteredStream())); + super(new InputStreamSource(stream.createInputStream())); this.document = document; this.stream = stream; this.xrefTrailerResolver = resolver; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocument.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocument.java index ec73cddcf..a5fa44e52 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocument.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocument.java @@ -458,7 +458,7 @@ private void prepareNonVisibleSignature(PDSignatureField signatureField, PDAcroF // Create empty visual appearance stream COSStream apsStream = getDocument().createCOSStream(); - apsStream.createUnfilteredStream().close(); + apsStream.createOutputStream().close(); PDAppearanceStream aps = new PDAppearanceStream(apsStream); COSDictionary cosObject = (COSDictionary) aps.getCOSObject(); cosObject.setItem(COSName.SUBTYPE, COSName.FORM); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPage.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPage.java index e993cff1f..8bd6e1a76 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPage.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPage.java @@ -141,7 +141,7 @@ public InputStream getContents() throws IOException COSBase base = page.getDictionaryObject(COSName.CONTENTS); if (base instanceof COSStream) { - return ((COSStream) base).getUnfilteredStream(); + return ((COSStream) base).createInputStream(); } else if (base instanceof COSArray && ((COSArray)base).size() > 0) { @@ -151,7 +151,7 @@ else if (base instanceof COSArray && ((COSArray)base).size() > 0) for (int i = 0; i < streams.size(); i++) { COSStream stream = (COSStream) streams.getObject(i); - inputStreams.add(stream.getUnfilteredStream()); + inputStreams.add(stream.createInputStream()); inputStreams.add(new ByteArrayInputStream(delimiter)); } return new SequenceInputStream(Collections.enumeration(inputStreams)); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDTextStream.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDTextStream.java index aefd47896..4250742a0 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDTextStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDTextStream.java @@ -119,7 +119,7 @@ public String getAsString() throws IOException return string.getString(); } ByteArrayOutputStream out = new ByteArrayOutputStream(); - InputStream is = stream.getUnfilteredStream(); + InputStream is = stream.createInputStream(); IOUtils.copy(is, out); IOUtils.closeQuietly(is); return new String(out.toByteArray(), "ISO-8859-1"); @@ -142,7 +142,7 @@ public InputStream getAsStream() throws IOException } else { - retval = stream.getUnfilteredStream(); + retval = stream.createInputStream(); } return retval; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java index c96d1c4f0..fbdf11031 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java @@ -44,12 +44,13 @@ import com.tom_roush.pdfbox.cos.COSString; import com.tom_roush.pdfbox.pdmodel.PDDocument; +import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1OutputStream; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.DEROctetString; -import org.bouncycastle.asn1.DEROutputStream; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.asn1.cms.ContentInfo; import org.bouncycastle.asn1.cms.EncryptedContentInfo; @@ -60,7 +61,7 @@ import org.bouncycastle.asn1.cms.RecipientInfo; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; -import org.bouncycastle.asn1.x509.TBSCertificateStructure; +import org.bouncycastle.asn1.x509.TBSCertificate; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cms.CMSEnvelopedData; import org.bouncycastle.cms.CMSException; @@ -396,7 +397,7 @@ private byte[][] computeRecipientsField(byte[] seed) throws GeneralSecurityExcep ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DEROutputStream k = new DEROutputStream(baos); + ASN1OutputStream k = ASN1OutputStream.create(baos, ASN1Encoding.DER); k.writeObject(obj); @@ -460,7 +461,7 @@ private KeyTransRecipientInfo computeRecipientInfo(X509Certificate x509certifica BadPaddingException, IllegalBlockSizeException { ASN1InputStream input = new ASN1InputStream(x509certificate.getTBSCertificate()); - TBSCertificateStructure certificate = TBSCertificateStructure.getInstance(input.readObject()); + TBSCertificate certificate = TBSCertificate.getInstance(input.readObject()); input.close(); AlgorithmIdentifier algorithmId = certificate.getSubjectPublicKeyInfo().getAlgorithm(); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandler.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandler.java index 3ed0b1d76..b74858794 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandler.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandler.java @@ -408,7 +408,7 @@ public void decryptStream(COSStream stream, long objNum, long genNum) throws IOE return; } decryptDictionary(stream, objNum, genNum); - byte[] encrypted = IOUtils.toByteArray(stream.getFilteredStream()); + byte[] encrypted = IOUtils.toByteArray(stream.createRawInputStream()); ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encrypted); OutputStream output = stream.createRawOutputStream(); try diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotation.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotation.java index 21b309e8c..d6714ecdd 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotation.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotation.java @@ -941,7 +941,7 @@ else if (base instanceof COSString) } else if (base instanceof COSStream) { - return ((COSStream) base).getString(); + return ((COSStream) base).toTextString(); } else { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFField.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFField.java index 9f5a50d48..00e8e91b8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFField.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFField.java @@ -125,7 +125,7 @@ public void writeXML( Writer output ) throws IOException } else if(value instanceof COSStream) { - output.write("" + escapeXML(((COSStream) value).getString()) + "\n"); + output.write("" + escapeXML(((COSStream) value).toTextString()) + "\n"); } } String rt = getRichText(); @@ -768,7 +768,7 @@ else if (rv instanceof COSString) } else { - return ((COSStream) rv).getString(); + return ((COSStream) rv).toTextString(); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFJavaScript.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFJavaScript.java index d1cee99af..d2f7eda20 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFJavaScript.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFJavaScript.java @@ -81,7 +81,7 @@ public String getBefore() } else if (base instanceof COSStream) { - return ((COSStream)base).getString(); + return ((COSStream)base).toTextString(); } else { @@ -113,7 +113,7 @@ public String getAfter() } else if (base instanceof COSStream) { - return ((COSStream) base).getString(); + return ((COSStream) base).toTextString(); } else { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2.java index 6b30ef6f1..8c45b1a8b 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2.java @@ -186,7 +186,7 @@ private int[] readCIDToGIDMap() throws IOException if (map instanceof COSStream) { COSStream stream = (COSStream) map; - InputStream is = stream.getUnfilteredStream(); + InputStream is = stream.createInputStream(); byte[] mapAsBytes = IOUtils.toByteArray(is); IOUtils.closeQuietly(is); int numberOfInts = mapAsBytes.length / 2; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFont.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFont.java index 588a84c8a..321359c1d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFont.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFont.java @@ -170,7 +170,7 @@ else if (base instanceof COSStream) InputStream input = null; try { - input = ((COSStream)base).getUnfilteredStream(); + input = ((COSStream)base).createInputStream(); return CMapManager.parseCMap(input); } finally diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3CharProc.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3CharProc.java index 388b1a273..f263de1f0 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3CharProc.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3CharProc.java @@ -62,7 +62,7 @@ public PDStream getContentStream() @Override public InputStream getContents() throws IOException { - return charStream.getUnfilteredStream(); + return charStream.createInputStream(); } @Override diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDFormXObject.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDFormXObject.java index b67e44b70..6707f1691 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDFormXObject.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDFormXObject.java @@ -143,7 +143,7 @@ public PDStream getContentStream() @Override public InputStream getContents() throws IOException { - return getCOSStream().getUnfilteredStream(); + return getCOSStream().createInputStream(); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDTilingPattern.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDTilingPattern.java index cf4f21452..98f9e1cbc 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDTilingPattern.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDTilingPattern.java @@ -157,7 +157,7 @@ public PDStream getContentStream() @Override public InputStream getContents() throws IOException { - return ((COSStream) getCOSObject()).getUnfilteredStream(); + return ((COSStream) getCOSObject()).createInputStream(); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionJavaScript.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionJavaScript.java index ded46e174..af4143b7c 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionJavaScript.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionJavaScript.java @@ -84,7 +84,7 @@ public String getAction() } else if (base instanceof COSStream) { - return ((COSStream)base).getString(); + return ((COSStream)base).toTextString(); } else { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java index 7957170a9..851fc7052 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java @@ -185,7 +185,7 @@ public String getRichContents() } else if (base instanceof COSStream) { - return ((COSStream) base).getString(); + return ((COSStream) base).toTextString(); } else { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDVariableText.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDVariableText.java index 077a2d215..827109a79 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDVariableText.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDVariableText.java @@ -237,7 +237,7 @@ else if (base instanceof COSString) } else if (base instanceof COSStream) { - return ((COSStream) base).getString(); + return ((COSStream) base).toTextString(); } else { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFA.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFA.java index b7bee3148..284292ce2 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFA.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFA.java @@ -96,7 +96,7 @@ public byte[] getBytes() throws IOException COSBase cosObj = cosArray.getObject(i); if (cosObj instanceof COSStream) { - is = ((COSStream) cosObj).getUnfilteredStream(); + is = ((COSStream) cosObj).createInputStream(); int nRead = 0; while ((nRead = is.read(xfaBytes, 0, xfaBytes.length)) != -1) { @@ -110,7 +110,7 @@ public byte[] getBytes() throws IOException else if (xfa.getCOSObject() instanceof COSStream) { xfaBytes = new byte[1024]; - is = ((COSStream) xfa.getCOSObject()).getUnfilteredStream(); + is = ((COSStream) xfa.getCOSObject()).createInputStream(); int nRead = 0; while ((nRead = is.read(xfaBytes, 0, xfaBytes.length)) != -1) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFAResource.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFAResource.java index 493fd5582..2c028b5f5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFAResource.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFAResource.java @@ -96,7 +96,7 @@ public byte[] getBytes() throws IOException COSBase cosObj = cosArray.getObject(i); if (cosObj instanceof COSStream) { - is = ((COSStream) cosObj).getUnfilteredStream(); + is = ((COSStream) cosObj).createInputStream(); int nRead; while ((nRead = is.read(xfaBytes, 0, xfaBytes.length)) != -1) { @@ -110,7 +110,7 @@ public byte[] getBytes() throws IOException else if (xfa.getCOSObject() instanceof COSStream) { xfaBytes = new byte[1024]; - is = ((COSStream) xfa.getCOSObject()).getUnfilteredStream(); + is = ((COSStream) xfa.getCOSObject()).createInputStream(); int nRead; while ((nRead = is.read(xfaBytes, 0, xfaBytes.length)) != -1) { From 07bb9e3d8b1f75b949cf3523f9b597eca45281d1 Mon Sep 17 00:00:00 2001 From: Bruno Coelho <4brunu@users.noreply.github.com> Date: Sat, 6 Mar 2021 03:57:36 +0000 Subject: [PATCH 007/165] Migrate from external to internal storage to fix tests (#246) --- library/src/androidTest/AndroidManifest.xml | 2 -- .../encryption/TestSymmetricKeyEncryption.java | 2 +- .../pdfbox/multipdf/PDFMergerUtilityTest.java | 2 +- .../graphics/image/CCITTFactoryTest.java | 2 +- .../pdmodel/graphics/image/JPEGFactoryTest.java | 2 +- .../graphics/image/LosslessFactoryTest.java | 2 +- .../graphics/image/PDInlineImageTest.java | 2 +- .../pdmodel/interactive/form/AlignmentTest.java | 2 +- .../interactive/form/MultilineFieldsTest.java | 2 +- sample/src/main/AndroidManifest.xml | 2 -- .../tom_roush/pdfbox/sample/MainActivity.java | 17 ++--------------- 11 files changed, 10 insertions(+), 27 deletions(-) diff --git a/library/src/androidTest/AndroidManifest.xml b/library/src/androidTest/AndroidManifest.xml index dd12fc016..976470d94 100644 --- a/library/src/androidTest/AndroidManifest.xml +++ b/library/src/androidTest/AndroidManifest.xml @@ -1,6 +1,4 @@ - - \ No newline at end of file diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestSymmetricKeyEncryption.java b/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestSymmetricKeyEncryption.java index 649910c70..2f047b178 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestSymmetricKeyEncryption.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestSymmetricKeyEncryption.java @@ -88,7 +88,7 @@ public void setUp() throws Exception testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - testResultsDir = new File(android.os.Environment.getExternalStorageDirectory(), "Download/pdfbox-test-output/crypto"); + testResultsDir = new File(testContext.getCacheDir(), "Download/pdfbox-test-output/crypto"); testResultsDir.mkdirs(); permission = new AccessPermission(); diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtilityTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtilityTest.java index d910ac891..eca45e0d4 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtilityTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtilityTest.java @@ -49,7 +49,7 @@ public void setUp() throws Exception { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - TARGETTESTDIR = android.os.Environment.getExternalStorageDirectory() + "/Download/pdfbox-test-output/merge/"; + TARGETTESTDIR = testContext.getCacheDir() + "/Download/pdfbox-test-output/merge/"; new File(TARGETTESTDIR).mkdirs(); if (!new File(TARGETTESTDIR).exists()) { diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java index fa89d10f7..a1cb5ffff 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java @@ -50,7 +50,7 @@ public void setUp() { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - testResultsDir = new File(android.os.Environment.getExternalStorageDirectory() + + testResultsDir = new File(testContext.getCacheDir() + "/Download/pdfbox-test-output/graphics/"); testResultsDir.mkdirs(); } diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactoryTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactoryTest.java index b856d9cc0..c3836c217 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactoryTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactoryTest.java @@ -54,7 +54,7 @@ public void setUp() { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - testResultsDir = new File(android.os.Environment.getExternalStorageDirectory() + + testResultsDir = new File(testContext.getCacheDir() + "/Download/pdfbox-test-output/graphics/"); testResultsDir.mkdirs(); } diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactoryTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactoryTest.java index 7a08ae935..48606827f 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactoryTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactoryTest.java @@ -59,7 +59,7 @@ public void setUp() { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - testResultsDir = new File(android.os.Environment.getExternalStorageDirectory() + + testResultsDir = new File(testContext.getCacheDir() + "/Download/pdfbox-test-output/graphics/"); testResultsDir.mkdirs(); } diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImageTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImageTest.java index d9ded0d49..28f4d50fc 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImageTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImageTest.java @@ -58,7 +58,7 @@ public void setUp() { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - testResultsDir = new File(android.os.Environment.getExternalStorageDirectory() + + testResultsDir = new File(testContext.getCacheDir() + "/Download/pdfbox-test-output/graphics/"); testResultsDir.mkdirs(); } diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AlignmentTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AlignmentTest.java index 504bbe45c..6e53e5d2f 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AlignmentTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AlignmentTest.java @@ -48,7 +48,7 @@ public void setUp() throws IOException PDFBoxResourceLoader.init(testContext); document = PDDocument.load(testContext.getAssets().open(IN_DIR + "/" + NAME_OF_PDF)); acroForm = document.getDocumentCatalog().getAcroForm(); - OUT_DIR = new File(android.os.Environment.getExternalStorageDirectory(), "Download/pdfbox-test-output"); + OUT_DIR = new File(testContext.getCacheDir(), "Download/pdfbox-test-output"); OUT_DIR.mkdirs(); } diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/MultilineFieldsTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/MultilineFieldsTest.java index 696a038c9..478148872 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/MultilineFieldsTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/MultilineFieldsTest.java @@ -52,7 +52,7 @@ public void setUp() throws IOException System.out.println("Working Directory = " + System.getProperty("user.dir")); document = PDDocument.load(testContext.getAssets().open(IN_DIR + "/" + NAME_OF_PDF)); acroForm = document.getDocumentCatalog().getAcroForm(); - OUT_DIR = new File(android.os.Environment.getExternalStorageDirectory(), "Download/pdfbox-test-output"); + OUT_DIR = new File(testContext.getCacheDir(), "Download/pdfbox-test-output"); OUT_DIR.mkdirs(); } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index bc9b088d4..efa6f90be 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -2,8 +2,6 @@ - - Date: Mon, 15 Mar 2021 18:39:03 -0500 Subject: [PATCH 008/165] Bump Gradle plugin and junit versions (#249) --- build.gradle | 2 +- library/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index abc7341b1..d10ff2c9d 100755 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:4.1.2' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' } } diff --git a/library/build.gradle b/library/build.gradle index e2b4c9978..d1613ea9b 100755 --- a/library/build.gradle +++ b/library/build.gradle @@ -87,7 +87,7 @@ dependencies { api "org.bouncycastle:bcpkix-jdk15on:1.68" // Test dependencies - testImplementation 'junit:junit:4.13.1' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'com.android.support.test:runner:1.0.2' } From e440d0a8248f7cfe94817e765e6a8baad886cb94 Mon Sep 17 00:00:00 2001 From: Tom Roush Date: Mon, 15 Mar 2021 21:00:07 -0500 Subject: [PATCH 009/165] Release v1.8.10.2 (#250) --- README.md | 6 +----- gradle.properties | 2 +- sample/build.gradle | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 558dddd80..eeba09451 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Add the following to dependency to `build.gradle`: ```gradle dependencies { - implementation 'com.tom_roush:pdfbox-android:1.8.10.1' + implementation 'com.tom_roush:pdfbox-android:1.8.10.2' } ``` @@ -32,7 +32,3 @@ Important notes -Currently based on PdfBox v1.8.10 -Requires API 19 or greater for full functionality - -Dependencies -============== -PDFBox-Android depends on the following libraries: SpongyCastle core, prov, and pkix: https://github.com/rtyley/spongycastle/ diff --git a/gradle.properties b/gradle.properties index 18ea50285..eeab26a98 100755 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m org.gradle.parallel=true -VERSION_NAME=1.8.10.2-SNAPSHOT +VERSION_NAME=1.8.10.2 VERSION_CODE=1 ANDROID_BUILD_MIN_SDK_VERSION=14 diff --git a/sample/build.gradle b/sample/build.gradle index 61a3a2f41..d1e17232c 100755 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -24,6 +24,6 @@ android { dependencies { implementation project(':library') -// implementation 'com.tom_roush:pdfbox-android:1.8.10.1' +// implementation 'com.tom_roush:pdfbox-android:1.8.10.2' implementation 'com.android.support:appcompat-v7:28.0.0' } From 3d26a2f847aed2bf8e5449108f544d42cf573bcb Mon Sep 17 00:00:00 2001 From: Tom Roush <4317255+TomRoush@users.noreply.github.com> Date: Thu, 18 Mar 2021 23:13:33 -0500 Subject: [PATCH 010/165] Fix unsupported class exception from BouncyCastle (#252) --- build.gradle | 2 +- gradle.properties | 2 +- library/build.gradle | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index d10ff2c9d..de9621624 100755 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.android.tools.build:gradle:4.1.3' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' } } diff --git a/gradle.properties b/gradle.properties index eeab26a98..18ea50285 100755 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m org.gradle.parallel=true -VERSION_NAME=1.8.10.2 +VERSION_NAME=1.8.10.2-SNAPSHOT VERSION_CODE=1 ANDROID_BUILD_MIN_SDK_VERSION=14 diff --git a/library/build.gradle b/library/build.gradle index d1613ea9b..1b0ad9651 100755 --- a/library/build.gradle +++ b/library/build.gradle @@ -83,8 +83,8 @@ tasks.withType(Test) { } dependencies { - api "org.bouncycastle:bcprov-jdk15on:1.68" - api "org.bouncycastle:bcpkix-jdk15on:1.68" + api "org.bouncycastle:bcprov-jdk15to18:1.68" + api "org.bouncycastle:bcpkix-jdk15to18:1.68" // Test dependencies testImplementation 'junit:junit:4.13.2' From 77ab3adf436141294fe19cf81c25aa7ac41a0ca3 Mon Sep 17 00:00:00 2001 From: Tom Roush <4317255+TomRoush@users.noreply.github.com> Date: Sat, 20 Mar 2021 11:49:55 -0500 Subject: [PATCH 011/165] Release v1.8.10.3 (#253) --- README.md | 2 +- gradle.properties | 2 +- sample/build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eeba09451..3fec1d69c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Add the following to dependency to `build.gradle`: ```gradle dependencies { - implementation 'com.tom_roush:pdfbox-android:1.8.10.2' + implementation 'com.tom_roush:pdfbox-android:1.8.10.3' } ``` diff --git a/gradle.properties b/gradle.properties index 18ea50285..fd4e0198d 100755 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m org.gradle.parallel=true -VERSION_NAME=1.8.10.2-SNAPSHOT +VERSION_NAME=1.8.10.3 VERSION_CODE=1 ANDROID_BUILD_MIN_SDK_VERSION=14 diff --git a/sample/build.gradle b/sample/build.gradle index d1e17232c..3a3900852 100755 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -24,6 +24,6 @@ android { dependencies { implementation project(':library') -// implementation 'com.tom_roush:pdfbox-android:1.8.10.2' +// implementation 'com.tom_roush:pdfbox-android:1.8.10.3' implementation 'com.android.support:appcompat-v7:28.0.0' } From 252e8f2b15cd3fb18a09a6736c16ae2b575fd915 Mon Sep 17 00:00:00 2001 From: Tom Roush <4317255+TomRoush@users.noreply.github.com> Date: Sat, 20 Mar 2021 12:33:02 -0500 Subject: [PATCH 012/165] Travis CI cleanup (#254) --- .travis.yml | 7 ++++++- gradle.properties | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cd9433905..b63205b93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ -sudo: required +os: linux +dist: xenial language: android android: components: @@ -12,6 +13,10 @@ android: - sys-img-armeabi-v7a-android-21 - extra-android-support +branches: + only: + - master + jdk: - oraclejdk8 diff --git a/gradle.properties b/gradle.properties index fd4e0198d..d7c6fc12d 100755 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m org.gradle.parallel=true -VERSION_NAME=1.8.10.3 +VERSION_NAME=1.8.10.4-SNAPSHOT VERSION_CODE=1 ANDROID_BUILD_MIN_SDK_VERSION=14 From 5b6a877e8e1d175a8956d26f5141d33bea30900c Mon Sep 17 00:00:00 2001 From: Tiago Pereira <1698241+tiper@users.noreply.github.com> Date: Fri, 26 Mar 2021 02:36:09 +0000 Subject: [PATCH 013/165] Fix a NPE on PDFStreamEngine. Fix a leak on FlateFilter (#255) --- .../com/tom_roush/pdfbox/contentstream/PDFStreamEngine.java | 2 +- .../src/main/java/com/tom_roush/pdfbox/filter/FlateFilter.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFStreamEngine.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFStreamEngine.java index 3ca980da3..a293edeba 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFStreamEngine.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFStreamEngine.java @@ -563,7 +563,7 @@ public void showTextStrings(COSArray array) throws IOException PDTextState textState = getGraphicsState().getTextState(); float fontSize = textState.getFontSize(); float horizontalScaling = textState.getHorizontalScaling() / 100f; - boolean isVertical = textState.getFont().isVertical(); + boolean isVertical = textState.getFont() != null && textState.getFont().isVertical(); for (COSBase obj : array) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/FlateFilter.java b/library/src/main/java/com/tom_roush/pdfbox/filter/FlateFilter.java index bbfb37ad6..756428f4f 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/FlateFilter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/FlateFilter.java @@ -110,6 +110,7 @@ private static void decompress(InputStream in, OutputStream out) throws IOExcept read = in.read(buf); inflater.setInput(buf,0,read); } + inflater.end(); } out.flush(); } From 3c1af499f791e776457af5b5b268ac933d176a75 Mon Sep 17 00:00:00 2001 From: Tiago Pereira <1698241+tiper@users.noreply.github.com> Date: Sat, 10 Apr 2021 05:12:37 +0100 Subject: [PATCH 014/165] Close input stream after creating PDImageXObject from a JPG file. (#258) --- .../pdmodel/graphics/image/PDImageXObject.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImageXObject.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImageXObject.java index f8818d1f3..c103d1565 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImageXObject.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImageXObject.java @@ -178,7 +178,19 @@ public static PDImageXObject createFromFile(File file, PDDocument doc) throws IO String ext = name.substring(dot + 1).toLowerCase(); if ("jpg".equals(ext) || "jpeg".equals(ext)) { - return JPEGFactory.createFromStream(doc, new FileInputStream(file)); + InputStream stream = null; + try { + stream = new FileInputStream(file); + return JPEGFactory.createFromStream(doc, stream); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + // do nothing here + } + } + } } if ("tif".equals(ext) || "tiff".equals(ext)) { From b0ffa73ef98810b0088b40ed39c1a7c3bb1c0b0a Mon Sep 17 00:00:00 2001 From: Tom Roush <4317255+TomRoush@users.noreply.github.com> Date: Sat, 22 May 2021 16:52:05 -0500 Subject: [PATCH 015/165] Upgrade PDFBox API to v2.0.0 (#265) --- gradle.properties | 4 +- library/build.gradle | 2 + .../encryption/TestPublicKeyEncryption.java | 25 +- .../TestSymmetricKeyEncryption.java | 72 +- .../pdfbox/multipdf/PDFMergerUtilityTest.java | 37 +- .../graphics/image/CCITTFactoryTest.java | 18 +- .../graphics/image/JPEGFactoryTest.java | 39 +- .../graphics/image/LosslessFactoryTest.java | 12 +- .../graphics/image/PDInlineImageTest.java | 14 +- .../graphics/image/ValidateXImage.java | 7 +- .../interactive/form/AlignmentTest.java | 18 +- .../interactive/form/MultilineFieldsTest.java | 18 +- .../interactive/form/PDAcroFormTest.java | 120 + .../pdfbox/rendering/TestRendering.java | 37 +- .../com/tom_roush/fontbox/FontBoxFont.java | 5 +- .../com/tom_roush/fontbox/afm/AFMParser.java | 79 +- .../tom_roush/fontbox/afm/FontMetrics.java | 27 +- .../com/tom_roush/fontbox/cff/CFFCIDFont.java | 33 +- .../com/tom_roush/fontbox/cff/CFFCharset.java | 18 +- .../tom_roush/fontbox/cff/CFFEncoding.java | 6 +- .../fontbox/cff/CFFExpertCharset.java | 345 +- .../fontbox/cff/CFFExpertEncoding.java | 524 +- .../fontbox/cff/CFFExpertSubsetCharset.java | 187 +- .../com/tom_roush/fontbox/cff/CFFFont.java | 59 +- .../fontbox/cff/CFFISOAdobeCharset.java | 471 +- .../tom_roush/fontbox/cff/CFFOperator.java | 6 +- .../com/tom_roush/fontbox/cff/CFFParser.java | 728 +-- .../fontbox/cff/CFFStandardEncoding.java | 524 +- .../tom_roush/fontbox/cff/CFFType1Font.java | 17 +- .../fontbox/cff/CharStringCommand.java | 10 +- .../fontbox/cff/CharStringConverter.java | 392 -- .../fontbox/cff/CharStringHandler.java | 52 +- .../com/tom_roush/fontbox/cff/DataInput.java | 27 +- .../com/tom_roush/fontbox/cff/DataOutput.java | 1 - .../com/tom_roush/fontbox/cff/IndexData.java | 107 - .../fontbox/cff/Type1CharString.java | 28 +- .../fontbox/cff/Type1CharStringFormatter.java | 102 - .../fontbox/cff/Type1CharStringParser.java | 57 +- .../fontbox/cff/Type2CharString.java | 4 +- .../fontbox/cff/Type2CharStringParser.java | 70 +- .../com/tom_roush/fontbox/cmap/CIDRange.java | 2 +- .../java/com/tom_roush/fontbox/cmap/CMap.java | 106 +- .../tom_roush/fontbox/cmap/CMapParser.java | 21 +- .../fontbox/cmap/CodespaceRange.java | 81 +- .../tom_roush/fontbox/encoding/Encoding.java | 4 +- .../fontbox/encoding/MacRomanEncoding.java | 433 +- .../fontbox/encoding/StandardEncoding.java | 312 +- .../com/tom_roush/fontbox/pfb/PfbParser.java | 8 +- .../fontbox/ttf/BufferedRandomAccessFile.java | 197 + .../com/tom_roush/fontbox/ttf/CFFTable.java | 25 +- .../tom_roush/fontbox/ttf/CmapSubtable.java | 39 +- .../com/tom_roush/fontbox/ttf/CmapTable.java | 1 + .../fontbox/ttf/GlyfSimpleDescript.java | 10 +- .../com/tom_roush/fontbox/ttf/GlyphData.java | 11 +- .../tom_roush/fontbox/ttf/GlyphRenderer.java | 67 +- .../com/tom_roush/fontbox/ttf/GlyphTable.java | 161 +- .../tom_roush/fontbox/ttf/HeaderTable.java | 2 +- .../fontbox/ttf/HorizontalHeaderTable.java | 2 +- .../fontbox/ttf/HorizontalMetricsTable.java | 17 + .../fontbox/ttf/IndexToLocationTable.java | 2 +- .../fontbox/ttf/KerningSubtable.java | 3 +- .../fontbox/ttf/MaximumProfileTable.java | 2 +- .../fontbox/ttf/MemoryTTFDataStream.java | 2 +- .../tom_roush/fontbox/ttf/NamingTable.java | 71 +- .../fontbox/ttf/OS2WindowsMetricsTable.java | 28 +- .../tom_roush/fontbox/ttf/RAFDataStream.java | 25 +- .../tom_roush/fontbox/ttf/TTFDataStream.java | 25 +- .../com/tom_roush/fontbox/ttf/TTFParser.java | 78 +- .../tom_roush/fontbox/ttf/TTFSubsetter.java | 12 +- .../fontbox/ttf/TrueTypeCollection.java | 78 +- .../tom_roush/fontbox/ttf/TrueTypeFont.java | 36 +- .../fontbox/ttf/VerticalOriginTable.java | 3 +- .../com/tom_roush/fontbox/ttf/WGL4Names.java | 6 +- .../util/Charsets.java} | 37 +- .../util/autodetect/FontFileFinder.java | 33 +- .../util/autodetect/UnixFontDirFinder.java | 31 - .../pdfbox/contentstream/PDContentStream.java | 6 +- .../PDFGraphicsStreamEngine.java | 18 +- .../pdfbox/contentstream/PDFStreamEngine.java | 58 +- .../contentstream/operator/DrawObject.java | 28 +- .../operator/OperatorProcessor.java | 20 + .../color/SetNonStrokingDeviceCMYKColor.java | 48 + .../color/SetStrokingDeviceCMYKColor.java | 47 + .../graphics/AppendRectangleToPath.java | 10 + .../operator/graphics/BeginInlineImage.java} | 39 +- .../operator/graphics/CurveTo.java | 10 + .../graphics/CurveToReplicateFinalPoint.java | 10 + .../CurveToReplicateInitialPoint.java | 10 + .../operator/graphics/DrawObject.java | 28 +- .../operator/graphics/LineTo.java | 19 +- .../operator/graphics/MoveTo.java | 25 +- .../operator/graphics/ShadingFill.java | 11 +- .../operator/markedcontent/DrawObject.java | 72 + .../operator/state/Concatenate.java | 4 + .../contentstream/operator/state/Restore.java | 8 +- .../state/SetGraphicsStateParameters.java | 20 +- .../operator/state/SetLineCapStyle.java | 6 + .../operator/state/SetLineDashPattern.java | 52 +- .../operator/state/SetLineJoinStyle.java | 6 + .../operator/state/SetLineMiterLimit.java | 5 + .../operator/state/SetLineWidth.java | 8 +- .../operator/state/SetRenderingIntent.java | 5 + .../contentstream/operator/text/MoveText.java | 15 +- .../operator/text/MoveTextSetLeading.java | 7 +- .../operator/text/SetFontAndSize.java | 20 +- .../operator/text/SetTextRenderingMode.java | 18 +- .../operator/text/SetTextRise.java | 17 +- .../operator/text/SetWordSpacing.java | 15 +- .../contentstream/operator/text/ShowText.java | 29 +- .../operator/text/ShowTextAdjusted.java | 22 +- .../operator/text/ShowTextLineAndSpace.java | 5 + .../com/tom_roush/pdfbox/cos/COSArray.java | 21 +- .../tom_roush/pdfbox/cos/COSDictionary.java | 142 +- .../com/tom_roush/pdfbox/cos/COSDocument.java | 66 +- .../com/tom_roush/pdfbox/cos/COSFloat.java | 87 +- .../com/tom_roush/pdfbox/cos/COSName.java | 11 +- .../com/tom_roush/pdfbox/cos/COSNull.java | 16 + .../com/tom_roush/pdfbox/cos/COSNumber.java | 63 +- .../com/tom_roush/pdfbox/cos/COSObject.java | 33 +- .../tom_roush/pdfbox/cos/COSObjectKey.java | 33 +- .../com/tom_roush/pdfbox/cos/COSStream.java | 73 +- .../com/tom_roush/pdfbox/cos/ICOSVisitor.java | 16 + .../tom_roush/pdfbox/cos/PDFDocEncoding.java | 20 +- .../pdfbox/cos/UnmodifiableCOSDictionary.java | 2 +- .../exceptions/CryptographyException.java | 68 - .../exceptions/InvalidPasswordException.java | 37 - .../pdfbox/exceptions/SignatureException.java | 94 - .../pdfbox/filter/ASCII85OutputStream.java | 1 + .../pdfbox/filter/ASCIIHexFilter.java | 8 +- .../pdfbox/filter/CCITTFaxFilter.java | 12 +- .../tom_roush/pdfbox/filter/DCTFilter.java | 18 +- .../com/tom_roush/pdfbox/filter/Filter.java | 10 +- .../tom_roush/pdfbox/filter/FlateFilter.java | 61 +- .../tom_roush/pdfbox/filter/LZWFilter.java | 44 +- .../filter/MissingImageReaderException.java | 8 +- .../tom_roush/pdfbox/filter/Predictor.java | 99 +- .../ccitt/CCITTFaxG31DDecodeInputStream.java | 14 +- .../pdfbox/filter/ccitt/TIFFFaxDecoder.java | 2 +- .../java/com/tom_roush/pdfbox/io/IOUtils.java | 24 +- .../pdfbox/io/MemoryUsageSetting.java | 24 +- .../pdfbox/io/RandomAccessBuffer.java | 41 +- .../RandomAccessBufferedFileInputStream.java | 4 +- .../com/tom_roush/pdfbox/io/ScratchFile.java | 25 + .../pdfbox/io/ScratchFileBuffer.java | 31 + .../pdfbox/multipdf/LayerUtility.java | 10 +- .../tom_roush/pdfbox/multipdf/Overlay.java | 107 +- .../pdfbox/multipdf/PDFCloneUtility.java | 19 +- .../pdfbox/multipdf/PDFMergerUtility.java | 87 +- .../pdfbox/multipdf/PageExtractor.java | 5 +- .../tom_roush/pdfbox/multipdf/Splitter.java | 56 +- .../pdfbox/pdfparser/BaseParser.java | 1303 ++--- .../tom_roush/pdfbox/pdfparser/COSParser.java | 171 +- .../tom_roush/pdfbox/pdfparser/FDFParser.java | 6 +- .../pdfparser/PDFObjectStreamParser.java | 9 +- .../tom_roush/pdfbox/pdfparser/PDFParser.java | 98 +- .../pdfbox/pdfparser/PDFStreamParser.java | 40 +- .../pdfbox/pdfparser/PDFXRefStream.java | 26 +- .../pdfbox/pdfparser/PDFXrefStreamParser.java | 24 +- .../pdfbox/pdfparser/XrefTrailerResolver.java | 24 +- .../tom_roush/pdfbox/pdfwriter/COSWriter.java | 136 +- .../pdfbox/pdfwriter/COSWriterXRefEntry.java | 30 +- .../pdfbox/pdfwriter/ContentStreamWriter.java | 2 +- .../pdmodel/PDDestinationNameTreeNode.java | 2 +- .../tom_roush/pdfbox/pdmodel/PDDocument.java | 406 +- .../pdfbox/pdmodel/PDDocumentCatalog.java | 64 +- .../pdfbox/pdmodel/PDDocumentInformation.java | 8 +- .../PDDocumentNameDestinationDictionary.java | 24 +- .../com/tom_roush/pdfbox/pdmodel/PDPage.java | 43 +- .../pdfbox/pdmodel/PDPageContentStream.java | 870 ++-- .../tom_roush/pdfbox/pdmodel/PDPageTree.java | 156 +- .../tom_roush/pdfbox/pdmodel/PDResources.java | 72 +- .../pdfbox/pdmodel/common/COSArrayList.java | 2 +- .../pdmodel/common/COSDictionaryMap.java | 6 +- .../pdmodel/common/PDDictionaryWrapper.java | 2 +- .../pdfbox/pdmodel/common/PDMatrix.java | 115 - .../pdfbox/pdmodel/common/PDMetadata.java | 14 +- .../pdfbox/pdmodel/common/PDNameTreeNode.java | 5 +- .../pdmodel/common/PDNumberTreeNode.java | 7 +- .../pdfbox/pdmodel/common/PDObjectStream.java | 17 +- .../pdfbox/pdmodel/common/PDPageLabels.java | 13 +- .../pdfbox/pdmodel/common/PDRectangle.java | 18 +- .../pdfbox/pdmodel/common/PDStream.java | 45 +- .../pdfbox/pdmodel/common/PDTextStream.java | 149 - .../pdfbox/pdmodel/common/XrefEntry.java | 42 - .../filespecification/PDEmbeddedFile.java | 61 +- .../pdmodel/common/function/PDFunction.java | 6 +- .../common/function/PDFunctionType0.java | 460 ++ .../common/function/PDFunctionType3.java | 2 +- .../pdmodel/common/function/type4/Parser.java | 12 +- .../PDMarkedContentReference.java | 4 +- .../logicalstructure/PDObjectReference.java | 4 +- .../logicalstructure/PDStructureElement.java | 6 +- .../logicalstructure/PDStructureTreeRoot.java | 4 +- .../logicalstructure/PDUserProperty.java | 58 +- .../markedcontent/PDMarkedContent.java | 6 +- .../prepress/PDBoxStyle.java | 6 +- .../encryption/PDCryptFilterDictionary.java | 10 +- .../pdmodel/encryption/PDEncryption.java | 18 +- .../PublicKeyDecryptionMaterial.java | 15 - .../encryption/PublicKeySecurityHandler.java | 5 +- .../pdmodel/encryption/SecurityHandler.java | 135 +- .../encryption/SecurityHandlerFactory.java | 11 +- .../StandardDecryptionMaterial.java | 9 - .../encryption/StandardSecurityHandler.java | 71 +- .../pdfbox/pdmodel/fdf/FDFAnnotation.java | 868 ++-- .../pdmodel/fdf/FDFAnnotationCaret.java | 37 +- .../pdmodel/fdf/FDFAnnotationCircle.java | 27 +- .../fdf/FDFAnnotationFileAttachment.java | 5 +- .../pdmodel/fdf/FDFAnnotationFreeText.java | 196 +- .../pdmodel/fdf/FDFAnnotationHighlight.java | 4 +- .../pdfbox/pdmodel/fdf/FDFAnnotationInk.java | 7 +- .../pdfbox/pdmodel/fdf/FDFAnnotationLine.java | 4 +- .../pdfbox/pdmodel/fdf/FDFAnnotationLink.java | 5 +- .../pdmodel/fdf/FDFAnnotationPolygon.java | 11 +- .../pdmodel/fdf/FDFAnnotationPolyline.java | 11 +- .../pdmodel/fdf/FDFAnnotationSound.java | 5 +- .../pdmodel/fdf/FDFAnnotationSquare.java | 27 +- .../pdmodel/fdf/FDFAnnotationSquiggly.java | 4 +- .../pdmodel/fdf/FDFAnnotationStamp.java | 5 +- .../pdmodel/fdf/FDFAnnotationStrikeOut.java | 4 +- .../pdfbox/pdmodel/fdf/FDFAnnotationText.java | 4 +- .../pdmodel/fdf/FDFAnnotationTextMarkup.java | 6 +- .../pdmodel/fdf/FDFAnnotationUnderline.java | 4 +- .../pdfbox/pdmodel/fdf/FDFDictionary.java | 20 +- .../pdfbox/pdmodel/fdf/FDFDocument.java | 16 +- .../pdfbox/pdmodel/fdf/FDFField.java | 58 +- .../pdfbox/pdmodel/fdf/FDFIconFit.java | 14 +- .../pdmodel/fdf/FDFNamedPageReference.java | 4 +- .../pdfbox/pdmodel/fdf/FDFOptionElement.java | 7 +- .../tom_roush/pdfbox/pdmodel/fdf/XMLUtil.java | 20 +- .../pdfbox/pdmodel/font/CIDFontMapping.java | 2 +- .../pdfbox/pdmodel/font/CIDSystemInfo.java | 4 +- .../pdfbox/pdmodel/font/CmapManager.java | 2 +- .../pdmodel/font/FileSystemFontProvider.java | 319 +- .../pdfbox/pdmodel/font/FontCache.java | 6 +- .../pdfbox/pdmodel/font/FontMapper.java | 692 +-- .../pdfbox/pdmodel/font/FontMapperImpl.java | 721 +++ .../FontMappers.java} | 47 +- .../pdfbox/pdmodel/font/FontMapping.java | 2 +- .../pdfbox/pdmodel/font/PDCIDFont.java | 109 +- .../pdfbox/pdmodel/font/PDCIDFontType0.java | 41 +- .../pdfbox/pdmodel/font/PDCIDFontType2.java | 221 +- .../pdmodel/font/PDCIDFontType2Embedder.java | 56 +- .../tom_roush/pdfbox/pdmodel/font/PDFont.java | 28 +- .../pdfbox/pdmodel/font/PDFontDescriptor.java | 36 +- .../pdfbox/pdmodel/font/PDFontFactory.java | 2 +- .../pdfbox/pdmodel/font/PDFontLike.java | 17 +- .../pdmodel/font/PDPanoseClassification.java | 11 +- .../pdfbox/pdmodel/font/PDSimpleFont.java | 31 +- .../pdfbox/pdmodel/font/PDTrueTypeFont.java | 40 +- .../pdmodel/font/PDTrueTypeFontEmbedder.java | 25 +- .../pdfbox/pdmodel/font/PDType0Font.java | 87 +- .../pdfbox/pdmodel/font/PDType1CFont.java | 88 +- .../pdfbox/pdmodel/font/PDType1Font.java | 56 +- .../pdmodel/font/PDType1FontEmbedder.java | 9 +- .../pdfbox/pdmodel/font/PDType3CharProc.java | 113 +- .../pdfbox/pdmodel/font/PDType3Font.java | 76 +- .../pdfbox/pdmodel/font/Standard14Fonts.java | 8 +- .../pdfbox/pdmodel/font/ToUnicodeWriter.java | 42 +- .../pdfbox/pdmodel/font/TrueTypeEmbedder.java | 20 +- .../pdfbox/pdmodel/font/UniUtil.java | 47 + .../font/encoding/BuiltInEncoding.java | 10 +- .../font/encoding/DictionaryEncoding.java | 10 +- .../pdmodel/font/encoding/Encoding.java | 36 +- .../pdmodel/font/encoding/GlyphList.java | 30 +- .../font/encoding/MacOSRomanEncoding.java | 47 +- .../font/encoding/MacRomanEncoding.java | 437 +- .../font/encoding/StandardEncoding.java | 316 +- .../pdmodel/font/encoding/Type1Encoding.java | 8 +- .../font/encoding/WinAnsiEncoding.java | 459 +- .../pdfbox/pdmodel/graphics/PDXObject.java | 26 +- .../pdmodel/graphics/blend/BlendMode.java | 33 +- .../pdmodel/graphics/color/PDColorSpace.java | 186 +- .../pdmodel/graphics/color/PDDeviceGray.java | 2 +- .../pdmodel/graphics/color/PDDeviceRGB.java | 18 +- .../graphics/color/PDOutputIntent.java | 6 +- .../pdmodel/graphics/form/PDFormXObject.java | 38 +- .../graphics/form/PDTransparencyGroup.java | 61 + ...ava => PDTransparencyGroupAttributes.java} | 24 +- .../pdmodel/graphics/image/CCITTFactory.java | 42 +- .../pdmodel/graphics/image/JPEGFactory.java | 26 +- .../graphics/image/LosslessFactory.java | 24 +- .../pdmodel/graphics/image/PDImage.java | 9 +- .../graphics/image/PDImageXObject.java | 189 +- .../pdmodel/graphics/image/PDInlineImage.java | 85 +- .../graphics/image/SampledImageReader.java | 2 +- .../PDOptionalContentGroup.java | 2 +- .../PDOptionalContentProperties.java | 4 +- .../graphics/pattern/PDAbstractPattern.java | 36 +- .../graphics/pattern/PDShadingPattern.java | 4 +- .../graphics/pattern/PDTilingPattern.java | 7 +- .../pdmodel/graphics/predictor/Average.java | 65 - .../pdmodel/graphics/predictor/None.java | 88 - .../pdmodel/graphics/predictor/Optimum.java | 137 - .../pdmodel/graphics/predictor/Paeth.java | 106 - .../predictor/PredictorAlgorithm.java | 322 -- .../pdmodel/graphics/predictor/Sub.java | 72 - .../pdfbox/pdmodel/graphics/predictor/Up.java | 86 - .../pdmodel/graphics/shading/PDShading.java | 22 +- .../graphics/shading/PDShadingType1.java | 16 +- .../graphics/shading/PDShadingType2.java | 2 +- .../shading/PDTriangleBasedShadingType.java | 3 +- .../state/PDExtendedGraphicsState.java | 78 + .../graphics/state/PDGraphicsState.java | 84 +- .../pdmodel/graphics/state/PDSoftMask.java | 15 +- .../graphics/state/RenderingIntent.java | 6 +- .../interactive/action/PDActionFactory.java | 33 +- .../interactive/action/PDActionHide.java | 92 + .../action/PDActionImportData.java | 77 + .../interactive/action/PDActionMovie.java | 75 + .../action/PDActionRemoteGoTo.java | 14 +- .../interactive/action/PDActionResetForm.java | 93 + .../interactive/action/PDActionSound.java | 77 + .../action/PDActionSubmitForm.java | 118 + .../interactive/action/PDActionThread.java | 113 + .../interactive/action/PDActionURI.java | 9 +- .../action/PDAdditionalActions.java | 5 +- .../action/PDAnnotationAdditionalActions.java | 5 +- .../action/PDPageAdditionalActions.java | 5 +- .../action/PDWindowsLaunchParams.java | 17 +- .../interactive/annotation/PDAnnotation.java | 59 +- .../annotation/PDAnnotationLine.java | 14 +- .../annotation/PDAnnotationLink.java | 51 +- .../annotation/PDAnnotationMarkup.java | 42 +- .../annotation/PDAnnotationRubberStamp.java | 4 +- .../annotation/PDAnnotationSquareCircle.java | 23 +- .../annotation/PDAnnotationText.java | 4 +- .../annotation/PDAnnotationTextMarkup.java | 1 - .../annotation/PDAnnotationWidget.java | 15 +- ...PDAppearanceCharacteristicsDictionary.java | 51 +- .../annotation/PDAppearanceDictionary.java | 26 +- .../annotation/PDAppearanceEntry.java | 1 + .../annotation/PDAppearanceStream.java | 2 + .../annotation/PDBorderEffectDictionary.java | 1 + .../annotation/PDExternalDataDictionary.java | 6 +- .../digitalsignature/PDPropBuildDataDict.java | 99 +- .../digitalsignature/SignatureOptions.java | 24 +- .../visible/PDFTemplateBuilder.java | 33 +- .../visible/PDFTemplateCreator.java | 22 +- .../visible/PDFTemplateStructure.java | 34 +- .../visible/PDVisibleSigBuilder.java | 78 +- .../visible/PDVisibleSigProperties.java | 10 +- .../visible/PDVisibleSignDesigner.java | 144 +- .../destination/PDDestination.java | 6 +- .../destination/PDNamedDestination.java | 1 - .../destination/PDPageDestination.java | 12 +- .../destination/PDPageXYZDestination.java | 6 +- .../outline/PDDocumentOutline.java | 12 +- .../outline/PDOutlineItem.java | 115 +- .../form/AppearanceGeneratorHelper.java | 102 +- .../pdmodel/interactive/form/FieldUtils.java | 78 +- .../pdmodel/interactive/form/PDAcroForm.java | 254 +- .../pdmodel/interactive/form/PDButton.java | 179 +- .../pdmodel/interactive/form/PDCheckBox.java | 123 + .../pdmodel/interactive/form/PDCheckbox.java | 248 - .../pdmodel/interactive/form/PDChoice.java | 58 +- .../pdmodel/interactive/form/PDComboBox.java | 10 +- .../form/PDDefaultAppearanceString.java | 221 +- .../pdmodel/interactive/form/PDField.java | 64 +- .../interactive/form/PDFieldFactory.java | 10 +- .../pdmodel/interactive/form/PDFieldTree.java | 107 + .../pdmodel/interactive/form/PDListBox.java | 6 +- .../interactive/form/PDNonTerminalField.java | 52 +- .../interactive/form/PDPushButton.java | 25 +- .../interactive/form/PDRadioButton.java | 169 +- .../interactive/form/PDSignatureField.java | 36 +- .../interactive/form/PDTerminalField.java | 35 +- .../pdmodel/interactive/form/PDTextField.java | 41 +- .../interactive/form/PDVariableText.java | 18 +- .../pdmodel/interactive/form/PDXFA.java | 156 - .../interactive/form/PDXFAResource.java | 12 +- .../pdmodel/interactive/form/PlainText.java | 1 + .../interactive/form/PlainTextFormatter.java | 23 +- .../pagenavigation/PDTransitionDirection.java | 2 +- .../pagenavigation/PDTransitionMotion.java | 2 +- .../pagenavigation/PDTransitionStyle.java | 2 +- .../PDViewerPreferences.java | 14 +- .../pdfbox/rendering/CIDType0Glyph2D.java | 39 +- .../tom_roush/pdfbox/rendering/Glyph2D.java | 4 +- .../tom_roush/pdfbox/rendering/ImageType.java | 75 + .../pdfbox/rendering/PDFRenderer.java | 165 +- .../pdfbox/rendering/PageDrawer.java | 543 ++- .../pdfbox/rendering/TTFGlyph2D.java | 18 +- .../pdfbox/rendering/Type1Glyph2D.java | 45 +- .../text/PDFMarkedContentExtractor.java | 11 +- .../pdfbox/text/PDFTextStreamEngine.java | 118 +- .../pdfbox/text/PDFTextStripper.java | 535 +- .../pdfbox/text/PDFTextStripperByArea.java | 22 +- .../tom_roush/pdfbox/text/TextPosition.java | 84 +- .../com/tom_roush/pdfbox/util/Matrix.java | 10 +- .../tom_roush/pdfbox/util/PDFHighlighter.java | 229 - .../com/tom_roush/pdfbox/util/QuickSort.java | 84 +- .../com/tom_roush/pdfbox/util/XMLUtil.java | 89 - .../util/filetypedetector/ByteTrie.java | 137 + .../filetypedetector/FileType.java} | 55 +- .../filetypedetector/FileTypeDetector.java | 121 + .../pdfbox/resources/text/BidiMirroring.txt | 604 +++ .../pdfbox/filter/PredictorTest.java | 129 + .../pdfbox/io/TestRandomAccessBuffer.java | 38 +- .../pdfbox/multipdf/TestLayerUtility.java | 12 +- .../pdfbox/pdfparser/TestPDFParser.java | 43 +- .../com/tom_roush/pdfbox/pdmodel/TestFDF.java | 90 +- .../pdfbox/pdmodel/TestPDDocument.java | 62 +- .../pdmodel/TestPDDocumentInformation.java | 21 +- .../pdmodel/TestPDPageContentStream.java | 10 +- .../pdmodel/common/PDIntegerNameTreeNode.java | 0 .../pdfbox/pdmodel/common/PDStreamTest.java | 105 + .../TestOptionalContentGroups.java | 16 +- .../interactive/form/PDButtonTest.java | 165 +- .../form/PDDefaultAppearanceStringTest.java | 77 + .../form/PDSignatureFieldTest.java | 24 +- .../interactive/form/TestCheckBox.java | 2 +- .../com/tom_roush/pdfbox/text/BidiTest.java | 299 ++ .../pdfbox/text/TestTextStripper.java | 600 +++ .../tom_roush/pdfbox/util/TestQuickSort.java | 22 +- .../pdfbox/pdfparser/MissingCatalog.pdf | Bin 0 -> 429 bytes .../tom_roush/pdfbox/pdmodel/PDFBOX-3068.pdf | Bin 0 -> 801 bytes .../pdfbox/pdmodel/graphics/image/gif.gif | Bin 0 -> 12491 bytes .../interactive/form/AcroFormsBasicFields.pdf | Bin 152856 -> 170599 bytes .../com/tom_roush/pdfbox/text/BidiSample.pdf | Bin 0 -> 21130 bytes .../pdfbox/text/BidiSample.pdf-sorted.txt | 8 + .../tom_roush/pdfbox/text/BidiSample.pdf.txt | 8 + .../resources/pdfbox/input/FC60_Times.pdf | Bin 0 -> 25815 bytes .../pdfbox/input/FC60_Times.pdf-sorted.txt | 2 + .../resources/pdfbox/input/FC60_Times.pdf.txt | 2 + .../input/Liste732004001452_001_0.pdf_0_.pdf | Bin 0 -> 8056 bytes ...te732004001452_001_0.pdf_0_.pdf-sorted.txt | 7 + .../Liste732004001452_001_0.pdf_0_.pdf.txt | 7 + .../resources/pdfbox/input/PDFBOX-3025.pdf | Bin 0 -> 42418 bytes .../pdfbox/input/PDFBOX-3025.pdf-sorted.txt | 1 + .../pdfbox/input/PDFBOX-3025.pdf.txt | 1 + .../pdfbox/input/PDFBOX-3038-001033-p2.pdf | Bin 0 -> 56750 bytes .../PDFBOX-3038-001033-p2.pdf-sorted.txt | 25 + .../input/PDFBOX-3038-001033-p2.pdf.txt | 25 + .../pdfbox/input/PDFBOX-3042-003177-p2.pdf | Bin 0 -> 394897 bytes .../PDFBOX-3042-003177-p2.pdf-sorted.txt | 24 + .../input/PDFBOX-3042-003177-p2.pdf.txt | 24 + .../input/PDFBOX-3044-010197-p5-ligatures.pdf | Bin 0 -> 24624 bytes ...OX-3044-010197-p5-ligatures.pdf-sorted.txt | 51 + .../PDFBOX-3044-010197-p5-ligatures.pdf.txt | 51 + .../pdfbox/input/PDFBOX-3053-reduced.pdf | Bin 0 -> 50134 bytes .../input/PDFBOX-3053-reduced.pdf-sorted.txt | 2 + .../pdfbox/input/PDFBOX-3053-reduced.pdf.txt | 2 + .../input/PDFBOX-3061-092465-reduced.pdf | Bin 0 -> 23266 bytes .../PDFBOX-3061-092465-reduced.pdf-sorted.txt | 1 + .../input/PDFBOX-3061-092465-reduced.pdf.txt | 1 + .../pdfbox/input/PDFBOX-3062-002207-p1.pdf | Bin 0 -> 52066 bytes .../PDFBOX-3062-002207-p1.pdf-sorted.txt | 26 + .../input/PDFBOX-3062-002207-p1.pdf.txt | 26 + .../pdfbox/input/PDFBOX-3062-005717-p1.pdf | Bin 0 -> 11976 bytes .../PDFBOX-3062-005717-p1.pdf-sorted.txt | 9 + .../input/PDFBOX-3062-005717-p1.pdf.txt | 10 + ...MOQ7YZICIYGTPLQJAWJ4HLN6CCEMHZ-reduced.pdf | Bin 0 -> 111124 bytes ...TPLQJAWJ4HLN6CCEMHZ-reduced.pdf-sorted.txt | 2 + ...YZICIYGTPLQJAWJ4HLN6CCEMHZ-reduced.pdf.txt | 2 + .../pdfbox/input/PDFBOX-3067-negativeTf.pdf | Bin 0 -> 2459 bytes .../PDFBOX-3067-negativeTf.pdf-sorted.txt | 1 + .../input/PDFBOX-3067-negativeTf.pdf.txt | 1 + .../input/PDFBOX-3110-poems-beads-cropbox.pdf | Bin 0 -> 44500 bytes ...OX-3110-poems-beads-cropbox.pdf-sorted.txt | 71 + .../PDFBOX-3110-poems-beads-cropbox.pdf.txt | 75 + .../pdfbox/input/PDFBOX-3110-poems-beads.pdf | Bin 0 -> 45598 bytes .../PDFBOX-3110-poems-beads.pdf-sorted.txt | 71 + .../input/PDFBOX-3110-poems-beads.pdf.txt | 75 + ...SFWTRB3HBZBZKEVESVTBRZC2MNKZF5_reduced.pdf | Bin 0 -> 13363 bytes ...ZKEVESVTBRZC2MNKZF5_reduced.pdf-sorted.txt | 1 + ...RB3HBZBZKEVESVTBRZC2MNKZF5_reduced.pdf.txt | 1 + ...RAU4G6QMOVRYBISJU7R6MOVZCRFUO7P4-VFont.pdf | Bin 0 -> 80944 bytes ...YBISJU7R6MOVZCRFUO7P4-VFont.pdf-sorted.txt | 16 + ...G6QMOVRYBISJU7R6MOVZCRFUO7P4-VFont.pdf.txt | 14 + .../resources/pdfbox/input/PDFBOX-3195.pdf | Bin 0 -> 1435 bytes .../pdfbox/input/PDFBOX-3195.pdf-sorted.txt | 2 + .../pdfbox/input/PDFBOX-3195.pdf.txt | 2 + .../pdfbox/input/cweb.pdf-sorted.txt | 1509 ++++++ .../test/resources/pdfbox/input/cweb.pdf.txt | 1509 ++++++ .../resources/pdfbox/input/data-000001.pdf | Bin 0 -> 3072 bytes .../pdfbox/input/data-000001.pdf-sorted.txt | 5 + .../pdfbox/input/data-000001.pdf.txt | 5 + .../pdfbox/input/hello3.pdf-sorted.txt | 1 + .../resources/pdfbox/input/hello3.pdf.txt | 1 + .../resources/pdfbox/input/merge/jpegrgb.pdf | Bin 0 -> 41204 bytes .../pdfbox/input/merge/multitiff.pdf | Bin 0 -> 2928 bytes .../pdfbox/input/openoffice-test-document.pdf | Bin 0 -> 12418 bytes .../openoffice-test-document.pdf-sorted.txt | 1 + .../input/openoffice-test-document.pdf.txt | 1 + .../pdfbox/input/rendering/ARCHIVERGB.ai | 4290 +++++++++++++++++ .../pdfbox/input/rendering/FANTASTICCMYK.ai | 673 +++ .../pdfbox/input/rendering/HOTRODCMYK.ai | 3063 ++++++++++++ .../pdfbox/input/rendering/source.pdf | Bin 0 -> 231552 bytes .../pdfbox/input/rendering/survey.pdf | Bin 0 -> 127295 bytes .../input/rendering/test-landscape2.pdf | Bin 0 -> 10381 bytes .../input/rendering/tiger-as-form-xobject.pdf | Bin 0 -> 50798 bytes .../test/resources/pdfbox/input/rotation.pdf | Bin 0 -> 6605 bytes .../pdfbox/input/rotation.pdf-sorted.txt | 10 + .../resources/pdfbox/input/rotation.pdf.txt | 10 + .../resources/pdfbox/input/sampleForSpec.pdf | Bin 0 -> 9364 bytes .../pdfbox/input/sampleForSpec.pdf-sorted.txt | 11 + .../pdfbox/input/sampleForSpec.pdf.txt | 11 + .../input/sample_fonts_solidconvertor.pdf | Bin 0 -> 57107 bytes ...sample_fonts_solidconvertor.pdf-sorted.txt | 9 + .../input/sample_fonts_solidconvertor.pdf.txt | 9 + .../pdfbox/input/simple-openoffice.pdf | Bin 0 -> 15023 bytes .../input/simple-openoffice.pdf-sorted.txt | 1 + .../pdfbox/input/simple-openoffice.pdf.txt | 1 + .../pdfbox/input/yaddatest.pdf-sorted.txt | 1 + .../resources/pdfbox/input/yaddatest.pdf.txt | 1 + .../tom_roush/pdfbox/sample/MainActivity.java | 7 +- 507 files changed, 29148 insertions(+), 11956 deletions(-) create mode 100644 library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDAcroFormTest.java delete mode 100644 library/src/main/java/com/tom_roush/fontbox/cff/CharStringConverter.java delete mode 100644 library/src/main/java/com/tom_roush/fontbox/cff/IndexData.java delete mode 100644 library/src/main/java/com/tom_roush/fontbox/cff/Type1CharStringFormatter.java create mode 100644 library/src/main/java/com/tom_roush/fontbox/ttf/BufferedRandomAccessFile.java rename library/src/main/java/com/tom_roush/{pdfbox/exceptions/COSVisitorException.java => fontbox/util/Charsets.java} (51%) create mode 100644 library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetNonStrokingDeviceCMYKColor.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetStrokingDeviceCMYKColor.java rename library/src/main/java/com/tom_roush/pdfbox/{exceptions/WrappedIOException.java => contentstream/operator/graphics/BeginInlineImage.java} (53%) create mode 100644 library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/markedcontent/DrawObject.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/exceptions/CryptographyException.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/exceptions/InvalidPasswordException.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/exceptions/SignatureException.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDMatrix.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDTextStream.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/XrefEntry.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunctionType0.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapperImpl.java rename library/src/main/java/com/tom_roush/pdfbox/pdmodel/{encryption/BadSecurityHandlerException.java => font/FontMappers.java} (55%) create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/UniUtil.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDTransparencyGroup.java rename library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/{PDGroup.java => PDTransparencyGroupAttributes.java} (83%) delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Average.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/None.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Optimum.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Paeth.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/PredictorAlgorithm.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Sub.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Up.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionHide.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionImportData.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionMovie.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionResetForm.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionSound.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionSubmitForm.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionThread.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDCheckBox.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDCheckbox.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDFieldTree.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFA.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/rendering/ImageType.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/util/PDFHighlighter.java delete mode 100644 library/src/main/java/com/tom_roush/pdfbox/util/XMLUtil.java create mode 100644 library/src/main/java/com/tom_roush/pdfbox/util/filetypedetector/ByteTrie.java rename library/src/main/java/com/tom_roush/pdfbox/{exceptions/OutlineNotLocalException.java => util/filetypedetector/FileType.java} (54%) create mode 100644 library/src/main/java/com/tom_roush/pdfbox/util/filetypedetector/FileTypeDetector.java create mode 100644 library/src/main/resources/com/tom_roush/pdfbox/resources/text/BidiMirroring.txt create mode 100644 library/src/test/java/com/tom_roush/pdfbox/filter/PredictorTest.java rename library/src/{main => test}/java/com/tom_roush/pdfbox/pdmodel/common/PDIntegerNameTreeNode.java (100%) create mode 100644 library/src/test/java/com/tom_roush/pdfbox/pdmodel/common/PDStreamTest.java create mode 100644 library/src/test/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDDefaultAppearanceStringTest.java create mode 100644 library/src/test/java/com/tom_roush/pdfbox/text/BidiTest.java create mode 100644 library/src/test/java/com/tom_roush/pdfbox/text/TestTextStripper.java create mode 100644 library/src/test/resources/pdfbox/com/tom_roush/pdfbox/pdfparser/MissingCatalog.pdf create mode 100644 library/src/test/resources/pdfbox/com/tom_roush/pdfbox/pdmodel/PDFBOX-3068.pdf create mode 100644 library/src/test/resources/pdfbox/com/tom_roush/pdfbox/pdmodel/graphics/image/gif.gif create mode 100644 library/src/test/resources/pdfbox/com/tom_roush/pdfbox/text/BidiSample.pdf create mode 100644 library/src/test/resources/pdfbox/com/tom_roush/pdfbox/text/BidiSample.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/com/tom_roush/pdfbox/text/BidiSample.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/FC60_Times.pdf create mode 100644 library/src/test/resources/pdfbox/input/FC60_Times.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/FC60_Times.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/Liste732004001452_001_0.pdf_0_.pdf create mode 100644 library/src/test/resources/pdfbox/input/Liste732004001452_001_0.pdf_0_.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/Liste732004001452_001_0.pdf_0_.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3025.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3025.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3025.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3038-001033-p2.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3038-001033-p2.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3038-001033-p2.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3042-003177-p2.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3042-003177-p2.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3042-003177-p2.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3044-010197-p5-ligatures.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3044-010197-p5-ligatures.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3044-010197-p5-ligatures.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3053-reduced.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3053-reduced.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3053-reduced.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3061-092465-reduced.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3061-092465-reduced.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3061-092465-reduced.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3062-002207-p1.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3062-002207-p1.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3062-002207-p1.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3062-005717-p1.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3062-005717-p1.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3062-005717-p1.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3062-N2MOQ7YZICIYGTPLQJAWJ4HLN6CCEMHZ-reduced.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3062-N2MOQ7YZICIYGTPLQJAWJ4HLN6CCEMHZ-reduced.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3062-N2MOQ7YZICIYGTPLQJAWJ4HLN6CCEMHZ-reduced.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3067-negativeTf.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3067-negativeTf.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3067-negativeTf.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3110-poems-beads-cropbox.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3110-poems-beads-cropbox.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3110-poems-beads-cropbox.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3110-poems-beads.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3110-poems-beads.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3110-poems-beads.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3123-ADSFWTRB3HBZBZKEVESVTBRZC2MNKZF5_reduced.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3123-ADSFWTRB3HBZBZKEVESVTBRZC2MNKZF5_reduced.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3123-ADSFWTRB3HBZBZKEVESVTBRZC2MNKZF5_reduced.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3127-RAU4G6QMOVRYBISJU7R6MOVZCRFUO7P4-VFont.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3127-RAU4G6QMOVRYBISJU7R6MOVZCRFUO7P4-VFont.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3127-RAU4G6QMOVRYBISJU7R6MOVZCRFUO7P4-VFont.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3195.pdf create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3195.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/PDFBOX-3195.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/cweb.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/cweb.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/data-000001.pdf create mode 100644 library/src/test/resources/pdfbox/input/data-000001.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/data-000001.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/hello3.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/hello3.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/merge/jpegrgb.pdf create mode 100644 library/src/test/resources/pdfbox/input/merge/multitiff.pdf create mode 100644 library/src/test/resources/pdfbox/input/openoffice-test-document.pdf create mode 100644 library/src/test/resources/pdfbox/input/openoffice-test-document.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/openoffice-test-document.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/rendering/ARCHIVERGB.ai create mode 100644 library/src/test/resources/pdfbox/input/rendering/FANTASTICCMYK.ai create mode 100644 library/src/test/resources/pdfbox/input/rendering/HOTRODCMYK.ai create mode 100644 library/src/test/resources/pdfbox/input/rendering/source.pdf create mode 100644 library/src/test/resources/pdfbox/input/rendering/survey.pdf create mode 100644 library/src/test/resources/pdfbox/input/rendering/test-landscape2.pdf create mode 100644 library/src/test/resources/pdfbox/input/rendering/tiger-as-form-xobject.pdf create mode 100644 library/src/test/resources/pdfbox/input/rotation.pdf create mode 100644 library/src/test/resources/pdfbox/input/rotation.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/rotation.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/sampleForSpec.pdf create mode 100644 library/src/test/resources/pdfbox/input/sampleForSpec.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/sampleForSpec.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/sample_fonts_solidconvertor.pdf create mode 100644 library/src/test/resources/pdfbox/input/sample_fonts_solidconvertor.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/sample_fonts_solidconvertor.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/simple-openoffice.pdf create mode 100644 library/src/test/resources/pdfbox/input/simple-openoffice.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/simple-openoffice.pdf.txt create mode 100644 library/src/test/resources/pdfbox/input/yaddatest.pdf-sorted.txt create mode 100644 library/src/test/resources/pdfbox/input/yaddatest.pdf.txt diff --git a/gradle.properties b/gradle.properties index d7c6fc12d..5825f37d7 100755 --- a/gradle.properties +++ b/gradle.properties @@ -14,9 +14,9 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m org.gradle.parallel=true -VERSION_NAME=1.8.10.4-SNAPSHOT +VERSION_NAME=2.0.0.0-SNAPSHOT VERSION_CODE=1 ANDROID_BUILD_MIN_SDK_VERSION=14 ANDROID_BUILD_TARGET_SDK_VERSION=29 -ANDROID_BUILD_SDK_VERSION=29 \ No newline at end of file +ANDROID_BUILD_SDK_VERSION=29 diff --git a/library/build.gradle b/library/build.gradle index 1b0ad9651..8308151a5 100755 --- a/library/build.gradle +++ b/library/build.gradle @@ -18,6 +18,7 @@ android { versionName project.VERSION_NAME versionCode Integer.parseInt(project.VERSION_CODE) testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArgument "notAnnotation", "android.support.test.filters.FlakyTest" } buildTypes { @@ -88,6 +89,7 @@ dependencies { // Test dependencies testImplementation 'junit:junit:4.13.2' + testImplementation group: 'com.googlecode.java-diff-utils', name: 'diffutils', version: '1.3.0' androidTestImplementation 'com.android.support.test:runner:1.0.2' } diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestPublicKeyEncryption.java b/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestPublicKeyEncryption.java index 43a07d388..10019009a 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestPublicKeyEncryption.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestPublicKeyEncryption.java @@ -19,6 +19,17 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import javax.crypto.Cipher; + +import com.tom_roush.pdfbox.io.MemoryUsageSetting; import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.pdmodel.encryption.AccessPermission; import com.tom_roush.pdfbox.pdmodel.encryption.PublicKeyProtectionPolicy; @@ -30,16 +41,6 @@ import org.junit.Before; import org.junit.Test; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -import javax.crypto.Cipher; - import static org.junit.Assert.fail; /** @@ -77,7 +78,7 @@ public void setUp() throws Exception if (Cipher.getMaxAllowedKeyLength("AES") != Integer.MAX_VALUE) { // we need strong encryption for these tests -// fail("JCE unlimited strength jurisdiction policy files are not installed"); + fail("JCE unlimited strength jurisdiction policy files are not installed"); } testContext = InstrumentationRegistry.getInstrumentation().getContext(); @@ -271,7 +272,7 @@ private PDDocument reload(PDDocument doc, String decryptionPassword, InputStream ByteArrayOutputStream buffer = new ByteArrayOutputStream(); doc.save(buffer); return PDDocument.load(new ByteArrayInputStream(buffer.toByteArray()), decryptionPassword, - keyStore, null, false); + keyStore, null, MemoryUsageSetting.setupMainMemoryOnly()); } /** diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestSymmetricKeyEncryption.java b/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestSymmetricKeyEncryption.java index 2f047b178..a2a977e34 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestSymmetricKeyEncryption.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/encryption/TestSymmetricKeyEncryption.java @@ -21,6 +21,19 @@ import android.support.test.InstrumentationRegistry; import android.util.Log; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.crypto.Cipher; + import com.tom_roush.pdfbox.io.IOUtils; import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.pdmodel.PDDocumentCatalog; @@ -30,6 +43,7 @@ import com.tom_roush.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import com.tom_roush.pdfbox.pdmodel.encryption.AccessPermission; import com.tom_roush.pdfbox.pdmodel.encryption.StandardProtectionPolicy; +import com.tom_roush.pdfbox.pdmodel.graphics.image.ValidateXImage; import com.tom_roush.pdfbox.rendering.PDFRenderer; import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; @@ -37,19 +51,6 @@ import org.junit.Before; import org.junit.Test; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.crypto.Cipher; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -83,12 +84,12 @@ public void setUp() throws Exception if (Cipher.getMaxAllowedKeyLength("AES") != Integer.MAX_VALUE) { // we need strong encryption for these tests -// fail("JCE unlimited strength jurisdiction policy files are not installed"); + fail("JCE unlimited strength jurisdiction policy files are not installed"); } testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - testResultsDir = new File(testContext.getCacheDir(), "Download/pdfbox-test-output/crypto"); + testResultsDir = new File(testContext.getCacheDir(), "pdfbox-test-output/crypto"); testResultsDir.mkdirs(); permission = new AccessPermission(); @@ -151,18 +152,18 @@ public void testPermissions() throws Exception assertEquals("Cannot decrypt PDF, the password is incorrect", ex.getMessage()); } -// inputFileAsByteArray = getFileResourceAsByteArray("PasswordSample-256bit.pdf"); -// checkPerms(inputFileAsByteArray, "owner", fullAP); -// checkPerms(inputFileAsByteArray, "user", restrAP); -// try -// { -// checkPerms(inputFileAsByteArray, "", null); -// fail("wrong password not detected"); -// } -// catch (IOException ex) -// { -// assertEquals("Cannot decrypt PDF, the password is incorrect", ex.getMessage()); -// } TODO: PdfBox-Android + inputFileAsByteArray = getFileResourceAsByteArray("PasswordSample-256bit.pdf"); + checkPerms(inputFileAsByteArray, "owner", fullAP); + checkPerms(inputFileAsByteArray, "user", restrAP); + try + { + checkPerms(inputFileAsByteArray, "", null); + fail("wrong password not detected"); + } + catch (IOException ex) + { + assertEquals("Cannot decrypt PDF, the password is incorrect", ex.getMessage()); + } } private void checkPerms(byte[] inputFileAsByteArray, String password, @@ -186,7 +187,7 @@ private void checkPerms(byte[] inputFileAsByteArray, String password, assertEquals(expectedPermissions.canPrint(), currentAccessPermission.canPrint()); assertEquals(expectedPermissions.canPrintDegraded(), currentAccessPermission.canPrintDegraded()); -// new PDFRenderer(doc).renderImage(0); TODO: PdfBox-Android + new PDFRenderer(doc).renderImage(0); doc.close(); } @@ -209,8 +210,8 @@ public void testProtection() throws Exception testSymmEncrForKeySize(128, sizePriorToEncryption, inputFileAsByteArray, USERPASSWORD, OWNERPASSWORD, permission); -// testSymmEncrForKeySize(256, sizePriorToEncryption, inputFileAsByteArray, -// USERPASSWORD, OWNERPASSWORD, permission); TODO: PdfBox-Android + testSymmEncrForKeySize(256, sizePriorToEncryption, inputFileAsByteArray, USERPASSWORD, + OWNERPASSWORD, permission); } /** @@ -236,8 +237,9 @@ public void testProtectionInnerAttachment() throws Exception testSymmEncrForKeySizeInner(128, sizeOfFileWithEmbeddedFile, inputFileWithEmbeddedFileAsByteArray, extractedEmbeddedFile, USERPASSWORD, OWNERPASSWORD); -// testSymmEncrForKeySizeInner(256, sizeOfFileWithEmbeddedFile, -// inputFileWithEmbeddedFileAsByteArray, extractedEmbeddedFile, USERPASSWORD, OWNERPASSWORD); TODO: PdfBox-Android + testSymmEncrForKeySizeInner(256, sizeOfFileWithEmbeddedFile, + inputFileWithEmbeddedFileAsByteArray, extractedEmbeddedFile, USERPASSWORD, + OWNERPASSWORD); } private void testSymmEncrForKeySize(int keyLength, @@ -253,7 +255,7 @@ private void testSymmEncrForKeySize(int keyLength, List srcContentStreamTab = new ArrayList(); for (int i = 0; i < numSrcPages; ++i) { -// srcImgTab.add(pdfRenderer.renderImage(i)); TODO: PdfBox-Android + srcImgTab.add(pdfRenderer.renderImage(i)); InputStream unfilteredStream = document.getPage(i).getContents(); byte[] bytes = IOUtils.toByteArray(unfilteredStream); unfilteredStream.close(); @@ -268,8 +270,8 @@ private void testSymmEncrForKeySize(int keyLength, for (int i = 0; i < encryptedDoc.getNumberOfPages(); ++i) { // compare rendering -// Bitmap bim = pdfRenderer.renderImage(i); -// ValidateXImage.checkIdent(bim, srcImgTab.get(i)); TODO: PdfBox-Android + Bitmap bim = pdfRenderer.renderImage(i); + ValidateXImage.checkIdent(bim, srcImgTab.get(i)); // compare content streams InputStream unfilteredStream = encryptedDoc.getPage(i).getContents(); diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtilityTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtilityTest.java index eca45e0d4..c79d2c77c 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtilityTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtilityTest.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.IOException; +import com.tom_roush.pdfbox.io.MemoryUsageSetting; import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.rendering.PDFRenderer; import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; @@ -49,7 +50,7 @@ public void setUp() throws Exception { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - TARGETTESTDIR = testContext.getCacheDir() + "/Download/pdfbox-test-output/merge/"; + TARGETTESTDIR = testContext.getCacheDir() + "/pdfbox-test-output/merge/"; new File(TARGETTESTDIR).mkdirs(); if (!new File(TARGETTESTDIR).exists()) { @@ -72,14 +73,29 @@ public void testPDFMergerUtility() throws IOException { checkMergeIdentical("PDFBox.GlobalResourceMergeTest.Doc01.decoded.pdf", "PDFBox.GlobalResourceMergeTest.Doc02.decoded.pdf", - "GlobalResourceMergeTestResult.pdf", - false); + "GlobalResourceMergeTestResult.pdf", MemoryUsageSetting.setupMainMemoryOnly()); // once again, with scratch file checkMergeIdentical("PDFBox.GlobalResourceMergeTest.Doc01.decoded.pdf", "PDFBox.GlobalResourceMergeTest.Doc02.decoded.pdf", - "GlobalResourceMergeTestResult2.pdf", - true); + "GlobalResourceMergeTestResult2.pdf", MemoryUsageSetting.setupTempFileOnly()); + } + + /** + * Tests whether the merge of two PDF files with JPEG and CCITT works. A few revisions before + * 1704911 this test failed because the clone utility attempted to decode and re-encode the + * streams, see PDFBOX-2893 on 23.9.2015. + * + * @throws IOException if something goes wrong. + */ + public void testJpegCcitt() throws IOException + { + checkMergeIdentical("jpegrgb.pdf", "multitiff.pdf", "JpegMultiMergeTestResult.pdf", + MemoryUsageSetting.setupMainMemoryOnly()); + + // once again, with scratch file + checkMergeIdentical("jpegrgb.pdf", "multitiff.pdf", "JpegMultiMergeTestResult.pdf", + MemoryUsageSetting.setupTempFileOnly()); } // see PDFBOX-2893 @@ -88,21 +104,18 @@ public void testPDFMergerUtility2() throws IOException { checkMergeIdentical("PDFBox.GlobalResourceMergeTest.Doc01.pdf", "PDFBox.GlobalResourceMergeTest.Doc02.pdf", - "GlobalResourceMergeTestResult.pdf", - false); + "GlobalResourceMergeTestResult.pdf", MemoryUsageSetting.setupMainMemoryOnly()); // once again, with scratch file checkMergeIdentical("PDFBox.GlobalResourceMergeTest.Doc01.pdf", "PDFBox.GlobalResourceMergeTest.Doc02.pdf", - "GlobalResourceMergeTestResult2.pdf", - true); + "GlobalResourceMergeTestResult2.pdf", MemoryUsageSetting.setupTempFileOnly()); } // checks that the result file of a merge has the same rendering as the two // source files private void checkMergeIdentical(String filename1, String filename2, String mergeFilename, - boolean useScratchFiles) - throws IOException + MemoryUsageSetting memUsageSetting) throws IOException { PDDocument srcDoc1 = PDDocument.load(testContext.getAssets().open(SRCDIR + "/" + filename1), (String) null); int src1PageCount = srcDoc1.getNumberOfPages(); @@ -128,7 +141,7 @@ private void checkMergeIdentical(String filename1, String filename2, String merg pdfMergerUtility.addSource(testContext.getAssets().open(SRCDIR + "/" + filename1)); pdfMergerUtility.addSource(testContext.getAssets().open(SRCDIR + "/" + filename2)); pdfMergerUtility.setDestinationFileName(TARGETTESTDIR + mergeFilename); - pdfMergerUtility.mergeDocuments(useScratchFiles); + pdfMergerUtility.mergeDocuments(memUsageSetting); PDDocument mergedDoc = PDDocument.load(new File(TARGETTESTDIR, mergeFilename), (String) null); diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java index a1cb5ffff..40cdfd8bd 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java @@ -18,6 +18,9 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; +import java.io.File; +import java.io.IOException; + import com.tom_roush.pdfbox.io.RandomAccessBuffer; import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.pdmodel.PDPage; @@ -29,9 +32,6 @@ import org.junit.Before; import org.junit.Test; -import java.io.File; -import java.io.IOException; - import static com.tom_roush.pdfbox.pdmodel.graphics.image.ValidateXImage.validate; import static org.junit.Assert.assertEquals; @@ -50,8 +50,7 @@ public void setUp() { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - testResultsDir = new File(testContext.getCacheDir() + - "/Download/pdfbox-test-output/graphics/"); + testResultsDir = new File(testContext.getCacheDir(), "pdfbox-test-output/graphics/"); testResultsDir.mkdirs(); } @@ -73,7 +72,8 @@ public void testCreateFromRandomAccessSingle() throws IOException // checkIdent(bim3, ximage3.getOpaqueImage()); PDPage page = new PDPage(PDRectangle.A4); document.addPage(page); - PDPageContentStream contentStream = new PDPageContentStream(document, page, true, false); + PDPageContentStream contentStream = new PDPageContentStream(document, page, + PDPageContentStream.AppendMode.APPEND, false); contentStream.drawImage(ximage3, 0, 0, ximage3.getWidth(), ximage3.getHeight()); contentStream.close(); @@ -84,7 +84,8 @@ public void testCreateFromRandomAccessSingle() throws IOException // checkIdent(bim4, ximage4.getOpaqueImage()); page = new PDPage(PDRectangle.A4); document.addPage(page); - contentStream = new PDPageContentStream(document, page, true, false); + contentStream = new PDPageContentStream(document, page, + PDPageContentStream.AppendMode.APPEND, false); contentStream.drawImage(ximage4, 0, 0); contentStream.close(); @@ -131,7 +132,8 @@ public void testCreateFromRandomAccessMulti() throws IOException float fY = ximage.getHeight() / page.getMediaBox().getHeight(); float factor = Math.max(fX, fY); document.addPage(page); - PDPageContentStream contentStream = new PDPageContentStream(document, page, true, false); + PDPageContentStream contentStream = new PDPageContentStream(document, page, + PDPageContentStream.AppendMode.APPEND, false); contentStream.drawImage(ximage, 0, 0, ximage.getWidth() / factor, ximage.getHeight() / factor); contentStream.close(); ++pdfPageNum; diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactoryTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactoryTest.java index c3836c217..a5e5b12ce 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactoryTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactoryTest.java @@ -21,6 +21,14 @@ import android.support.test.InstrumentationRegistry; import android.util.Log; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.io.IOUtils; import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceGray; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceRGB; @@ -29,13 +37,10 @@ import org.junit.Before; import org.junit.Test; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - import static com.tom_roush.pdfbox.pdmodel.graphics.image.ValidateXImage.colorCount; import static com.tom_roush.pdfbox.pdmodel.graphics.image.ValidateXImage.doWritePDF; import static com.tom_roush.pdfbox.pdmodel.graphics.image.ValidateXImage.validate; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -54,8 +59,7 @@ public void setUp() { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - testResultsDir = new File(testContext.getCacheDir() + - "/Download/pdfbox-test-output/graphics/"); + testResultsDir = new File(testContext.getCacheDir(), "pdfbox-test-output/graphics/"); testResultsDir.mkdirs(); } @@ -73,6 +77,8 @@ public void testCreateFromStream() throws IOException validate(ximage, 8, 344, 287, "jpg", PDDeviceRGB.INSTANCE.getName()); doWritePDF(document, ximage, testResultsDir, "jpegrgbstream.pdf"); + checkJpegStream(testResultsDir, "jpegrgbstream.pdf", testContext.getAssets().open( + "pdfbox/com/tom_roush/pdfbox/pdmodel/graphics/image/jpeg.jpg")); } /** @@ -89,6 +95,8 @@ public void testCreateFromStream256() throws IOException validate(ximage, 8, 344, 287, "jpg", PDDeviceGray.INSTANCE.getName()); doWritePDF(document, ximage, testResultsDir, "jpeg256stream.pdf"); + checkJpegStream(testResultsDir, "jpeg256stream.pdf", testContext.getAssets().open( + "pdfbox/com/tom_roush/pdfbox/pdmodel/graphics/image/jpeg256.jpg")); } /** @@ -227,4 +235,23 @@ public void testCreateFromImageARGB_4444() throws IOException doWritePDF(document, ximage, testResultsDir, "jpeg-4bargb.pdf"); } + + // check whether it is possible to extract the jpeg stream exactly + // as it was passed to createFromStream + private void checkJpegStream(File testResultsDir, String filename, InputStream resourceStream) + throws IOException + { + PDDocument doc = PDDocument.load(new File(testResultsDir, filename)); + PDImageXObject img = + (PDImageXObject) doc.getPage(0).getResources().getXObject(COSName.getPDFName("Im1")); + InputStream dctStream = img.createInputStream(Arrays.asList(COSName.DCT_DECODE.getName())); + ByteArrayOutputStream baos1 = new ByteArrayOutputStream(); + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + IOUtils.copy(resourceStream, baos1); + IOUtils.copy(dctStream, baos2); + resourceStream.close(); + dctStream.close(); + assertArrayEquals(baos1.toByteArray(), baos2.toByteArray()); + doc.close(); + } } diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactoryTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactoryTest.java index 48606827f..10e8e6159 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactoryTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactoryTest.java @@ -22,6 +22,9 @@ import android.graphics.Paint; import android.support.test.InstrumentationRegistry; +import java.io.File; +import java.io.IOException; + import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.pdmodel.PDPage; import com.tom_roush.pdfbox.pdmodel.PDPageContentStream; @@ -33,9 +36,6 @@ import org.junit.Before; import org.junit.Test; -import java.io.File; -import java.io.IOException; - import static com.tom_roush.pdfbox.pdmodel.graphics.image.ValidateXImage.checkIdent; import static com.tom_roush.pdfbox.pdmodel.graphics.image.ValidateXImage.colorCount; import static com.tom_roush.pdfbox.pdmodel.graphics.image.ValidateXImage.doWritePDF; @@ -59,8 +59,7 @@ public void setUp() { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - testResultsDir = new File(testContext.getCacheDir() + - "/Download/pdfbox-test-output/graphics/"); + testResultsDir = new File(testContext.getCacheDir(), "pdfbox-test-output/graphics/"); testResultsDir.mkdirs(); } @@ -112,7 +111,8 @@ public void testCreateLosslessFromImageRGB() throws IOException // if something goes wrong in the future and we want to have a PDF to open. PDPage page = new PDPage(); document.addPage(page); - PDPageContentStream contentStream = new PDPageContentStream(document, page, true, false); + PDPageContentStream contentStream = new PDPageContentStream(document, page, + PDPageContentStream.AppendMode.APPEND, false); contentStream.drawImage(ximage1, 200, 300, ximage1.getWidth() / 2, ximage1.getHeight() / 2); contentStream.drawImage(ximage2, 200, 450, ximage2.getWidth() / 2, ximage2.getHeight() / 2); // contentStream.drawImage(ximage3, 200, 600, ximage3.getWidth() / 2, ximage3.getHeight() / 2); diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImageTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImageTest.java index 28f4d50fc..0b25c09cc 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImageTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImageTest.java @@ -22,6 +22,10 @@ import android.graphics.Paint; import android.support.test.InstrumentationRegistry; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSInteger; @@ -35,10 +39,6 @@ import org.junit.Before; import org.junit.Test; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -58,8 +58,7 @@ public void setUp() { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); - testResultsDir = new File(testContext.getCacheDir() + - "/Download/pdfbox-test-output/graphics/"); + testResultsDir = new File(testContext.getCacheDir(), "pdfbox-test-output/graphics/"); testResultsDir.mkdirs(); } @@ -186,7 +185,8 @@ public void testInlineImage() throws IOException PDDocument document = new PDDocument(); PDPage page = new PDPage(); document.addPage(page); - PDPageContentStream contentStream = new PDPageContentStream(document, page, true, false); + PDPageContentStream contentStream = new PDPageContentStream(document, page, + PDPageContentStream.AppendMode.APPEND, false); contentStream.drawImage(inlineImage1, 150, 400); contentStream.drawImage(inlineImage1, 150, 500, inlineImage1.getWidth() * 2, inlineImage1.getHeight() * 2); diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/ValidateXImage.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/ValidateXImage.java index 831b3a72d..eec7aadfb 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/ValidateXImage.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/graphics/image/ValidateXImage.java @@ -46,11 +46,11 @@ public static void validate(PDImageXObject ximage, int bpc, int width, int heigh { // check the dictionary assertNotNull(ximage); - COSStream cosStream = ximage.getCOSStream(); + COSStream cosStream = ximage.getCOSObject(); assertNotNull(cosStream); assertEquals(COSName.XOBJECT, cosStream.getItem(COSName.TYPE)); assertEquals(COSName.IMAGE, cosStream.getItem(COSName.SUBTYPE)); - assertTrue(ximage.getCOSStream().getLength() > 0); + assertTrue(ximage.getCOSObject().getLength() > 0); assertEquals(bpc, ximage.getBitsPerComponent()); assertEquals(width, ximage.getWidth()); assertEquals(height, ximage.getHeight()); @@ -115,7 +115,8 @@ static void doWritePDF(PDDocument document, PDImageXObject ximage, File testResu PDPage page = new PDPage(); document.addPage(page); - PDPageContentStream contentStream = new PDPageContentStream(document, page, true, false); + PDPageContentStream contentStream = new PDPageContentStream(document, page, + PDPageContentStream.AppendMode.APPEND, false); contentStream.drawImage(ximage, 150, 300); contentStream.drawImage(ximage, 200, 350); contentStream.close(); diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AlignmentTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AlignmentTest.java index 6e53e5d2f..2e74ffd4a 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AlignmentTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AlignmentTest.java @@ -19,16 +19,17 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; +import java.io.File; +import java.io.IOException; + import com.tom_roush.pdfbox.pdmodel.PDDocument; +import com.tom_roush.pdfbox.rendering.TestRendering; import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.io.File; -import java.io.IOException; - public class AlignmentTest { private static File OUT_DIR; @@ -48,7 +49,7 @@ public void setUp() throws IOException PDFBoxResourceLoader.init(testContext); document = PDDocument.load(testContext.getAssets().open(IN_DIR + "/" + NAME_OF_PDF)); acroForm = document.getDocumentCatalog().getAcroForm(); - OUT_DIR = new File(testContext.getCacheDir(), "Download/pdfbox-test-output"); + OUT_DIR = new File(testContext.getCacheDir(), "pdfbox-test-output"); OUT_DIR.mkdirs(); } @@ -112,12 +113,9 @@ public void fillFields() throws IOException // compare rendering File file = new File(OUT_DIR, NAME_OF_PDF); document.save(file); -// TestPDFToImage testPDFToImage = new TestPDFToImage(TestPDFToImage.class.getName()); -// if (!testPDFToImage.doTestFile(file, IN_DIR.getAbsolutePath(), OUT_DIR.getAbsolutePath())) -// { -// // don't fail, rendering is different on different systems, result must be viewed manually -// System.err.println ("Rendering of " + file + " failed or is not identical to expected rendering in " + IN_DIR + " directory"); -// } + TestRendering testRendering = new TestRendering(); + testRendering.setUp(); + testRendering.render(file); } @After diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/MultilineFieldsTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/MultilineFieldsTest.java index 478148872..7090a268d 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/MultilineFieldsTest.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/MultilineFieldsTest.java @@ -20,16 +20,17 @@ import android.content.Context; import android.support.test.InstrumentationRegistry; +import java.io.File; +import java.io.IOException; + import com.tom_roush.pdfbox.pdmodel.PDDocument; +import com.tom_roush.pdfbox.rendering.TestRendering; import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.io.File; -import java.io.IOException; - public class MultilineFieldsTest { private static File OUT_DIR; @@ -52,7 +53,7 @@ public void setUp() throws IOException System.out.println("Working Directory = " + System.getProperty("user.dir")); document = PDDocument.load(testContext.getAssets().open(IN_DIR + "/" + NAME_OF_PDF)); acroForm = document.getDocumentCatalog().getAcroForm(); - OUT_DIR = new File(testContext.getCacheDir(), "Download/pdfbox-test-output"); + OUT_DIR = new File(testContext.getCacheDir(), "pdfbox-test-output"); OUT_DIR.mkdirs(); } @@ -98,12 +99,9 @@ public void fillFields() throws IOException // compare rendering File file = new File(OUT_DIR, NAME_OF_PDF); document.save(file); -// TestPDFToImage testPDFToImage = new TestPDFToImage(TestPDFToImage.class.getName()); -// if (!testPDFToImage.doTestFile(file, IN_DIR.getAbsolutePath(), OUT_DIR.getAbsolutePath())) -// { -// // don't fail, rendering is different on different systems, result must be viewed manually -// System.err.println ("Rendering of " + file + " failed or is not identical to expected rendering in " + IN_DIR + " directory"); -// } TODO: PdfBox-Android + TestRendering testRendering = new TestRendering(); + testRendering.setUp(); + testRendering.render(file); } @After diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDAcroFormTest.java b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDAcroFormTest.java new file mode 100644 index 000000000..171d00093 --- /dev/null +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDAcroFormTest.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.interactive.form; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; + +import java.io.File; +import java.io.IOException; + +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.PDDocument; +import com.tom_roush.pdfbox.rendering.TestRendering; +import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Test for the PDButton class. + */ +public class PDAcroFormTest +{ + + private PDDocument document; + private PDAcroForm acroForm; + + private static File OUT_DIR; + private static final String IN_DIR = "pdfbox/com/tom_roush/pdfbox/pdmodel/interactive/form"; + + Context testContext; + + @Before + public void setUp() + { + testContext = InstrumentationRegistry.getInstrumentation().getContext(); + PDFBoxResourceLoader.init(testContext); + + document = new PDDocument(); + acroForm = new PDAcroForm(document); + document.getDocumentCatalog().setAcroForm(acroForm); + + OUT_DIR = new File(testContext.getCacheDir(), "pdfbox-test-output"); + OUT_DIR.mkdirs(); + } + + @Test + public void testFieldsEntry() + { + // the /Fields entry has been created with the AcroForm + // as this is a required entry + assertNotNull(acroForm.getFields()); + assertEquals(acroForm.getFields().size(), 0); + + // there shouldn't be an exception if there is no such field + assertNull(acroForm.getField("foo")); + + // remove the required entry which is the case for some + // PDFs (see PDFBOX-2965) + acroForm.getCOSObject().removeItem(COSName.FIELDS); + + // ensure there is always an empty collection returned + assertNotNull(acroForm.getFields()); + assertEquals(acroForm.getFields().size(), 0); + + // there shouldn't be an exception if there is no such field + assertNull(acroForm.getField("foo")); + } + + @Test + public void testAcroFormProperties() + { + assertTrue(acroForm.getDefaultAppearance().isEmpty()); + acroForm.setDefaultAppearance("/Helv 0 Tf 0 g"); + assertEquals(acroForm.getDefaultAppearance(), "/Helv 0 Tf 0 g"); + } + + @Test + public void testFlatten() throws IOException + { + PDDocument testPdf = PDDocument.load( + testContext.getAssets().open(IN_DIR + "/" + "AlignmentTests.pdf")); + testPdf.getDocumentCatalog().getAcroForm().flatten(); + assertTrue(testPdf.getDocumentCatalog().getAcroForm().getFields().isEmpty()); + File file = new File(OUT_DIR, "AlignmentTests-flattened.pdf"); + testPdf.save(file); + // compare rendering + TestRendering testRendering = new TestRendering(); + testRendering.setUp(); + testRendering.render(file); + } + + @After + public void tearDown() throws IOException + { + document.close(); + } + +} + diff --git a/library/src/androidTest/java/com/tom_roush/pdfbox/rendering/TestRendering.java b/library/src/androidTest/java/com/tom_roush/pdfbox/rendering/TestRendering.java index 8e8671938..a03273d18 100644 --- a/library/src/androidTest/java/com/tom_roush/pdfbox/rendering/TestRendering.java +++ b/library/src/androidTest/java/com/tom_roush/pdfbox/rendering/TestRendering.java @@ -18,18 +18,22 @@ package com.tom_roush.pdfbox.rendering; import android.content.Context; +import android.graphics.Bitmap; import android.support.test.InstrumentationRegistry; +import android.support.test.filters.FlakyTest; import android.util.Log; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; + import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; import org.junit.Before; import org.junit.Test; -import java.io.IOException; -import java.util.ArrayList; - /** * Functional test for PDF rendering. This test simply tries to render * a series of PDFs using PDFBox to make sure that no exceptions are thrown. @@ -41,6 +45,7 @@ public class TestRendering { private static final String INPUT_DIR = "pdfbox/input/rendering"; + private static File OUT_DIR; Context testContext; @@ -85,8 +90,11 @@ public void setUp() throws IOException { testContext = InstrumentationRegistry.getInstrumentation().getContext(); PDFBoxResourceLoader.init(testContext); + OUT_DIR = new File(testContext.getCacheDir(), "pdfbox-test-output/rendering"); + OUT_DIR.mkdirs(); } + @FlakyTest(detail = "May cause OutOfMemoryExceptions in some cases, though test should pass") @Test public void testRendering() { @@ -108,8 +116,27 @@ public void testRendering() public void render(String fileName) throws IOException { PDDocument document = PDDocument.load(testContext.getAssets().open(fileName)); + render(document, fileName.substring(fileName.lastIndexOf("/") + 1)); + } + + public void render(File file) throws IOException + { + PDDocument document = PDDocument.load(file); + render(document, file.getName()); + } + + private void render(PDDocument document, String name) throws IOException + { PDFRenderer renderer = new PDFRenderer(document); - renderer.renderImage(0); + for (int i = 0; i < document.getNumberOfPages(); i++) + { + Bitmap image = renderer.renderImage(i); + + File renderFile = new File(OUT_DIR, name + "-" + (i + 1) + ".png"); + FileOutputStream fileOut = new FileOutputStream(renderFile); + image.compress(Bitmap.CompressFormat.PNG, 100, fileOut); + fileOut.close(); + } // We don't actually do anything with the image for the same reason that // TestPDFToImage is disabled - different JVMs produce different results @@ -117,6 +144,6 @@ public void render(String fileName) throws IOException // during the rendering process. document.close(); - Log.e("PdfBox-Android", "Rendered " + fileName + " without dying"); + Log.e("PdfBox-Android", "Rendered " + name + " without dying"); } } diff --git a/library/src/main/java/com/tom_roush/fontbox/FontBoxFont.java b/library/src/main/java/com/tom_roush/fontbox/FontBoxFont.java index b440a1676..5350452f6 100644 --- a/library/src/main/java/com/tom_roush/fontbox/FontBoxFont.java +++ b/library/src/main/java/com/tom_roush/fontbox/FontBoxFont.java @@ -18,11 +18,11 @@ import android.graphics.Path; -import com.tom_roush.fontbox.util.BoundingBox; - import java.io.IOException; import java.util.List; +import com.tom_roush.fontbox.util.BoundingBox; + /** * Common interface for all FontBox fonts. * @@ -63,6 +63,7 @@ public interface FontBoxFont /** * Returns true if the font contains the given glyph. + * * @param name PostScript glyph name */ boolean hasGlyph(String name) throws IOException; diff --git a/library/src/main/java/com/tom_roush/fontbox/afm/AFMParser.java b/library/src/main/java/com/tom_roush/fontbox/afm/AFMParser.java index 3c78937c7..a198be501 100644 --- a/library/src/main/java/com/tom_roush/fontbox/afm/AFMParser.java +++ b/library/src/main/java/com/tom_roush/fontbox/afm/AFMParser.java @@ -16,14 +16,15 @@ */ package com.tom_roush.fontbox.afm; -import com.tom_roush.fontbox.util.BoundingBox; - -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; import java.util.StringTokenizer; +import com.tom_roush.fontbox.util.BoundingBox; +import com.tom_roush.fontbox.util.Charsets; + /** * This class is used to parse AFM(Adobe Font Metrics) documents. * @@ -288,35 +289,6 @@ public class AFMParser private final InputStream input; - /** - * A method to test parsing of all AFM documents in the resources - * directory. - * - * @param args Ignored. - * - * @throws IOException If there is an error parsing one of the documents. - */ - public static void main( String[] args ) throws IOException - { - java.io.File afmDir = new java.io.File( "Resources/afm" ); - java.io.File[] files = afmDir.listFiles(); - if (files != null) - { - for (File file : files) - { - if (file.getPath().toUpperCase().endsWith(".AFM")) - { - long start = System.currentTimeMillis(); - FileInputStream input = new FileInputStream(file); - AFMParser parser = new AFMParser(input); - parser.parse(); - long stop = System.currentTimeMillis(); - System.out.println("Parsing:" + file.getPath() + " " + (stop - start)); - } - } - } - } - /** * Constructor. * @@ -328,7 +300,7 @@ public AFMParser( InputStream in ) } /** - * This will parse the AFM document. This will close the Input stream + * This will parse the AFM document. The input stream is closed * when the parsing is finished. * * @return the parsed FontMetric @@ -337,7 +309,20 @@ public AFMParser( InputStream in ) */ public FontMetrics parse() throws IOException { - return parseFontMetric(); + return parseFontMetric(false); + } + + /** + * This will parse the AFM document. The input stream is closed + * when the parsing is finished. + * + * @param reducedDataset parse a reduced subset of data if set to true + * @return the parsed FontMetric + * @throws IOException If there is an IO error reading the document. + */ + public FontMetrics parse(boolean reducedDataset) throws IOException + { + return parseFontMetric(reducedDataset); } /** @@ -347,7 +332,7 @@ public FontMetrics parse() throws IOException * * @throws IOException If there is an error reading the AFM file. */ - private FontMetrics parseFontMetric() throws IOException + private FontMetrics parseFontMetric(boolean reducedDataset) throws IOException { FontMetrics fontMetrics = new FontMetrics(); String startFontMetrics = readString(); @@ -358,6 +343,7 @@ private FontMetrics parseFontMetric() throws IOException } fontMetrics.setAFMVersion( readFloat() ); String nextCommand; + boolean charMetricsRead = false; while( !END_FONT_METRICS.equals( (nextCommand = readString() ) ) ) { if( FONT_NAME.equals( nextCommand ) ) @@ -482,10 +468,11 @@ else if( IS_FIXED_PITCH.equals( nextCommand ) ) else if( START_CHAR_METRICS.equals( nextCommand ) ) { int count = readInt(); + List charMetrics = new ArrayList(count); for( int i=0; i charMetrics = new ArrayList(); - private final Map charMetricsMap = new HashMap(); + private Map charMetricsMap = new HashMap(); private List trackKern = new ArrayList(); private List composites = new ArrayList(); private List kernPairs = new ArrayList(); @@ -89,13 +89,9 @@ public FontMetrics() */ public float getCharacterWidth( String name ) { - float result; + float result = 0; CharMetric metric = charMetricsMap.get( name ); - if( metric == null ) - { - result=0; - } - else + if (metric != null) { result = metric.getWx(); } @@ -110,13 +106,9 @@ public float getCharacterWidth( String name ) */ public float getCharacterHeight( String name ) { - float result; + float result = 0; CharMetric metric = charMetricsMap.get( name ); - if( metric == null ) - { - result=0; - } - else + if (metric != null) { if( metric.getWy() == 0 ) { @@ -719,6 +711,11 @@ public List getCharMetrics() public void setCharMetrics(List charMetricsValue) { charMetrics = charMetricsValue; + charMetricsMap = new HashMap(charMetrics.size()); + for (CharMetric metric : charMetricsValue) + { + charMetricsMap.put( metric.getName(), metric ); + } } /** diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFCIDFont.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFCIDFont.java index ccae980d8..091dbccaa 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFCIDFont.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFCIDFont.java @@ -19,14 +19,14 @@ import android.graphics.Path; -import com.tom_roush.fontbox.type1.Type1CharStringReader; - import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.tom_roush.fontbox.type1.Type1CharStringReader; + /** * A Type 0 CIDFont represented in a CFF file. Thread safe. * @@ -171,7 +171,6 @@ void setFdSelect(FDSelect fdSelect) * Returns the defaultWidthX for the given GID. * * @param gid GID - * @return defaultWidthX */ private int getDefaultWidthX(int gid) { @@ -188,7 +187,6 @@ private int getDefaultWidthX(int gid) * Returns the nominalWidthX for the given GID. * * @param gid GID - * @return defaultWidthX */ private int getNominalWidthX(int gid) { @@ -200,21 +198,21 @@ private int getNominalWidthX(int gid) Map privDict = this.privateDictionaries.get(fdArrayIndex); return privDict.containsKey("nominalWidthX") ? ((Number)privDict.get("nominalWidthX")).intValue() : 0; } - + /** - * Returns the LocalSubrIndex for the given GID. - * - * @param gid GID + * Returns the LocalSubrIndex for the given GID. + * + * @param gid GID */ - private IndexData getLocalSubrIndex(int gid) + private byte[][] getLocalSubrIndex(int gid) { int fdArrayIndex = this.fdSelect.getFDIndex(gid); if (fdArrayIndex == -1) { - return new IndexData(0); + return null; } Map privDict = this.privateDictionaries.get(fdArrayIndex); - return (IndexData)privDict.get("Subrs"); + return (byte[][])privDict.get("Subrs"); } /** @@ -223,6 +221,7 @@ private IndexData getLocalSubrIndex(int gid) * @param cid CID * @throws IOException if the charstring could not be read */ + @Override public CIDKeyedType2CharString getType2CharString(int cid) throws IOException { CIDKeyedType2CharString type2 = charStringCache.get(cid); @@ -230,15 +229,15 @@ public CIDKeyedType2CharString getType2CharString(int cid) throws IOException { int gid = charset.getGIDForCID(cid); - byte[] bytes = charStrings.get(gid); + byte[] bytes = charStrings[gid]; if (bytes == null) { - bytes = charStrings.get(0); // .notdef + bytes = charStrings[0]; // .notdef } Type2CharStringParser parser = new Type2CharStringParser(fontName, cid); List type2seq = parser.parse(bytes, globalSubrIndex, getLocalSubrIndex(gid)); - type2 = new CIDKeyedType2CharString(reader, fontName, cid, gid, type2seq, - getDefaultWidthX(cid), getNominalWidthX(cid)); + type2 = new CIDKeyedType2CharString(reader, fontName, cid, gid, type2seq, + getDefaultWidthX(gid), getNominalWidthX(gid)); charStringCache.put(cid, type2); } return type2; @@ -247,8 +246,8 @@ public CIDKeyedType2CharString getType2CharString(int cid) throws IOException @Override public List getFontMatrix() { - // our parser guarantees that FontMatrix will be present and correct in the Top DICT - return (List)topDict.get("FontMatrix"); + // our parser guarantees that FontMatrix will be present and correct in the Top DICT + return (List)topDict.get("FontMatrix"); } @Override diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFCharset.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFCharset.java index 38657a209..a7b3e3120 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFCharset.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFCharset.java @@ -29,13 +29,13 @@ public abstract class CFFCharset { private final boolean isCIDFont; - private final Map sidOrCidToGid = new HashMap(); - private final Map gidToSid = new HashMap(); - private final Map nameToSid = new HashMap(); + private final Map sidOrCidToGid = new HashMap(250); + private final Map gidToSid = new HashMap(250); + private final Map nameToSid = new HashMap(250); // inverse private final Map gidToCid = new HashMap(); - private final Map gidToName = new HashMap(); + private final Map gidToName = new HashMap(250); /** * Package-private constructor for use by subclasses. @@ -47,6 +47,16 @@ public abstract class CFFCharset this.isCIDFont = isCIDFont; } + /** + * Indicates if the charset belongs to a CID font. + * + * @return true for CID fonts + */ + public boolean isCIDFont() + { + return isCIDFont; + } + /** * Adds a new GID/SID/name combination to the charset. * diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFEncoding.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFEncoding.java index a87056437..7979465e2 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFEncoding.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFEncoding.java @@ -16,11 +16,11 @@ */ package com.tom_roush.fontbox.cff; -import com.tom_roush.fontbox.encoding.Encoding; - import java.util.HashMap; import java.util.Map; +import com.tom_roush.fontbox.encoding.Encoding; + /** * A CFF Type 1-equivalent Encoding. An encoding is an array of codes associated with some or all * glyphs in a font @@ -29,7 +29,7 @@ */ public abstract class CFFEncoding extends Encoding { - private final Map codeToName = new HashMap(); + private final Map codeToName = new HashMap(250); /** * Package-private constructor for subclasses. diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertCharset.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertCharset.java index 46c560a1e..5aee1326e 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertCharset.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertCharset.java @@ -23,6 +23,180 @@ */ public final class CFFExpertCharset extends CFFCharset { + private static final int CHAR_CODE = 0; + private static final int CHAR_NAME = 1; + + /** + * Table of character codes and their corresponding names. + */ + private static final Object[][] CFF_EXPERT_CHARSET_TABLE = { + {0, ".notdef"}, + {1, "space"}, + {229, "exclamsmall"}, + {230, "Hungarumlautsmall"}, + {231, "dollaroldstyle"}, + {232, "dollarsuperior"}, + {233, "ampersandsmall"}, + {234, "Acutesmall"}, + {235, "parenleftsuperior"}, + {236, "parenrightsuperior"}, + {237, "twodotenleader"}, + {238, "onedotenleader"}, + {13, "comma"}, + {14, "hyphen"}, + {15, "period"}, + {99, "fraction"}, + {239, "zerooldstyle"}, + {240, "oneoldstyle"}, + {241, "twooldstyle"}, + {242, "threeoldstyle"}, + {243, "fouroldstyle"}, + {244, "fiveoldstyle"}, + {245, "sixoldstyle"}, + {246, "sevenoldstyle"}, + {247, "eightoldstyle"}, + {248, "nineoldstyle"}, + {27, "colon"}, + {28, "semicolon"}, + {249, "commasuperior"}, + {250, "threequartersemdash"}, + {251, "periodsuperior"}, + {252, "questionsmall"}, + {253, "asuperior"}, + {254, "bsuperior"}, + {255, "centsuperior"}, + {256, "dsuperior"}, + {257, "esuperior"}, + {258, "isuperior"}, + {259, "lsuperior"}, + {260, "msuperior"}, + {261, "nsuperior"}, + {262, "osuperior"}, + {263, "rsuperior"}, + {264, "ssuperior"}, + {265, "tsuperior"}, + {266, "ff"}, + {109, "fi"}, + {110, "fl"}, + {267, "ffi"}, + {268, "ffl"}, + {269, "parenleftinferior"}, + {270, "parenrightinferior"}, + {271, "Circumflexsmall"}, + {272, "hyphensuperior"}, + {273, "Gravesmall"}, + {274, "Asmall"}, + {275, "Bsmall"}, + {276, "Csmall"}, + {277, "Dsmall"}, + {278, "Esmall"}, + {279, "Fsmall"}, + {280, "Gsmall"}, + {281, "Hsmall"}, + {282, "Ismall"}, + {283, "Jsmall"}, + {284, "Ksmall"}, + {285, "Lsmall"}, + {286, "Msmall"}, + {287, "Nsmall"}, + {288, "Osmall"}, + {289, "Psmall"}, + {290, "Qsmall"}, + {291, "Rsmall"}, + {292, "Ssmall"}, + {293, "Tsmall"}, + {294, "Usmall"}, + {295, "Vsmall"}, + {296, "Wsmall"}, + {297, "Xsmall"}, + {298, "Ysmall"}, + {299, "Zsmall"}, + {300, "colonmonetary"}, + {301, "onefitted"}, + {302, "rupiah"}, + {303, "Tildesmall"}, + {304, "exclamdownsmall"}, + {305, "centoldstyle"}, + {306, "Lslashsmall"}, + {307, "Scaronsmall"}, + {308, "Zcaronsmall"}, + {309, "Dieresissmall"}, + {310, "Brevesmall"}, + {311, "Caronsmall"}, + {312, "Dotaccentsmall"}, + {313, "Macronsmall"}, + {314, "figuredash"}, + {315, "hypheninferior"}, + {316, "Ogoneksmall"}, + {317, "Ringsmall"}, + {318, "Cedillasmall"}, + {158, "onequarter"}, + {155, "onehalf"}, + {163, "threequarters"}, + {319, "questiondownsmall"}, + {320, "oneeighth"}, + {321, "threeeighths"}, + {322, "fiveeighths"}, + {323, "seveneighths"}, + {324, "onethird"}, + {325, "twothirds"}, + {326, "zerosuperior"}, + {150, "onesuperior"}, + {164, "twosuperior"}, + {169, "threesuperior"}, + {327, "foursuperior"}, + {328, "fivesuperior"}, + {329, "sixsuperior"}, + {330, "sevensuperior"}, + {331, "eightsuperior"}, + {332, "ninesuperior"}, + {333, "zeroinferior"}, + {334, "oneinferior"}, + {335, "twoinferior"}, + {336, "threeinferior"}, + {337, "fourinferior"}, + {338, "fiveinferior"}, + {339, "sixinferior"}, + {340, "seveninferior"}, + {341, "eightinferior"}, + {342, "nineinferior"}, + {343, "centinferior"}, + {344, "dollarinferior"}, + {345, "periodinferior"}, + {346, "commainferior"}, + {347, "Agravesmall"}, + {348, "Aacutesmall"}, + {349, "Acircumflexsmall"}, + {350, "Atildesmall"}, + {351, "Adieresissmall"}, + {352, "Aringsmall"}, + {353, "AEsmall"}, + {354, "Ccedillasmall"}, + {355, "Egravesmall"}, + {356, "Eacutesmall"}, + {357, "Ecircumflexsmall"}, + {358, "Edieresissmall"}, + {359, "Igravesmall"}, + {360, "Iacutesmall"}, + {361, "Icircumflexsmall"}, + {362, "Idieresissmall"}, + {363, "Ethsmall"}, + {364, "Ntildesmall"}, + {365, "Ogravesmall"}, + {366, "Oacutesmall"}, + {367, "Ocircumflexsmall"}, + {368, "Otildesmall"}, + {369, "Odieresissmall"}, + {370, "OEsmall"}, + {371, "Oslashsmall"}, + {372, "Ugravesmall"}, + {373, "Uacutesmall"}, + {374, "Ucircumflexsmall"}, + {375, "Udieresissmall"}, + {376, "Yacutesmall"}, + {377, "Thornsmall"}, + {378, "Ydieresissmall"} + }; private CFFExpertCharset() { @@ -43,171 +217,10 @@ public static CFFExpertCharset getInstance() static { int gid = 0; - INSTANCE.addSID(gid++, 0, ".notdef"); - INSTANCE.addSID(gid++, 1, "space"); - INSTANCE.addSID(gid++, 229, "exclamsmall"); - INSTANCE.addSID(gid++, 230, "Hungarumlautsmall"); - INSTANCE.addSID(gid++, 231, "dollaroldstyle"); - INSTANCE.addSID(gid++, 232, "dollarsuperior"); - INSTANCE.addSID(gid++, 233, "ampersandsmall"); - INSTANCE.addSID(gid++, 234, "Acutesmall"); - INSTANCE.addSID(gid++, 235, "parenleftsuperior"); - INSTANCE.addSID(gid++, 236, "parenrightsuperior"); - INSTANCE.addSID(gid++, 237, "twodotenleader"); - INSTANCE.addSID(gid++, 238, "onedotenleader"); - INSTANCE.addSID(gid++, 13, "comma"); - INSTANCE.addSID(gid++, 14, "hyphen"); - INSTANCE.addSID(gid++, 15, "period"); - INSTANCE.addSID(gid++, 99, "fraction"); - INSTANCE.addSID(gid++, 239, "zerooldstyle"); - INSTANCE.addSID(gid++, 240, "oneoldstyle"); - INSTANCE.addSID(gid++, 241, "twooldstyle"); - INSTANCE.addSID(gid++, 242, "threeoldstyle"); - INSTANCE.addSID(gid++, 243, "fouroldstyle"); - INSTANCE.addSID(gid++, 244, "fiveoldstyle"); - INSTANCE.addSID(gid++, 245, "sixoldstyle"); - INSTANCE.addSID(gid++, 246, "sevenoldstyle"); - INSTANCE.addSID(gid++, 247, "eightoldstyle"); - INSTANCE.addSID(gid++, 248, "nineoldstyle"); - INSTANCE.addSID(gid++, 27, "colon"); - INSTANCE.addSID(gid++, 28, "semicolon"); - INSTANCE.addSID(gid++, 249, "commasuperior"); - INSTANCE.addSID(gid++, 250, "threequartersemdash"); - INSTANCE.addSID(gid++, 251, "periodsuperior"); - INSTANCE.addSID(gid++, 252, "questionsmall"); - INSTANCE.addSID(gid++, 253, "asuperior"); - INSTANCE.addSID(gid++, 254, "bsuperior"); - INSTANCE.addSID(gid++, 255, "centsuperior"); - INSTANCE.addSID(gid++, 256, "dsuperior"); - INSTANCE.addSID(gid++, 257, "esuperior"); - INSTANCE.addSID(gid++, 258, "isuperior"); - INSTANCE.addSID(gid++, 259, "lsuperior"); - INSTANCE.addSID(gid++, 260, "msuperior"); - INSTANCE.addSID(gid++, 261, "nsuperior"); - INSTANCE.addSID(gid++, 262, "osuperior"); - INSTANCE.addSID(gid++, 263, "rsuperior"); - INSTANCE.addSID(gid++, 264, "ssuperior"); - INSTANCE.addSID(gid++, 265, "tsuperior"); - INSTANCE.addSID(gid++, 266, "ff"); - INSTANCE.addSID(gid++, 109, "fi"); - INSTANCE.addSID(gid++, 110, "fl"); - INSTANCE.addSID(gid++, 267, "ffi"); - INSTANCE.addSID(gid++, 268, "ffl"); - INSTANCE.addSID(gid++, 269, "parenleftinferior"); - INSTANCE.addSID(gid++, 270, "parenrightinferior"); - INSTANCE.addSID(gid++, 271, "Circumflexsmall"); - INSTANCE.addSID(gid++, 272, "hyphensuperior"); - INSTANCE.addSID(gid++, 273, "Gravesmall"); - INSTANCE.addSID(gid++, 274, "Asmall"); - INSTANCE.addSID(gid++, 275, "Bsmall"); - INSTANCE.addSID(gid++, 276, "Csmall"); - INSTANCE.addSID(gid++, 277, "Dsmall"); - INSTANCE.addSID(gid++, 278, "Esmall"); - INSTANCE.addSID(gid++, 279, "Fsmall"); - INSTANCE.addSID(gid++, 280, "Gsmall"); - INSTANCE.addSID(gid++, 281, "Hsmall"); - INSTANCE.addSID(gid++, 282, "Ismall"); - INSTANCE.addSID(gid++, 283, "Jsmall"); - INSTANCE.addSID(gid++, 284, "Ksmall"); - INSTANCE.addSID(gid++, 285, "Lsmall"); - INSTANCE.addSID(gid++, 286, "Msmall"); - INSTANCE.addSID(gid++, 287, "Nsmall"); - INSTANCE.addSID(gid++, 288, "Osmall"); - INSTANCE.addSID(gid++, 289, "Psmall"); - INSTANCE.addSID(gid++, 290, "Qsmall"); - INSTANCE.addSID(gid++, 291, "Rsmall"); - INSTANCE.addSID(gid++, 292, "Ssmall"); - INSTANCE.addSID(gid++, 293, "Tsmall"); - INSTANCE.addSID(gid++, 294, "Usmall"); - INSTANCE.addSID(gid++, 295, "Vsmall"); - INSTANCE.addSID(gid++, 296, "Wsmall"); - INSTANCE.addSID(gid++, 297, "Xsmall"); - INSTANCE.addSID(gid++, 298, "Ysmall"); - INSTANCE.addSID(gid++, 299, "Zsmall"); - INSTANCE.addSID(gid++, 300, "colonmonetary"); - INSTANCE.addSID(gid++, 301, "onefitted"); - INSTANCE.addSID(gid++, 302, "rupiah"); - INSTANCE.addSID(gid++, 303, "Tildesmall"); - INSTANCE.addSID(gid++, 304, "exclamdownsmall"); - INSTANCE.addSID(gid++, 305, "centoldstyle"); - INSTANCE.addSID(gid++, 306, "Lslashsmall"); - INSTANCE.addSID(gid++, 307, "Scaronsmall"); - INSTANCE.addSID(gid++, 308, "Zcaronsmall"); - INSTANCE.addSID(gid++, 309, "Dieresissmall"); - INSTANCE.addSID(gid++, 310, "Brevesmall"); - INSTANCE.addSID(gid++, 311, "Caronsmall"); - INSTANCE.addSID(gid++, 312, "Dotaccentsmall"); - INSTANCE.addSID(gid++, 313, "Macronsmall"); - INSTANCE.addSID(gid++, 314, "figuredash"); - INSTANCE.addSID(gid++, 315, "hypheninferior"); - INSTANCE.addSID(gid++, 316, "Ogoneksmall"); - INSTANCE.addSID(gid++, 317, "Ringsmall"); - INSTANCE.addSID(gid++, 318, "Cedillasmall"); - INSTANCE.addSID(gid++, 158, "onequarter"); - INSTANCE.addSID(gid++, 155, "onehalf"); - INSTANCE.addSID(gid++, 163, "threequarters"); - INSTANCE.addSID(gid++, 319, "questiondownsmall"); - INSTANCE.addSID(gid++, 320, "oneeighth"); - INSTANCE.addSID(gid++, 321, "threeeighths"); - INSTANCE.addSID(gid++, 322, "fiveeighths"); - INSTANCE.addSID(gid++, 323, "seveneighths"); - INSTANCE.addSID(gid++, 324, "onethird"); - INSTANCE.addSID(gid++, 325, "twothirds"); - INSTANCE.addSID(gid++, 326, "zerosuperior"); - INSTANCE.addSID(gid++, 150, "onesuperior"); - INSTANCE.addSID(gid++, 164, "twosuperior"); - INSTANCE.addSID(gid++, 169, "threesuperior"); - INSTANCE.addSID(gid++, 327, "foursuperior"); - INSTANCE.addSID(gid++, 328, "fivesuperior"); - INSTANCE.addSID(gid++, 329, "sixsuperior"); - INSTANCE.addSID(gid++, 330, "sevensuperior"); - INSTANCE.addSID(gid++, 331, "eightsuperior"); - INSTANCE.addSID(gid++, 332, "ninesuperior"); - INSTANCE.addSID(gid++, 333, "zeroinferior"); - INSTANCE.addSID(gid++, 334, "oneinferior"); - INSTANCE.addSID(gid++, 335, "twoinferior"); - INSTANCE.addSID(gid++, 336, "threeinferior"); - INSTANCE.addSID(gid++, 337, "fourinferior"); - INSTANCE.addSID(gid++, 338, "fiveinferior"); - INSTANCE.addSID(gid++, 339, "sixinferior"); - INSTANCE.addSID(gid++, 340, "seveninferior"); - INSTANCE.addSID(gid++, 341, "eightinferior"); - INSTANCE.addSID(gid++, 342, "nineinferior"); - INSTANCE.addSID(gid++, 343, "centinferior"); - INSTANCE.addSID(gid++, 344, "dollarinferior"); - INSTANCE.addSID(gid++, 345, "periodinferior"); - INSTANCE.addSID(gid++, 346, "commainferior"); - INSTANCE.addSID(gid++, 347, "Agravesmall"); - INSTANCE.addSID(gid++, 348, "Aacutesmall"); - INSTANCE.addSID(gid++, 349, "Acircumflexsmall"); - INSTANCE.addSID(gid++, 350, "Atildesmall"); - INSTANCE.addSID(gid++, 351, "Adieresissmall"); - INSTANCE.addSID(gid++, 352, "Aringsmall"); - INSTANCE.addSID(gid++, 353, "AEsmall"); - INSTANCE.addSID(gid++, 354, "Ccedillasmall"); - INSTANCE.addSID(gid++, 355, "Egravesmall"); - INSTANCE.addSID(gid++, 356, "Eacutesmall"); - INSTANCE.addSID(gid++, 357, "Ecircumflexsmall"); - INSTANCE.addSID(gid++, 358, "Edieresissmall"); - INSTANCE.addSID(gid++, 359, "Igravesmall"); - INSTANCE.addSID(gid++, 360, "Iacutesmall"); - INSTANCE.addSID(gid++, 361, "Icircumflexsmall"); - INSTANCE.addSID(gid++, 362, "Idieresissmall"); - INSTANCE.addSID(gid++, 363, "Ethsmall"); - INSTANCE.addSID(gid++, 364, "Ntildesmall"); - INSTANCE.addSID(gid++, 365, "Ogravesmall"); - INSTANCE.addSID(gid++, 366, "Oacutesmall"); - INSTANCE.addSID(gid++, 367, "Ocircumflexsmall"); - INSTANCE.addSID(gid++, 368, "Otildesmall"); - INSTANCE.addSID(gid++, 369, "Odieresissmall"); - INSTANCE.addSID(gid++, 370, "OEsmall"); - INSTANCE.addSID(gid++, 371, "Oslashsmall"); - INSTANCE.addSID(gid++, 372, "Ugravesmall"); - INSTANCE.addSID(gid++, 373, "Uacutesmall"); - INSTANCE.addSID(gid++, 374, "Ucircumflexsmall"); - INSTANCE.addSID(gid++, 375, "Udieresissmall"); - INSTANCE.addSID(gid++, 376, "Yacutesmall"); - INSTANCE.addSID(gid++, 377, "Thornsmall"); - INSTANCE.addSID(gid++, 378, "Ydieresissmall"); + for (Object[] charsetEntry : CFF_EXPERT_CHARSET_TABLE) + { + INSTANCE.addSID(gid++, (Integer)charsetEntry[CHAR_CODE], + charsetEntry[CHAR_NAME].toString()); + } } } \ No newline at end of file diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertEncoding.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertEncoding.java index ce7debf73..76b62d4dd 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertEncoding.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertEncoding.java @@ -23,6 +23,270 @@ */ public final class CFFExpertEncoding extends CFFEncoding { + private static final int CHAR_CODE = 0; + private static final int CHAR_SID = 1; + + /** + * Table of character codes and their corresponding sid. + */ + private static final int[][] CFF_EXPERT_ENCODING_TABLE = { + {0, 0}, + {1, 0}, + {2, 0}, + {3, 0}, + {4, 0}, + {5, 0}, + {6, 0}, + {7, 0}, + {8, 0}, + {9, 0}, + {10, 0}, + {11, 0}, + {12, 0}, + {13, 0}, + {14, 0}, + {15, 0}, + {16, 0}, + {17, 0}, + {18, 0}, + {19, 0}, + {20, 0}, + {21, 0}, + {22, 0}, + {23, 0}, + {24, 0}, + {25, 0}, + {26, 0}, + {27, 0}, + {28, 0}, + {29, 0}, + {30, 0}, + {31, 0}, + {32, 1}, + {33, 229}, + {34, 230}, + {35, 0}, + {36, 231}, + {37, 232}, + {38, 233}, + {39, 234}, + {40, 235}, + {41, 236}, + {42, 237}, + {43, 238}, + {44, 13}, + {45, 14}, + {46, 15}, + {47, 99}, + {48, 239}, + {49, 240}, + {50, 241}, + {51, 242}, + {52, 243}, + {53, 244}, + {54, 245}, + {55, 246}, + {56, 247}, + {57, 248}, + {58, 27}, + {59, 28}, + {60, 249}, + {61, 250}, + {62, 251}, + {63, 252}, + {64, 0}, + {65, 253}, + {66, 254}, + {67, 255}, + {68, 256}, + {69, 257}, + {70, 0}, + {71, 0}, + {72, 0}, + {73, 258}, + {74, 0}, + {75, 0}, + {76, 259}, + {77, 260}, + {78, 261}, + {79, 262}, + {80, 0}, + {81, 0}, + {82, 263}, + {83, 264}, + {84, 265}, + {85, 0}, + {86, 266}, + {87, 109}, + {88, 110}, + {89, 267}, + {90, 268}, + {91, 269}, + {92, 0}, + {93, 270}, + {94, 271}, + {95, 272}, + {96, 273}, + {97, 274}, + {98, 275}, + {99, 276}, + {100, 277}, + {101, 278}, + {102, 279}, + {103, 280}, + {104, 281}, + {105, 282}, + {106, 283}, + {107, 284}, + {108, 285}, + {109, 286}, + {110, 287}, + {111, 288}, + {112, 289}, + {113, 290}, + {114, 291}, + {115, 292}, + {116, 293}, + {117, 294}, + {118, 295}, + {119, 296}, + {120, 297}, + {121, 298}, + {122, 299}, + {123, 300}, + {124, 301}, + {125, 302}, + {126, 303}, + {127, 0}, + {128, 0}, + {129, 0}, + {130, 0}, + {131, 0}, + {132, 0}, + {133, 0}, + {134, 0}, + {135, 0}, + {136, 0}, + {137, 0}, + {138, 0}, + {139, 0}, + {140, 0}, + {141, 0}, + {142, 0}, + {143, 0}, + {144, 0}, + {145, 0}, + {146, 0}, + {147, 0}, + {148, 0}, + {149, 0}, + {150, 0}, + {151, 0}, + {152, 0}, + {153, 0}, + {154, 0}, + {155, 0}, + {156, 0}, + {157, 0}, + {158, 0}, + {159, 0}, + {160, 0}, + {161, 304}, + {162, 305}, + {163, 306}, + {164, 0}, + {165, 0}, + {166, 307}, + {167, 308}, + {168, 309}, + {169, 310}, + {170, 311}, + {171, 0}, + {172, 312}, + {173, 0}, + {174, 0}, + {175, 313}, + {176, 0}, + {177, 0}, + {178, 314}, + {179, 315}, + {180, 0}, + {181, 0}, + {182, 316}, + {183, 317}, + {184, 318}, + {185, 0}, + {186, 0}, + {187, 0}, + {188, 158}, + {189, 155}, + {190, 163}, + {191, 319}, + {192, 320}, + {193, 321}, + {194, 322}, + {195, 323}, + {196, 324}, + {197, 325}, + {198, 0}, + {199, 0}, + {200, 326}, + {201, 150}, + {202, 164}, + {203, 169}, + {204, 327}, + {205, 328}, + {206, 329}, + {207, 330}, + {208, 331}, + {209, 332}, + {210, 333}, + {211, 334}, + {212, 335}, + {213, 336}, + {214, 337}, + {215, 338}, + {216, 339}, + {217, 340}, + {218, 341}, + {219, 342}, + {220, 343}, + {221, 344}, + {222, 345}, + {223, 346}, + {224, 347}, + {225, 348}, + {226, 349}, + {227, 350}, + {228, 351}, + {229, 352}, + {230, 353}, + {231, 354}, + {232, 355}, + {233, 356}, + {234, 357}, + {235, 358}, + {236, 359}, + {237, 360}, + {238, 361}, + {239, 362}, + {240, 363}, + {241, 364}, + {242, 365}, + {243, 366}, + {244, 367}, + {245, 368}, + {246, 369}, + {247, 370}, + {248, 371}, + {249, 372}, + {250, 373}, + {251, 374}, + {252, 375}, + {253, 376}, + {254, 377}, + {255, 378} + }; private CFFExpertEncoding() { @@ -41,261 +305,9 @@ public static CFFExpertEncoding getInstance() static { - INSTANCE.add(0, 0); - INSTANCE.add(1, 0); - INSTANCE.add(2, 0); - INSTANCE.add(3, 0); - INSTANCE.add(4, 0); - INSTANCE.add(5, 0); - INSTANCE.add(6, 0); - INSTANCE.add(7, 0); - INSTANCE.add(8, 0); - INSTANCE.add(9, 0); - INSTANCE.add(10, 0); - INSTANCE.add(11, 0); - INSTANCE.add(12, 0); - INSTANCE.add(13, 0); - INSTANCE.add(14, 0); - INSTANCE.add(15, 0); - INSTANCE.add(16, 0); - INSTANCE.add(17, 0); - INSTANCE.add(18, 0); - INSTANCE.add(19, 0); - INSTANCE.add(20, 0); - INSTANCE.add(21, 0); - INSTANCE.add(22, 0); - INSTANCE.add(23, 0); - INSTANCE.add(24, 0); - INSTANCE.add(25, 0); - INSTANCE.add(26, 0); - INSTANCE.add(27, 0); - INSTANCE.add(28, 0); - INSTANCE.add(29, 0); - INSTANCE.add(30, 0); - INSTANCE.add(31, 0); - INSTANCE.add(32, 1); - INSTANCE.add(33, 229); - INSTANCE.add(34, 230); - INSTANCE.add(35, 0); - INSTANCE.add(36, 231); - INSTANCE.add(37, 232); - INSTANCE.add(38, 233); - INSTANCE.add(39, 234); - INSTANCE.add(40, 235); - INSTANCE.add(41, 236); - INSTANCE.add(42, 237); - INSTANCE.add(43, 238); - INSTANCE.add(44, 13); - INSTANCE.add(45, 14); - INSTANCE.add(46, 15); - INSTANCE.add(47, 99); - INSTANCE.add(48, 239); - INSTANCE.add(49, 240); - INSTANCE.add(50, 241); - INSTANCE.add(51, 242); - INSTANCE.add(52, 243); - INSTANCE.add(53, 244); - INSTANCE.add(54, 245); - INSTANCE.add(55, 246); - INSTANCE.add(56, 247); - INSTANCE.add(57, 248); - INSTANCE.add(58, 27); - INSTANCE.add(59, 28); - INSTANCE.add(60, 249); - INSTANCE.add(61, 250); - INSTANCE.add(62, 251); - INSTANCE.add(63, 252); - INSTANCE.add(64, 0); - INSTANCE.add(65, 253); - INSTANCE.add(66, 254); - INSTANCE.add(67, 255); - INSTANCE.add(68, 256); - INSTANCE.add(69, 257); - INSTANCE.add(70, 0); - INSTANCE.add(71, 0); - INSTANCE.add(72, 0); - INSTANCE.add(73, 258); - INSTANCE.add(74, 0); - INSTANCE.add(75, 0); - INSTANCE.add(76, 259); - INSTANCE.add(77, 260); - INSTANCE.add(78, 261); - INSTANCE.add(79, 262); - INSTANCE.add(80, 0); - INSTANCE.add(81, 0); - INSTANCE.add(82, 263); - INSTANCE.add(83, 264); - INSTANCE.add(84, 265); - INSTANCE.add(85, 0); - INSTANCE.add(86, 266); - INSTANCE.add(87, 109); - INSTANCE.add(88, 110); - INSTANCE.add(89, 267); - INSTANCE.add(90, 268); - INSTANCE.add(91, 269); - INSTANCE.add(92, 0); - INSTANCE.add(93, 270); - INSTANCE.add(94, 271); - INSTANCE.add(95, 272); - INSTANCE.add(96, 273); - INSTANCE.add(97, 274); - INSTANCE.add(98, 275); - INSTANCE.add(99, 276); - INSTANCE.add(100, 277); - INSTANCE.add(101, 278); - INSTANCE.add(102, 279); - INSTANCE.add(103, 280); - INSTANCE.add(104, 281); - INSTANCE.add(105, 282); - INSTANCE.add(106, 283); - INSTANCE.add(107, 284); - INSTANCE.add(108, 285); - INSTANCE.add(109, 286); - INSTANCE.add(110, 287); - INSTANCE.add(111, 288); - INSTANCE.add(112, 289); - INSTANCE.add(113, 290); - INSTANCE.add(114, 291); - INSTANCE.add(115, 292); - INSTANCE.add(116, 293); - INSTANCE.add(117, 294); - INSTANCE.add(118, 295); - INSTANCE.add(119, 296); - INSTANCE.add(120, 297); - INSTANCE.add(121, 298); - INSTANCE.add(122, 299); - INSTANCE.add(123, 300); - INSTANCE.add(124, 301); - INSTANCE.add(125, 302); - INSTANCE.add(126, 303); - INSTANCE.add(127, 0); - INSTANCE.add(128, 0); - INSTANCE.add(129, 0); - INSTANCE.add(130, 0); - INSTANCE.add(131, 0); - INSTANCE.add(132, 0); - INSTANCE.add(133, 0); - INSTANCE.add(134, 0); - INSTANCE.add(135, 0); - INSTANCE.add(136, 0); - INSTANCE.add(137, 0); - INSTANCE.add(138, 0); - INSTANCE.add(139, 0); - INSTANCE.add(140, 0); - INSTANCE.add(141, 0); - INSTANCE.add(142, 0); - INSTANCE.add(143, 0); - INSTANCE.add(144, 0); - INSTANCE.add(145, 0); - INSTANCE.add(146, 0); - INSTANCE.add(147, 0); - INSTANCE.add(148, 0); - INSTANCE.add(149, 0); - INSTANCE.add(150, 0); - INSTANCE.add(151, 0); - INSTANCE.add(152, 0); - INSTANCE.add(153, 0); - INSTANCE.add(154, 0); - INSTANCE.add(155, 0); - INSTANCE.add(156, 0); - INSTANCE.add(157, 0); - INSTANCE.add(158, 0); - INSTANCE.add(159, 0); - INSTANCE.add(160, 0); - INSTANCE.add(161, 304); - INSTANCE.add(162, 305); - INSTANCE.add(163, 306); - INSTANCE.add(164, 0); - INSTANCE.add(165, 0); - INSTANCE.add(166, 307); - INSTANCE.add(167, 308); - INSTANCE.add(168, 309); - INSTANCE.add(169, 310); - INSTANCE.add(170, 311); - INSTANCE.add(171, 0); - INSTANCE.add(172, 312); - INSTANCE.add(173, 0); - INSTANCE.add(174, 0); - INSTANCE.add(175, 313); - INSTANCE.add(176, 0); - INSTANCE.add(177, 0); - INSTANCE.add(178, 314); - INSTANCE.add(179, 315); - INSTANCE.add(180, 0); - INSTANCE.add(181, 0); - INSTANCE.add(182, 316); - INSTANCE.add(183, 317); - INSTANCE.add(184, 318); - INSTANCE.add(185, 0); - INSTANCE.add(186, 0); - INSTANCE.add(187, 0); - INSTANCE.add(188, 158); - INSTANCE.add(189, 155); - INSTANCE.add(190, 163); - INSTANCE.add(191, 319); - INSTANCE.add(192, 320); - INSTANCE.add(193, 321); - INSTANCE.add(194, 322); - INSTANCE.add(195, 323); - INSTANCE.add(196, 324); - INSTANCE.add(197, 325); - INSTANCE.add(198, 0); - INSTANCE.add(199, 0); - INSTANCE.add(200, 326); - INSTANCE.add(201, 150); - INSTANCE.add(202, 164); - INSTANCE.add(203, 169); - INSTANCE.add(204, 327); - INSTANCE.add(205, 328); - INSTANCE.add(206, 329); - INSTANCE.add(207, 330); - INSTANCE.add(208, 331); - INSTANCE.add(209, 332); - INSTANCE.add(210, 333); - INSTANCE.add(211, 334); - INSTANCE.add(212, 335); - INSTANCE.add(213, 336); - INSTANCE.add(214, 337); - INSTANCE.add(215, 338); - INSTANCE.add(216, 339); - INSTANCE.add(217, 340); - INSTANCE.add(218, 341); - INSTANCE.add(219, 342); - INSTANCE.add(220, 343); - INSTANCE.add(221, 344); - INSTANCE.add(222, 345); - INSTANCE.add(223, 346); - INSTANCE.add(224, 347); - INSTANCE.add(225, 348); - INSTANCE.add(226, 349); - INSTANCE.add(227, 350); - INSTANCE.add(228, 351); - INSTANCE.add(229, 352); - INSTANCE.add(230, 353); - INSTANCE.add(231, 354); - INSTANCE.add(232, 355); - INSTANCE.add(233, 356); - INSTANCE.add(234, 357); - INSTANCE.add(235, 358); - INSTANCE.add(236, 359); - INSTANCE.add(237, 360); - INSTANCE.add(238, 361); - INSTANCE.add(239, 362); - INSTANCE.add(240, 363); - INSTANCE.add(241, 364); - INSTANCE.add(242, 365); - INSTANCE.add(243, 366); - INSTANCE.add(244, 367); - INSTANCE.add(245, 368); - INSTANCE.add(246, 369); - INSTANCE.add(247, 370); - INSTANCE.add(248, 371); - INSTANCE.add(249, 372); - INSTANCE.add(250, 373); - INSTANCE.add(251, 374); - INSTANCE.add(252, 375); - INSTANCE.add(253, 376); - INSTANCE.add(254, 377); - INSTANCE.add(255, 378); + for (int[] encodingEntry : CFF_EXPERT_ENCODING_TABLE) + { + INSTANCE.add(encodingEntry[CHAR_CODE], encodingEntry[CHAR_SID]); + } } } \ No newline at end of file diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertSubsetCharset.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertSubsetCharset.java index 76094c70c..43ac44002 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertSubsetCharset.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFExpertSubsetCharset.java @@ -24,6 +24,101 @@ */ public final class CFFExpertSubsetCharset extends CFFCharset { + private static final int CHAR_CODE = 0; + private static final int CHAR_NAME = 1; + + /** + * Table of character codes and their corresponding names. + */ + private static final Object[][] CFF_EXPERT_SUBSET_CHARSET_TABLE = { + {0, ".notdef"}, + {1, "space"}, + {231, "dollaroldstyle"}, + {232, "dollarsuperior"}, + {235, "parenleftsuperior"}, + {236, "parenrightsuperior"}, + {237, "twodotenleader"}, + {238, "onedotenleader"}, + {13, "comma"}, + {14, "hyphen"}, + {15, "period"}, + {99, "fraction"}, + {239, "zerooldstyle"}, + {240, "oneoldstyle"}, + {241, "twooldstyle"}, + {242, "threeoldstyle"}, + {243, "fouroldstyle"}, + {244, "fiveoldstyle"}, + {245, "sixoldstyle"}, + {246, "sevenoldstyle"}, + {247, "eightoldstyle"}, + {248, "nineoldstyle"}, + {27, "colon"}, + {28, "semicolon"}, + {249, "commasuperior"}, + {250, "threequartersemdash"}, + {251, "periodsuperior"}, + {253, "asuperior"}, + {254, "bsuperior"}, + {255, "centsuperior"}, + {256, "dsuperior"}, + {257, "esuperior"}, + {258, "isuperior"}, + {259, "lsuperior"}, + {260, "msuperior"}, + {261, "nsuperior"}, + {262, "osuperior"}, + {263, "rsuperior"}, + {264, "ssuperior"}, + {265, "tsuperior"}, + {266, "ff"}, + {109, "fi"}, + {110, "fl"}, + {267, "ffi"}, + {268, "ffl"}, + {269, "parenleftinferior"}, + {270, "parenrightinferior"}, + {272, "hyphensuperior"}, + {300, "colonmonetary"}, + {301, "onefitted"}, + {302, "rupiah"}, + {305, "centoldstyle"}, + {314, "figuredash"}, + {315, "hypheninferior"}, + {158, "onequarter"}, + {155, "onehalf"}, + {163, "threequarters"}, + {320, "oneeighth"}, + {321, "threeeighths"}, + {322, "fiveeighths"}, + {323, "seveneighths"}, + {324, "onethird"}, + {325, "twothirds"}, + {326, "zerosuperior"}, + {150, "onesuperior"}, + {164, "twosuperior"}, + {169, "threesuperior"}, + {327, "foursuperior"}, + {328, "fivesuperior"}, + {329, "sixsuperior"}, + {330, "sevensuperior"}, + {331, "eightsuperior"}, + {332, "ninesuperior"}, + {333, "zeroinferior"}, + {334, "oneinferior"}, + {335, "twoinferior"}, + {336, "threeinferior"}, + {337, "fourinferior"}, + {338, "fiveinferior"}, + {339, "sixinferior"}, + {340, "seveninferior"}, + {341, "eightinferior"}, + {342, "nineinferior"}, + {343, "centinferior"}, + {344, "dollarinferior"}, + {345, "periodinferior"}, + {346, "commainferior"} + }; private CFFExpertSubsetCharset() { @@ -44,92 +139,10 @@ public static CFFExpertSubsetCharset getInstance() static { int gid = 0; - INSTANCE.addSID(gid++, 0, ".notdef"); - INSTANCE.addSID(gid++, 1, "space"); - INSTANCE.addSID(gid++, 231, "dollaroldstyle"); - INSTANCE.addSID(gid++, 232, "dollarsuperior"); - INSTANCE.addSID(gid++, 235, "parenleftsuperior"); - INSTANCE.addSID(gid++, 236, "parenrightsuperior"); - INSTANCE.addSID(gid++, 237, "twodotenleader"); - INSTANCE.addSID(gid++, 238, "onedotenleader"); - INSTANCE.addSID(gid++, 13, "comma"); - INSTANCE.addSID(gid++, 14, "hyphen"); - INSTANCE.addSID(gid++, 15, "period"); - INSTANCE.addSID(gid++, 99, "fraction"); - INSTANCE.addSID(gid++, 239, "zerooldstyle"); - INSTANCE.addSID(gid++, 240, "oneoldstyle"); - INSTANCE.addSID(gid++, 241, "twooldstyle"); - INSTANCE.addSID(gid++, 242, "threeoldstyle"); - INSTANCE.addSID(gid++, 243, "fouroldstyle"); - INSTANCE.addSID(gid++, 244, "fiveoldstyle"); - INSTANCE.addSID(gid++, 245, "sixoldstyle"); - INSTANCE.addSID(gid++, 246, "sevenoldstyle"); - INSTANCE.addSID(gid++, 247, "eightoldstyle"); - INSTANCE.addSID(gid++, 248, "nineoldstyle"); - INSTANCE.addSID(gid++, 27, "colon"); - INSTANCE.addSID(gid++, 28, "semicolon"); - INSTANCE.addSID(gid++, 249, "commasuperior"); - INSTANCE.addSID(gid++, 250, "threequartersemdash"); - INSTANCE.addSID(gid++, 251, "periodsuperior"); - INSTANCE.addSID(gid++, 253, "asuperior"); - INSTANCE.addSID(gid++, 254, "bsuperior"); - INSTANCE.addSID(gid++, 255, "centsuperior"); - INSTANCE.addSID(gid++, 256, "dsuperior"); - INSTANCE.addSID(gid++, 257, "esuperior"); - INSTANCE.addSID(gid++, 258, "isuperior"); - INSTANCE.addSID(gid++, 259, "lsuperior"); - INSTANCE.addSID(gid++, 260, "msuperior"); - INSTANCE.addSID(gid++, 261, "nsuperior"); - INSTANCE.addSID(gid++, 262, "osuperior"); - INSTANCE.addSID(gid++, 263, "rsuperior"); - INSTANCE.addSID(gid++, 264, "ssuperior"); - INSTANCE.addSID(gid++, 265, "tsuperior"); - INSTANCE.addSID(gid++, 266, "ff"); - INSTANCE.addSID(gid++, 109, "fi"); - INSTANCE.addSID(gid++, 110, "fl"); - INSTANCE.addSID(gid++, 267, "ffi"); - INSTANCE.addSID(gid++, 268, "ffl"); - INSTANCE.addSID(gid++, 269, "parenleftinferior"); - INSTANCE.addSID(gid++, 270, "parenrightinferior"); - INSTANCE.addSID(gid++, 272, "hyphensuperior"); - INSTANCE.addSID(gid++, 300, "colonmonetary"); - INSTANCE.addSID(gid++, 301, "onefitted"); - INSTANCE.addSID(gid++, 302, "rupiah"); - INSTANCE.addSID(gid++, 305, "centoldstyle"); - INSTANCE.addSID(gid++, 314, "figuredash"); - INSTANCE.addSID(gid++, 315, "hypheninferior"); - INSTANCE.addSID(gid++, 158, "onequarter"); - INSTANCE.addSID(gid++, 155, "onehalf"); - INSTANCE.addSID(gid++, 163, "threequarters"); - INSTANCE.addSID(gid++, 320, "oneeighth"); - INSTANCE.addSID(gid++, 321, "threeeighths"); - INSTANCE.addSID(gid++, 322, "fiveeighths"); - INSTANCE.addSID(gid++, 323, "seveneighths"); - INSTANCE.addSID(gid++, 324, "onethird"); - INSTANCE.addSID(gid++, 325, "twothirds"); - INSTANCE.addSID(gid++, 326, "zerosuperior"); - INSTANCE.addSID(gid++, 150, "onesuperior"); - INSTANCE.addSID(gid++, 164, "twosuperior"); - INSTANCE.addSID(gid++, 169, "threesuperior"); - INSTANCE.addSID(gid++, 327, "foursuperior"); - INSTANCE.addSID(gid++, 328, "fivesuperior"); - INSTANCE.addSID(gid++, 329, "sixsuperior"); - INSTANCE.addSID(gid++, 330, "sevensuperior"); - INSTANCE.addSID(gid++, 331, "eightsuperior"); - INSTANCE.addSID(gid++, 332, "ninesuperior"); - INSTANCE.addSID(gid++, 333, "zeroinferior"); - INSTANCE.addSID(gid++, 334, "oneinferior"); - INSTANCE.addSID(gid++, 335, "twoinferior"); - INSTANCE.addSID(gid++, 336, "threeinferior"); - INSTANCE.addSID(gid++, 337, "fourinferior"); - INSTANCE.addSID(gid++, 338, "fiveinferior"); - INSTANCE.addSID(gid++, 339, "sixinferior"); - INSTANCE.addSID(gid++, 340, "seveninferior"); - INSTANCE.addSID(gid++, 341, "eightinferior"); - INSTANCE.addSID(gid++, 342, "nineinferior"); - INSTANCE.addSID(gid++, 343, "centinferior"); - INSTANCE.addSID(gid++, 344, "dollarinferior"); - INSTANCE.addSID(gid++, 345, "periodinferior"); - INSTANCE.addSID(gid++, 346, "commainferior"); + for (Object[] charsetEntry : CFF_EXPERT_SUBSET_CHARSET_TABLE) + { + INSTANCE.addSID(gid++, (Integer)charsetEntry[CHAR_CODE], + charsetEntry[CHAR_NAME].toString()); + } } } \ No newline at end of file diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFFont.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFFont.java index 78e42a233..c8855f85c 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFFont.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFFont.java @@ -16,15 +16,15 @@ */ package com.tom_roush.fontbox.cff; -import com.tom_roush.fontbox.FontBoxFont; -import com.tom_roush.fontbox.util.BoundingBox; - import java.io.IOException; -import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import com.tom_roush.fontbox.FontBoxFont; +import com.tom_roush.fontbox.util.BoundingBox; + /** * An Adobe Compact Font Format (CFF) font. Thread safe. * @@ -36,15 +36,16 @@ public abstract class CFFFont implements FontBoxFont protected String fontName; protected final Map topDict = new LinkedHashMap(); protected CFFCharset charset; - protected final List charStrings = new ArrayList(); - protected IndexData globalSubrIndex; - private byte[] data; + protected byte[][] charStrings; + protected byte[][] globalSubrIndex; + private CFFParser.ByteSource source; /** * The name of the font. * * @return the name of the font */ + @Override public String getName() { return fontName; @@ -87,11 +88,13 @@ public Map getTopDict() /** * Returns the FontMatrix. */ + @Override public abstract List getFontMatrix(); /** * Returns the FontBBox. */ + @Override public BoundingBox getFontBBox() { List numbers = (List)topDict.get("FontBBox"); @@ -119,33 +122,29 @@ void setCharset(CFFCharset charset) } /** - * Returns the character strings dictionary. + * Returns the character strings dictionary. For expert users only. * * @return the dictionary */ - List getCharStringBytes() + public final List getCharStringBytes() { - return charStrings; + return Arrays.asList(charStrings); } /** - * Sets the original data. - * - * @param data the original data. + * Sets a byte source to re-read the CFF data in the future. */ - void setData(byte[] data) + final void setData(CFFParser.ByteSource source) { - this.data = data; + this.source = source; } /** - * Returns the the original data. - * - * @return the dictionary + * Returns the CFF data. */ - public byte[] getData() + public byte[] getData() throws IOException { - return data; + return source.getBytes(); } /** @@ -153,27 +152,27 @@ public byte[] getData() */ public int getNumCharStrings() { - return charStrings.size(); + return charStrings.length; } /** * Sets the global subroutine index data. - * - * @param globalSubrIndexValue the IndexData object containing the global subroutines + * + * @param globalSubrIndexValue a list containing the global subroutines */ - void setGlobalSubrIndex(IndexData globalSubrIndexValue) + void setGlobalSubrIndex(byte[][] globalSubrIndexValue) { globalSubrIndex = globalSubrIndexValue; } /** - * Returns the global subroutine index data. - * + * Returns the list containing the global subroutine . + * * @return the dictionary */ - public IndexData getGlobalSubrIndex() + public List getGlobalSubrIndex() { - return globalSubrIndex; + return Arrays.asList(globalSubrIndex); } /** @@ -187,8 +186,8 @@ public IndexData getGlobalSubrIndex() @Override public String toString() { - return getClass().getSimpleName() + "[name=" + fontName + ", topDict=" + topDict - + ", charset=" + charset + ", charStrings=" + charStrings + return getClass().getSimpleName() + "[name=" + fontName + ", topDict=" + topDict + + ", charset=" + charset + ", charStrings=" + Arrays.deepToString(charStrings) + "]"; } } diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFISOAdobeCharset.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFISOAdobeCharset.java index a1e67af70..a643da054 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFISOAdobeCharset.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFISOAdobeCharset.java @@ -24,6 +24,243 @@ */ public final class CFFISOAdobeCharset extends CFFCharset { + private static final int CHAR_CODE = 0; + private static final int CHAR_NAME = 1; + + /** + * Table of character codes and their corresponding names. + */ + private static final Object[][] CFF_ISO_ADOBE_CHARSET_TABLE = { + {0, ".notdef"}, + {1, "space"}, + {2, "exclam"}, + {3, "quotedbl"}, + {4, "numbersign"}, + {5, "dollar"}, + {6, "percent"}, + {7, "ampersand"}, + {8, "quoteright"}, + {9, "parenleft"}, + {10, "parenright"}, + {11, "asterisk"}, + {12, "plus"}, + {13, "comma"}, + {14, "hyphen"}, + {15, "period"}, + {16, "slash"}, + {17, "zero"}, + {18, "one"}, + {19, "two"}, + {20, "three"}, + {21, "four"}, + {22, "five"}, + {23, "six"}, + {24, "seven"}, + {25, "eight"}, + {26, "nine"}, + {27, "colon"}, + {28, "semicolon"}, + {29, "less"}, + {30, "equal"}, + {31, "greater"}, + {32, "question"}, + {33, "at"}, + {34, "A"}, + {35, "B"}, + {36, "C"}, + {37, "D"}, + {38, "E"}, + {39, "F"}, + {40, "G"}, + {41, "H"}, + {42, "I"}, + {43, "J"}, + {44, "K"}, + {45, "L"}, + {46, "M"}, + {47, "N"}, + {48, "O"}, + {49, "P"}, + {50, "Q"}, + {51, "R"}, + {52, "S"}, + {53, "T"}, + {54, "U"}, + {55, "V"}, + {56, "W"}, + {57, "X"}, + {58, "Y"}, + {59, "Z"}, + {60, "bracketleft"}, + {61, "backslash"}, + {62, "bracketright"}, + {63, "asciicircum"}, + {64, "underscore"}, + {65, "quoteleft"}, + {66, "a"}, + {67, "b"}, + {68, "c"}, + {69, "d"}, + {70, "e"}, + {71, "f"}, + {72, "g"}, + {73, "h"}, + {74, "i"}, + {75, "j"}, + {76, "k"}, + {77, "l"}, + {78, "m"}, + {79, "n"}, + {80, "o"}, + {81, "p"}, + {82, "q"}, + {83, "r"}, + {84, "s"}, + {85, "t"}, + {86, "u"}, + {87, "v"}, + {88, "w"}, + {89, "x"}, + {90, "y"}, + {91, "z"}, + {92, "braceleft"}, + {93, "bar"}, + {94, "braceright"}, + {95, "asciitilde"}, + {96, "exclamdown"}, + {97, "cent"}, + {98, "sterling"}, + {99, "fraction"}, + {100, "yen"}, + {101, "florin"}, + {102, "section"}, + {103, "currency"}, + {104, "quotesingle"}, + {105, "quotedblleft"}, + {106, "guillemotleft"}, + {107, "guilsinglleft"}, + {108, "guilsinglright"}, + {109, "fi"}, + {110, "fl"}, + {111, "endash"}, + {112, "dagger"}, + {113, "daggerdbl"}, + {114, "periodcentered"}, + {115, "paragraph"}, + {116, "bullet"}, + {117, "quotesinglbase"}, + {118, "quotedblbase"}, + {119, "quotedblright"}, + {120, "guillemotright"}, + {121, "ellipsis"}, + {122, "perthousand"}, + {123, "questiondown"}, + {124, "grave"}, + {125, "acute"}, + {126, "circumflex"}, + {127, "tilde"}, + {128, "macron"}, + {129, "breve"}, + {130, "dotaccent"}, + {131, "dieresis"}, + {132, "ring"}, + {133, "cedilla"}, + {134, "hungarumlaut"}, + {135, "ogonek"}, + {136, "caron"}, + {137, "emdash"}, + {138, "AE"}, + {139, "ordfeminine"}, + {140, "Lslash"}, + {141, "Oslash"}, + {142, "OE"}, + {143, "ordmasculine"}, + {144, "ae"}, + {145, "dotlessi"}, + {146, "lslash"}, + {147, "oslash"}, + {148, "oe"}, + {149, "germandbls"}, + {150, "onesuperior"}, + {151, "logicalnot"}, + {152, "mu"}, + {153, "trademark"}, + {154, "Eth"}, + {155, "onehalf"}, + {156, "plusminus"}, + {157, "Thorn"}, + {158, "onequarter"}, + {159, "divide"}, + {160, "brokenbar"}, + {161, "degree"}, + {162, "thorn"}, + {163, "threequarters"}, + {164, "twosuperior"}, + {165, "registered"}, + {166, "minus"}, + {167, "eth"}, + {168, "multiply"}, + {169, "threesuperior"}, + {170, "copyright"}, + {171, "Aacute"}, + {172, "Acircumflex"}, + {173, "Adieresis"}, + {174, "Agrave"}, + {175, "Aring"}, + {176, "Atilde"}, + {177, "Ccedilla"}, + {178, "Eacute"}, + {179, "Ecircumflex"}, + {180, "Edieresis"}, + {181, "Egrave"}, + {182, "Iacute"}, + {183, "Icircumflex"}, + {184, "Idieresis"}, + {185, "Igrave"}, + {186, "Ntilde"}, + {187, "Oacute"}, + {188, "Ocircumflex"}, + {189, "Odieresis"}, + {190, "Ograve"}, + {191, "Otilde"}, + {192, "Scaron"}, + {193, "Uacute"}, + {194, "Ucircumflex"}, + {195, "Udieresis"}, + {196, "Ugrave"}, + {197, "Yacute"}, + {198, "Ydieresis"}, + {199, "Zcaron"}, + {200, "aacute"}, + {201, "acircumflex"}, + {202, "adieresis"}, + {203, "agrave"}, + {204, "aring"}, + {205, "atilde"}, + {206, "ccedilla"}, + {207, "eacute"}, + {208, "ecircumflex"}, + {209, "edieresis"}, + {210, "egrave"}, + {211, "iacute"}, + {212, "icircumflex"}, + {213, "idieresis"}, + {214, "igrave"}, + {215, "ntilde"}, + {216, "oacute"}, + {217, "ocircumflex"}, + {218, "odieresis"}, + {219, "ograve"}, + {220, "otilde"}, + {221, "scaron"}, + {222, "uacute"}, + {223, "ucircumflex"}, + {224, "udieresis"}, + {225, "ugrave"}, + {226, "yacute"}, + {227, "ydieresis"}, + {228, "zcaron"} + }; private CFFISOAdobeCharset() { @@ -44,234 +281,10 @@ public static CFFISOAdobeCharset getInstance() static { int gid = 0; - INSTANCE.addSID(gid++, 0, ".notdef"); - INSTANCE.addSID(gid++, 1, "space"); - INSTANCE.addSID(gid++, 2, "exclam"); - INSTANCE.addSID(gid++, 3, "quotedbl"); - INSTANCE.addSID(gid++, 4, "numbersign"); - INSTANCE.addSID(gid++, 5, "dollar"); - INSTANCE.addSID(gid++, 6, "percent"); - INSTANCE.addSID(gid++, 7, "ampersand"); - INSTANCE.addSID(gid++, 8, "quoteright"); - INSTANCE.addSID(gid++, 9, "parenleft"); - INSTANCE.addSID(gid++, 10, "parenright"); - INSTANCE.addSID(gid++, 11, "asterisk"); - INSTANCE.addSID(gid++, 12, "plus"); - INSTANCE.addSID(gid++, 13, "comma"); - INSTANCE.addSID(gid++, 14, "hyphen"); - INSTANCE.addSID(gid++, 15, "period"); - INSTANCE.addSID(gid++, 16, "slash"); - INSTANCE.addSID(gid++, 17, "zero"); - INSTANCE.addSID(gid++, 18, "one"); - INSTANCE.addSID(gid++, 19, "two"); - INSTANCE.addSID(gid++, 20, "three"); - INSTANCE.addSID(gid++, 21, "four"); - INSTANCE.addSID(gid++, 22, "five"); - INSTANCE.addSID(gid++, 23, "six"); - INSTANCE.addSID(gid++, 24, "seven"); - INSTANCE.addSID(gid++, 25, "eight"); - INSTANCE.addSID(gid++, 26, "nine"); - INSTANCE.addSID(gid++, 27, "colon"); - INSTANCE.addSID(gid++, 28, "semicolon"); - INSTANCE.addSID(gid++, 29, "less"); - INSTANCE.addSID(gid++, 30, "equal"); - INSTANCE.addSID(gid++, 31, "greater"); - INSTANCE.addSID(gid++, 32, "question"); - INSTANCE.addSID(gid++, 33, "at"); - INSTANCE.addSID(gid++, 34, "A"); - INSTANCE.addSID(gid++, 35, "B"); - INSTANCE.addSID(gid++, 36, "C"); - INSTANCE.addSID(gid++, 37, "D"); - INSTANCE.addSID(gid++, 38, "E"); - INSTANCE.addSID(gid++, 39, "F"); - INSTANCE.addSID(gid++, 40, "G"); - INSTANCE.addSID(gid++, 41, "H"); - INSTANCE.addSID(gid++, 42, "I"); - INSTANCE.addSID(gid++, 43, "J"); - INSTANCE.addSID(gid++, 44, "K"); - INSTANCE.addSID(gid++, 45, "L"); - INSTANCE.addSID(gid++, 46, "M"); - INSTANCE.addSID(gid++, 47, "N"); - INSTANCE.addSID(gid++, 48, "O"); - INSTANCE.addSID(gid++, 49, "P"); - INSTANCE.addSID(gid++, 50, "Q"); - INSTANCE.addSID(gid++, 51, "R"); - INSTANCE.addSID(gid++, 52, "S"); - INSTANCE.addSID(gid++, 53, "T"); - INSTANCE.addSID(gid++, 54, "U"); - INSTANCE.addSID(gid++, 55, "V"); - INSTANCE.addSID(gid++, 56, "W"); - INSTANCE.addSID(gid++, 57, "X"); - INSTANCE.addSID(gid++, 58, "Y"); - INSTANCE.addSID(gid++, 59, "Z"); - INSTANCE.addSID(gid++, 60, "bracketleft"); - INSTANCE.addSID(gid++, 61, "backslash"); - INSTANCE.addSID(gid++, 62, "bracketright"); - INSTANCE.addSID(gid++, 63, "asciicircum"); - INSTANCE.addSID(gid++, 64, "underscore"); - INSTANCE.addSID(gid++, 65, "quoteleft"); - INSTANCE.addSID(gid++, 66, "a"); - INSTANCE.addSID(gid++, 67, "b"); - INSTANCE.addSID(gid++, 68, "c"); - INSTANCE.addSID(gid++, 69, "d"); - INSTANCE.addSID(gid++, 70, "e"); - INSTANCE.addSID(gid++, 71, "f"); - INSTANCE.addSID(gid++, 72, "g"); - INSTANCE.addSID(gid++, 73, "h"); - INSTANCE.addSID(gid++, 74, "i"); - INSTANCE.addSID(gid++, 75, "j"); - INSTANCE.addSID(gid++, 76, "k"); - INSTANCE.addSID(gid++, 77, "l"); - INSTANCE.addSID(gid++, 78, "m"); - INSTANCE.addSID(gid++, 79, "n"); - INSTANCE.addSID(gid++, 80, "o"); - INSTANCE.addSID(gid++, 81, "p"); - INSTANCE.addSID(gid++, 82, "q"); - INSTANCE.addSID(gid++, 83, "r"); - INSTANCE.addSID(gid++, 84, "s"); - INSTANCE.addSID(gid++, 85, "t"); - INSTANCE.addSID(gid++, 86, "u"); - INSTANCE.addSID(gid++, 87, "v"); - INSTANCE.addSID(gid++, 88, "w"); - INSTANCE.addSID(gid++, 89, "x"); - INSTANCE.addSID(gid++, 90, "y"); - INSTANCE.addSID(gid++, 91, "z"); - INSTANCE.addSID(gid++, 92, "braceleft"); - INSTANCE.addSID(gid++, 93, "bar"); - INSTANCE.addSID(gid++, 94, "braceright"); - INSTANCE.addSID(gid++, 95, "asciitilde"); - INSTANCE.addSID(gid++, 96, "exclamdown"); - INSTANCE.addSID(gid++, 97, "cent"); - INSTANCE.addSID(gid++, 98, "sterling"); - INSTANCE.addSID(gid++, 99, "fraction"); - INSTANCE.addSID(gid++, 100, "yen"); - INSTANCE.addSID(gid++, 101, "florin"); - INSTANCE.addSID(gid++, 102, "section"); - INSTANCE.addSID(gid++, 103, "currency"); - INSTANCE.addSID(gid++, 104, "quotesingle"); - INSTANCE.addSID(gid++, 105, "quotedblleft"); - INSTANCE.addSID(gid++, 106, "guillemotleft"); - INSTANCE.addSID(gid++, 107, "guilsinglleft"); - INSTANCE.addSID(gid++, 108, "guilsinglright"); - INSTANCE.addSID(gid++, 109, "fi"); - INSTANCE.addSID(gid++, 110, "fl"); - INSTANCE.addSID(gid++, 111, "endash"); - INSTANCE.addSID(gid++, 112, "dagger"); - INSTANCE.addSID(gid++, 113, "daggerdbl"); - INSTANCE.addSID(gid++, 114, "periodcentered"); - INSTANCE.addSID(gid++, 115, "paragraph"); - INSTANCE.addSID(gid++, 116, "bullet"); - INSTANCE.addSID(gid++, 117, "quotesinglbase"); - INSTANCE.addSID(gid++, 118, "quotedblbase"); - INSTANCE.addSID(gid++, 119, "quotedblright"); - INSTANCE.addSID(gid++, 120, "guillemotright"); - INSTANCE.addSID(gid++, 121, "ellipsis"); - INSTANCE.addSID(gid++, 122, "perthousand"); - INSTANCE.addSID(gid++, 123, "questiondown"); - INSTANCE.addSID(gid++, 124, "grave"); - INSTANCE.addSID(gid++, 125, "acute"); - INSTANCE.addSID(gid++, 126, "circumflex"); - INSTANCE.addSID(gid++, 127, "tilde"); - INSTANCE.addSID(gid++, 128, "macron"); - INSTANCE.addSID(gid++, 129, "breve"); - INSTANCE.addSID(gid++, 130, "dotaccent"); - INSTANCE.addSID(gid++, 131, "dieresis"); - INSTANCE.addSID(gid++, 132, "ring"); - INSTANCE.addSID(gid++, 133, "cedilla"); - INSTANCE.addSID(gid++, 134, "hungarumlaut"); - INSTANCE.addSID(gid++, 135, "ogonek"); - INSTANCE.addSID(gid++, 136, "caron"); - INSTANCE.addSID(gid++, 137, "emdash"); - INSTANCE.addSID(gid++, 138, "AE"); - INSTANCE.addSID(gid++, 139, "ordfeminine"); - INSTANCE.addSID(gid++, 140, "Lslash"); - INSTANCE.addSID(gid++, 141, "Oslash"); - INSTANCE.addSID(gid++, 142, "OE"); - INSTANCE.addSID(gid++, 143, "ordmasculine"); - INSTANCE.addSID(gid++, 144, "ae"); - INSTANCE.addSID(gid++, 145, "dotlessi"); - INSTANCE.addSID(gid++, 146, "lslash"); - INSTANCE.addSID(gid++, 147, "oslash"); - INSTANCE.addSID(gid++, 148, "oe"); - INSTANCE.addSID(gid++, 149, "germandbls"); - INSTANCE.addSID(gid++, 150, "onesuperior"); - INSTANCE.addSID(gid++, 151, "logicalnot"); - INSTANCE.addSID(gid++, 152, "mu"); - INSTANCE.addSID(gid++, 153, "trademark"); - INSTANCE.addSID(gid++, 154, "Eth"); - INSTANCE.addSID(gid++, 155, "onehalf"); - INSTANCE.addSID(gid++, 156, "plusminus"); - INSTANCE.addSID(gid++, 157, "Thorn"); - INSTANCE.addSID(gid++, 158, "onequarter"); - INSTANCE.addSID(gid++, 159, "divide"); - INSTANCE.addSID(gid++, 160, "brokenbar"); - INSTANCE.addSID(gid++, 161, "degree"); - INSTANCE.addSID(gid++, 162, "thorn"); - INSTANCE.addSID(gid++, 163, "threequarters"); - INSTANCE.addSID(gid++, 164, "twosuperior"); - INSTANCE.addSID(gid++, 165, "registered"); - INSTANCE.addSID(gid++, 166, "minus"); - INSTANCE.addSID(gid++, 167, "eth"); - INSTANCE.addSID(gid++, 168, "multiply"); - INSTANCE.addSID(gid++, 169, "threesuperior"); - INSTANCE.addSID(gid++, 170, "copyright"); - INSTANCE.addSID(gid++, 171, "Aacute"); - INSTANCE.addSID(gid++, 172, "Acircumflex"); - INSTANCE.addSID(gid++, 173, "Adieresis"); - INSTANCE.addSID(gid++, 174, "Agrave"); - INSTANCE.addSID(gid++, 175, "Aring"); - INSTANCE.addSID(gid++, 176, "Atilde"); - INSTANCE.addSID(gid++, 177, "Ccedilla"); - INSTANCE.addSID(gid++, 178, "Eacute"); - INSTANCE.addSID(gid++, 179, "Ecircumflex"); - INSTANCE.addSID(gid++, 180, "Edieresis"); - INSTANCE.addSID(gid++, 181, "Egrave"); - INSTANCE.addSID(gid++, 182, "Iacute"); - INSTANCE.addSID(gid++, 183, "Icircumflex"); - INSTANCE.addSID(gid++, 184, "Idieresis"); - INSTANCE.addSID(gid++, 185, "Igrave"); - INSTANCE.addSID(gid++, 186, "Ntilde"); - INSTANCE.addSID(gid++, 187, "Oacute"); - INSTANCE.addSID(gid++, 188, "Ocircumflex"); - INSTANCE.addSID(gid++, 189, "Odieresis"); - INSTANCE.addSID(gid++, 190, "Ograve"); - INSTANCE.addSID(gid++, 191, "Otilde"); - INSTANCE.addSID(gid++, 192, "Scaron"); - INSTANCE.addSID(gid++, 193, "Uacute"); - INSTANCE.addSID(gid++, 194, "Ucircumflex"); - INSTANCE.addSID(gid++, 195, "Udieresis"); - INSTANCE.addSID(gid++, 196, "Ugrave"); - INSTANCE.addSID(gid++, 197, "Yacute"); - INSTANCE.addSID(gid++, 198, "Ydieresis"); - INSTANCE.addSID(gid++, 199, "Zcaron"); - INSTANCE.addSID(gid++, 200, "aacute"); - INSTANCE.addSID(gid++, 201, "acircumflex"); - INSTANCE.addSID(gid++, 202, "adieresis"); - INSTANCE.addSID(gid++, 203, "agrave"); - INSTANCE.addSID(gid++, 204, "aring"); - INSTANCE.addSID(gid++, 205, "atilde"); - INSTANCE.addSID(gid++, 206, "ccedilla"); - INSTANCE.addSID(gid++, 207, "eacute"); - INSTANCE.addSID(gid++, 208, "ecircumflex"); - INSTANCE.addSID(gid++, 209, "edieresis"); - INSTANCE.addSID(gid++, 210, "egrave"); - INSTANCE.addSID(gid++, 211, "iacute"); - INSTANCE.addSID(gid++, 212, "icircumflex"); - INSTANCE.addSID(gid++, 213, "idieresis"); - INSTANCE.addSID(gid++, 214, "igrave"); - INSTANCE.addSID(gid++, 215, "ntilde"); - INSTANCE.addSID(gid++, 216, "oacute"); - INSTANCE.addSID(gid++, 217, "ocircumflex"); - INSTANCE.addSID(gid++, 218, "odieresis"); - INSTANCE.addSID(gid++, 219, "ograve"); - INSTANCE.addSID(gid++, 220, "otilde"); - INSTANCE.addSID(gid++, 221, "scaron"); - INSTANCE.addSID(gid++, 222, "uacute"); - INSTANCE.addSID(gid++, 223, "ucircumflex"); - INSTANCE.addSID(gid++, 224, "udieresis"); - INSTANCE.addSID(gid++, 225, "ugrave"); - INSTANCE.addSID(gid++, 226, "yacute"); - INSTANCE.addSID(gid++, 227, "ydieresis"); - INSTANCE.addSID(gid++, 228, "zcaron"); + for (Object[] charsetEntry : CFF_ISO_ADOBE_CHARSET_TABLE) + { + INSTANCE.addSID(gid++, (Integer)charsetEntry[CHAR_CODE], + charsetEntry[CHAR_NAME].toString()); + } } } \ No newline at end of file diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFOperator.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFOperator.java index 71c8bd66d..6e4d24bc4 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFOperator.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFOperator.java @@ -202,8 +202,8 @@ public boolean equals(Object object) } } - private static Map keyMap = new LinkedHashMap(); - private static Map nameMap = new LinkedHashMap(); + private static Map keyMap = new LinkedHashMap(52); + private static Map nameMap = new LinkedHashMap(52); static { @@ -263,4 +263,4 @@ public boolean equals(Object object) register(new Key(20), "defaultWidthX"); register(new Key(21), "nominalWidthX"); } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFParser.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFParser.java index f9faf8d9e..dbbe935c6 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFParser.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFParser.java @@ -19,11 +19,14 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import com.tom_roush.fontbox.util.Charsets; + /** * This class represents a parser for a CFF font. * @author Villu Ruusmann @@ -34,24 +37,47 @@ public class CFFParser 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; - private IndexData topDictIndex = null; - private IndexData stringIndex = null; + private String[] stringIndex = null; + private ByteSource source; // for debugging only private String debugFontName; /** - * Parsing CFF Font using a byte array as input. + * Source from which bytes may be read in the future. + */ + public interface ByteSource + { + /** + * Returns the source bytes. May be called more than once. + */ + byte[] getBytes() throws IOException; + } + + /** + * Parse CFF font using byte array, also passing in a byte source for future use. + * + * @param bytes source bytes + * @param source source to re-read bytes from in the future + * @return the parsed CFF fonts + * @throws IOException If there is an error reading from the stream + */ + public List parse(byte[] bytes, ByteSource source) throws IOException + { + this.source = source; + return parse(bytes); + } + + /** + * Parse CFF Font using a byte array as input. + * * @param bytes the given byte array * @return the parsed CFF fonts * @throws IOException If there is an error reading from the stream */ public List parse(byte[] bytes) throws IOException { - input = new CFFDataInput(bytes); + CFFDataInput input = new CFFDataInput(bytes); String firstTag = readTagName(input); // try to determine which kind of font we have @@ -60,14 +86,18 @@ public List parse(byte[] bytes) throws IOException // this is OpenType font containing CFF data // so find CFF tag short numTables = input.readShort(); + @SuppressWarnings("unused") short searchRange = input.readShort(); + @SuppressWarnings("unused") short entrySelector = input.readShort(); + @SuppressWarnings("unused") short rangeShift = input.readShort(); boolean cffFound = false; for (int q = 0; q < numTables; q++) { String tagName = readTagName(input); + @SuppressWarnings("unused") long checksum = readLong(input); long offset = readLong(input); long length = readLong(input); @@ -98,18 +128,19 @@ else if (TAG_TTFONLY.equals(firstTag)) input.setPosition(0); } - header = readHeader(input); - nameIndex = readIndexData(input); - topDictIndex = readIndexData(input); - stringIndex = readIndexData(input); - IndexData globalSubrIndex = readIndexData(input); + @SuppressWarnings("unused") + Header header = readHeader(input); + String[] nameIndex = readStringIndexData(input); + byte[][] topDictIndex = readIndexData(input); + stringIndex = readStringIndexData(input); + byte[][] globalSubrIndex = readIndexData(input); List fonts = new ArrayList(); - for (int i = 0; i < nameIndex.getCount(); i++) + for (int i = 0; i < nameIndex.length; i++) { - CFFFont font = parseFont(i); + CFFFont font = parseFont(input, nameIndex[i], topDictIndex[i]); font.setGlobalSubrIndex(globalSubrIndex); - font.setData(bytes); + font.setData(source); fonts.add(font); } return fonts; @@ -118,7 +149,7 @@ else if (TAG_TTFONLY.equals(firstTag)) private static String readTagName(CFFDataInput input) throws IOException { byte[] b = input.readBytes(4); - return new String(b, "ISO-8859-1"); + return new String(b, Charsets.ISO_8859_1); } private static long readLong(CFFDataInput input) throws IOException @@ -136,41 +167,78 @@ private static Header readHeader(CFFDataInput input) throws IOException return cffHeader; } - private static IndexData readIndexData(CFFDataInput input) throws IOException + private static int[] readIndexDataOffsets(CFFDataInput input) throws IOException { int count = input.readCard16(); - IndexData index = new IndexData(count); if (count == 0) { - return index; + return null; } int offSize = input.readOffSize(); + int[] offsets = new int[count + 1]; for (int i = 0; i <= count; i++) { - int offset = input.readOffset(offSize); - if (offset > input.length()) - { - throw new IOException("illegal offset value " + offset + " in CFF font"); - } - index.setOffset(i, offset); + int offset = input.readOffset(offSize); + if (offset > input.length()) + { + throw new IOException("illegal offset value " + offset + " in CFF font"); + } + offsets[i] = offset; + } + return offsets; + } + + private static byte[][] readIndexData(CFFDataInput input) throws IOException + { + int[] offsets = readIndexDataOffsets(input); + if (offsets == null) + { + return null; + } + int count = offsets.length - 1; + byte[][] indexDataValues = new byte[count][]; + for (int i = 0; i < count; i++) + { + int length = offsets[i + 1] - offsets[i]; + indexDataValues[i] = input.readBytes(length); + } + return indexDataValues; + } + + private static String[] readStringIndexData(CFFDataInput input) throws IOException + { + int[] offsets = readIndexDataOffsets(input); + if (offsets == null) + { + return null; } - int dataSize = index.getOffset(count) - index.getOffset(0); - index.initData(dataSize); - for (int i = 0; i < dataSize; i++) + int count = offsets.length - 1; + String[] indexDataValues = new String[count]; + for (int i = 0; i < count; i++) { - index.setData(i, input.readCard8()); + int length = offsets[i + 1] - offsets[i]; + indexDataValues[i] = new String(input.readBytes(length), Charsets.ISO_8859_1); } - return index; + return indexDataValues; } private static DictData readDictData(CFFDataInput input) throws IOException { DictData dict = new DictData(); - dict.entries = new ArrayList(); while (input.hasRemaining()) { - DictData.Entry entry = readEntry(input); - dict.entries.add(entry); + dict.add(readEntry(input)); + } + return dict; + } + + private static DictData readDictData(CFFDataInput input, int dictSize) throws IOException + { + DictData dict = new DictData(); + int endPosition = input.getPosition() + dictSize; + while (input.getPosition() < endPosition) + { + dict.add(readEntry(input)); } return dict; } @@ -227,17 +295,11 @@ private static Integer readIntegerNumber(CFFDataInput input, int b0) throws IOEx { if (b0 == 28) { - int b1 = input.readUnsignedByte(); - int b2 = input.readUnsignedByte(); - return (int) (short) (b1 << 8 | b2); + return (int)input.readShort(); } else if (b0 == 29) { - int b1 = input.readUnsignedByte(); - int b2 = input.readUnsignedByte(); - int b3 = input.readUnsignedByte(); - int b4 = input.readUnsignedByte(); - return b1 << 24 | b2 << 16 | b3 << 8 | b4; + return input.readInt(); } else if (b0 >= 32 && b0 <= 246) { @@ -264,7 +326,7 @@ else if (b0 >= 251 && b0 <= 254) */ private static Double readRealNumber(CFFDataInput input, int b0) throws IOException { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); boolean done = false; boolean exponentMissing = false; while (!done) @@ -322,14 +384,11 @@ private static Double readRealNumber(CFFDataInput input, int b0) throws IOExcept return Double.valueOf(sb.toString()); } - private CFFFont parseFont(int index) throws IOException + private CFFFont parseFont(CFFDataInput input, String name, byte[] topDictIndex) + throws IOException { - // name index - DataInput nameInput = new DataInput(nameIndex.getBytes(index)); - String name = nameInput.getString(); - // top dict - CFFDataInput topDictInput = new CFFDataInput(topDictIndex.getBytes(index)); + CFFDataInput topDictInput = new CFFDataInput(topDictIndex); DictData topDict = readDictData(topDictInput); // we dont't support synthetic fonts @@ -366,100 +425,95 @@ private CFFFont parseFont(int index) throws IOException 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( + font.addValueToTopDict("isFixedPitch", topDict.getBoolean("isFixedPitch", false)); + font.addValueToTopDict("ItalicAngle", topDict.getNumber("ItalicAngle", 0)); + font.addValueToTopDict("UnderlinePosition", topDict.getNumber("UnderlinePosition", -100)); + font.addValueToTopDict("UnderlineThickness", topDict.getNumber("UnderlineThickness", 50)); + font.addValueToTopDict("PaintType", topDict.getNumber("PaintType", 0)); + font.addValueToTopDict("CharstringType", topDict.getNumber("CharstringType", 2)); + font.addValueToTopDict("FontMatrix", topDict.getArray("FontMatrix", Arrays.asList( 0.001, (double) 0, (double) 0, 0.001, (double) 0, (double) 0))); - font.addValueToTopDict("UniqueID", getNumber(topDict, "UniqueID", null)); - font.addValueToTopDict("FontBBox", getArray(topDict, "FontBBox", + font.addValueToTopDict("UniqueID", topDict.getNumber("UniqueID", null)); + font.addValueToTopDict("FontBBox", topDict.getArray("FontBBox", Arrays. asList(0, 0, 0, 0))); - font.addValueToTopDict("StrokeWidth", getNumber(topDict, "StrokeWidth", 0)); - font.addValueToTopDict("XUID", getArray(topDict, "XUID", null)); + font.addValueToTopDict("StrokeWidth", topDict.getNumber("StrokeWidth", 0)); + font.addValueToTopDict("XUID", topDict.getArray("XUID", null)); // charstrings index DictData.Entry charStringsEntry = topDict.getEntry("CharStrings"); int charStringsOffset = charStringsEntry.getNumber(0).intValue(); input.setPosition(charStringsOffset); - IndexData charStringsIndex = readIndexData(input); + byte[][] charStringsIndex = readIndexData(input); // charset DictData.Entry charsetEntry = topDict.getEntry("charset"); CFFCharset charset; if (charsetEntry != null) { - int charsetId = charsetEntry.getNumber(0).intValue(); - if (!isCIDFont && charsetId == 0) - { - charset = CFFISOAdobeCharset.getInstance(); - } - else if (!isCIDFont && charsetId == 1) - { - charset = CFFExpertCharset.getInstance(); - } - else if (!isCIDFont && charsetId == 2) - { - charset = CFFExpertSubsetCharset.getInstance(); - } - else - { - input.setPosition(charsetId); - charset = readCharset(input, charStringsIndex.getCount(), isCIDFont); - } + int charsetId = charsetEntry.getNumber(0).intValue(); + if (!isCIDFont && charsetId == 0) + { + charset = CFFISOAdobeCharset.getInstance(); + } + else if (!isCIDFont && charsetId == 1) + { + charset = CFFExpertCharset.getInstance(); + } + else if (!isCIDFont && charsetId == 2) + { + charset = CFFExpertSubsetCharset.getInstance(); + } + else + { + input.setPosition(charsetId); + charset = readCharset(input, charStringsIndex.length, isCIDFont); + } } else { - // a CID font with no charset does not default to any predefined charset - if (isCIDFont) - { - // a CID font with no charset does not default to any predefined charset - charset = new EmptyCharset(charStringsIndex.getCount()); - } - else - { - //FIXME PDFBOX-2571 - charset = CFFISOAdobeCharset.getInstance(); - } + if (isCIDFont) + { + // a CID font with no charset does not default to any predefined charset + charset = new EmptyCharset(charStringsIndex.length); + } + else + { + //FIXME PDFBOX-2571 + charset = CFFISOAdobeCharset.getInstance(); + } } font.setCharset(charset); // charstrings dict - font.getCharStringBytes().add(charStringsIndex.getBytes(0)); // .notdef - for (int i = 1; i < charStringsIndex.getCount(); i++) - { - byte[] bytes = charStringsIndex.getBytes(i); - font.getCharStringBytes().add(bytes); - } + font.charStrings = charStringsIndex; // format-specific dictionaries if (isCIDFont) { - parseCIDFontDicts(topDict, (CFFCIDFont) font, charStringsIndex); - // some malformed fonts have FontMatrix in their Font DICT, see PDFBOX-2495 - if (topDict.getEntry("FontMatrix") == null) - { - List> fontDicts = ((CFFCIDFont) font).getFontDicts(); - if (fontDicts.size() > 0 && fontDicts.get(0).containsKey("FontMatrix")) - { - List matrix = (List)fontDicts.get(0).get("FontMatrix"); - font.addValueToTopDict("FontMatrix", matrix); - } - else - { - // default - font.addValueToTopDict("FontMatrix", getArray(topDict, "FontMatrix", - Arrays.asList(0.001, (double) 0, (double) 0, 0.001, - (double) 0, (double) 0))); - } - } + parseCIDFontDicts(input, topDict, (CFFCIDFont)font, charStringsIndex.length); + + // some malformed fonts have FontMatrix in their Font DICT, see PDFBOX-2495 + if (topDict.getEntry("FontMatrix") == null) + { + List> fontDicts = ((CFFCIDFont)font).getFontDicts(); + if (fontDicts.size() > 0 && fontDicts.get(0).containsKey("FontMatrix")) + { + List matrix = (List)fontDicts.get(0).get("FontMatrix"); + font.addValueToTopDict("FontMatrix", matrix); + } + else + { + // default + font.addValueToTopDict("FontMatrix", topDict.getArray("FontMatrix", + Arrays.asList(0.001, (double)0, (double)0, 0.001, (double)0, + (double)0))); + } + } } else { - parseType1Dicts(topDict, (CFFType1Font) font, charset); + parseType1Dicts(input, topDict, (CFFType1Font)font, charset); } return font; @@ -468,7 +522,8 @@ else if (!isCIDFont && charsetId == 2) /** * Parse dictionaries specific to a CIDFont. */ - private void parseCIDFontDicts(DictData topDict, CFFCIDFont font, IndexData charStringsIndex) + private void parseCIDFontDicts(CFFDataInput input, DictData topDict, CFFCIDFont font, + int nrOfcharStrings) throws IOException { // In a CIDKeyed Font, the Private dictionary isn't in the Top Dict but in the Font dict @@ -482,53 +537,48 @@ private void parseCIDFontDicts(DictData topDict, CFFCIDFont font, IndexData char // font dict index int fontDictOffset = fdArrayEntry.getNumber(0).intValue(); input.setPosition(fontDictOffset); - IndexData fdIndex = readIndexData(input); + byte[][] fdIndex = readIndexData(input); List> privateDictionaries = new LinkedList>(); List> fontDictionaries = new LinkedList>(); - for (int i = 0; i < fdIndex.getCount(); ++i) + for (int i = 0; i < fdIndex.length; ++i) { - byte[] bytes = fdIndex.getBytes(i); + byte[] bytes = fdIndex[i]; CFFDataInput fontDictInput = new CFFDataInput(bytes); DictData fontDict = readDictData(fontDictInput); - // font dict - Map fontDictMap = new LinkedHashMap(); - fontDictMap.put("FontName", getString(fontDict, "FontName")); - fontDictMap.put("FontType", getNumber(fontDict, "FontType", 0)); - fontDictMap.put("FontBBox", getDelta(fontDict, "FontBBox", null)); - fontDictMap.put("FontMatrix", getDelta(fontDict, "FontMatrix", null)); - // TODO OD-4 : Add here other keys - fontDictionaries.add(fontDictMap); - // read private dict DictData.Entry privateEntry = fontDict.getEntry("Private"); if (privateEntry == null) { throw new IOException("Font DICT invalid without \"Private\" entry"); } + + // font dict + Map fontDictMap = new LinkedHashMap(4); + fontDictMap.put("FontName", getString(fontDict, "FontName")); + fontDictMap.put("FontType", fontDict.getNumber("FontType", 0)); + fontDictMap.put("FontBBox", fontDict.getArray("FontBBox", null)); + fontDictMap.put("FontMatrix", fontDict.getArray("FontMatrix", null)); + // TODO OD-4 : Add here other keys + fontDictionaries.add(fontDictMap); + int privateOffset = privateEntry.getNumber(1).intValue(); input.setPosition(privateOffset); int privateSize = privateEntry.getNumber(0).intValue(); - CFFDataInput privateDictData = new CFFDataInput(input.readBytes(privateSize)); - DictData privateDict = readDictData(privateDictData); + DictData privateDict = readDictData(input, privateSize); // populate private dict Map privDict = readPrivateDict(privateDict); privateDictionaries.add(privDict); // local subrs - int localSubrOffset = (Integer) getNumber(privateDict, "Subrs", 0); - if (localSubrOffset == 0) - { - privDict.put("Subrs", new IndexData(0)); - } - else + int localSubrOffset = (Integer)privateDict.getNumber("Subrs", 0); + if (localSubrOffset > 0) { input.setPosition(privateOffset + localSubrOffset); - IndexData idx = readIndexData(input); - privDict.put("Subrs", idx); + privDict.put("Subrs", readIndexData(input)); } } @@ -536,9 +586,9 @@ private void parseCIDFontDicts(DictData topDict, CFFCIDFont font, IndexData char DictData.Entry fdSelectEntry = topDict.getEntry("FDSelect"); int fdSelectPos = fdSelectEntry.getNumber(0).intValue(); input.setPosition(fdSelectPos); - FDSelect fdSelect = readFDSelect(input, charStringsIndex.getCount(), font); + FDSelect fdSelect = readFDSelect(input, nrOfcharStrings, font); - // TODO: almost certainly erroneous - CIDFonts do not have a top-level private dict + // TODO almost certainly erroneous - CIDFonts do not have a top-level private dict // font.addValueToPrivateDict("defaultWidthX", 1000); // font.addValueToPrivateDict("nominalWidthX", 0); @@ -546,34 +596,35 @@ private void parseCIDFontDicts(DictData topDict, CFFCIDFont font, IndexData char font.setPrivDict(privateDictionaries); font.setFdSelect(fdSelect); } - + private Map readPrivateDict(DictData privateDict) { - Map privDict = new LinkedHashMap(); - privDict.put("BlueValues", getDelta(privateDict, "BlueValues", null)); - privDict.put("OtherBlues", getDelta(privateDict, "OtherBlues", null)); - privDict.put("FamilyBlues", getDelta(privateDict, "FamilyBlues", null)); - privDict.put("FamilyOtherBlues", getDelta(privateDict, "FamilyOtherBlues", null)); - privDict.put("BlueScale", getNumber(privateDict, "BlueScale", 0.039625)); - privDict.put("BlueShift", getNumber(privateDict, "BlueShift", 7)); - privDict.put("BlueFuzz", getNumber(privateDict, "BlueFuzz", 1)); - privDict.put("StdHW", getNumber(privateDict, "StdHW", null)); - privDict.put("StdVW", getNumber(privateDict, "StdVW", null)); - privDict.put("StemSnapH", getDelta(privateDict, "StemSnapH", null)); - privDict.put("StemSnapV", getDelta(privateDict, "StemSnapV", null)); - privDict.put("ForceBold", getBoolean(privateDict, "ForceBold", false)); - privDict.put("LanguageGroup", getNumber(privateDict, "LanguageGroup", 0)); - privDict.put("ExpansionFactor", getNumber(privateDict, "ExpansionFactor", 0.06)); - privDict.put("initialRandomSeed", getNumber(privateDict, "initialRandomSeed", 0)); - privDict.put("defaultWidthX", getNumber(privateDict, "defaultWidthX", 0)); - privDict.put("nominalWidthX", getNumber(privateDict, "nominalWidthX", 0)); - return privDict; + Map privDict = new LinkedHashMap(17); + privDict.put("BlueValues", privateDict.getArray("BlueValues", null)); + privDict.put("OtherBlues", privateDict.getArray("OtherBlues", null)); + privDict.put("FamilyBlues", privateDict.getArray("FamilyBlues", null)); + privDict.put("FamilyOtherBlues", privateDict.getArray("FamilyOtherBlues", null)); + privDict.put("BlueScale", privateDict.getNumber("BlueScale", 0.039625)); + privDict.put("BlueShift", privateDict.getNumber("BlueShift", 7)); + privDict.put("BlueFuzz", privateDict.getNumber("BlueFuzz", 1)); + privDict.put("StdHW", privateDict.getNumber("StdHW", null)); + privDict.put("StdVW", privateDict.getNumber("StdVW", null)); + privDict.put("StemSnapH", privateDict.getArray("StemSnapH", null)); + privDict.put("StemSnapV", privateDict.getArray("StemSnapV", null)); + privDict.put("ForceBold", privateDict.getBoolean("ForceBold", false)); + privDict.put("LanguageGroup", privateDict.getNumber("LanguageGroup", 0)); + privDict.put("ExpansionFactor", privateDict.getNumber("ExpansionFactor", 0.06)); + privDict.put("initialRandomSeed", privateDict.getNumber("initialRandomSeed", 0)); + privDict.put("defaultWidthX", privateDict.getNumber("defaultWidthX", 0)); + privDict.put("nominalWidthX", privateDict.getNumber("nominalWidthX", 0)); + return privDict; } /** * Parse dictionaries specific to a Type 1-equivalent font. */ - private void parseType1Dicts(DictData topDict, CFFType1Font font, CFFCharset charset) + private void parseType1Dicts(CFFDataInput input, DictData topDict, CFFType1Font font, + CFFCharset charset) throws IOException { // encoding @@ -597,26 +648,25 @@ else if (encodingId == 1) // read private dict DictData.Entry privateEntry = topDict.getEntry("Private"); + if (privateEntry == null) + { + throw new IOException("Private dictionary entry missing for font " + font.fontName); + } int privateOffset = privateEntry.getNumber(1).intValue(); input.setPosition(privateOffset); int privateSize = privateEntry.getNumber(0).intValue(); - CFFDataInput privateDictData = new CFFDataInput(input.readBytes(privateSize)); - DictData privateDict = readDictData(privateDictData); + DictData privateDict = readDictData(input, privateSize); // populate private dict Map privDict = readPrivateDict(privateDict); for (Map.Entry entry : privDict.entrySet()) { - font.addToPrivateDict(entry.getKey(), entry.getValue()); + font.addToPrivateDict(entry.getKey(), entry.getValue()); } // local subrs - int localSubrOffset = (Integer) getNumber(privateDict, "Subrs", 0); - if (localSubrOffset == 0) - { - font.addToPrivateDict("Subrs", new IndexData(0)); - } - else + int localSubrOffset = (Integer)privateDict.getNumber("Subrs", 0); + if (localSubrOffset > 0) { input.setPosition(privateOffset + localSubrOffset); font.addToPrivateDict("Subrs", readIndexData(input)); @@ -629,13 +679,15 @@ private String readString(int index) throws IOException { return CFFStandardString.getName(index); } - if (index - 391 < stringIndex.getCount()) + if (index - 391 < stringIndex.length) { - DataInput dataInput = new DataInput(stringIndex.getBytes(index - 391)); - return dataInput.getString(); + return stringIndex[index - 391]; + } + else + { + // technically this maps to .notdef, but we need a unique sid name + return "SID" + index; } - // technically this maps to .notdef, but we need a unique sid name - return "SID" + index; } private String getString(DictData dict, String name) throws IOException @@ -644,32 +696,6 @@ private String getString(DictData dict, String name) throws IOException return entry != null ? readString(entry.getNumber(0).intValue()) : null; } - private static Boolean getBoolean(DictData dict, String name, boolean defaultValue) - { - DictData.Entry entry = dict.getEntry(name); - return entry != null ? entry.getBoolean(0) : defaultValue; - } - - private static Number getNumber(DictData dict, String name, Number defaultValue) - { - DictData.Entry entry = dict.getEntry(name); - return entry != null ? entry.getNumber(0) : defaultValue; - } - - // TODO Where is the difference to getDelta?? - private static List getArray(DictData dict, String name, List defaultValue) - { - DictData.Entry entry = dict.getEntry(name); - return entry != null ? entry.getArray() : defaultValue; - } - - // TODO Where is the difference to getArray?? - private static List getDelta(DictData dict, String name, List defaultValue) - { - DictData.Entry entry = dict.getEntry(name); - return entry != null ? entry.getArray() : defaultValue; - } - private CFFEncoding readEncoding(CFFDataInput dataInput, CFFCharset charset) throws IOException { int format = dataInput.readCard8(); @@ -695,12 +721,10 @@ private Format0Encoding readFormat0Encoding(CFFDataInput dataInput, CFFCharset c Format0Encoding encoding = new Format0Encoding(); encoding.format = format; encoding.nCodes = dataInput.readCard8(); - encoding.code = new int[encoding.nCodes]; encoding.add(0, 0, ".notdef"); for (int gid = 1; gid <= encoding.nCodes; gid++) { int code = dataInput.readCard8(); - encoding.code[gid - 1] = code; int sid = charset.getSIDForGID(gid); encoding.add(code, sid, readString(sid)); } @@ -717,19 +741,16 @@ private Format1Encoding readFormat1Encoding(CFFDataInput dataInput, CFFCharset c Format1Encoding encoding = new Format1Encoding(); encoding.format = format; encoding.nRanges = dataInput.readCard8(); - encoding.range = new Format1Encoding.Range1[encoding.nRanges]; encoding.add(0, 0, ".notdef"); int gid = 1; - for (int i = 0; i < encoding.range.length; i++) + for (int i = 0; i < encoding.nRanges; i++) { - Format1Encoding.Range1 range = new Format1Encoding.Range1(); - range.first = dataInput.readCard8(); - range.nLeft = dataInput.readCard8(); - encoding.range[i] = range; - for (int j = 0; j < 1 + range.nLeft; j++) + int rangeFirst = dataInput.readCard8(); + int rangeLeft = dataInput.readCard8(); + for (int j = 0; j < 1 + rangeLeft; j++) { int sid = charset.getSIDForGID(gid); - int code = range.first + j; + int code = rangeFirst + j; encoding.add(code, sid, readString(sid)); gid++; } @@ -799,7 +820,6 @@ private static Format0FDSelect readFormat0FDSelect(CFFDataInput dataInput, int f for (int i = 0; i < fdselect.fds.length; i++) { fdselect.fds[i] = dataInput.readCard8(); - } return fdselect; } @@ -907,6 +927,7 @@ public String toString() */ private static class Format0FDSelect extends FDSelect { + @SuppressWarnings("unused") private int format; private int[] fds; @@ -959,8 +980,6 @@ private Format0Charset readFormat0Charset(CFFDataInput dataInput, int format, in { Format0Charset charset = new Format0Charset(isCIDFont); charset.format = format; - charset.glyph = new int[nGlyphs]; - charset.glyph[0] = 0; if (isCIDFont) { charset.addCID(0, 0); @@ -970,10 +989,9 @@ private Format0Charset readFormat0Charset(CFFDataInput dataInput, int format, in charset.addSID(0, 0, ".notdef"); } - for (int gid = 1; gid < charset.glyph.length; gid++) + for (int gid = 1; gid < nGlyphs; gid++) { int sid = dataInput.readSID(); - charset.glyph[gid] = sid; if (isCIDFont) { charset.addCID(gid, sid); @@ -991,10 +1009,10 @@ private Format1Charset readFormat1Charset(CFFDataInput dataInput, int format, in { Format1Charset charset = new Format1Charset(isCIDFont); charset.format = format; - List ranges = new ArrayList(); if (isCIDFont) { charset.addCID(0, 0); + charset.rangesCID2GID = new ArrayList(); } else { @@ -1003,25 +1021,22 @@ private Format1Charset readFormat1Charset(CFFDataInput dataInput, int format, in for (int gid = 1; gid < nGlyphs; gid++) { - Format1Charset.Range1 range = new Format1Charset.Range1(); - range.first = dataInput.readSID(); - range.nLeft = dataInput.readCard8(); - ranges.add(range); - for (int j = 0; j < 1 + range.nLeft; j++) + int rangeFirst = dataInput.readSID(); + int rangeLeft = dataInput.readCard8(); + if (!isCIDFont) { - int sid = range.first + j; - if (isCIDFont) - { - charset.addCID(gid + j, sid); - } - else + for (int j = 0; j < 1 + rangeLeft; j++) { + int sid = rangeFirst + j; charset.addSID(gid + j, sid, readString(sid)); } } - gid += range.nLeft; + else + { + charset.rangesCID2GID.add(new RangeMapping(gid, rangeFirst, rangeLeft)); + } + gid += rangeLeft; } - charset.range = ranges.toArray(new Format1Charset.Range1[0]); return charset; } @@ -1030,10 +1045,10 @@ private Format2Charset readFormat2Charset(CFFDataInput dataInput, int format, in { Format2Charset charset = new Format2Charset(isCIDFont); charset.format = format; - charset.range = new Format2Charset.Range2[0]; if (isCIDFont) { charset.addCID(0, 0); + charset.rangesCID2GID = new ArrayList(); } else { @@ -1042,26 +1057,21 @@ private Format2Charset readFormat2Charset(CFFDataInput dataInput, int format, in for (int gid = 1; gid < nGlyphs; gid++) { - Format2Charset.Range2[] newRange = new Format2Charset.Range2[charset.range.length + 1]; - System.arraycopy(charset.range, 0, newRange, 0, charset.range.length); - charset.range = newRange; - Format2Charset.Range2 range = new Format2Charset.Range2(); - range.first = dataInput.readSID(); - range.nLeft = dataInput.readCard16(); - charset.range[charset.range.length - 1] = range; - for (int j = 0; j < 1 + range.nLeft; j++) + int first = dataInput.readSID(); + int nLeft = dataInput.readCard16(); + if (!isCIDFont) { - int sid = range.first + j; - if (isCIDFont) - { - charset.addCID(gid + j, sid); - } - else + for (int j = 0; j < 1 + nLeft; j++) { + int sid = first + j; charset.addSID(gid + j, sid, readString(sid)); } } - gid += range.nLeft; + else + { + charset.rangesCID2GID.add(new RangeMapping(gid, first, nLeft)); + } + gid += nLeft; } return charset; } @@ -1089,30 +1099,37 @@ public String toString() */ private static class DictData { + private final Map entries = new HashMap(); - private List entries = null; - - public Entry getEntry(CFFOperator.Key key) + public void add(Entry entry) { - return getEntry(CFFOperator.getOperator(key)); + if (entry.operator != null) + { + entries.put(entry.operator.getName(), entry); + } } public Entry getEntry(String name) { - return getEntry(CFFOperator.getOperator(name)); + return entries.get(name); } - private Entry getEntry(CFFOperator operator) + public Boolean getBoolean(String name, boolean defaultValue) { - for (Entry entry : entries) - { - // Check for null entry before comparing the Font - if (entry != null && entry.operator != null && entry.operator.equals(operator)) - { - return entry; - } - } - return null; + Entry entry = getEntry(name); + return entry != null ? entry.getBoolean(0) : defaultValue; + } + + public List getArray(String name, List defaultValue) + { + Entry entry = getEntry(name); + return entry != null ? entry.getArray() : defaultValue; + } + + public Number getNumber(String name, Number defaultValue) + { + Entry entry = getEntry(name); + return entry != null ? entry.getNumber(0) : defaultValue; } /** @@ -1155,29 +1172,11 @@ public Boolean getBoolean(int index) throw new IllegalArgumentException(); } - // TODO unused?? - public Integer getSID(int index) - { - Number operand = operands.get(index); - if (operand instanceof Integer) - { - return (Integer) operand; - } - throw new IllegalArgumentException(); - } - - // TODO Where is the difference to getDelta?? public List getArray() { return operands; } - // TODO Where is the difference to getArray?? - public List getDelta() - { - return operands; - } - @Override public String toString() { @@ -1233,13 +1232,12 @@ private static class Format0Encoding extends CFFBuiltInEncoding { private int format; private int nCodes; - private int[] code; @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 + + ", supplement=" + Arrays.toString(super.supplement) + "]"; } } @@ -1250,28 +1248,12 @@ private static class Format1Encoding extends CFFBuiltInEncoding { private int format; private int nRanges; - private Range1[] range; @Override public String toString() { - return getClass().getName() + "[format=" + format + ", nRanges=" + nRanges + ", range=" - + Arrays.toString(range) + ", supplement=" + Arrays.toString(super.supplement) + "]"; - } - - /** - * Inner class representing a range of an encoding. - */ - private static class Range1 - { - private int first; - private int nLeft; - - @Override - public String toString() - { - return getClass().getName() + "[first=" + first + ", nLeft=" + nLeft + "]"; - } + return getClass().getName() + "[format=" + format + ", nRanges=" + nRanges + + ", supplement=" + Arrays.toString(super.supplement) + "]"; } } @@ -1291,23 +1273,23 @@ protected EmbeddedCharset(boolean isCIDFont) */ private static class EmptyCharset extends EmbeddedCharset { - protected EmptyCharset(int numCharStrings) - { - super(true); - addCID(0 ,0); // .notdef - - // Adobe Reader treats CID as GID, PDFBOX-2571 p11. - for (int i = 1; i <= numCharStrings; i++) - { - addCID(i, i); - } - } - - @Override - public String toString() - { - return getClass().getName(); - } + protected EmptyCharset(int numCharStrings) + { + super(true); + addCID(0, 0); // .notdef + + // Adobe Reader treats CID as GID, PDFBOX-2571 p11. + for (int i = 1; i <= numCharStrings; i++) + { + addCID(i, i); + } + } + + @Override + public String toString() + { + return getClass().getName(); + } } /** @@ -1316,7 +1298,6 @@ public String toString() private static class Format0Charset extends EmbeddedCharset { private int format; - private int[] glyph; protected Format0Charset(boolean isCIDFont) { @@ -1326,7 +1307,7 @@ protected Format0Charset(boolean isCIDFont) @Override public String toString() { - return getClass().getName() + "[format=" + format + ", glyph=" + Arrays.toString(glyph) + "]"; + return getClass().getName() + "[format=" + format + "]"; } } @@ -1336,7 +1317,7 @@ public String toString() private static class Format1Charset extends EmbeddedCharset { private int format; - private Range1[] range; + private List rangesCID2GID; protected Format1Charset(boolean isCIDFont) { @@ -1344,24 +1325,41 @@ protected Format1Charset(boolean isCIDFont) } @Override - public String toString() + public int getCIDForGID(int gid) { - return getClass().getName() + "[format=" + format + ", range=" + Arrays.toString(range) + "]"; + if (isCIDFont()) + { + for (RangeMapping mapping : rangesCID2GID) + { + if (mapping.isInRange(gid)) + { + return mapping.mapValue(gid); + } + } + } + return super.getCIDForGID(gid); } - /** - * Inner class representing a range of a charset. - */ - private static class Range1 + @Override + public int getGIDForCID(int cid) { - private int first; - private int nLeft; - - @Override - public String toString() + if (isCIDFont()) { - return getClass().getName() + "[first=" + first + ", nLeft=" + nLeft + "]"; + for (RangeMapping mapping : rangesCID2GID) + { + if (mapping.isInReverseRange(cid)) + { + return mapping.mapReverseValue(cid); + } + } } + return super.getGIDForCID(cid); + } + + @Override + public String toString() + { + return getClass().getName() + "[format=" + format + "]"; } } @@ -1371,7 +1369,7 @@ public String toString() private static class Format2Charset extends EmbeddedCharset { private int format; - private Range2[] range; + private List rangesCID2GID; protected Format2Charset(boolean isCIDFont) { @@ -1381,23 +1379,69 @@ protected Format2Charset(boolean isCIDFont) @Override public String toString() { - return getClass().getName() + "[format=" + format + ", range=" + Arrays.toString(range) + "]"; + return getClass().getName() + "[format=" + format + "]"; } + } - /** - * Inner class representing a range of a charset. - */ - private static class Range2 + /** + * Inner class representing a rang mapping for a CID charset. + */ + private static final class RangeMapping + { + private final int startValue; + private final int endValue; + private final int startMappedValue; + private final int endMappedValue; + + private RangeMapping(int startGID, int first, int nLeft) { - private int first; - private int nLeft; + this.startValue = startGID; + endValue = startValue + nLeft; + this.startMappedValue = first; + endMappedValue = startMappedValue + nLeft; + } - @Override - public String toString() + boolean isInRange(int value) + { + return value >= startValue && value <= endValue; + } + + boolean isInReverseRange(int value) + { + return value >= startMappedValue && value <= endMappedValue; + } + + int mapValue(int value) + { + if (isInRange(value)) + { + return startMappedValue + (value - startValue); + } + else { - return getClass().getName() + "[first=" + first + ", nLeft=" + nLeft + "]"; + return 0; } } + + int mapReverseValue(int value) + { + if (isInReverseRange(value)) + { + return startValue + (value - startMappedValue); + } + else + { + return 0; + } + } + + @Override + public String toString() + { + return getClass().getName() + "[start value=" + startValue + ", end value=" + endValue + + ", start mapped-value=" + startMappedValue + ", end mapped-value=" + + endMappedValue + "]"; + } } @Override diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFStandardEncoding.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFStandardEncoding.java index 1a9272457..b25ebfde9 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFStandardEncoding.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFStandardEncoding.java @@ -23,6 +23,270 @@ */ public final class CFFStandardEncoding extends CFFEncoding { + private static final int CHAR_CODE = 0; + private static final int CHAR_SID = 1; + + /** + * Table of character codes and their corresponding sid. + */ + private static final int[][] CFF_STANDARD_ENCODING_TABLE = { + {0, 0}, + {1, 0}, + {2, 0}, + {3, 0}, + {4, 0}, + {5, 0}, + {6, 0}, + {7, 0}, + {8, 0}, + {9, 0}, + {10, 0}, + {11, 0}, + {12, 0}, + {13, 0}, + {14, 0}, + {15, 0}, + {16, 0}, + {17, 0}, + {18, 0}, + {19, 0}, + {20, 0}, + {21, 0}, + {22, 0}, + {23, 0}, + {24, 0}, + {25, 0}, + {26, 0}, + {27, 0}, + {28, 0}, + {29, 0}, + {30, 0}, + {31, 0}, + {32, 1}, + {33, 2}, + {34, 3}, + {35, 4}, + {36, 5}, + {37, 6}, + {38, 7}, + {39, 8}, + {40, 9}, + {41, 10}, + {42, 11}, + {43, 12}, + {44, 13}, + {45, 14}, + {46, 15}, + {47, 16}, + {48, 17}, + {49, 18}, + {50, 19}, + {51, 20}, + {52, 21}, + {53, 22}, + {54, 23}, + {55, 24}, + {56, 25}, + {57, 26}, + {58, 27}, + {59, 28}, + {60, 29}, + {61, 30}, + {62, 31}, + {63, 32}, + {64, 33}, + {65, 34}, + {66, 35}, + {67, 36}, + {68, 37}, + {69, 38}, + {70, 39}, + {71, 40}, + {72, 41}, + {73, 42}, + {74, 43}, + {75, 44}, + {76, 45}, + {77, 46}, + {78, 47}, + {79, 48}, + {80, 49}, + {81, 50}, + {82, 51}, + {83, 52}, + {84, 53}, + {85, 54}, + {86, 55}, + {87, 56}, + {88, 57}, + {89, 58}, + {90, 59}, + {91, 60}, + {92, 61}, + {93, 62}, + {94, 63}, + {95, 64}, + {96, 65}, + {97, 66}, + {98, 67}, + {99, 68}, + {100, 69}, + {101, 70}, + {102, 71}, + {103, 72}, + {104, 73}, + {105, 74}, + {106, 75}, + {107, 76}, + {108, 77}, + {109, 78}, + {110, 79}, + {111, 80}, + {112, 81}, + {113, 82}, + {114, 83}, + {115, 84}, + {116, 85}, + {117, 86}, + {118, 87}, + {119, 88}, + {120, 89}, + {121, 90}, + {122, 91}, + {123, 92}, + {124, 93}, + {125, 94}, + {126, 95}, + {127, 0}, + {128, 0}, + {129, 0}, + {130, 0}, + {131, 0}, + {132, 0}, + {133, 0}, + {134, 0}, + {135, 0}, + {136, 0}, + {137, 0}, + {138, 0}, + {139, 0}, + {140, 0}, + {141, 0}, + {142, 0}, + {143, 0}, + {144, 0}, + {145, 0}, + {146, 0}, + {147, 0}, + {148, 0}, + {149, 0}, + {150, 0}, + {151, 0}, + {152, 0}, + {153, 0}, + {154, 0}, + {155, 0}, + {156, 0}, + {157, 0}, + {158, 0}, + {159, 0}, + {160, 0}, + {161, 96}, + {162, 97}, + {163, 98}, + {164, 99}, + {165, 100}, + {166, 101}, + {167, 102}, + {168, 103}, + {169, 104}, + {170, 105}, + {171, 106}, + {172, 107}, + {173, 108}, + {174, 109}, + {175, 110}, + {176, 0}, + {177, 111}, + {178, 112}, + {179, 113}, + {180, 114}, + {181, 0}, + {182, 115}, + {183, 116}, + {184, 117}, + {185, 118}, + {186, 119}, + {187, 120}, + {188, 121}, + {189, 122}, + {190, 0}, + {191, 123}, + {192, 0}, + {193, 124}, + {194, 125}, + {195, 126}, + {196, 127}, + {197, 128}, + {198, 129}, + {199, 130}, + {200, 131}, + {201, 0}, + {202, 132}, + {203, 133}, + {204, 0}, + {205, 134}, + {206, 135}, + {207, 136}, + {208, 137}, + {209, 0}, + {210, 0}, + {211, 0}, + {212, 0}, + {213, 0}, + {214, 0}, + {215, 0}, + {216, 0}, + {217, 0}, + {218, 0}, + {219, 0}, + {220, 0}, + {221, 0}, + {222, 0}, + {223, 0}, + {224, 0}, + {225, 138}, + {226, 0}, + {227, 139}, + {228, 0}, + {229, 0}, + {230, 0}, + {231, 0}, + {232, 140}, + {233, 141}, + {234, 142}, + {235, 143}, + {236, 0}, + {237, 0}, + {238, 0}, + {239, 0}, + {240, 0}, + {241, 144}, + {242, 0}, + {243, 0}, + {244, 0}, + {245, 145}, + {246, 0}, + {247, 0}, + {248, 146}, + {249, 147}, + {250, 148}, + {251, 149}, + {252, 0}, + {253, 0}, + {254, 0}, + {255, 0} + }; private CFFStandardEncoding() { @@ -41,261 +305,9 @@ public static CFFStandardEncoding getInstance() static { - INSTANCE.add(0, 0); - INSTANCE.add(1, 0); - INSTANCE.add(2, 0); - INSTANCE.add(3, 0); - INSTANCE.add(4, 0); - INSTANCE.add(5, 0); - INSTANCE.add(6, 0); - INSTANCE.add(7, 0); - INSTANCE.add(8, 0); - INSTANCE.add(9, 0); - INSTANCE.add(10, 0); - INSTANCE.add(11, 0); - INSTANCE.add(12, 0); - INSTANCE.add(13, 0); - INSTANCE.add(14, 0); - INSTANCE.add(15, 0); - INSTANCE.add(16, 0); - INSTANCE.add(17, 0); - INSTANCE.add(18, 0); - INSTANCE.add(19, 0); - INSTANCE.add(20, 0); - INSTANCE.add(21, 0); - INSTANCE.add(22, 0); - INSTANCE.add(23, 0); - INSTANCE.add(24, 0); - INSTANCE.add(25, 0); - INSTANCE.add(26, 0); - INSTANCE.add(27, 0); - INSTANCE.add(28, 0); - INSTANCE.add(29, 0); - INSTANCE.add(30, 0); - INSTANCE.add(31, 0); - INSTANCE.add(32, 1); - INSTANCE.add(33, 2); - INSTANCE.add(34, 3); - INSTANCE.add(35, 4); - INSTANCE.add(36, 5); - INSTANCE.add(37, 6); - INSTANCE.add(38, 7); - INSTANCE.add(39, 8); - INSTANCE.add(40, 9); - INSTANCE.add(41, 10); - INSTANCE.add(42, 11); - INSTANCE.add(43, 12); - INSTANCE.add(44, 13); - INSTANCE.add(45, 14); - INSTANCE.add(46, 15); - INSTANCE.add(47, 16); - INSTANCE.add(48, 17); - INSTANCE.add(49, 18); - INSTANCE.add(50, 19); - INSTANCE.add(51, 20); - INSTANCE.add(52, 21); - INSTANCE.add(53, 22); - INSTANCE.add(54, 23); - INSTANCE.add(55, 24); - INSTANCE.add(56, 25); - INSTANCE.add(57, 26); - INSTANCE.add(58, 27); - INSTANCE.add(59, 28); - INSTANCE.add(60, 29); - INSTANCE.add(61, 30); - INSTANCE.add(62, 31); - INSTANCE.add(63, 32); - INSTANCE.add(64, 33); - INSTANCE.add(65, 34); - INSTANCE.add(66, 35); - INSTANCE.add(67, 36); - INSTANCE.add(68, 37); - INSTANCE.add(69, 38); - INSTANCE.add(70, 39); - INSTANCE.add(71, 40); - INSTANCE.add(72, 41); - INSTANCE.add(73, 42); - INSTANCE.add(74, 43); - INSTANCE.add(75, 44); - INSTANCE.add(76, 45); - INSTANCE.add(77, 46); - INSTANCE.add(78, 47); - INSTANCE.add(79, 48); - INSTANCE.add(80, 49); - INSTANCE.add(81, 50); - INSTANCE.add(82, 51); - INSTANCE.add(83, 52); - INSTANCE.add(84, 53); - INSTANCE.add(85, 54); - INSTANCE.add(86, 55); - INSTANCE.add(87, 56); - INSTANCE.add(88, 57); - INSTANCE.add(89, 58); - INSTANCE.add(90, 59); - INSTANCE.add(91, 60); - INSTANCE.add(92, 61); - INSTANCE.add(93, 62); - INSTANCE.add(94, 63); - INSTANCE.add(95, 64); - INSTANCE.add(96, 65); - INSTANCE.add(97, 66); - INSTANCE.add(98, 67); - INSTANCE.add(99, 68); - INSTANCE.add(100, 69); - INSTANCE.add(101, 70); - INSTANCE.add(102, 71); - INSTANCE.add(103, 72); - INSTANCE.add(104, 73); - INSTANCE.add(105, 74); - INSTANCE.add(106, 75); - INSTANCE.add(107, 76); - INSTANCE.add(108, 77); - INSTANCE.add(109, 78); - INSTANCE.add(110, 79); - INSTANCE.add(111, 80); - INSTANCE.add(112, 81); - INSTANCE.add(113, 82); - INSTANCE.add(114, 83); - INSTANCE.add(115, 84); - INSTANCE.add(116, 85); - INSTANCE.add(117, 86); - INSTANCE.add(118, 87); - INSTANCE.add(119, 88); - INSTANCE.add(120, 89); - INSTANCE.add(121, 90); - INSTANCE.add(122, 91); - INSTANCE.add(123, 92); - INSTANCE.add(124, 93); - INSTANCE.add(125, 94); - INSTANCE.add(126, 95); - INSTANCE.add(127, 0); - INSTANCE.add(128, 0); - INSTANCE.add(129, 0); - INSTANCE.add(130, 0); - INSTANCE.add(131, 0); - INSTANCE.add(132, 0); - INSTANCE.add(133, 0); - INSTANCE.add(134, 0); - INSTANCE.add(135, 0); - INSTANCE.add(136, 0); - INSTANCE.add(137, 0); - INSTANCE.add(138, 0); - INSTANCE.add(139, 0); - INSTANCE.add(140, 0); - INSTANCE.add(141, 0); - INSTANCE.add(142, 0); - INSTANCE.add(143, 0); - INSTANCE.add(144, 0); - INSTANCE.add(145, 0); - INSTANCE.add(146, 0); - INSTANCE.add(147, 0); - INSTANCE.add(148, 0); - INSTANCE.add(149, 0); - INSTANCE.add(150, 0); - INSTANCE.add(151, 0); - INSTANCE.add(152, 0); - INSTANCE.add(153, 0); - INSTANCE.add(154, 0); - INSTANCE.add(155, 0); - INSTANCE.add(156, 0); - INSTANCE.add(157, 0); - INSTANCE.add(158, 0); - INSTANCE.add(159, 0); - INSTANCE.add(160, 0); - INSTANCE.add(161, 96); - INSTANCE.add(162, 97); - INSTANCE.add(163, 98); - INSTANCE.add(164, 99); - INSTANCE.add(165, 100); - INSTANCE.add(166, 101); - INSTANCE.add(167, 102); - INSTANCE.add(168, 103); - INSTANCE.add(169, 104); - INSTANCE.add(170, 105); - INSTANCE.add(171, 106); - INSTANCE.add(172, 107); - INSTANCE.add(173, 108); - INSTANCE.add(174, 109); - INSTANCE.add(175, 110); - INSTANCE.add(176, 0); - INSTANCE.add(177, 111); - INSTANCE.add(178, 112); - INSTANCE.add(179, 113); - INSTANCE.add(180, 114); - INSTANCE.add(181, 0); - INSTANCE.add(182, 115); - INSTANCE.add(183, 116); - INSTANCE.add(184, 117); - INSTANCE.add(185, 118); - INSTANCE.add(186, 119); - INSTANCE.add(187, 120); - INSTANCE.add(188, 121); - INSTANCE.add(189, 122); - INSTANCE.add(190, 0); - INSTANCE.add(191, 123); - INSTANCE.add(192, 0); - INSTANCE.add(193, 124); - INSTANCE.add(194, 125); - INSTANCE.add(195, 126); - INSTANCE.add(196, 127); - INSTANCE.add(197, 128); - INSTANCE.add(198, 129); - INSTANCE.add(199, 130); - INSTANCE.add(200, 131); - INSTANCE.add(201, 0); - INSTANCE.add(202, 132); - INSTANCE.add(203, 133); - INSTANCE.add(204, 0); - INSTANCE.add(205, 134); - INSTANCE.add(206, 135); - INSTANCE.add(207, 136); - INSTANCE.add(208, 137); - INSTANCE.add(209, 0); - INSTANCE.add(210, 0); - INSTANCE.add(211, 0); - INSTANCE.add(212, 0); - INSTANCE.add(213, 0); - INSTANCE.add(214, 0); - INSTANCE.add(215, 0); - INSTANCE.add(216, 0); - INSTANCE.add(217, 0); - INSTANCE.add(218, 0); - INSTANCE.add(219, 0); - INSTANCE.add(220, 0); - INSTANCE.add(221, 0); - INSTANCE.add(222, 0); - INSTANCE.add(223, 0); - INSTANCE.add(224, 0); - INSTANCE.add(225, 138); - INSTANCE.add(226, 0); - INSTANCE.add(227, 139); - INSTANCE.add(228, 0); - INSTANCE.add(229, 0); - INSTANCE.add(230, 0); - INSTANCE.add(231, 0); - INSTANCE.add(232, 140); - INSTANCE.add(233, 141); - INSTANCE.add(234, 142); - INSTANCE.add(235, 143); - INSTANCE.add(236, 0); - INSTANCE.add(237, 0); - INSTANCE.add(238, 0); - INSTANCE.add(239, 0); - INSTANCE.add(240, 0); - INSTANCE.add(241, 144); - INSTANCE.add(242, 0); - INSTANCE.add(243, 0); - INSTANCE.add(244, 0); - INSTANCE.add(245, 145); - INSTANCE.add(246, 0); - INSTANCE.add(247, 0); - INSTANCE.add(248, 146); - INSTANCE.add(249, 147); - INSTANCE.add(250, 148); - INSTANCE.add(251, 149); - INSTANCE.add(252, 0); - INSTANCE.add(253, 0); - INSTANCE.add(254, 0); - INSTANCE.add(255, 0); + for (int[] encodingEntry : CFF_STANDARD_ENCODING_TABLE) + { + INSTANCE.add(encodingEntry[CHAR_CODE], encodingEntry[CHAR_SID]); + } } } \ No newline at end of file diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CFFType1Font.java b/library/src/main/java/com/tom_roush/fontbox/cff/CFFType1Font.java index 1fc5c7b15..1a8179bc3 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CFFType1Font.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CFFType1Font.java @@ -18,15 +18,15 @@ import android.graphics.Path; -import com.tom_roush.fontbox.EncodedFont; -import com.tom_roush.fontbox.type1.Type1CharStringReader; - import java.io.IOException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.tom_roush.fontbox.EncodedFont; +import com.tom_roush.fontbox.type1.Type1CharStringReader; + /** * A Type 1-equivalent font program represented in a CFF file. Thread safe. * @@ -116,6 +116,7 @@ public int nameToGID(String name) * @param gid GID * @throws IOException if the charstring could not be read */ + @Override public Type2CharString getType2CharString(int gid) throws IOException { String name = "GID+" + gid; // for debugging only @@ -129,14 +130,14 @@ private Type2CharString getType2CharString(int gid, String name) throws IOExcept if (type2 == null) { byte[] bytes = null; - if (gid < charStrings.size()) + if (gid < charStrings.length) { - bytes = charStrings.get(gid); + bytes = charStrings[gid]; } if (bytes == null) { // .notdef - bytes = charStrings.get(0); + bytes = charStrings[0]; } Type2CharStringParser parser = new Type2CharStringParser(fontName, name); List type2seq = parser.parse(bytes, globalSubrIndex, getLocalSubrIndex()); @@ -193,9 +194,9 @@ void setEncoding(CFFEncoding encoding) this.encoding = encoding; } - private IndexData getLocalSubrIndex() + private byte[][] getLocalSubrIndex() { - return (IndexData)privateDict.get("Subrs"); + return (byte[][])privateDict.get("Subrs"); } // helper for looking up keys/values diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CharStringCommand.java b/library/src/main/java/com/tom_roush/fontbox/cff/CharStringCommand.java index 3ab7c8f81..51ac8ff37 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CharStringCommand.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CharStringCommand.java @@ -186,9 +186,9 @@ public String toString() @Override public int hashCode() { - if (keyValues[0] == 12 && keyValues.length > 1) - { - return keyValues[0] ^ keyValues[1]; + if (keyValues[0] == 12 && keyValues.length > 1) + { + return keyValues[0] ^ keyValues[1]; } return keyValues[0]; } @@ -223,7 +223,7 @@ public boolean equals(Object object) static { - Map map = new LinkedHashMap(); + Map map = new LinkedHashMap(26); map.put(new Key(1), "hstem"); map.put(new Key(3), "vstem"); map.put(new Key(4), "vmoveto"); @@ -261,7 +261,7 @@ public boolean equals(Object object) static { - Map map = new LinkedHashMap(); + Map map = new LinkedHashMap(48); map.put(new Key(1), "hstem"); map.put(new Key(3), "vstem"); map.put(new Key(4), "vmoveto"); diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CharStringConverter.java b/library/src/main/java/com/tom_roush/fontbox/cff/CharStringConverter.java deleted file mode 100644 index 61ccc3bd5..000000000 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CharStringConverter.java +++ /dev/null @@ -1,392 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.fontbox.cff; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * A class to translate Type2 CharString command sequence to Type1 CharString command sequence. - * - * @author Villu Ruusmann - * @version $Revision$ - */ -public class CharStringConverter extends CharStringHandler -{ - - private int defaultWidthX = 0; - private int nominalWidthX = 0; - private List sequence = null; - private int pathCount = 0; - - /** - * Constructor. - * - * @param defaultWidth default width - * @param nominalWidth nominal width - * - * @deprecated Use {@link CharStringConverter#CharStringConverter(int, int)} instead - */ - public CharStringConverter(int defaultWidth, int nominalWidth, IndexData fontGlobalSubrIndex, IndexData fontLocalSubrIndex) - { - defaultWidthX = defaultWidth; - nominalWidthX = nominalWidth; - } - - /** - * Constructor. - * - * @param defaultWidth default width - * @param nominalWidth nominal width - * - */ - public CharStringConverter(int defaultWidth, int nominalWidth) - { - defaultWidthX = defaultWidth; - nominalWidthX = nominalWidth; - } - /** - * Converts a sequence of Type1/Type2 commands into a sequence of CharStringCommands. - * @param commandSequence the type1/type2 sequence - * @return the CHarStringCommandSequence - */ - public List convert(List commandSequence) - { - sequence = new ArrayList(); - pathCount = 0; - handleSequence(commandSequence); - return sequence; - } - - /** - * {@inheritDoc} - */ - @Override - public List handleCommand(List numbers, CharStringCommand command) - { - - if (CharStringCommand.TYPE1_VOCABULARY.containsKey(command.getKey())) - { - return handleType1Command(numbers, command); - } - else - { - return handleType2Command(numbers, command); - } - } - - private List handleType1Command(List numbers, - CharStringCommand command) - { - String name = CharStringCommand.TYPE1_VOCABULARY.get(command.getKey()); - - if ("hstem".equals(name)) - { - numbers = clearStack(numbers, numbers.size() % 2 != 0); - expandStemHints(numbers, true); - } - else if ("vstem".equals(name)) - { - numbers = clearStack(numbers, numbers.size() % 2 != 0); - expandStemHints(numbers, false); - } - else if ("vmoveto".equals(name)) - { - numbers = clearStack(numbers, numbers.size() > 1); - markPath(); - addCommand(numbers, command); - } - else if ("rlineto".equals(name)) - { - addCommandList(split(numbers, 2), command); - } - else if ("hlineto".equals(name)) - { - drawAlternatingLine(numbers, true); - } - else if ("vlineto".equals(name)) - { - drawAlternatingLine(numbers, false); - } - else if ("rrcurveto".equals(name)) - { - addCommandList(split(numbers, 6), command); - } - else if ("endchar".equals(name)) - { - numbers = clearStack(numbers, numbers.size() > 0); - closePath(); - addCommand(numbers, command); - } - else if ("rmoveto".equals(name)) - { - numbers = clearStack(numbers, numbers.size() > 2); - markPath(); - addCommand(numbers, command); - } - else if ("hmoveto".equals(name)) - { - numbers = clearStack(numbers, numbers.size() > 1); - markPath(); - addCommand(numbers, command); - } - else if ("vhcurveto".equals(name)) - { - drawAlternatingCurve(numbers, false); - } - else if ("hvcurveto".equals(name)) - { - drawAlternatingCurve(numbers, true); - } - else if ("return".equals(name)) - { - return numbers; - } - else - { - addCommand(numbers, command); - } - return null; - } - - @SuppressWarnings(value = { "unchecked" }) - private List handleType2Command(List numbers, - CharStringCommand command) - { - String name = CharStringCommand.TYPE2_VOCABULARY.get(command.getKey()); - if ("hflex".equals(name)) - { - List first = Arrays.asList(numbers.get(0), Integer.valueOf(0), - numbers.get(1), numbers.get(2), numbers.get(3), Integer.valueOf(0)); - List second = Arrays.asList(numbers.get(4), Integer.valueOf(0), - numbers.get(5), Integer.valueOf(-numbers.get(2).intValue()), - numbers.get(6), Integer.valueOf(0)); - addCommandList(Arrays.asList(first, second), new CharStringCommand(8)); - } - else if ("flex".equals(name)) - { - List first = numbers.subList(0, 6); - List second = numbers.subList(6, 12); - addCommandList(Arrays.asList(first, second), new CharStringCommand(8)); - } - else if ("hflex1".equals(name)) - { - List first = Arrays.asList(numbers.get(0), numbers.get(1), - numbers.get(2), numbers.get(3), numbers.get(4), Integer.valueOf(0)); - List second = Arrays.asList(numbers.get(5), Integer.valueOf(0), - numbers.get(6), numbers.get(7), numbers.get(8), Integer.valueOf(0)); - addCommandList(Arrays.asList(first, second), new CharStringCommand(8)); - } - else if ("flex1".equals(name)) - { - int dx = 0; - int dy = 0; - for(int i = 0; i < 5; i++){ - dx += numbers.get(i * 2).intValue(); - dy += numbers.get(i * 2 + 1).intValue(); - } - List first = numbers.subList(0, 6); - List second = Arrays.asList(numbers.get(6), numbers.get(7), numbers.get(8), - numbers.get(9), (Math.abs(dx) > Math.abs(dy) ? numbers.get(10) : Integer.valueOf(-dx)), - (Math.abs(dx) > Math.abs(dy) ? Integer.valueOf(-dy) : numbers.get(10))); - addCommandList(Arrays.asList(first, second), new CharStringCommand(8)); - } - else if ("hstemhm".equals(name)) - { - numbers = clearStack(numbers, numbers.size() % 2 != 0); - expandStemHints(numbers, true); - } - else if ("hintmask".equals(name) || "cntrmask".equals(name)) - { - numbers = clearStack(numbers, numbers.size() % 2 != 0); - if (numbers.size() > 0) - { - expandStemHints(numbers, false); - } - } - else if ("vstemhm".equals(name)) - { - numbers = clearStack(numbers, numbers.size() % 2 != 0); - expandStemHints(numbers, false); - } - else if ("rcurveline".equals(name)) - { - addCommandList(split(numbers.subList(0, numbers.size() - 2), 6), - new CharStringCommand(8)); - addCommand(numbers.subList(numbers.size() - 2, numbers.size()), - new CharStringCommand(5)); - } - else if ("rlinecurve".equals(name)) - { - addCommandList(split(numbers.subList(0, numbers.size() - 6), 2), - new CharStringCommand(5)); - addCommand(numbers.subList(numbers.size() - 6, numbers.size()), - new CharStringCommand(8)); - } - else if ("vvcurveto".equals(name)) - { - drawCurve(numbers, false); - } - else if ("hhcurveto".equals(name)) - { - drawCurve(numbers, true); - } - else - { - addCommand(numbers, command); - } - return null; - } - - private List clearStack(List numbers, boolean flag) - { - - if (sequence.size() == 0) - { - if (flag) - { - addCommand(Arrays.asList(Integer.valueOf(0), Integer - .valueOf(numbers.get(0).intValue() + nominalWidthX)), - new CharStringCommand(13)); - - numbers = numbers.subList(1, numbers.size()); - } - else - { - addCommand(Arrays.asList(Integer.valueOf(0), Integer - .valueOf(defaultWidthX)), new CharStringCommand(13)); - } - } - - return numbers; - } - - private void expandStemHints(List numbers, boolean horizontal) - { - // TODO - } - - private void markPath() - { - if (pathCount > 0) - { - closePath(); - } - pathCount++; - } - - private void closePath() - { - CharStringCommand command = pathCount > 0 ? (CharStringCommand) sequence - .get(sequence.size() - 1) - : null; - - CharStringCommand closepathCommand = new CharStringCommand(9); - if (command != null && !closepathCommand.equals(command)) - { - addCommand(Collections. emptyList(), closepathCommand); - } - } - - private void drawAlternatingLine(List numbers, boolean horizontal) - { - while (numbers.size() > 0) - { - addCommand(numbers.subList(0, 1), new CharStringCommand( - horizontal ? 6 : 7)); - numbers = numbers.subList(1, numbers.size()); - horizontal = !horizontal; - } - } - - private void drawAlternatingCurve(List numbers, boolean horizontal) - { - while (numbers.size() > 0) - { - boolean last = numbers.size() == 5; - if (horizontal) - { - addCommand(Arrays.asList(numbers.get(0), Integer.valueOf(0), - numbers.get(1), numbers.get(2), last ? numbers.get(4) - : Integer.valueOf(0), numbers.get(3)), - new CharStringCommand(8)); - } - else - { - addCommand(Arrays.asList(Integer.valueOf(0), numbers.get(0), - numbers.get(1), numbers.get(2), numbers.get(3), - last ? numbers.get(4) : Integer.valueOf(0)), - new CharStringCommand(8)); - } - numbers = numbers.subList(last ? 5 : 4, numbers.size()); - horizontal = !horizontal; - } - } - - private void drawCurve(List numbers, boolean horizontal) - { - while (numbers.size() > 0) - { - boolean first = numbers.size() % 4 == 1; - - if (horizontal) - { - addCommand(Arrays.asList(numbers.get(first ? 1 : 0), - first ? numbers.get(0) : Integer.valueOf(0), numbers - .get(first ? 2 : 1), - numbers.get(first ? 3 : 2), numbers.get(first ? 4 : 3), - Integer.valueOf(0)), new CharStringCommand(8)); - } - else - { - addCommand(Arrays.asList(first ? numbers.get(0) : Integer - .valueOf(0), numbers.get(first ? 1 : 0), numbers - .get(first ? 2 : 1), numbers.get(first ? 3 : 2), - Integer.valueOf(0), numbers.get(first ? 4 : 3)), - new CharStringCommand(8)); - } - numbers = numbers.subList(first ? 5 : 4, numbers.size()); - } - } - - private void addCommandList(List> numbers, - CharStringCommand command) - { - for (int i = 0; i < numbers.size(); i++) - { - addCommand(numbers.get(i), command); - } - } - - private void addCommand(List numbers, CharStringCommand command) - { - sequence.addAll(numbers); - sequence.add(command); - } - - private static List> split(List list, int size) - { - List> result = new ArrayList>(); - for (int i = 0; i < list.size() / size; i++) - { - result.add(list.subList(i * size, (i + 1) * size)); - } - return result; - } -} \ No newline at end of file diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/CharStringHandler.java b/library/src/main/java/com/tom_roush/fontbox/cff/CharStringHandler.java index de377b65f..dbb6c7604 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/CharStringHandler.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/CharStringHandler.java @@ -18,58 +18,48 @@ import java.util.List; +import java.util.Stack; /** * A Handler for CharStringCommands. - * + * * @author Villu Ruusmann - * @version $Revision$ + * @author John Henson */ public abstract class CharStringHandler { - /** * Handler for a sequence of CharStringCommands. - * + * * @param sequence of CharStringCommands - * - * @return may return a command sequence of a subroutine */ - @SuppressWarnings(value = { "unchecked" }) public List handleSequence(List sequence) { - List numbers = null; - int offset = 0; - int size = sequence.size(); - for (int i = 0; i < size; i++) + Stack stack = new Stack(); + for (Object obj : sequence) { - Object object = sequence.get(i); - if (object instanceof CharStringCommand) + if (obj instanceof CharStringCommand) { - if (numbers == null) - numbers = (List) sequence.subList(offset, i); - else - numbers.addAll((List) sequence.subList(offset, i)); - List stack = handleCommand(numbers, (CharStringCommand) object); - if (stack != null && !stack.isEmpty()) - numbers = stack; - else - numbers = null; - offset = i + 1; + List results = handleCommand(stack, (CharStringCommand)obj); + stack.clear(); // this is basically returning the new stack + if (results != null) + { + stack.addAll(results); + } + } + else + { + stack.push((Integer)obj); } } - if (numbers != null && !numbers.isEmpty()) - return numbers; - else - return null; + return stack; } + /** * Handler for CharStringCommands. - * + * * @param numbers a list of numbers * @param command the CharStringCommand - * - * @return may return a command sequence of a subroutine */ public abstract List handleCommand(List numbers, CharStringCommand command); -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/DataInput.java b/library/src/main/java/com/tom_roush/fontbox/cff/DataInput.java index 8e738a7bf..7681b7e14 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/DataInput.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/DataInput.java @@ -19,6 +19,8 @@ import java.io.EOFException; import java.io.IOException; +import com.tom_roush.fontbox.util.Charsets; + /** * This class contains some functionality to read a byte buffer. * @@ -73,7 +75,7 @@ public void setPosition(int position) */ public String getString() throws IOException { - return new String(inputBuffer, "ISO-8859-1"); + return new String(inputBuffer, Charsets.ISO_8859_1); } /** @@ -83,7 +85,16 @@ public String getString() throws IOException */ public byte readByte() throws IOException { - return (byte) readUnsignedByte(); + try + { + byte value = inputBuffer[bufferPosition]; + bufferPosition++; + return value; + } + catch (RuntimeException re) + { + return -1; + } } /** @@ -168,11 +179,13 @@ public int readInt() throws IOException */ public byte[] readBytes(int length) throws IOException { - byte[] bytes = new byte[length]; - for (int i = 0; i < length; i++) + if (inputBuffer.length - bufferPosition < length) { - bytes[i] = readByte(); + throw new EOFException(); } + byte[] bytes = new byte[length]; + System.arraycopy(inputBuffer, bufferPosition, bytes, 0, length); + bufferPosition += length; return bytes; } @@ -205,6 +218,6 @@ private int peek(int offset) public int length() { - return inputBuffer.length; + return inputBuffer.length; } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/DataOutput.java b/library/src/main/java/com/tom_roush/fontbox/cff/DataOutput.java index 2933fc6fc..a75b75acf 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/DataOutput.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/DataOutput.java @@ -20,7 +20,6 @@ import java.io.IOException; /** - * * @author Villu Ruusmann */ public class DataOutput diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/IndexData.java b/library/src/main/java/com/tom_roush/fontbox/cff/IndexData.java deleted file mode 100644 index e32a49869..000000000 --- a/library/src/main/java/com/tom_roush/fontbox/cff/IndexData.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.fontbox.cff; - -import java.util.Arrays; - -/** - * Class holding the IndexData of a CFF font. - */ -public class IndexData -{ - private final int count; - private final int[] offset; - private int[] data; - - /** - * Constructor. - * - * @param count number of index values - */ - public IndexData(int count) - { - this.count = count; - this.offset = new int[count+1]; - } - - public byte[] getBytes(int index) - { - int length = offset[index + 1] - offset[index]; - byte[] bytes = new byte[length]; - for (int i = 0; i < length; i++) - { - bytes[i] = (byte) data[offset[index] - 1 + i]; - } - return bytes; - } - - @Override - public String toString() - { - return getClass().getName() + "[count=" + count - + ", offset=" + Arrays.toString(offset) - + ", data=" + Arrays.toString(data) + "]"; - } - - /** - * Returns the count value. - * @return the count value - */ - public int getCount() - { - return count; - } - - /** - * Sets the offset value to the given value. - * @param index the index of the offset value - * @param value the given offset value - */ - public void setOffset(int index, int value) - { - offset[index] = value; - } - - /** - * Returns the offset at the given index. - * @param index the index - * @return the offset value at the given index - */ - public int getOffset(int index) - { - return offset[index]; - } - - /** - * Initializes the data array with the given size. - * @param dataSize the size of the data array - */ - public void initData(int dataSize) - { - data = new int[dataSize]; - } - - /** - * Sets the data value to the given value. - * @param index the index of the data value - * @param value the given data value - */ - public void setData(int index, int value) - { - data[index] = value; - } -} \ No newline at end of file diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharString.java b/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharString.java index cb31103bf..c87c8be14 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharString.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharString.java @@ -17,18 +17,19 @@ package com.tom_roush.fontbox.cff; import android.graphics.Path; +import android.graphics.PathMeasure; import android.graphics.PointF; import android.graphics.RectF; import android.util.Log; -import com.tom_roush.fontbox.encoding.StandardEncoding; -import com.tom_roush.fontbox.type1.Type1CharStringReader; -import com.tom_roush.harmony.awt.geom.AffineTransform; - import java.io.IOException; import java.util.ArrayList; import java.util.List; +import com.tom_roush.fontbox.encoding.StandardEncoding; +import com.tom_roush.fontbox.type1.Type1CharStringReader; +import com.tom_roush.harmony.awt.geom.AffineTransform; + /** * This class represents and renders a Type 1 CharString. * @@ -48,7 +49,6 @@ public class Type1CharString protected List type1Sequence; protected int commandCount; - /** * Constructs a new Type1CharString object. * @param font Parent Type 1 CharString font @@ -404,7 +404,6 @@ private void rlineTo(Number dx, Number dy) { float x = current.x + dx.floatValue(); float y = current.y + dy.floatValue(); - // if (path.getCurrentPoint() == null) TODO: Patch for now if(path.isEmpty()) { Log.w("PdfBox-Android", "rlineTo without initial moveTo in font " + fontName + ", glyph " + glyphName); @@ -429,7 +428,6 @@ private void rrcurveTo(Number dx1, Number dy1, Number dx2, Number dy2, float y2 = y1 + dy2.floatValue(); float x3 = x2 + dx3.floatValue(); float y3 = y2 + dy3.floatValue(); - // if (path.getCurrentPoint() == null) TODO: Patch for now if(path.isEmpty()) { Log.w("PdfBox-Android", "rrcurveTo without initial moveTo in font " + fontName + ", glyph " + glyphName); @@ -447,7 +445,6 @@ private void rrcurveTo(Number dx1, Number dy1, Number dx2, Number dy2, */ private void closepath() { - // if (path.getCurrentPoint() == null) TODO: Patch for now if(path.isEmpty()) { Log.w("PdfBox-Android", "closepath without initial moveTo in font " + fontName + ", glyph " + glyphName); @@ -474,8 +471,16 @@ private void seac(Number asb, Number adx, Number ady, Number bchar, Number achar try { Type1CharString base = font.getType1CharString(baseName); - // path.append(base.getPath().getPathIterator(null), false); TODO: check this - path.op(base.getPath(), Path.Op.UNION); + path.op(base.getPath(), Path.Op.UNION); // TODO: PdfBox-Android + PathMeasure pm = new PathMeasure(path, false); + //coordinates will be here + float aCoordinates[] = {0f, 0f}; + + //get coordinates of the middle point + for (int i = 0; i < pm.getLength(); i++) + { + pm.getPosTan(pm.getLength() * 0.5f, aCoordinates, null); + } } catch (IOException e) { @@ -492,8 +497,7 @@ private void seac(Number asb, Number adx, Number ady, Number bchar, Number achar AffineTransform at = AffineTransform.getTranslateInstance( leftSideBearing.x + adx.floatValue(), leftSideBearing.y + ady.floatValue()); - // path.append(accent.getPath().getPathIterator(at), false); TODO: Check this - path.op(accent.getPath(), Path.Op.UNION); + path.op(accent.getPath(), Path.Op.UNION); // TODO: PdfBox-Android } catch (IOException e) { diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharStringFormatter.java b/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharStringFormatter.java deleted file mode 100644 index e73d0608b..000000000 --- a/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharStringFormatter.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.fontbox.cff; - -import java.io.ByteArrayOutputStream; -import java.util.List; - -/** - * This class represents a formatter for CharString commands of a Type1 font. - * @author Villu Ruusmann - * @version $Revision: 1.0 $ - */ -public class Type1CharStringFormatter -{ - - private ByteArrayOutputStream output = null; - - /** - * Formats the given command sequence to a byte array. - * @param sequence the given command sequence - * @return the formatted seuqence as byte array - */ - public byte[] format(List sequence) - { - output = new ByteArrayOutputStream(); - - for (Object object : sequence) - { - if (object instanceof CharStringCommand) - { - writeCommand((CharStringCommand) object); - } - else if (object instanceof Integer) - { - writeNumber((Integer) object); - } - else - { - throw new IllegalArgumentException(); - } - } - return output.toByteArray(); - } - - private void writeCommand(CharStringCommand command) - { - int[] value = command.getKey().getValue(); - for (int i = 0; i < value.length; i++) - { - output.write(value[i]); - } - } - - private void writeNumber(Integer number) - { - int value = number.intValue(); - if (value >= -107 && value <= 107) - { - output.write(value + 139); - } - else if (value >= 108 && value <= 1131) - { - int b1 = (value - 108) % 256; - int b0 = (value - 108 - b1) / 256 + 247; - output.write(b0); - output.write(b1); - } - else if (value >= -1131 && value <= -108) - { - int b1 = -((value + 108) % 256); - int b0 = -((value + 108 + b1) / 256 - 251); - output.write(b0); - output.write(b1); - } - else - { - int b1 = value >>> 24 & 0xff; - int b2 = value >>> 16 & 0xff; - int b3 = value >>> 8 & 0xff; - int b4 = value >>> 0 & 0xff; - output.write(255); - output.write(b1); - output.write(b2); - output.write(b3); - output.write(b4); - } - } -} \ No newline at end of file diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharStringParser.java b/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharStringParser.java index a9b1bc26d..9e0f8afd8 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharStringParser.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/Type1CharStringParser.java @@ -78,7 +78,15 @@ private List parse(byte[] bytes, List subrs, List sequen if (b0 == CALLSUBR) { // callsubr command - Integer operand=(Integer)sequence.remove(sequence.size()-1); + Object obj = sequence.remove(sequence.size() - 1); + if (!(obj instanceof Integer)) + { + Log.w("PdfBox-Android", "Parameter " + obj + + " for CALLSUBR is ignored, integer expected in glyph '" + glyphName + + "' of font " + fontName); + continue; + } + Integer operand = (Integer)obj; if (operand >= 0 && operand < subrs.size()) { @@ -127,8 +135,7 @@ else if (othersubrNum == 3) // all remaining othersubrs use this fallback mechanism for (int i = 0; i < numArgs; i++) { - Integer arg = removeInteger(sequence); - results.push(arg); + results.push(removeInteger(sequence)); } } @@ -137,8 +144,7 @@ else if (othersubrNum == 3) { input.readByte(); // B0_POP input.readByte(); // B1_POP - Integer val = results.pop(); - sequence.add(val); + sequence.add(results.pop()); } if (results.size() > 0) @@ -164,23 +170,23 @@ else if (b0 >= 32 && b0 <= 255) // this method is a workaround for the fact that Type1CharStringParser assumes that subrs and // othersubrs can be unrolled without executing the 'div' operator, which isn't true - private Integer removeInteger(List sequence) throws IOException + private static Integer removeInteger(List sequence) throws IOException { - Object item = sequence.remove(sequence.size() - 1); - if (item instanceof Integer) - { - return (Integer)item; - } - CharStringCommand command = (CharStringCommand)item; - - // div - if (command.getKey().getValue()[0] == 12 && command.getKey().getValue()[1] == 12) - { - int a = (Integer)sequence.remove(sequence.size() - 1); - int b = (Integer)sequence.remove(sequence.size() - 1); - return b / a; - } - throw new IOException("Unexpected char string command: " + command.getKey()); + Object item = sequence.remove(sequence.size() - 1); + if (item instanceof Integer) + { + return (Integer)item; + } + CharStringCommand command = (CharStringCommand)item; + + // div + if (command.getKey().getValue()[0] == 12 && command.getKey().getValue()[1] == 12) + { + int a = (Integer)sequence.remove(sequence.size() - 1); + int b = (Integer)sequence.remove(sequence.size() - 1); + return b / a; + } + throw new IOException("Unexpected char string command: " + command.getKey()); } private CharStringCommand readCommand(DataInput input, int b0) throws IOException @@ -211,16 +217,11 @@ else if (b0 >= 251 && b0 <= 254) } else if (b0 == 255) { - int b1 = input.readUnsignedByte(); - int b2 = input.readUnsignedByte(); - int b3 = input.readUnsignedByte(); - int b4 = input.readUnsignedByte(); - - return b1 << 24 | b2 << 16 | b3 << 8 | b4; + return input.readInt(); } else { throw new IllegalArgumentException(); } } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/Type2CharString.java b/library/src/main/java/com/tom_roush/fontbox/cff/Type2CharString.java index 1e009c3d6..d2c74a7f1 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/Type2CharString.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/Type2CharString.java @@ -83,7 +83,7 @@ private void convertType1ToType2(List sequence) type1Sequence = new ArrayList(); pathCount = 0; CharStringHandler handler = new CharStringHandler() { - @Override + @Override public List handleCommand(List numbers, CharStringCommand command) { return Type2CharString.this.handleCommand(numbers, command); @@ -384,4 +384,4 @@ private static List> split(List list, int size) } return result; } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/fontbox/cff/Type2CharStringParser.java b/library/src/main/java/com/tom_roush/fontbox/cff/Type2CharStringParser.java index 9c642dda9..eb467c536 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cff/Type2CharStringParser.java +++ b/library/src/main/java/com/tom_roush/fontbox/cff/Type2CharStringParser.java @@ -29,6 +29,7 @@ public class Type2CharStringParser private int hstemCount = 0; private int vstemCount = 0; private List sequence = null; + @SuppressWarnings("unused") private final String fontName, glyphName; /** @@ -58,38 +59,41 @@ public Type2CharStringParser(String fontName, int cid) /** * The given byte array will be parsed and converted to a Type2 sequence. * @param bytes the given mapping as byte array - * @param globalSubrIndex index containing all global subroutines - * @param localSubrIndex index containing all local subroutines + * @param globalSubrIndex array containing all global subroutines + * @param localSubrIndex array containing all local subroutines * * @return the Type2 sequence * @throws IOException if an error occurs during reading */ - public List parse(byte[] bytes, IndexData globalSubrIndex, IndexData localSubrIndex) throws IOException + public List parse(byte[] bytes, byte[][] globalSubrIndex, byte[][] localSubrIndex) + throws IOException { - return parse(bytes, globalSubrIndex, localSubrIndex, true); + return parse(bytes, globalSubrIndex, localSubrIndex, true); } - - private List parse(byte[] bytes, IndexData globalSubrIndex, IndexData localSubrIndex, boolean init) throws IOException + + private List parse(byte[] bytes, byte[][] globalSubrIndex, byte[][] localSubrIndex, + boolean init) throws IOException { if (init) { - hstemCount = 0; - vstemCount = 0; - sequence = new ArrayList(); + hstemCount = 0; + vstemCount = 0; + sequence = new ArrayList(); } DataInput input = new DataInput(bytes); - boolean localSubroutineIndexProvided = localSubrIndex != null && localSubrIndex.getCount() > 0; - boolean globalSubroutineIndexProvided = globalSubrIndex != null && globalSubrIndex.getCount() > 0; + boolean localSubroutineIndexProvided = localSubrIndex != null && localSubrIndex.length > 0; + boolean globalSubroutineIndexProvided = + globalSubrIndex != null && globalSubrIndex.length > 0; while (input.hasRemaining()) { int b0 = input.readUnsignedByte(); if (b0 == 10 && localSubroutineIndexProvided) { // process subr command - Integer operand=(Integer)sequence.remove(sequence.size()-1); - //get subrbias + Integer operand = (Integer)sequence.remove(sequence.size() - 1); + //get subrbias int bias = 0; - int nSubrs = localSubrIndex.getCount(); + int nSubrs = localSubrIndex.length; if (nSubrs < 1240) { @@ -104,9 +108,9 @@ else if (nSubrs < 33900) bias = 32768; } int subrNumber = bias+operand; - if (subrNumber < localSubrIndex.getCount()) + if (subrNumber < localSubrIndex.length) { - byte[] subrBytes = localSubrIndex.getBytes(subrNumber); + byte[] subrBytes = localSubrIndex[subrNumber]; parse(subrBytes, globalSubrIndex, localSubrIndex, false); Object lastItem=sequence.get(sequence.size()-1); if (lastItem instanceof CharStringCommand && ((CharStringCommand)lastItem).getKey().getValue()[0] == 11) @@ -114,14 +118,14 @@ else if (nSubrs < 33900) sequence.remove(sequence.size()-1); // remove "return" command } } - + } else if (b0 == 29 && globalSubroutineIndexProvided) { // process globalsubr command Integer operand=(Integer)sequence.remove(sequence.size()-1); //get subrbias int bias = 0; - int nSubrs = globalSubrIndex.getCount(); + int nSubrs = globalSubrIndex.length; if (nSubrs < 1240) { @@ -137,9 +141,9 @@ else if (nSubrs < 33900) } int subrNumber = bias+operand; - if (subrNumber < globalSubrIndex.getCount()) + if (subrNumber < globalSubrIndex.length) { - byte[] subrBytes = globalSubrIndex.getBytes(subrNumber); + byte[] subrBytes = globalSubrIndex[subrNumber]; parse(subrBytes, globalSubrIndex, localSubrIndex, false); Object lastItem=sequence.get(sequence.size()-1); if (lastItem instanceof CharStringCommand && ((CharStringCommand)lastItem).getKey().getValue()[0]==11) @@ -147,7 +151,7 @@ else if (nSubrs < 33900) sequence.remove(sequence.size()-1); // remove "return" command } } - + } else if (b0 >= 0 && b0 <= 27) { @@ -212,10 +216,7 @@ private Integer readNumber(int b0, DataInput input) throws IOException if (b0 == 28) { - int b1 = input.readUnsignedByte(); - int b2 = input.readUnsignedByte(); - - return (int) (short) (b1 << 8 | b2); + return (int)input.readShort(); } else if (b0 >= 32 && b0 <= 246) { @@ -235,13 +236,12 @@ else if (b0 >= 251 && b0 <= 254) } else if (b0 == 255) { - int b1 = input.readUnsignedByte(); - int b2 = input.readUnsignedByte(); + short value = input.readShort(); // The lower bytes are representing the digits after // the decimal point and aren't needed in this context input.readUnsignedByte(); input.readUnsignedByte(); - return (int) (short)(b1 << 8 | b2); + return (int)value; } else { @@ -251,37 +251,29 @@ else if (b0 == 255) private int getMaskLength() { - int length = 1; - int hintCount = hstemCount + vstemCount; - while ((hintCount -= 8) > 0) + int length = (int)(hintCount / 8); + if (hintCount % 8 > 0) { length++; } - return length; } private List peekNumbers() { List numbers = new ArrayList(); - for (int i = sequence.size() - 1; i > -1; i--) { Object object = sequence.get(i); if (object instanceof Number) { - Number number = (Number) object; - - numbers.add(0, number); - + numbers.add(0, (Number) object); continue; } - return numbers; } - return numbers; } } diff --git a/library/src/main/java/com/tom_roush/fontbox/cmap/CIDRange.java b/library/src/main/java/com/tom_roush/fontbox/cmap/CIDRange.java index 9d0ac590b..ee4677ba6 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cmap/CIDRange.java +++ b/library/src/main/java/com/tom_roush/fontbox/cmap/CIDRange.java @@ -65,4 +65,4 @@ public int unmap(int code) } return -1; } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/fontbox/cmap/CMap.java b/library/src/main/java/com/tom_roush/fontbox/cmap/CMap.java index 690a2932c..faf514045 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cmap/CMap.java +++ b/library/src/main/java/com/tom_roush/fontbox/cmap/CMap.java @@ -20,7 +20,6 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -35,11 +34,14 @@ public class CMap private String cmapName = null; private String cmapVersion = null; private int cmapType = -1; - + private String registry = null; private String ordering = null; private int supplement = 0; + private int minCodeLength = 4; + private int maxCodeLength; + // code lengths private final List codespaceRanges = new ArrayList(); @@ -48,7 +50,7 @@ public class CMap // CID mappings private final Map codeToCid = new HashMap(); - private final List codeToCidRanges = new LinkedList(); + private final List codeToCidRanges = new ArrayList(); private static final String SPACE = " "; private int spaceMapping = -1; @@ -101,81 +103,36 @@ public String toUnicode(int code) */ public int readCode(InputStream in) throws IOException { - // save the position in the string - in.mark(4); - - // mapping algorithm - List bytes = new ArrayList(4); - for (int i = 0; i < 4; i++) + byte[] bytes = new byte[maxCodeLength]; + in.read(bytes, 0, minCodeLength); + for (int i = minCodeLength - 1; i < maxCodeLength; i++) { - bytes.add((byte)in.read()); + final int byteCount = i + 1; for (CodespaceRange range : codespaceRanges) { - if (range.isFullMatch(bytes)) + if (range.isFullMatch(bytes, byteCount)) { - return toInt(bytes); + return toInt(bytes, byteCount); } } - } - - // reset to the original position in the string - in.reset(); - - // modified mapping algorithm - bytes = new ArrayList(4); - for (int i = 0; i < 4; i++) - { - bytes.add((byte)in.read()); - CodespaceRange match = null; - CodespaceRange shortest = null; - for (CodespaceRange range : codespaceRanges) + if (byteCount < maxCodeLength) { - if (range.isPartialMatch(bytes.get(i), i)) - { - if (match == null) - { - match = range; - } - else if (range.getStart().length < match.getStart().length) - { - // for multiple matches, choose the codespace with the shortest codes - match = range; - } - } - - // find shortest range - if (shortest == null || range.getStart().length < shortest.getStart().length) - { - shortest = range; - } - } - - // if there are no matches, the range with the shortest codes is chosen - if (match == null) - { - match = shortest; - } - - // we're done when we have enough bytes for the matched range - if (match != null && match.getStart().length == bytes.size()) - { - return toInt(bytes); + bytes[byteCount] = (byte)in.read(); } } - throw new IOException("CMap is invalid"); } /** - * Returns an int given a List + * Returns an int for the given byte array */ - private int toInt(List data) + private int toInt(byte[] data, int dataLen) { int code = 0; - for (byte b : data) + for (int i = 0; i < dataLen; ++i) { code <<= 8; - code |= (b + 256) % 256; + code |= (data[i] + 256) % 256; } return code; } @@ -195,15 +152,15 @@ public int toCID(int code) } for (CIDRange range : codeToCidRanges) { - int ch = range.map((char)code); - if (ch != -1) - { - return ch; - } + int ch = range.map((char)code); + if (ch != -1) + { + return ch; + } } return 0; } - + /** * Convert the given part of a byte array to an integer. * @param data the byte array @@ -261,7 +218,7 @@ void addCIDMapping(int code, int cid) */ void addCIDRange(char from, char to, int cid) { - codeToCidRanges.add(0, new CIDRange(from, to, cid)); + codeToCidRanges.add(new CIDRange(from, to, cid)); } /** @@ -272,6 +229,8 @@ void addCIDRange(char from, char to, int cid) void addCodespaceRange( CodespaceRange range ) { codespaceRanges.add(range); + maxCodeLength = Math.max(maxCodeLength, range.getCodeLength()); + minCodeLength = Math.min(minCodeLength, range.getCodeLength()); } /** @@ -282,12 +241,15 @@ void addCodespaceRange( CodespaceRange range ) */ void useCmap( CMap cmap ) { - this.codespaceRanges.addAll(cmap.codespaceRanges); - this.charToUnicode.putAll(cmap.charToUnicode); - this.codeToCid.putAll(cmap.codeToCid); - this.codeToCidRanges.addAll(cmap.codeToCidRanges); + for (CodespaceRange codespaceRange : cmap.codespaceRanges) + { + addCodespaceRange(codespaceRange); + } + charToUnicode.putAll(cmap.charToUnicode); + codeToCid.putAll(cmap.codeToCid); + codeToCidRanges.addAll(cmap.codeToCidRanges); } - + /** * Returns the WMode of a CMap. * diff --git a/library/src/main/java/com/tom_roush/fontbox/cmap/CMapParser.java b/library/src/main/java/com/tom_roush/fontbox/cmap/CMapParser.java index f9e088dcb..ff2757483 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cmap/CMapParser.java +++ b/library/src/main/java/com/tom_roush/fontbox/cmap/CMapParser.java @@ -16,8 +16,6 @@ */ package com.tom_roush.fontbox.cmap; -import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; - import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -29,6 +27,9 @@ import java.util.List; import java.util.Map; +import com.tom_roush.pdfbox.util.Charsets; +import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; + /** * Parses a CMap stream. * @@ -76,6 +77,7 @@ public CMap parse(File file) throws IOException * Parses a predefined CMap. * * @param name CMap name. + * @return The parsed predefined CMap as a java object. * @throws IOException If the CMap could not be parsed. */ public CMap parsePredefined(String name) throws IOException @@ -375,8 +377,6 @@ private void parseBeginbfrange(Object previousToken, PushbackInputStream cmapStr tokenBytes = (byte[]) nextToken; } boolean done = false; - - String value = null; int arrayIndex = 0; while (!done) @@ -385,7 +385,7 @@ private void parseBeginbfrange(Object previousToken, PushbackInputStream cmapStr { done = true; } - value = createStringFromBytes(tokenBytes); + String value = createStringFromBytes(tokenBytes); result.addCharMapping(startCode, value); increment(startCode); @@ -421,7 +421,6 @@ protected InputStream getExternalCMap(String name) throws IOException throw new IOException("Error: Could not find referenced cmap stream " + name); } return url.openStream(); - } private Object parseNextToken(PushbackInputStream is) throws IOException @@ -481,7 +480,7 @@ private Object parseNextToken(PushbackInputStream is) throws IOException List list = new ArrayList(); Object nextToken = parseNextToken(is); - while (nextToken != null && nextToken != MARK_END_OF_ARRAY) + while (nextToken != null && MARK_END_OF_ARRAY.equals(nextToken)) { list.add(nextToken); nextToken = parseNextToken(is); @@ -497,7 +496,7 @@ private Object parseNextToken(PushbackInputStream is) throws IOException Map result = new HashMap(); // we are reading a dictionary Object key = parseNextToken(is); - while (key instanceof LiteralName && key != MARK_END_OF_DICTIONARY) + while (key instanceof LiteralName && MARK_END_OF_DICTIONARY.equals(key)) { Object value = parseNextToken(is); result.put(((LiteralName) key).name, value); @@ -609,7 +608,7 @@ else if (isWhitespaceOrEOF(theNextByte)) } else { - retval = new Integer(value); + retval = Integer.valueOf(value); } break; } @@ -708,11 +707,11 @@ private String createStringFromBytes(byte[] bytes) throws IOException String retval; if (bytes.length == 1) { - retval = new String(bytes, "ISO-8859-1"); + retval = new String(bytes, Charsets.ISO_8859_1); } else { - retval = new String(bytes, "UTF-16BE"); + retval = new String(bytes, Charsets.UTF_16BE); } return retval; } diff --git a/library/src/main/java/com/tom_roush/fontbox/cmap/CodespaceRange.java b/library/src/main/java/com/tom_roush/fontbox/cmap/CodespaceRange.java index b7bb4a2eb..f4dc2cf69 100644 --- a/library/src/main/java/com/tom_roush/fontbox/cmap/CodespaceRange.java +++ b/library/src/main/java/com/tom_roush/fontbox/cmap/CodespaceRange.java @@ -16,8 +16,6 @@ */ package com.tom_roush.fontbox.cmap; -import java.util.List; - /** * This represents a single entry in the codespace range. * @@ -28,6 +26,10 @@ public class CodespaceRange private byte[] start; private byte[] end; + private int startInt; + private int endInt; + private int codeLength = 0; + /** * Creates a new instance of CodespaceRange. */ @@ -35,13 +37,23 @@ public CodespaceRange() { } + /** + * Returns the length of the codes of the codespace. + * + * @return the code length + */ + public int getCodeLength() + { + return codeLength; + } + /** Getter for property end. * @return Value of property end. * */ public byte[] getEnd() { - return this.end; + return end; } /** Setter for property end. @@ -51,6 +63,7 @@ public byte[] getEnd() void setEnd(byte[] endBytes) { end = endBytes; + endInt = toInt(endBytes, endBytes.length); } /** Getter for property start. @@ -59,7 +72,7 @@ void setEnd(byte[] endBytes) */ public byte[] getStart() { - return this.start; + return start; } /** Setter for property start. @@ -69,6 +82,8 @@ public byte[] getStart() void setStart(byte[] startBytes) { start = startBytes; + codeLength = start.length; + startInt = toInt(startBytes, startBytes.length); } /** @@ -76,59 +91,37 @@ void setStart(byte[] startBytes) */ public boolean matches(byte[] code) { - // code must be the same length as the bounding codes - if (code.length >= start.length && code.length <= end.length) - { - // each of it bytes must lie between the corresponding bytes of the upper & lower bounds - for (int i = 0; i < code.length; i++) - { - int startNum = start[i] & 0xff; - int endNum = end[i] & 0xff; - int codeNum = code[i] & 0xff; + return isFullMatch(code, code.length); + } - if (codeNum > endNum || codeNum < startNum) - { - return false; - } - } - return true; + /** + * Returns an int for the given byte array + */ + private int toInt(byte[] data, int dataLen) + { + int code = 0; + for (int i = 0; i < dataLen; ++i) + { + code <<= 8; + code |= (data[i] + 256) % 256; } - return false; + return code; } /** * Returns true if the given code bytes match this codespace range. */ - public boolean isFullMatch(List code) + public boolean isFullMatch(byte[] code, int codeLen) { // code must be the same length as the bounding codes - if (code.size() >= start.length && code.size() <= end.length) + if (codeLen == codeLength) { - // each of it bytes must lie between the corresponding bytes of the upper & lower bounds - for (int i = 0; i < code.size(); i++) + int value = toInt(code, codeLen); + if (value >= startInt && value <= endInt) { - int startNum = start[i] & 0xff; - int endNum = end[i] & 0xff; - int codeNum = code.get(i) & 0xff; - - if (codeNum > endNum || codeNum < startNum) - { - return false; - } + return true; } - return true; } return false; } - - /** - * Returns true if the given byte matches the byte at the given index of this codespace range. - */ - public boolean isPartialMatch(byte b, int index) - { - int startNum = start[index] & 0xff; - int endNum = end[index] & 0xff; - int codeNum = b & 0xff; - return !(codeNum > endNum || codeNum < startNum); - } } diff --git a/library/src/main/java/com/tom_roush/fontbox/encoding/Encoding.java b/library/src/main/java/com/tom_roush/fontbox/encoding/Encoding.java index 6660594af..9754b588c 100644 --- a/library/src/main/java/com/tom_roush/fontbox/encoding/Encoding.java +++ b/library/src/main/java/com/tom_roush/fontbox/encoding/Encoding.java @@ -30,12 +30,12 @@ public abstract class Encoding /** * This is a mapping from a character code to a character name. */ - protected Map codeToName = new HashMap(); + protected Map codeToName = new HashMap(256); /** * This is a mapping from a character name to a character code. */ - protected Map nameToCode = new HashMap(); + protected Map nameToCode = new HashMap(256); /** * This will add a character encoding. diff --git a/library/src/main/java/com/tom_roush/fontbox/encoding/MacRomanEncoding.java b/library/src/main/java/com/tom_roush/fontbox/encoding/MacRomanEncoding.java index 7d96fa360..96ca52ab5 100644 --- a/library/src/main/java/com/tom_roush/fontbox/encoding/MacRomanEncoding.java +++ b/library/src/main/java/com/tom_roush/fontbox/encoding/MacRomanEncoding.java @@ -25,223 +25,238 @@ */ public class MacRomanEncoding extends Encoding { + private static final int CHAR_CODE = 0; + private static final int CHAR_NAME = 1; + + /** + * Table of octal character codes and their corresponding names. + */ + private static final Object[][] MAC_ROMAN_ENCODING_TABLE = { + {0101, "A"}, + {0256, "AE"}, + {0347, "Aacute"}, + {0345, "Acircumflex"}, + {0200, "Adieresis"}, + {0313, "Agrave"}, + {0201, "Aring"}, + {0314, "Atilde"}, + {0102, "B"}, + {0103, "C"}, + {0202, "Ccedilla"}, + {0104, "D"}, + {0105, "E"}, + {0203, "Eacute"}, + {0346, "Ecircumflex"}, + {0350, "Edieresis"}, + {0351, "Egrave"}, + {0106, "F"}, + {0107, "G"}, + {0110, "H"}, + {0111, "I"}, + {0352, "Iacute"}, + {0353, "Icircumflex"}, + {0354, "Idieresis"}, + {0355, "Igrave"}, + {0112, "J"}, + {0113, "K"}, + {0114, "L"}, + {0115, "M"}, + {0116, "N"}, + {0204, "Ntilde"}, + {0117, "O"}, + {0316, "OE"}, + {0356, "Oacute"}, + {0357, "Ocircumflex"}, + {0205, "Odieresis"}, + {0361, "Ograve"}, + {0257, "Oslash"}, + {0315, "Otilde"}, + {0120, "P"}, + {0121, "Q"}, + {0122, "R"}, + {0123, "S"}, + {0124, "T"}, + {0125, "U"}, + {0362, "Uacute"}, + {0363, "Ucircumflex"}, + {0206, "Udieresis"}, + {0364, "Ugrave"}, + {0126, "V"}, + {0127, "W"}, + {0130, "X"}, + {0131, "Y"}, + {0331, "Ydieresis"}, + {0132, "Z"}, + {0141, "a"}, + {0207, "aacute"}, + {0211, "acircumflex"}, + {0253, "acute"}, + {0212, "adieresis"}, + {0276, "ae"}, + {0210, "agrave"}, + {046, "ampersand"}, + {0214, "aring"}, + {0136, "asciicircum"}, + {0176, "asciitilde"}, + {052, "asterisk"}, + {0100, "at"}, + {0213, "atilde"}, + {0142, "b"}, + {0134, "backslash"}, + {0174, "bar"}, + {0173, "braceleft"}, + {0175, "braceright"}, + {0133, "bracketleft"}, + {0135, "bracketright"}, + {0371, "breve"}, + {0245, "bullet"}, + {0143, "c"}, + {0377, "caron"}, + {0215, "ccedilla"}, + {0374, "cedilla"}, + {0242, "cent"}, + {0366, "circumflex"}, + {072, "colon"}, + {054, "comma"}, + {0251, "copyright"}, + {0333, "currency"}, + {0144, "d"}, + {0240, "dagger"}, + {0340, "daggerdbl"}, + {0241, "degree"}, + {0254, "dieresis"}, + {0326, "divide"}, + {044, "dollar"}, + {0372, "dotaccent"}, + {0365, "dotlessi"}, + {0145, "e"}, + {0216, "eacute"}, + {0220, "ecircumflex"}, + {0221, "edieresis"}, + {0217, "egrave"}, + {070, "eight"}, + {0311, "ellipsis"}, + {0321, "emdash"}, + {0320, "endash"}, + {075, "equal"}, + {041, "exclam"}, + {0301, "exclamdown"}, + {0146, "f"}, + {0336, "fi"}, + {065, "five"}, + {0337, "fl"}, + {0304, "florin"}, + {064, "four"}, + {0332, "fraction"}, + {0147, "g"}, + {0247, "germandbls"}, + {0140, "grave"}, + {076, "greater"}, + {0307, "guillemotleft"}, + {0310, "guillemotright"}, + {0334, "guilsinglleft"}, + {0335, "guilsinglright"}, + {0150, "h"}, + {0375, "hungarumlaut"}, + {055, "hyphen"}, + {0151, "i"}, + {0222, "iacute"}, + {0224, "icircumflex"}, + {0225, "idieresis"}, + {0223, "igrave"}, + {0152, "j"}, + {0153, "k"}, + {0154, "l"}, + {074, "less"}, + {0302, "logicalnot"}, + {0155, "m"}, + {0370, "macron"}, + {0265, "mu"}, + {0156, "n"}, + {071, "nine"}, + {0226, "ntilde"}, + {043, "numbersign"}, + {0157, "o"}, + {0227, "oacute"}, + {0231, "ocircumflex"}, + {0232, "odieresis"}, + {0317, "oe"}, + {0376, "ogonek"}, + {0230, "ograve"}, + {061, "one"}, + {0273, "ordfeminine"}, + {0274, "ordmasculine"}, + {0277, "oslash"}, + {0233, "otilde"}, + {0160, "p"}, + {0246, "paragraph"}, + {050, "parenleft"}, + {051, "parenright"}, + {045, "percent"}, + {056, "period"}, + {0341, "periodcentered"}, + {0344, "perthousand"}, + {053, "plus"}, + {0261, "plusminus"}, + {0161, "q"}, + {077, "question"}, + {0300, "questiondown"}, + {042, "quotedbl"}, + {0343, "quotedblbase"}, + {0322, "quotedblleft"}, + {0323, "quotedblright"}, + {0324, "quoteleft"}, + {0325, "quoteright"}, + {0342, "quotesinglbase"}, + {047, "quotesingle"}, + {0162, "r"}, + {0250, "registered"}, + {0373, "ring"}, + {0163, "s"}, + {0244, "section"}, + {073, "semicolon"}, + {067, "seven"}, + {066, "six"}, + {057, "slash"}, + {040, "space"}, + {0243, "sterling"}, + {0164, "t"}, + {063, "three"}, + {0367, "tilde"}, + {0252, "trademark"}, + {062, "two"}, + {0165, "u"}, + {0234, "uacute"}, + {0236, "ucircumflex"}, + {0237, "udieresis"}, + {0235, "ugrave"}, + {0137, "underscore"}, + {0166, "v"}, + {0167, "w"}, + {0170, "x"}, + {0171, "y"}, + {0330, "ydieresis"}, + {0264, "yen"}, + {0172, "z"}, + {060, "zero"} + + }; + /** * Singleton instance of this class. * */ public static final MacRomanEncoding INSTANCE = new MacRomanEncoding(); - + /** * Constructor. */ public MacRomanEncoding() { - addCharacterEncoding( 0101, "A" ); - addCharacterEncoding( 0256, "AE" ); - addCharacterEncoding( 0347, "Aacute" ); - addCharacterEncoding( 0345, "Acircumflex" ); - addCharacterEncoding( 0200, "Adieresis" ); - addCharacterEncoding( 0313, "Agrave" ); - addCharacterEncoding( 0201, "Aring" ); - addCharacterEncoding( 0314, "Atilde" ); - addCharacterEncoding( 0102, "B" ); - addCharacterEncoding( 0103, "C" ); - addCharacterEncoding( 0202, "Ccedilla" ); - addCharacterEncoding( 0104, "D" ); - addCharacterEncoding( 0105, "E" ); - addCharacterEncoding( 0203, "Eacute" ); - addCharacterEncoding( 0346, "Ecircumflex" ); - addCharacterEncoding( 0350, "Edieresis" ); - addCharacterEncoding( 0351, "Egrave" ); - addCharacterEncoding( 0106, "F" ); - addCharacterEncoding( 0107, "G" ); - addCharacterEncoding( 0110, "H" ); - addCharacterEncoding( 0111, "I" ); - addCharacterEncoding( 0352, "Iacute" ); - addCharacterEncoding( 0353, "Icircumflex" ); - addCharacterEncoding( 0354, "Idieresis" ); - addCharacterEncoding( 0355, "Igrave" ); - addCharacterEncoding( 0112, "J" ); - addCharacterEncoding( 0113, "K" ); - addCharacterEncoding( 0114, "L" ); - addCharacterEncoding( 0115, "M" ); - addCharacterEncoding( 0116, "N" ); - addCharacterEncoding( 0204, "Ntilde" ); - addCharacterEncoding( 0117, "O" ); - addCharacterEncoding( 0316, "OE" ); - addCharacterEncoding( 0356, "Oacute" ); - addCharacterEncoding( 0357, "Ocircumflex" ); - addCharacterEncoding( 0205, "Odieresis" ); - addCharacterEncoding( 0361, "Ograve" ); - addCharacterEncoding( 0257, "Oslash" ); - addCharacterEncoding( 0315, "Otilde" ); - addCharacterEncoding( 0120, "P" ); - addCharacterEncoding( 0121, "Q" ); - addCharacterEncoding( 0122, "R" ); - addCharacterEncoding( 0123, "S" ); - addCharacterEncoding( 0124, "T" ); - addCharacterEncoding( 0125, "U" ); - addCharacterEncoding( 0362, "Uacute" ); - addCharacterEncoding( 0363, "Ucircumflex" ); - addCharacterEncoding( 0206, "Udieresis" ); - addCharacterEncoding( 0364, "Ugrave" ); - addCharacterEncoding( 0126, "V" ); - addCharacterEncoding( 0127, "W" ); - addCharacterEncoding( 0130, "X" ); - addCharacterEncoding( 0131, "Y" ); - addCharacterEncoding( 0331, "Ydieresis" ); - addCharacterEncoding( 0132, "Z" ); - addCharacterEncoding( 0141, "a" ); - addCharacterEncoding( 0207, "aacute" ); - addCharacterEncoding( 0211, "acircumflex" ); - addCharacterEncoding( 0253, "acute" ); - addCharacterEncoding( 0212, "adieresis" ); - addCharacterEncoding( 0276, "ae" ); - addCharacterEncoding( 0210, "agrave" ); - addCharacterEncoding( 046, "ampersand" ); - addCharacterEncoding( 0214, "aring" ); - addCharacterEncoding( 0136, "asciicircum" ); - addCharacterEncoding( 0176, "asciitilde" ); - addCharacterEncoding( 052, "asterisk" ); - addCharacterEncoding( 0100, "at" ); - addCharacterEncoding( 0213, "atilde" ); - addCharacterEncoding( 0142, "b" ); - addCharacterEncoding( 0134, "backslash" ); - addCharacterEncoding( 0174, "bar" ); - addCharacterEncoding( 0173, "braceleft" ); - addCharacterEncoding( 0175, "braceright" ); - addCharacterEncoding( 0133, "bracketleft" ); - addCharacterEncoding( 0135, "bracketright" ); - addCharacterEncoding( 0371, "breve" ); - addCharacterEncoding( 0245, "bullet" ); - addCharacterEncoding( 0143, "c" ); - addCharacterEncoding( 0377, "caron" ); - addCharacterEncoding( 0215, "ccedilla" ); - addCharacterEncoding( 0374, "cedilla" ); - addCharacterEncoding( 0242, "cent" ); - addCharacterEncoding( 0366, "circumflex" ); - addCharacterEncoding( 072, "colon" ); - addCharacterEncoding( 054, "comma" ); - addCharacterEncoding( 0251, "copyright" ); - addCharacterEncoding( 0333, "currency" ); - addCharacterEncoding( 0144, "d" ); - addCharacterEncoding( 0240, "dagger" ); - addCharacterEncoding( 0340, "daggerdbl" ); - addCharacterEncoding( 0241, "degree" ); - addCharacterEncoding( 0254, "dieresis" ); - addCharacterEncoding( 0326, "divide" ); - addCharacterEncoding( 044, "dollar" ); - addCharacterEncoding( 0372, "dotaccent" ); - addCharacterEncoding( 0365, "dotlessi" ); - addCharacterEncoding( 0145, "e" ); - addCharacterEncoding( 0216, "eacute" ); - addCharacterEncoding( 0220, "ecircumflex" ); - addCharacterEncoding( 0221, "edieresis" ); - addCharacterEncoding( 0217, "egrave" ); - addCharacterEncoding( 070, "eight" ); - addCharacterEncoding( 0311, "ellipsis" ); - addCharacterEncoding( 0321, "emdash" ); - addCharacterEncoding( 0320, "endash" ); - addCharacterEncoding( 075, "equal" ); - addCharacterEncoding( 041, "exclam" ); - addCharacterEncoding( 0301, "exclamdown" ); - addCharacterEncoding( 0146, "f" ); - addCharacterEncoding( 0336, "fi" ); - addCharacterEncoding( 065, "five" ); - addCharacterEncoding( 0337, "fl" ); - addCharacterEncoding( 0304, "florin" ); - addCharacterEncoding( 064, "four" ); - addCharacterEncoding( 0332, "fraction" ); - addCharacterEncoding( 0147, "g" ); - addCharacterEncoding( 0247, "germandbls" ); - addCharacterEncoding( 0140, "grave" ); - addCharacterEncoding( 076, "greater" ); - addCharacterEncoding( 0307, "guillemotleft" ); - addCharacterEncoding( 0310, "guillemotright" ); - addCharacterEncoding( 0334, "guilsinglleft" ); - addCharacterEncoding( 0335, "guilsinglright" ); - addCharacterEncoding( 0150, "h" ); - addCharacterEncoding( 0375, "hungarumlaut" ); - addCharacterEncoding( 055, "hyphen" ); - addCharacterEncoding( 0151, "i" ); - addCharacterEncoding( 0222, "iacute" ); - addCharacterEncoding( 0224, "icircumflex" ); - addCharacterEncoding( 0225, "idieresis" ); - addCharacterEncoding( 0223, "igrave" ); - addCharacterEncoding( 0152, "j" ); - addCharacterEncoding( 0153, "k" ); - addCharacterEncoding( 0154, "l" ); - addCharacterEncoding( 074, "less" ); - addCharacterEncoding( 0302, "logicalnot" ); - addCharacterEncoding( 0155, "m" ); - addCharacterEncoding( 0370, "macron" ); - addCharacterEncoding( 0265, "mu" ); - addCharacterEncoding( 0156, "n" ); - addCharacterEncoding( 071, "nine" ); - addCharacterEncoding( 0226, "ntilde" ); - addCharacterEncoding( 043, "numbersign" ); - addCharacterEncoding( 0157, "o" ); - addCharacterEncoding( 0227, "oacute" ); - addCharacterEncoding( 0231, "ocircumflex" ); - addCharacterEncoding( 0232, "odieresis" ); - addCharacterEncoding( 0317, "oe" ); - addCharacterEncoding( 0376, "ogonek" ); - addCharacterEncoding( 0230, "ograve" ); - addCharacterEncoding( 061, "one" ); - addCharacterEncoding( 0273, "ordfeminine" ); - addCharacterEncoding( 0274, "ordmasculine" ); - addCharacterEncoding( 0277, "oslash" ); - addCharacterEncoding( 0233, "otilde" ); - addCharacterEncoding( 0160, "p" ); - addCharacterEncoding( 0246, "paragraph" ); - addCharacterEncoding( 050, "parenleft" ); - addCharacterEncoding( 051, "parenright" ); - addCharacterEncoding( 045, "percent" ); - addCharacterEncoding( 056, "period" ); - addCharacterEncoding( 0341, "periodcentered" ); - addCharacterEncoding( 0344, "perthousand" ); - addCharacterEncoding( 053, "plus" ); - addCharacterEncoding( 0261, "plusminus" ); - addCharacterEncoding( 0161, "q" ); - addCharacterEncoding( 077, "question" ); - addCharacterEncoding( 0300, "questiondown" ); - addCharacterEncoding( 042, "quotedbl" ); - addCharacterEncoding( 0343, "quotedblbase" ); - addCharacterEncoding( 0322, "quotedblleft" ); - addCharacterEncoding( 0323, "quotedblright" ); - addCharacterEncoding( 0324, "quoteleft" ); - addCharacterEncoding( 0325, "quoteright" ); - addCharacterEncoding( 0342, "quotesinglbase" ); - addCharacterEncoding( 047, "quotesingle" ); - addCharacterEncoding( 0162, "r" ); - addCharacterEncoding( 0250, "registered" ); - addCharacterEncoding( 0373, "ring" ); - addCharacterEncoding( 0163, "s" ); - addCharacterEncoding( 0244, "section" ); - addCharacterEncoding( 073, "semicolon" ); - addCharacterEncoding( 067, "seven" ); - addCharacterEncoding( 066, "six" ); - addCharacterEncoding( 057, "slash" ); - addCharacterEncoding( 040, "space" ); - addCharacterEncoding( 0243, "sterling" ); - addCharacterEncoding( 0164, "t" ); - addCharacterEncoding( 063, "three" ); - addCharacterEncoding( 0367, "tilde" ); - addCharacterEncoding( 0252, "trademark" ); - addCharacterEncoding( 062, "two" ); - addCharacterEncoding( 0165, "u" ); - addCharacterEncoding( 0234, "uacute" ); - addCharacterEncoding( 0236, "ucircumflex" ); - addCharacterEncoding( 0237, "udieresis" ); - addCharacterEncoding( 0235, "ugrave" ); - addCharacterEncoding( 0137, "underscore" ); - addCharacterEncoding( 0166, "v" ); - addCharacterEncoding( 0167, "w" ); - addCharacterEncoding( 0170, "x" ); - addCharacterEncoding( 0171, "y" ); - addCharacterEncoding( 0330, "ydieresis" ); - addCharacterEncoding( 0264, "yen" ); - addCharacterEncoding( 0172, "z" ); - addCharacterEncoding( 060, "zero" ); + for (Object[] encodingEntry : MAC_ROMAN_ENCODING_TABLE) + { + addCharacterEncoding((Integer)encodingEntry[CHAR_CODE], + encodingEntry[CHAR_NAME].toString()); + } } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/fontbox/encoding/StandardEncoding.java b/library/src/main/java/com/tom_roush/fontbox/encoding/StandardEncoding.java index dee1fb798..18ecc235f 100644 --- a/library/src/main/java/com/tom_roush/fontbox/encoding/StandardEncoding.java +++ b/library/src/main/java/com/tom_roush/fontbox/encoding/StandardEncoding.java @@ -25,6 +25,164 @@ */ public class StandardEncoding extends Encoding { + private static final int CHAR_CODE = 0; + private static final int CHAR_NAME = 1; + + /** + * Table of octal character codes and their corresponding names. + */ + private static final Object[][] STANDARD_ENCODING_TABLE = { + {0101, "A"}, + {0341, "AE"}, + {0102, "B"}, + {0103, "C"}, + {0104, "D"}, + {0105, "E"}, + {0106, "F"}, + {0107, "G"}, + {0110, "H"}, + {0111, "I"}, + {0112, "J"}, + {0113, "K"}, + {0114, "L"}, + {0350, "Lslash"}, + {0115, "M"}, + {0116, "N"}, + {0117, "O"}, + {0352, "OE"}, + {0351, "Oslash"}, + {0120, "P"}, + {0121, "Q"}, + {0122, "R"}, + {0123, "S"}, + {0124, "T"}, + {0125, "U"}, + {0126, "V"}, + {0127, "W"}, + {0130, "X"}, + {0131, "Y"}, + {0132, "Z"}, + {0141, "a"}, + {0302, "acute"}, + {0361, "ae"}, + {0046, "ampersand"}, + {0136, "asciicircum"}, + {0176, "asciitilde"}, + {0052, "asterisk"}, + {0100, "at"}, + {0142, "b"}, + {0134, "backslash"}, + {0174, "bar"}, + {0173, "braceleft"}, + {0175, "braceright"}, + {0133, "bracketleft"}, + {0135, "bracketright"}, + {0306, "breve"}, + {0267, "bullet"}, + {0143, "c"}, + {0317, "caron"}, + {0313, "cedilla"}, + {0242, "cent"}, + {0303, "circumflex"}, + {0072, "colon"}, + {0054, "comma"}, + {0250, "currency"}, + {0144, "d"}, + {0262, "dagger"}, + {0263, "daggerdbl"}, + {0310, "dieresis"}, + {0044, "dollar"}, + {0307, "dotaccent"}, + {0365, "dotlessi"}, + {0145, "e"}, + {0070, "eight"}, + {0274, "ellipsis"}, + {0320, "emdash"}, + {0261, "endash"}, + {0075, "equal"}, + {0041, "exclam"}, + {0241, "exclamdown"}, + {0146, "f"}, + {0256, "fi"}, + {0065, "five"}, + {0257, "fl"}, + {0246, "florin"}, + {0064, "four"}, + {0244, "fraction"}, + {0147, "g"}, + {0373, "germandbls"}, + {0301, "grave"}, + {0076, "greater"}, + {0253, "guillemotleft"}, + {0273, "guillemotright"}, + {0254, "guilsinglleft"}, + {0255, "guilsinglright"}, + {0150, "h"}, + {0315, "hungarumlaut"}, + {0055, "hyphen"}, + {0151, "i"}, + {0152, "j"}, + {0153, "k"}, + {0154, "l"}, + {0074, "less"}, + {0370, "lslash"}, + {0155, "m"}, + {0305, "macron"}, + {0156, "n"}, + {0071, "nine"}, + {0043, "numbersign"}, + {0157, "o"}, + {0372, "oe"}, + {0316, "ogonek"}, + {0061, "one"}, + {0343, "ordfeminine"}, + {0353, "ordmasculine"}, + {0371, "oslash"}, + {0160, "p"}, + {0266, "paragraph"}, + {0050, "parenleft"}, + {0051, "parenright"}, + {0045, "percent"}, + {0056, "period"}, + {0264, "periodcentered"}, + {0275, "perthousand"}, + {0053, "plus"}, + {0161, "q"}, + {0077, "question"}, + {0277, "questiondown"}, + {0042, "quotedbl"}, + {0271, "quotedblbase"}, + {0252, "quotedblleft"}, + {0272, "quotedblright"}, + {0140, "quoteleft"}, + {0047, "quoteright"}, + {0270, "quotesinglbase"}, + {0251, "quotesingle"}, + {0162, "r"}, + {0312, "ring"}, + {0163, "s"}, + {0247, "section"}, + {0073, "semicolon"}, + {0067, "seven"}, + {0066, "six"}, + {0057, "slash"}, + {0040, "space"}, + {0243, "sterling"}, + {0164, "t"}, + {0063, "three"}, + {0304, "tilde"}, + {0062, "two"}, + {0165, "u"}, + {0137, "underscore"}, + {0166, "v"}, + {0167, "w"}, + {0170, "x"}, + {0171, "y"}, + {0245, "yen"}, + {0172, "z"}, + {0060, "zero"} + }; + /** * Singleton instance of this class. */ @@ -35,154 +193,10 @@ public class StandardEncoding extends Encoding */ public StandardEncoding() { - addCharacterEncoding( 0101, "A" ); - addCharacterEncoding( 0341, "AE" ); - addCharacterEncoding( 0102, "B" ); - addCharacterEncoding( 0103, "C" ); - addCharacterEncoding( 0104, "D" ); - addCharacterEncoding( 0105, "E" ); - addCharacterEncoding( 0106, "F" ); - addCharacterEncoding( 0107, "G" ); - addCharacterEncoding( 0110, "H" ); - addCharacterEncoding( 0111, "I" ); - addCharacterEncoding( 0112, "J" ); - addCharacterEncoding( 0113, "K" ); - addCharacterEncoding( 0114, "L" ); - addCharacterEncoding( 0350, "Lslash" ); - addCharacterEncoding( 0115, "M" ); - addCharacterEncoding( 0116, "N" ); - addCharacterEncoding( 0117, "O" ); - addCharacterEncoding( 0352, "OE" ); - addCharacterEncoding( 0351, "Oslash" ); - addCharacterEncoding( 0120, "P" ); - addCharacterEncoding( 0121, "Q" ); - addCharacterEncoding( 0122, "R" ); - addCharacterEncoding( 0123, "S" ); - addCharacterEncoding( 0124, "T" ); - addCharacterEncoding( 0125, "U" ); - addCharacterEncoding( 0126, "V" ); - addCharacterEncoding( 0127, "W" ); - addCharacterEncoding( 0130, "X" ); - addCharacterEncoding( 0131, "Y" ); - addCharacterEncoding( 0132, "Z" ); - addCharacterEncoding( 0141, "a" ); - addCharacterEncoding( 0302, "acute" ); - addCharacterEncoding( 0361, "ae" ); - addCharacterEncoding( 0046, "ampersand" ); - addCharacterEncoding( 0136, "asciicircum" ); - addCharacterEncoding( 0176, "asciitilde" ); - addCharacterEncoding( 0052, "asterisk" ); - addCharacterEncoding( 0100, "at" ); - addCharacterEncoding( 0142, "b" ); - addCharacterEncoding( 0134, "backslash" ); - addCharacterEncoding( 0174, "bar" ); - addCharacterEncoding( 0173, "braceleft" ); - addCharacterEncoding( 0175, "braceright" ); - addCharacterEncoding( 0133, "bracketleft" ); - addCharacterEncoding( 0135, "bracketright" ); - addCharacterEncoding( 0306, "breve" ); - addCharacterEncoding( 0267, "bullet" ); - addCharacterEncoding( 0143, "c" ); - addCharacterEncoding( 0317, "caron" ); - addCharacterEncoding( 0313, "cedilla" ); - addCharacterEncoding( 0242, "cent" ); - addCharacterEncoding( 0303, "circumflex" ); - addCharacterEncoding( 0072, "colon" ); - addCharacterEncoding( 0054, "comma" ); - addCharacterEncoding( 0250, "currency" ); - addCharacterEncoding( 0144, "d" ); - addCharacterEncoding( 0262, "dagger" ); - addCharacterEncoding( 0263, "daggerdbl" ); - addCharacterEncoding( 0310, "dieresis" ); - addCharacterEncoding( 0044, "dollar" ); - addCharacterEncoding( 0307, "dotaccent" ); - addCharacterEncoding( 0365, "dotlessi" ); - addCharacterEncoding( 0145, "e" ); - addCharacterEncoding( 0070, "eight" ); - addCharacterEncoding( 0274, "ellipsis" ); - addCharacterEncoding( 0320, "emdash" ); - addCharacterEncoding( 0261, "endash" ); - addCharacterEncoding( 0075, "equal" ); - addCharacterEncoding( 0041, "exclam" ); - addCharacterEncoding( 0241, "exclamdown" ); - addCharacterEncoding( 0146, "f" ); - addCharacterEncoding( 0256, "fi" ); - addCharacterEncoding( 0065, "five" ); - addCharacterEncoding( 0257, "fl" ); - addCharacterEncoding( 0246, "florin" ); - addCharacterEncoding( 0064, "four" ); - addCharacterEncoding( 0244, "fraction" ); - addCharacterEncoding( 0147, "g" ); - addCharacterEncoding( 0373, "germandbls" ); - addCharacterEncoding( 0301, "grave" ); - addCharacterEncoding( 0076, "greater" ); - addCharacterEncoding( 0253, "guillemotleft" ); - addCharacterEncoding( 0273, "guillemotright" ); - addCharacterEncoding( 0254, "guilsinglleft" ); - addCharacterEncoding( 0255, "guilsinglright" ); - addCharacterEncoding( 0150, "h" ); - addCharacterEncoding( 0315, "hungarumlaut" ); - addCharacterEncoding( 0055, "hyphen" ); - addCharacterEncoding( 0151, "i" ); - addCharacterEncoding( 0152, "j" ); - addCharacterEncoding( 0153, "k" ); - addCharacterEncoding( 0154, "l" ); - addCharacterEncoding( 0074, "less" ); - addCharacterEncoding( 0370, "lslash" ); - addCharacterEncoding( 0155, "m" ); - addCharacterEncoding( 0305, "macron" ); - addCharacterEncoding( 0156, "n" ); - addCharacterEncoding( 0071, "nine" ); - addCharacterEncoding( 0043, "numbersign" ); - addCharacterEncoding( 0157, "o" ); - addCharacterEncoding( 0372, "oe" ); - addCharacterEncoding( 0316, "ogonek" ); - addCharacterEncoding( 0061, "one" ); - addCharacterEncoding( 0343, "ordfeminine" ); - addCharacterEncoding( 0353, "ordmasculine" ); - addCharacterEncoding( 0371, "oslash" ); - addCharacterEncoding( 0160, "p" ); - addCharacterEncoding( 0266, "paragraph" ); - addCharacterEncoding( 0050, "parenleft" ); - addCharacterEncoding( 0051, "parenright" ); - addCharacterEncoding( 0045, "percent" ); - addCharacterEncoding( 0056, "period" ); - addCharacterEncoding( 0264, "periodcentered" ); - addCharacterEncoding( 0275, "perthousand" ); - addCharacterEncoding( 0053, "plus" ); - addCharacterEncoding( 0161, "q" ); - addCharacterEncoding( 0077, "question" ); - addCharacterEncoding( 0277, "questiondown" ); - addCharacterEncoding( 0042, "quotedbl" ); - addCharacterEncoding( 0271, "quotedblbase" ); - addCharacterEncoding( 0252, "quotedblleft" ); - addCharacterEncoding( 0272, "quotedblright" ); - addCharacterEncoding( 0140, "quoteleft" ); - addCharacterEncoding( 0047, "quoteright" ); - addCharacterEncoding( 0270, "quotesinglbase" ); - addCharacterEncoding( 0251, "quotesingle" ); - addCharacterEncoding( 0162, "r" ); - addCharacterEncoding( 0312, "ring" ); - addCharacterEncoding( 0163, "s" ); - addCharacterEncoding( 0247, "section" ); - addCharacterEncoding( 0073, "semicolon" ); - addCharacterEncoding( 0067, "seven" ); - addCharacterEncoding( 0066, "six" ); - addCharacterEncoding( 0057, "slash" ); - addCharacterEncoding( 0040, "space" ); - addCharacterEncoding( 0243, "sterling" ); - addCharacterEncoding( 0164, "t" ); - addCharacterEncoding( 0063, "three" ); - addCharacterEncoding( 0304, "tilde" ); - addCharacterEncoding( 0062, "two" ); - addCharacterEncoding( 0165, "u" ); - addCharacterEncoding( 0137, "underscore" ); - addCharacterEncoding( 0166, "v" ); - addCharacterEncoding( 0167, "w" ); - addCharacterEncoding( 0170, "x" ); - addCharacterEncoding( 0171, "y" ); - addCharacterEncoding( 0245, "yen" ); - addCharacterEncoding( 0172, "z" ); - addCharacterEncoding( 0060, "zero" ); + for (Object[] encodingEntry : STANDARD_ENCODING_TABLE) + { + addCharacterEncoding((Integer)encodingEntry[CHAR_CODE], + encodingEntry[CHAR_NAME].toString()); + } } } \ No newline at end of file diff --git a/library/src/main/java/com/tom_roush/fontbox/pfb/PfbParser.java b/library/src/main/java/com/tom_roush/fontbox/pfb/PfbParser.java index 045f651de..3be326b1f 100644 --- a/library/src/main/java/com/tom_roush/fontbox/pfb/PfbParser.java +++ b/library/src/main/java/com/tom_roush/fontbox/pfb/PfbParser.java @@ -101,7 +101,7 @@ public PfbParser(final InputStream in) throws IOException byte[] pfb = readPfbInput(in); parsePfb(pfb); } - + /** * Create a new object. * @param bytes The input. @@ -109,7 +109,7 @@ public PfbParser(final InputStream in) throws IOException */ public PfbParser(final byte[] bytes) throws IOException { - parsePfb(bytes); + parsePfb(bytes); } /** @@ -141,6 +141,10 @@ private void parsePfb(final byte[] pfb) throws IOException size += in.read() << 16; size += in.read() << 24; lengths[records] = size; + if (pointer >= pfbdata.length) + { + throw new EOFException("attempted to read past EOF"); + } int got = in.read(pfbdata, pointer, size); if (got < 0) { diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/BufferedRandomAccessFile.java b/library/src/main/java/com/tom_roush/fontbox/ttf/BufferedRandomAccessFile.java new file mode 100644 index 000000000..bc6ecaa59 --- /dev/null +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/BufferedRandomAccessFile.java @@ -0,0 +1,197 @@ +/* + * Copyright 2015 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.fontbox.ttf; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * This class is a version of the one published at + * https://code.google.com/p/jmzreader/wiki/BufferedRandomAccessFile augmented to handle unsigned + * bytes. The original class is published under Apache 2.0 license. Fix is marked below + * + * This is an optimized version of the RandomAccessFile class as described by Nick Zhang on + * JavaWorld.com. The article can be found at + * http://www.javaworld.com/javaworld/javatips/jw-javatip26.html + * + * @author jg + */ +public class BufferedRandomAccessFile extends RandomAccessFile +{ + /** + * Uses a byte instead of a char buffer for efficiency reasons. + */ + private final byte buffer[]; + private int bufend = 0; + private int bufpos = 0; + + /** + * The position inside the actual file. + */ + private long realpos = 0; + + /** + * Buffer size. + */ + private final int BUFSIZE; + + /** + * Creates a new instance of the BufferedRandomAccessFile. + * + * @param filename The path of the file to open. + * @param mode Specifies the mode to use ("r", "rw", etc.) See the BufferedLineReader + * documentation for more information. + * @param bufsize The buffer size (in bytes) to use. + * @throws FileNotFoundException If the mode is "r" but the given string does not denote an + * existing regular file, or if the mode begins with "rw" but the given string does not denote + * an existing, writable regular file and a new regular file of that name cannot be created, or + * if some other error occurs while opening or creating the file. + */ + public BufferedRandomAccessFile(String filename, String mode, int bufsize) + throws FileNotFoundException + { + super(filename, mode); + BUFSIZE = bufsize; + buffer = new byte[BUFSIZE]; + } + + /** + * Creates a new instance of the BufferedRandomAccessFile. + * + * @param file The file to open. + * @param mode Specifies the mode to use ("r", "rw", etc.) See the BufferedLineReader + * documentation for more information. + * @param bufsize The buffer size (in bytes) to use. + * @throws FileNotFoundException If the mode is "r" but the given file path does not denote an + * existing regular file, or if the mode begins with "rw" but the given file path does not denote + * an existing, writable regular file and a new regular file of that name cannot be created, or + * if some other error occurs while opening or creating the file. + */ + public BufferedRandomAccessFile(File file, String mode, int bufsize) + throws FileNotFoundException + { + super(file, mode); + BUFSIZE = bufsize; + buffer = new byte[BUFSIZE]; + } + + /** + * {@inheritDoc} + */ + @Override + public final int read() throws IOException + { + if (bufpos >= bufend && fillBuffer() < 0) + { + return -1; + } + if (bufend == 0) + { + return -1; + } + // FIX to handle unsigned bytes + return (buffer[bufpos++] + 256) & 0xFF; + // End of fix + } + + /** + * Reads the next BUFSIZE bytes into the internal buffer. + * + * @return The total number of bytes read into the buffer, or -1 if there is no more data + * because the end of the file has been reached. + * @throws IOException If the first byte cannot be read for any reason other than end of file, + * or if the random access file has been closed, or if some other I/O error occurs. + */ + private int fillBuffer() throws IOException + { + int n = super.read(buffer, 0, BUFSIZE); + + if (n >= 0) + { + realpos += n; + bufend = n; + bufpos = 0; + } + return n; + } + + /** + * Clears the local buffer. + * + * @throws IOException If an I/O error occurs. + */ + private void invalidate() throws IOException + { + bufend = 0; + bufpos = 0; + realpos = super.getFilePointer(); + } + + /** + * {@inheritDoc} + */ + @Override + public int read(byte b[], int off, int len) throws IOException + { + int leftover = bufend - bufpos; + if (len <= leftover) + { + System.arraycopy(buffer, bufpos, b, off, len); + bufpos += len; + return len; + } + System.arraycopy(buffer, bufpos, b, off, leftover); + bufpos += leftover; + if (fillBuffer() > 0) + { + int bytesRead = read(b, off + leftover, len - leftover); + if (bytesRead > 0) + { + leftover += bytesRead; + } + } + return leftover; + } + + /** + * {@inheritDoc} + */ + @Override + public long getFilePointer() throws IOException + { + return realpos - bufend + bufpos; + } + + /** + * {@inheritDoc} + */ + @Override + public void seek(long pos) throws IOException + { + int n = (int)(realpos - pos); + if (n >= 0 && n <= bufend) + { + bufpos = bufend - n; + } + else + { + super.seek(pos); + invalidate(); + } + } +} diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/CFFTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/CFFTable.java index 8c08671e8..0f4f6cbc7 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/CFFTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/CFFTable.java @@ -17,11 +17,11 @@ package com.tom_roush.fontbox.ttf; +import java.io.IOException; + import com.tom_roush.fontbox.cff.CFFFont; import com.tom_roush.fontbox.cff.CFFParser; -import java.io.IOException; - /** * PostScript font program (compact font format). */ @@ -51,7 +51,7 @@ public void read(TrueTypeFont ttf, TTFDataStream data) throws IOException byte[] bytes = data.read((int)getLength()); CFFParser parser = new CFFParser(); - cffFont = parser.parse(bytes).get(0); + cffFont = parser.parse(bytes, new ByteSource(font)).get(0); initialized = true; } @@ -63,4 +63,23 @@ public CFFFont getFont() { return cffFont; } + + /** + * Allows bytes to be re-read later by CFFParser. + */ + private static class ByteSource implements CFFParser.ByteSource + { + private final TrueTypeFont ttf; + + ByteSource(TrueTypeFont ttf) + { + this.ttf = ttf; + } + + @Override + public byte[] getBytes() throws IOException + { + return ttf.getTableBytes(ttf.getTableMap().get(CFFTable.TAG)); + } + } } diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/CmapSubtable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/CmapSubtable.java index 5c338d474..9dfd2b410 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/CmapSubtable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/CmapSubtable.java @@ -39,7 +39,7 @@ public class CmapSubtable private int platformEncodingId; private long subTableOffset; private int[] glyphIdToCharacterCode; - private final Map characterCodeToGlyphId = new HashMap(); + private Map characterCodeToGlyphId; /** * This will read the required data from the stream. @@ -135,6 +135,7 @@ protected void processSubtype8(TTFDataStream data, int numGlyphs) throws IOExcep } glyphIdToCharacterCode = newGlyphIdToCharacterCode(numGlyphs); + characterCodeToGlyphId = new HashMap(numGlyphs); // -- Read all sub header for (long i = 0; i < nbGroups; ++i) { @@ -223,6 +224,7 @@ protected void processSubtype12(TTFDataStream data, int numGlyphs) throws IOExce { long nbGroups = data.readUnsignedInt(); glyphIdToCharacterCode = newGlyphIdToCharacterCode(numGlyphs); + characterCodeToGlyphId = new HashMap(numGlyphs); for (long i = 0; i < nbGroups; ++i) { long firstCode = data.readUnsignedInt(); @@ -230,14 +232,13 @@ protected void processSubtype12(TTFDataStream data, int numGlyphs) throws IOExce long startGlyph = data.readUnsignedInt(); if (firstCode < 0 || firstCode > 0x0010FFFF || - (firstCode >= 0x0000D800 && firstCode <= 0x0000DFFF)) + firstCode >= 0x0000D800 && firstCode <= 0x0000DFFF) { throw new IOException("Invalid characters codes"); } - if ((endCode > 0 && endCode < firstCode) || - endCode > 0x0010FFFF || - (endCode >= 0x0000D800 && endCode <= 0x0000DFFF)) + if (endCode > 0 && endCode < firstCode || endCode > 0x0010FFFF || + endCode >= 0x0000D800 && endCode <= 0x0000DFFF) { throw new IOException("Invalid characters codes"); } @@ -247,13 +248,15 @@ protected void processSubtype12(TTFDataStream data, int numGlyphs) throws IOExce long glyphIndex = startGlyph + j; if (glyphIndex >= numGlyphs) { - throw new IOException("Character Code greater than Integer.MAX_VALUE"); + Log.w("PdfBox-Android", "Format 12 cmap contains an invalid glyph index"); + break; } if (firstCode + j > 0x10FFFF) { Log.w("PdfBox-Android", "Format 12 cmap contains character beyond UCS-4"); } + glyphIdToCharacterCode[(int) glyphIndex] = (int) (firstCode + j); characterCodeToGlyphId.put((int) (firstCode + j), (int) glyphIndex); } @@ -270,6 +273,7 @@ protected void processSubtype12(TTFDataStream data, int numGlyphs) throws IOExce protected void processSubtype13(TTFDataStream data, int numGlyphs) throws IOException { long nbGroups = data.readUnsignedInt(); + characterCodeToGlyphId = new HashMap(numGlyphs); for (long i = 0; i < nbGroups; ++i) { long firstCode = data.readUnsignedInt(); @@ -295,7 +299,6 @@ protected void processSubtype13(TTFDataStream data, int numGlyphs) throws IOExce for (long j = 0; j <= endCode - firstCode; ++j) { - if (firstCode + j > Integer.MAX_VALUE) { throw new IOException("Character Code greater than Integer.MAX_VALUE"); @@ -337,7 +340,13 @@ protected void processSubtype6(TTFDataStream data, int numGlyphs) throws IOExcep { int firstCode = data.readUnsignedShort(); int entryCount = data.readUnsignedShort(); - Map tmpGlyphToChar = new HashMap(); + // skip emtpy tables + if (entryCount == 0) + { + return; + } + Map tmpGlyphToChar = new HashMap(numGlyphs); + characterCodeToGlyphId = new HashMap(numGlyphs); int[] glyphIdArray = data.readUnsignedShortArray(entryCount); for (int i = 0; i < entryCount; i++) { @@ -373,7 +382,8 @@ protected void processSubtype4(TTFDataStream data, int numGlyphs) throws IOExcep int[] idDelta = data.readUnsignedShortArray(segCount); int[] idRangeOffset = data.readUnsignedShortArray(segCount); - Map tmpGlyphToChar = new HashMap(); + Map tmpGlyphToChar = new HashMap(numGlyphs); + characterCodeToGlyphId = new HashMap(numGlyphs); long currentPosition = data.getCurrentPosition(); @@ -462,6 +472,7 @@ protected void processSubtype2(TTFDataStream data, int numGlyphs) throws IOExcep } long startGlyphIndexOffset = data.getCurrentPosition(); glyphIdToCharacterCode = newGlyphIdToCharacterCode(numGlyphs); + characterCodeToGlyphId = new HashMap(numGlyphs); for (int i = 0; i <= maxSubHeaderIndex; ++i) { SubHeader sh = subHeaders[i]; @@ -486,6 +497,14 @@ protected void processSubtype2(TTFDataStream data, int numGlyphs) throws IOExcep { p = (p + idDelta) % 65536; } + if (p >= numGlyphs) + { + Log.w("PdfBox-Android", + "glyphId " + p + " for charcode " + charCode + " ignored, numGlyphs is " + + numGlyphs); + continue; + } + glyphIdToCharacterCode[p] = charCode; characterCodeToGlyphId.put(charCode, p); } @@ -502,6 +521,7 @@ protected void processSubtype0(TTFDataStream data) throws IOException { byte[] glyphMapping = data.read(256); glyphIdToCharacterCode = newGlyphIdToCharacterCode(256); + characterCodeToGlyphId = new HashMap(glyphMapping.length); for (int i = 0; i < glyphMapping.length; i++) { int glyphIndex = (glyphMapping[i] + 256) % 256; @@ -577,6 +597,7 @@ public Integer getCharacterCode(int gid) { return null; } + // workaround for the fact that glyphIdToCharacterCode doesn't distinguish between // missing character codes and code 0. int code = glyphIdToCharacterCode[gid]; diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/CmapTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/CmapTable.java index 99a2cc3d3..a92958d89 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/CmapTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/CmapTable.java @@ -46,6 +46,7 @@ public class CmapTable extends TTFTable public static final int ENCODING_WIN_PRC = 4; public static final int ENCODING_WIN_WANSUNG = 5; public static final int ENCODING_WIN_JOHAB = 6; + public static final int ENCODING_WIN_UNICODE_FULL = 10; // Unicode Full (UCS-4) // Unicode encodings public static final int ENCODING_UNICODE_1_0 = 0; diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/GlyfSimpleDescript.java b/library/src/main/java/com/tom_roush/fontbox/ttf/GlyfSimpleDescript.java index bab96694b..f16479dd2 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/GlyfSimpleDescript.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/GlyfSimpleDescript.java @@ -37,9 +37,11 @@ public class GlyfSimpleDescript extends GlyfDescript * * @param numberOfContours number of contours * @param bais the stream to be read + * @param x0 the initial X-position * @throws IOException is thrown if something went wrong */ - public GlyfSimpleDescript(short numberOfContours, TTFDataStream bais) throws IOException + public GlyfSimpleDescript(short numberOfContours, TTFDataStream bais, short x0) + throws IOException { super(numberOfContours, bais); @@ -74,7 +76,7 @@ public GlyfSimpleDescript(short numberOfContours, TTFDataStream bais) throws IOE int instructionCount = bais.readUnsignedShort(); readInstructions(bais, instructionCount); readFlags(pointCount, bais); - readCoords(pointCount, bais); + readCoords(pointCount, bais, x0); } /** @@ -134,9 +136,9 @@ public int getPointCount() /** * The table is stored as relative values, but we'll store them as absolutes. */ - private void readCoords(int count, TTFDataStream bais) throws IOException + private void readCoords(int count, TTFDataStream bais, short x0) throws IOException { - short x = 0; + short x = x0; short y = 0; for (int i = 0; i < count; i++) { diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphData.java b/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphData.java index 678333913..44361301a 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphData.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphData.java @@ -16,12 +16,12 @@ */ package com.tom_roush.fontbox.ttf; +import android.graphics.Path; + import java.io.IOException; import com.tom_roush.fontbox.util.BoundingBox; -import android.graphics.Path; - /** * A glyph data record in the glyf table. * @@ -42,9 +42,11 @@ public class GlyphData * * @param glyphTable The glyph table this glyph belongs to. * @param data The stream to read the data from. + * @param leftSideBearing The left side bearing for this glyph. * @throws IOException If there is an error reading the data. */ - public void initData( GlyphTable glyphTable, TTFDataStream data ) throws IOException + public void initData(GlyphTable glyphTable, TTFDataStream data, int leftSideBearing) + throws IOException { numberOfContours = data.readSignedShort(); xMin = data.readSignedShort(); @@ -56,7 +58,8 @@ public void initData( GlyphTable glyphTable, TTFDataStream data ) throws IOExcep if (numberOfContours >= 0) { // create a simple glyph - glyphDescription = new GlyfSimpleDescript(numberOfContours, data); + short x0 = (short)(leftSideBearing - xMin); + glyphDescription = new GlyfSimpleDescript(numberOfContours, data, x0); } else { diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphRenderer.java b/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphRenderer.java index bda835412..0ce6f8e6c 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphRenderer.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphRenderer.java @@ -85,51 +85,52 @@ private Path calculatePath(Point[] points) int start = 0; for (int p = 0, len = points.length; p < len; ++p) { - if (points[p].endOfContour) + if (points[p].endOfContour) { - Point firstPoint = points[start]; - Point lastPoint = points[p]; - List contour = new ArrayList(); - for (int q = start; q <= p; ++q) + Point firstPoint = points[start]; + Point lastPoint = points[p]; + List contour = new ArrayList(); + for (int q = start; q <= p; ++q) { - contour.add(points[q]); + contour.add(points[q]); } - if (points[start].onCurve) + if (points[start].onCurve) { - // using start point at the contour end - contour.add(firstPoint); + // using start point at the contour end + contour.add(firstPoint); } - else if (points[p].onCurve) + else if (points[p].onCurve) { - // first is off-curve point, trying to use one from the end - contour.add(0, lastPoint); + // first is off-curve point, trying to use one from the end + contour.add(0, lastPoint); } else { - // start and end are off-curve points, creating implicit one - Point pmid = midValue(firstPoint, lastPoint); - contour.add(0, pmid); - contour.add(pmid); + // start and end are off-curve points, creating implicit one + Point pmid = midValue(firstPoint, lastPoint); + contour.add(0, pmid); + contour.add(pmid); } - moveTo(path, contour.get(0)); - for (int j = 1, clen = contour.size(); j < clen; j++) + moveTo(path, contour.get(0)); + for (int j = 1, clen = contour.size(); j < clen; j++) { - Point pnow = contour.get(j); - if (pnow.onCurve) - { - lineTo(path, pnow); - } - else if (contour.get(j + 1).onCurve) - { - quadTo(path, pnow, contour.get(j + 1)); - ++j; - } - else - { - quadTo(path, pnow, midValue(pnow, contour.get(j + 1))); - } + Point pnow = contour.get(j); + if (pnow.onCurve) + { + lineTo(path, pnow); + } + else if (contour.get(j + 1).onCurve) + { + quadTo(path, pnow, contour.get(j + 1)); + ++j; + } + else + { + quadTo(path, pnow, midValue(pnow, contour.get(j + 1))); + } } - start = p + 1; + path.close(); + start = p + 1; } } return path; diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphTable.java index f25ecc7fb..460de92ae 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/GlyphTable.java @@ -17,8 +17,6 @@ package com.tom_roush.fontbox.ttf; import java.io.IOException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * A table in a true type font. @@ -38,7 +36,18 @@ public class GlyphTable extends TTFTable private TTFDataStream data; private IndexToLocationTable loca; private int numGlyphs; - protected Map cache = new ConcurrentHashMap(); + + private int cached = 0; + + /** + * Don't even bother to cache huge fonts. + */ + private static final int MAX_CACHE_SIZE = 5000; + + /** + * Don't cache more glyphs than this. + */ + private static final int MAX_CACHED_GLYPHS = 100; GlyphTable(TrueTypeFont font) { @@ -52,75 +61,74 @@ public class GlyphTable extends TTFTable * @param data The stream to read the data from. * @throws IOException If there is an error reading the data. */ + @Override public void read(TrueTypeFont ttf, TTFDataStream data) throws IOException { loca = ttf.getIndexToLocation(); numGlyphs = ttf.getNumberOfGlyphs(); + if (numGlyphs < MAX_CACHE_SIZE) + { + // don't cache the huge fonts to save memory + glyphs = new GlyphData[numGlyphs]; + } + // we don't actually read the table yet because it can contain tens of thousands of glyphs this.data = data; initialized = true; } /** - * Reads all glyphs from the font. Can be very slow. + * Returns all glyphs. This method can be very slow. */ - private void readAll() throws IOException + public GlyphData[] getGlyphs() throws IOException { - // the glyph offsets - long[] offsets = loca.getOffsets(); - - // the end of the glyph table - // should not be 0, but sometimes is, see PDFBOX-2044 - // structure of this table: see - // https://developer.apple.com/fonts/TTRefMan/RM06/Chap6loca.html - long endOfGlyphs = offsets[numGlyphs]; - long offset = getOffset(); - glyphs = new GlyphData[numGlyphs]; - for (int i = 0; i < numGlyphs; i++) - { - // end of glyphs reached? - if (endOfGlyphs != 0 && - endOfGlyphs == offsets[i]) - { - break; - } - // the current glyph isn't defined - // if the next offset is equal or smaller to the current offset - if (offsets[i + 1] <= offsets[i]) - { - continue; - } - glyphs[i] = new GlyphData(); - data.seek(offset + offsets[i]); - glyphs[i].initData(this, data); - } - for (int i = 0; i < numGlyphs; i++) + synchronized (font) { - GlyphData glyph = glyphs[i]; - // resolve composite glyphs - if (glyph != null && glyph.getDescription().isComposite()) + // the glyph offsets + long[] offsets = loca.getOffsets(); + + // the end of the glyph table + // should not be 0, but sometimes is, see PDFBOX-2044 + // structure of this table: see + // https://developer.apple.com/fonts/TTRefMan/RM06/Chap6loca.html + long endOfGlyphs = offsets[numGlyphs]; + long offset = getOffset(); + if (glyphs == null) { - glyph.getDescription().resolve(); + glyphs = new GlyphData[numGlyphs]; } - } - initialized = true; - } - /** - * Returns all glyphs. This method can be very slow. - */ - public GlyphData[] getGlyphs() throws IOException - { - if (glyphs == null) - { - synchronized (font) + for (int gid = 0; gid < numGlyphs; gid++) { - readAll(); - ; + // end of glyphs reached? + if (endOfGlyphs != 0 && endOfGlyphs == offsets[gid]) + { + break; + } + // the current glyph isn't defined + // if the next offset is equal or smaller to the current offset + if (offsets[gid + 1] <= offsets[gid]) + { + continue; + } + if (glyphs[gid] != null) + { + // already cached + continue; + } + + data.seek(offset + offsets[gid]); + + if (glyphs[gid] == null) + { + ++cached; + } + glyphs[gid] = getGlyphData(gid); } + initialized = true; + return glyphs; } - return glyphs; } /** @@ -144,36 +152,53 @@ public GlyphData getGlyph(int gid) throws IOException return null; } - synchronized (font) + if (glyphs != null && glyphs[gid] != null) { - // save - long currentPosition = data.getCurrentPosition(); + return glyphs[gid]; + } + synchronized (font) + { // read a single glyph long[] offsets = loca.getOffsets(); - GlyphData glyph; if (offsets[gid] == offsets[gid + 1]) { // no outline - glyph = null; + return null; } - else - { - data.seek(getOffset() + offsets[gid]); - glyph = new GlyphData(); - glyph.initData(this, data); - // resolve composite glyph - if (glyph.getDescription().isComposite()) - { - glyph.getDescription().resolve(); - } - } + // save + long currentPosition = data.getCurrentPosition(); + + data.seek(getOffset() + offsets[gid]); + + GlyphData glyph = getGlyphData(gid); // restore data.seek(currentPosition); + + if (glyphs != null && glyphs[gid] == null && cached < MAX_CACHED_GLYPHS) + { + glyphs[gid] = glyph; + ++cached; + } + return glyph; } } + + private GlyphData getGlyphData(int gid) throws IOException + { + GlyphData glyph = new GlyphData(); + HorizontalMetricsTable hmt = font.getHorizontalMetrics(); + int leftSideBearing = hmt == null ? 0 : hmt.getLeftSideBearing(gid); + glyph.initData(this, data, leftSideBearing); + // resolve composite glyph + if (glyph.getDescription().isComposite()) + { + glyph.getDescription().resolve(); + } + return glyph; + } } diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/HeaderTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/HeaderTable.java index 502af3a24..a03f15727 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/HeaderTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/HeaderTable.java @@ -63,7 +63,7 @@ public class HeaderTable extends TTFTable { super(font); } - + /** * This will read the required data from the stream. * diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/HorizontalHeaderTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/HorizontalHeaderTable.java index f80023e4f..00145badb 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/HorizontalHeaderTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/HorizontalHeaderTable.java @@ -52,7 +52,7 @@ public class HorizontalHeaderTable extends TTFTable { super(font); } - + /** * This will read the required data from the stream. * diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/HorizontalMetricsTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/HorizontalMetricsTable.java index 7ab274ef5..c8e95deed 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/HorizontalMetricsTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/HorizontalMetricsTable.java @@ -105,4 +105,21 @@ public int getAdvanceWidth(int gid) return advanceWidth[advanceWidth.length -1]; } } + + /** + * Returns the left side bearing for the given GID. + * + * @param gid GID + */ + public int getLeftSideBearing(int gid) + { + if (gid < numHMetrics) + { + return leftSideBearing[gid]; + } + else + { + return nonHorizontalLeftSideBearing[gid - numHMetrics]; + } + } } diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/IndexToLocationTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/IndexToLocationTable.java index 8470fbd93..908f46f55 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/IndexToLocationTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/IndexToLocationTable.java @@ -39,7 +39,7 @@ public class IndexToLocationTable extends TTFTable { super(font); } - + /** * This will read the required data from the stream. * diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/KerningSubtable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/KerningSubtable.java index 8f9226c09..33cb41a2f 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/KerningSubtable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/KerningSubtable.java @@ -93,7 +93,7 @@ public boolean isHorizontalKerning() * Determine if subtable is designated for use in horizontal writing modes, contains * kerning pairs (as opposed to minimum pairs), and, if CROSS is true, then return * cross stream designator; otherwise, if CROSS is false, return true if cross stream - * esignator is false. + * designator is false. * * @param cross if true, then return cross stream designator in horizontal modes * @return true if subtable is for horizontal kerning in horizontal modes @@ -172,6 +172,7 @@ public int getKerning(int l, int r) { Log.w("PdfBox-Android", "No kerning subtable data available due to an unsupported kerning subtable version"); + return 0; } return pairs.getKerning(l, r); } diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/MaximumProfileTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/MaximumProfileTable.java index 572bdfe03..4914fe049 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/MaximumProfileTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/MaximumProfileTable.java @@ -50,7 +50,7 @@ public class MaximumProfileTable extends TTFTable { super(font); } - + /** * @return Returns the maxComponentDepth. */ diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/MemoryTTFDataStream.java b/library/src/main/java/com/tom_roush/fontbox/ttf/MemoryTTFDataStream.java index b124ad8d2..1cb244bd2 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/MemoryTTFDataStream.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/MemoryTTFDataStream.java @@ -35,7 +35,7 @@ class MemoryTTFDataStream extends TTFDataStream /** * Constructor from a stream. - * @param is The stream of read from. + * @param is The stream to read from. It will be closed by this method. * @throws IOException If an error occurs while reading from the stream. */ MemoryTTFDataStream( InputStream is ) throws IOException diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/NamingTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/NamingTable.java index 710843525..8ff839d18 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/NamingTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/NamingTable.java @@ -17,11 +17,14 @@ package com.tom_roush.fontbox.ttf; import java.io.IOException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import com.tom_roush.fontbox.util.Charsets; + /** * A table in a true type font. * @@ -34,10 +37,9 @@ public class NamingTable extends TTFTable */ public static final String TAG = "name"; - private final List nameRecords = new ArrayList(); + private List nameRecords; - private final Map>>> lookupTable = - new HashMap>>>(); + private Map>>> lookupTable; private String fontFamily = null; private String fontSubFamily = null; @@ -55,11 +57,13 @@ public class NamingTable extends TTFTable * @param data The stream to read the data from. * @throws IOException If there is an error reading the data. */ + @Override public void read(TrueTypeFont ttf, TTFDataStream data) throws IOException { int formatSelector = data.readUnsignedShort(); int numberOfNameRecords = data.readUnsignedShort(); int offsetToStartOfStringStorage = data.readUnsignedShort(); + nameRecords = new ArrayList(numberOfNameRecords); for (int i=0; i< numberOfNameRecords; i++) { NameRecord nr = new NameRecord(); @@ -67,39 +71,39 @@ public void read(TrueTypeFont ttf, TTFDataStream data) throws IOException nameRecords.add(nr); } - for (int i=0; i getLength()) { - nr.setString(null); - continue; + nr.setString(null); + continue; } data.seek(getOffset() + (2*3)+numberOfNameRecords*2*6+nr.getStringOffset()); int platform = nr.getPlatformId(); int encoding = nr.getPlatformEncodingId(); - String charset = "ISO-8859-1"; - if (platform == 3 && (encoding == 1 || encoding == 0)) + Charset charset = Charsets.ISO_8859_1; + if (platform == NameRecord.PLATFORM_WINDOWS && + (encoding == NameRecord.ENCODING_WINDOWS_SYMBOL || + encoding == NameRecord.ENCODING_WINDOWS_UNICODE_BMP)) { - charset = "UTF-16"; + charset = Charsets.UTF_16; } - else if (platform == 2) + else if (platform == NameRecord.PLATFORM_ISO) { if (encoding == 0) { - charset = "US-ASCII"; + charset = Charsets.US_ASCII; } else if (encoding == 1) { //not sure is this is correct?? - charset = "ISO-10646-1"; + charset = Charsets.ISO_10646; } else if (encoding == 2) { - charset = "ISO-8859-1"; + charset = Charsets.ISO_8859_1; } } @@ -108,33 +112,35 @@ else if (encoding == 2) } // build multi-dimensional lookup table + lookupTable = new HashMap>>>( + nameRecords.size()); for (NameRecord nr : nameRecords) { // name id - if (!lookupTable.containsKey(nr.getNameId())) + Map>> platformLookup = lookupTable.get( + nr.getNameId()); + if (platformLookup == null) { - lookupTable.put(nr.getNameId(), - new HashMap>>()); + platformLookup = new HashMap>>(); + lookupTable.put(nr.getNameId(), platformLookup); } - Map>> platformLookup = - lookupTable.get(nr.getNameId()); // platform id - if (!platformLookup.containsKey(nr.getPlatformId())) + Map> encodingLookup = platformLookup.get( + nr.getPlatformId()); + if (encodingLookup == null) { - platformLookup.put(nr.getPlatformId(), - new HashMap>()); + encodingLookup = new HashMap>(); + platformLookup.put(nr.getPlatformId(), encodingLookup); } - Map> encodingLookup = - platformLookup.get(nr.getPlatformId()); // encoding id - if (!encodingLookup.containsKey(nr.getPlatformEncodingId())) + Map languageLookup = encodingLookup.get(nr.getPlatformEncodingId()); + if (languageLookup == null) { - encodingLookup.put(nr.getPlatformEncodingId(), - new HashMap()); + languageLookup = new HashMap(); + encodingLookup.put(nr.getPlatformEncodingId(), languageLookup); } - Map languageLookup = encodingLookup.get(nr.getPlatformEncodingId()); // language id / string languageLookup.put(nr.getLanguageId(), nr.getString()); @@ -157,6 +163,11 @@ else if (encoding == 2) NameRecord.LANGUGAE_WINDOWS_EN_US); } + if (psName != null) + { + psName = psName.trim(); + } + initialized = true; } @@ -166,7 +177,7 @@ else if (encoding == 2) private String getEnglishName(int nameId) { // Unicode, Full, BMP, 1.1, 1.0 - for (int i = 4; i <= 0; i--) + for (int i = 4; i >= 0; i--) { String nameUni = getName(nameId, diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/OS2WindowsMetricsTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/OS2WindowsMetricsTable.java index 5f4ad295f..3cd516cf4 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/OS2WindowsMetricsTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/OS2WindowsMetricsTable.java @@ -512,7 +512,7 @@ public int getTypoLineGap() /** * @param typoLineGapValue The typoLineGap to set. */ - public void setTypeLineGap(int typoLineGapValue) + public void setTypoLineGap(int typoLineGapValue) { this.typoLineGap = typoLineGapValue; } @@ -698,7 +698,7 @@ public void setWinDescent(int winDescentValue) */ public int getHeight() { - return sxHeight; + return sxHeight; } /** @@ -706,7 +706,7 @@ public int getHeight() */ public int getCapHeight() { - return sCapHeight; + return sCapHeight; } /** @@ -714,7 +714,7 @@ public int getCapHeight() */ public int getDefaultChar() { - return usDefaultChar; + return usDefaultChar; } /** @@ -722,7 +722,7 @@ public int getDefaultChar() */ public int getBreakChar() { - return usBreakChar; + return usBreakChar; } /** @@ -730,7 +730,7 @@ public int getBreakChar() */ public int getMaxContext() { - return usMaxContext; + return usMaxContext; } private int version; @@ -817,17 +817,17 @@ public void read(TrueTypeFont ttf, TTFDataStream data) throws IOException winDescent = data.readUnsignedShort(); if (version >= 1) { - codePageRange1 = data.readUnsignedInt(); - codePageRange2 = data.readUnsignedInt(); + codePageRange1 = data.readUnsignedInt(); + codePageRange2 = data.readUnsignedInt(); } if (version >= 1.2) { - sxHeight = data.readSignedShort(); - sCapHeight = data.readSignedShort(); - usDefaultChar = data.readUnsignedShort(); - usBreakChar = data.readUnsignedShort(); - usMaxContext = data.readUnsignedShort(); + sxHeight = data.readSignedShort(); + sCapHeight = data.readSignedShort(); + usDefaultChar = data.readUnsignedShort(); + usBreakChar = data.readUnsignedShort(); + usMaxContext = data.readUnsignedShort(); } initialized = true; } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/RAFDataStream.java b/library/src/main/java/com/tom_roush/fontbox/ttf/RAFDataStream.java index abd25fc84..a065d4a50 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/RAFDataStream.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/RAFDataStream.java @@ -18,7 +18,6 @@ import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; @@ -32,18 +31,19 @@ class RAFDataStream extends TTFDataStream { private RandomAccessFile raf = null; private File ttfFile = null; + private static final int BUFFERSIZE = 16834; /** * Constructor. * * @param name The raf file. * @param mode The mode to open the RAF. - * - * @throws FileNotFoundException If there is a problem creating the RAF. + * + * @throws IOException If there is a problem creating the RAF. * * @see RandomAccessFile#RandomAccessFile( String, String ) */ - RAFDataStream(String name, String mode) throws FileNotFoundException + RAFDataStream(String name, String mode) throws IOException { this( new File( name ), mode ); } @@ -53,14 +53,14 @@ class RAFDataStream extends TTFDataStream * * @param file The raf file. * @param mode The mode to open the RAF. - * - * @throws FileNotFoundException If there is a problem creating the RAF. + * + * @throws IOException If there is a problem creating the RAF. * * @see RandomAccessFile#RandomAccessFile( File, String ) */ - RAFDataStream(File file, String mode) throws FileNotFoundException + RAFDataStream(File file, String mode) throws IOException { - raf = new RandomAccessFile( file, mode ); + raf = new BufferedRandomAccessFile(file, mode, BUFFERSIZE); ttfFile = file; } @@ -70,6 +70,7 @@ class RAFDataStream extends TTFDataStream * @return An signed short. * @throws IOException If there is an error reading the data. */ + @Override public short readSignedShort() throws IOException { return raf.readShort(); @@ -80,6 +81,7 @@ public short readSignedShort() throws IOException * @return The current position in the stream. * @throws IOException If an error occurs while reading the stream. */ + @Override public long getCurrentPosition() throws IOException { return raf.getFilePointer(); @@ -90,6 +92,7 @@ public long getCurrentPosition() throws IOException * * @throws IOException If there is an error closing the resources. */ + @Override public void close() throws IOException { raf.close(); @@ -101,6 +104,7 @@ public void close() throws IOException * @return An unsigned byte. * @throws IOException If there is an error reading the data. */ + @Override public int read() throws IOException { return raf.read(); @@ -112,6 +116,7 @@ public int read() throws IOException * @return An unsigned short. * @throws IOException If there is an error reading the data. */ + @Override public int readUnsignedShort() throws IOException { return raf.readUnsignedShort(); @@ -122,6 +127,7 @@ public int readUnsignedShort() throws IOException * @return An unsigned byte. * @throws IOException If there is an error reading the data. */ + @Override public long readLong() throws IOException { return raf.readLong(); @@ -133,6 +139,7 @@ public long readLong() throws IOException * @param pos The position to seek to. * @throws IOException If there is an error seeking to that position. */ + @Override public void seek(long pos) throws IOException { raf.seek( pos ); @@ -149,6 +156,7 @@ public void seek(long pos) throws IOException * * @throws IOException If there is an error reading from the stream. */ + @Override public int read(byte[] b, int off, int len) throws IOException { return raf.read(b, off, len); @@ -157,6 +165,7 @@ public int read(byte[] b, int off, int len) throws IOException /** * {@inheritDoc} */ + @Override public InputStream getOriginalData() throws IOException { return new FileInputStream( ttfFile ); diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/TTFDataStream.java b/library/src/main/java/com/tom_roush/fontbox/ttf/TTFDataStream.java index 919bd478d..d312700ae 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/TTFDataStream.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/TTFDataStream.java @@ -20,10 +20,13 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.TimeZone; +import com.tom_roush.fontbox.util.Charsets; + /** * An interface into a data stream. * @@ -58,11 +61,11 @@ public float read32Fixed() throws IOException */ public String readString(int length) throws IOException { - return readString(length, "ISO-8859-1"); + return readString(length, Charsets.ISO_8859_1); } /** - * Read a fixed length ascii string. + * Read a fixed length string. * * @param length The length of the string to read in bytes. * @param charset The expected character set of the string. @@ -75,6 +78,20 @@ public String readString(int length, String charset) throws IOException return new String(buffer, charset); } + /** + * Read a fixed length string. + * + * @param length The length of the string to read in bytes. + * @param charset The expected character set of the string. + * @return A string of the desired length. + * @throws IOException If there is an error reading the data. + */ + public String readString(int length, Charset charset) throws IOException + { + byte[] buffer = read(length); + return new String(buffer, charset); + } + /** * Read an unsigned byte. * @@ -207,12 +224,12 @@ public Calendar readInternationalDate() throws IOException } /** - * Reads a tag, an arrau of four uint8s used to identify a script, language system, feature, + * Reads a tag, an array of four uint8s used to identify a script, language system, feature, * or baseline. */ public String readTag() throws IOException { - return new String(read(4), "US-ASCII"); + return new String(read(4), Charsets.US_ASCII); } /** diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/TTFParser.java b/library/src/main/java/com/tom_roush/fontbox/ttf/TTFParser.java index 7e7ab8a89..719ac6c77 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/TTFParser.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/TTFParser.java @@ -61,11 +61,11 @@ public TTFParser(boolean isEmbedded, boolean parseOnDemand) } /** - * Parse a file and get a true type font. + * Parse a file and return a TrueType font. * - * @param ttfFile The TTF file. - * @return A true type font. - * @throws IOException If there is an error parsing the true type font. + * @param ttfFile The TrueType font filename. + * @return A TrueType font. + * @throws IOException If there is an error parsing the TrueType font. */ public TrueTypeFont parse(String ttfFile) throws IOException { @@ -73,48 +73,58 @@ public TrueTypeFont parse(String ttfFile) throws IOException } /** - * Parse a file and get a true type font. + * Parse a file and return a TrueType font. * - * @param ttfFile The TTF file. - * @return A true type font. - * @throws IOException If there is an error parsing the true type font. + * @param ttfFile The TrueType font file. + * @return A TrueType font. + * @throws IOException If there is an error parsing the TrueType font. */ public TrueTypeFont parse(File ttfFile) throws IOException { - return parse(new RAFDataStream(ttfFile, "r")); + RAFDataStream raf = new RAFDataStream(ttfFile, "r"); + try + { + return parse(raf); + } + catch (IOException ex) + { + // close only on error (file is still being accessed later) + raf.close(); + throw ex; + } } /** - * Parse a file and get a true type font. + * Parse an input stream and return a TrueType font. * - * @param ttfData The TTF data to parse. - * @return A true type font. - * @throws IOException If there is an error parsing the true type font. + * @param inputStream The TTF data stream to parse from. It will be closed before returning. + * @return A TrueType font. + * @throws IOException If there is an error parsing the TrueType font. */ - public TrueTypeFont parse(InputStream ttfData) throws IOException + public TrueTypeFont parse(InputStream inputStream) throws IOException { - return parse(new MemoryTTFDataStream(ttfData)); + return parse(new MemoryTTFDataStream(inputStream)); } /** - Parse a file and get a true type font. - - * @param ttfData The TTF data to parse. - * @return A true type font. - * @throws IOException If there is an error parsing the true type font. + * Parse an input stream and return a TrueType font that is to be embedded. + * + * @param inputStream The TTF data stream to parse from. It will be closed before returning. + * @return A TrueType font. + * @throws IOException If there is an error parsing the TrueType font. */ - public TrueTypeFont parseEmbedded(InputStream ttfData) throws IOException + public TrueTypeFont parseEmbedded(InputStream inputStream) throws IOException { - this.isEmbedded = true; - return parse(new MemoryTTFDataStream(ttfData)); + this.isEmbedded = true; + return parse(new MemoryTTFDataStream(inputStream)); } /** - * Parse a file and get a true type font. + * Parse a file and get a TrueType font. * * @param raf The TTF file. - * @return A true type font. - * @throws IOException If there is an error parsing the true type font. + * @return A TrueType font. + * @throws IOException If there is an error parsing the TrueType font. */ TrueTypeFont parse(TTFDataStream raf) throws IOException { @@ -127,7 +137,12 @@ TrueTypeFont parse(TTFDataStream raf) throws IOException for (int i = 0; i < numberOfTables; i++) { TTFTable table = readTableDirectory(font, raf); - font.addTable(table); + + // skip tables with zero length + if (table != null) + { + font.addTable(table); + } } // parse tables if wanted if (!parseOnDemandOnly) @@ -148,7 +163,7 @@ TrueTypeFont newFont(TTFDataStream raf) * * @param font the TrueTypeFont instance holding the parsed data. * @param raf the data stream of the to be parsed ttf font - * @throws IOException If there is an error parsing the true type font. + * @throws IOException If there is an error parsing the TrueType font. */ private void parseTables(TrueTypeFont font, TTFDataStream raf) throws IOException { @@ -283,6 +298,13 @@ else if (tag.equals(VerticalOriginTable.TAG)) table.setCheckSum(raf.readUnsignedInt()); table.setOffset(raf.readUnsignedInt()); table.setLength(raf.readUnsignedInt()); + + // skip tables with zero length + if (table.getLength() == 0) + { + return null; + } + return table; } diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/TTFSubsetter.java b/library/src/main/java/com/tom_roush/fontbox/ttf/TTFSubsetter.java index dfab73385..00f638412 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/TTFSubsetter.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/TTFSubsetter.java @@ -944,31 +944,31 @@ public void writeToStream(OutputStream os) throws IOException Map tables = new TreeMap(); if (os2 != null) { - tables.put("OS/2", os2); + tables.put("OS/2", os2); } if (cmap != null) { - tables.put("cmap", cmap); + tables.put("cmap", cmap); } if (glyf != null) { - tables.put("glyf", glyf); + tables.put("glyf", glyf); } tables.put("head", head); tables.put("hhea", hhea); tables.put("hmtx", hmtx); if (loca != null) { - tables.put("loca", loca); + tables.put("loca", loca); } tables.put("maxp", maxp); if (name != null) { - tables.put("name", name); + tables.put("name", name); } if (post != null) { - tables.put("post", post); + tables.put("post", post); } // copy all other tables diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/TrueTypeCollection.java b/library/src/main/java/com/tom_roush/fontbox/ttf/TrueTypeCollection.java index 70a516749..8892dfcbe 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/TrueTypeCollection.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/TrueTypeCollection.java @@ -21,9 +21,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; /** * A TrueType Collection, now more properly known as a "Font Collection" as it may contain either @@ -34,7 +31,8 @@ public class TrueTypeCollection implements Closeable { private final TTFDataStream stream; - private final List fonts; + private final int numFonts; + private final long[] fontOffsets; /** * Creates a new TrueTypeCollection from a .ttc file. @@ -75,48 +73,78 @@ public TrueTypeCollection(InputStream stream) throws IOException throw new IOException("Missing TTC header"); } float version = stream.read32Fixed(); - int numFonts = (int) stream.readUnsignedInt(); - long[] fontOffsets = new long[numFonts]; + numFonts = (int)stream.readUnsignedInt(); + fontOffsets = new long[numFonts]; for (int i = 0; i < numFonts; i++) { fontOffsets[i] = stream.readUnsignedInt(); } if (version >= 2) { + // not used at this time int ulDsigTag = stream.readUnsignedShort(); int ulDsigLength = stream.readUnsignedShort(); int ulDsigOffset = stream.readUnsignedShort(); } + } - // lazy-load the fonts - List fonts = new ArrayList(); + /** + * Run the callback for each TT font in the collection. + * + * @param trueTypeFontProcessor the object with the callback method. + * @throws IOException + */ + public void processAllFonts(TrueTypeFontProcessor trueTypeFontProcessor) throws IOException + { for (int i = 0; i < numFonts; i++) { - stream.seek(fontOffsets[i]); - if (stream.readTag().equals("OTTO")) - { - stream.seek(fontOffsets[i]); - OTFParser parser = new OTFParser(false, true); - OpenTypeFont otf = parser.parse(new TTCDataStream(stream)); - fonts.add(otf); - } - else + TrueTypeFont font = getFontAtIndex(i); + trueTypeFontProcessor.process(font); + } + } + + private TrueTypeFont getFontAtIndex(int idx) throws IOException + { + stream.seek(fontOffsets[idx]); + TTFParser parser; + if (stream.readTag().equals("OTTO")) + { + parser = new OTFParser(false, true); + } + else + { + parser = new TTFParser(false, true); + } + stream.seek(fontOffsets[idx]); + return parser.parse(new TTCDataStream(stream)); + } + + /** + * Get a TT font from a collection. + * + * @param name The postscript name of the font. + * @return The found font, nor null if none is found. + * @throws IOException + */ + public TrueTypeFont getFontByName(String name) throws IOException + { + for (int i = 0; i < numFonts; i++) + { + TrueTypeFont font = getFontAtIndex(i); + if (font.getName().equals(name)) { - stream.seek(fontOffsets[i]); - TTFParser parser = new TTFParser(false, true); - TrueTypeFont ttf = parser.parse(new TTCDataStream(stream)); - fonts.add(ttf); + return font; } } - this.fonts = Collections.unmodifiableList(fonts); + return null; } /** - * Returns the fonts in the collection, these may be {@link OpenTypeFont} instances. + * Implement the callback method to call {@link TrueTypeCollection#processAllFonts(TrueTypeFontProcessor)}. */ - public List getFonts() + public interface TrueTypeFontProcessor { - return fonts; + void process(TrueTypeFont ttf) throws IOException; } @Override diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/TrueTypeFont.java b/library/src/main/java/com/tom_roush/fontbox/ttf/TrueTypeFont.java index b37324453..278958b7f 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/TrueTypeFont.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/TrueTypeFont.java @@ -369,6 +369,7 @@ public InputStream getOriginalData() throws IOException * Read the given table if necessary. Package-private, used by TTFParser only. * * @param table the table to be initialized + * * @throws IOException if there was an error reading the table. */ void readTable(TTFTable table) throws IOException @@ -486,20 +487,21 @@ public String getName() throws IOException private synchronized void readPostScriptNames() throws IOException { - if (postScriptNames == null) + if (postScriptNames == null && getPostScript() != null) { - postScriptNames = new HashMap(); - if (getPostScript() != null) + String[] names = getPostScript().getGlyphNames(); + if (names != null) { - String[] names = getPostScript().getGlyphNames(); - if (names != null) + postScriptNames = new HashMap(names.length); + for (int i = 0; i < names.length; i++) { - for (int i = 0; i < names.length; i++) - { - postScriptNames.put(names[i], i); - } + postScriptNames.put(names[i], i); } } + else + { + postScriptNames = new HashMap(); + } } } @@ -526,7 +528,14 @@ public CmapSubtable getUnicodeCmap(boolean isStrict) throws IOException CmapTable cmapTable = getCmap(); if (cmapTable == null) { - return null; + if (isStrict) + { + throw new IOException("The TrueType font does not contain a 'cmap' table"); + } + else + { + return null; + } } CmapSubtable cmap = cmapTable.getSubtable(CmapTable.PLATFORM_UNICODE, @@ -577,6 +586,7 @@ public int nameToGID(String name) throws IOException { return gid; } + // look up in 'cmap' int uni = parseUniName(name); if (uni > -1) @@ -584,6 +594,7 @@ public int nameToGID(String name) throws IOException CmapSubtable cmap = getUnicodeCmap(false); return cmap.getGlyphId(uni); } + return 0; } @@ -624,12 +635,7 @@ private int parseUniName(String name) throws IOException @Override public Path getPath(String name) throws IOException { - readPostScriptNames(); int gid = nameToGID(name); - if (gid < 0 || gid >= getMaximumProfile().getNumGlyphs()) - { - gid = 0; - } // some glyphs have no outlines (e.g. space, table, newline) GlyphData glyph = getGlyph().getGlyph(gid); diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/VerticalOriginTable.java b/library/src/main/java/com/tom_roush/fontbox/ttf/VerticalOriginTable.java index 91b34ffb3..7d9b881ee 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/VerticalOriginTable.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/VerticalOriginTable.java @@ -43,7 +43,7 @@ public class VerticalOriginTable extends TTFTable private float version; private int defaultVertOriginY; - private Map origins = new ConcurrentHashMap(); + private Map origins; VerticalOriginTable(TrueTypeFont font) { @@ -63,6 +63,7 @@ public void read(TrueTypeFont ttf, TTFDataStream data) throws IOException version = data.read32Fixed(); defaultVertOriginY = data.readSignedShort(); int numVertOriginYMetrics = data.readUnsignedShort(); + origins = new ConcurrentHashMap(numVertOriginYMetrics); for (int i = 0; i < numVertOriginYMetrics; ++i) { int g = data.readUnsignedShort(); diff --git a/library/src/main/java/com/tom_roush/fontbox/ttf/WGL4Names.java b/library/src/main/java/com/tom_roush/fontbox/ttf/WGL4Names.java index eaeb441f8..2688505d9 100644 --- a/library/src/main/java/com/tom_roush/fontbox/ttf/WGL4Names.java +++ b/library/src/main/java/com/tom_roush/fontbox/ttf/WGL4Names.java @@ -85,14 +85,14 @@ public final class WGL4Names static { - MAC_GLYPH_NAMES_INDICES = new HashMap(); + MAC_GLYPH_NAMES_INDICES = new HashMap(NUMBER_OF_MAC_GLYPHS); for (int i = 0; i < NUMBER_OF_MAC_GLYPHS; ++i) { MAC_GLYPH_NAMES_INDICES.put(MAC_GLYPH_NAMES[i],i); } } - - private WGL4Names () + + private WGL4Names() { } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/exceptions/COSVisitorException.java b/library/src/main/java/com/tom_roush/fontbox/util/Charsets.java similarity index 51% rename from library/src/main/java/com/tom_roush/pdfbox/exceptions/COSVisitorException.java rename to library/src/main/java/com/tom_roush/fontbox/util/Charsets.java index 7bcd38e11..4a27abb9f 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/exceptions/COSVisitorException.java +++ b/library/src/main/java/com/tom_roush/fontbox/util/Charsets.java @@ -14,26 +14,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.tom_roush.pdfbox.exceptions; +package com.tom_roush.fontbox.util; + +import java.nio.charset.Charset; /** - * An exception that represents something gone wrong when visiting a PDF object. + * This class provides an instance of all common charsets used to transform byte arrays into strings. * - * @author Michael Traut - * @version $Revision: 1.6 $ */ -public class COSVisitorException extends Exception +public final class Charsets { + private Charsets() {} /** - * COSVisitorException constructor comment. - * - * @param e The root exception that caused this exception. + * ISO-8859-1 Charset */ - public COSVisitorException( Exception e ) - { - super( e ); - } - + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + /** + * UTF-16 Charset + */ + public static final Charset UTF_16 = Charset.forName("UTF-16"); + /** + * UTF-16BE Charset + */ + public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); + /** + * US-ASCII Charset + */ + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + /** + * ISO-10646 Charset + */ + public static final Charset ISO_10646 = Charset.forName("ISO-10646-UCS-2"); } diff --git a/library/src/main/java/com/tom_roush/fontbox/util/autodetect/FontFileFinder.java b/library/src/main/java/com/tom_roush/fontbox/util/autodetect/FontFileFinder.java index 0174b4773..935135bf4 100644 --- a/library/src/main/java/com/tom_roush/fontbox/util/autodetect/FontFileFinder.java +++ b/library/src/main/java/com/tom_roush/fontbox/util/autodetect/FontFileFinder.java @@ -40,25 +40,26 @@ public FontFileFinder() private FontDirFinder determineDirFinder() { // Should only return with an Android Font Directory - -// final String osName = System.getProperty("os.name"); -// if (osName.startsWith("Windows")) -// { -// return new WindowsFontDirFinder(); -// } -// else -// { -// if (osName.startsWith("Mac")) -// { -// return new MacFontDirFinder(); -// } -// else -// { if(System.getProperty("java.vendor").equals("The Android Project")) { return new AndroidFontDirFinder(); } else { - return new UnixFontDirFinder(); // Just in case -// } + // Should never happen, but it's here just in case + final String osName = System.getProperty("os.name"); + if (osName.startsWith("Windows")) + { + return new WindowsFontDirFinder(); + } + else + { + if (osName.startsWith("Mac")) + { + return new MacFontDirFinder(); + } + else + { + return new UnixFontDirFinder(); + } + } } } diff --git a/library/src/main/java/com/tom_roush/fontbox/util/autodetect/UnixFontDirFinder.java b/library/src/main/java/com/tom_roush/fontbox/util/autodetect/UnixFontDirFinder.java index e6a0eabd8..c8fda2cac 100644 --- a/library/src/main/java/com/tom_roush/fontbox/util/autodetect/UnixFontDirFinder.java +++ b/library/src/main/java/com/tom_roush/fontbox/util/autodetect/UnixFontDirFinder.java @@ -17,10 +17,6 @@ package com.tom_roush.fontbox.util.autodetect; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - /** * Unix font directory finder. This class is based on a class provided by Apache FOP. see * com.tom_roush.fop.fonts.autodetect.UnixFontDirFinder @@ -43,31 +39,4 @@ protected String[] getSearchableDirectories() "/usr/X11R6/lib/X11/fonts" // X }; } - - /** - * {@inheritDoc} - */ - public Map getCommonTTFMapping() - { - HashMap map = new HashMap(); - map.put("TimesNewRoman,BoldItalic","LiberationSerif-BoldItalic"); - map.put("TimesNewRoman,Bold","LiberationSerif-Bold"); - map.put("TimesNewRoman,Italic","LiberationSerif-Italic"); - map.put("TimesNewRoman","LiberationSerif"); - - map.put("Arial,BoldItalic","LiberationSans-BoldItalic"); - map.put("Arial,Italic","LiberationSans-Italic"); - map.put("Arial,Bold","LiberationSans-Bold"); - map.put("Arial","LiberationSans"); - - map.put("Courier,BoldItalic","LiberationMono-BoldItalic"); - map.put("Courier,Italic","LiberationMono-Italic"); - map.put("Courier,Bold","LiberationMono-Bold"); - map.put("Courier","LiberationMono"); - - map.put("Symbol", "OpenSymbol"); - map.put("ZapfDingbats", "Dingbats"); - return Collections.unmodifiableMap(map); - } - } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDContentStream.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDContentStream.java index c5a3274c3..9e8ac77bb 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDContentStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDContentStream.java @@ -39,12 +39,12 @@ public interface PDContentStream InputStream getContents() throws IOException; /** - * Returns this stream's resources + * Returns this stream's resources, if any. */ PDResources getResources(); /** - * Returns the bounding box of the contents, if any. + * Returns the bounding box of the contents. */ PDRectangle getBBox(); @@ -52,4 +52,4 @@ public interface PDContentStream * Returns the matrix which transforms from the stream's space to user space. */ Matrix getMatrix(); -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFGraphicsStreamEngine.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFGraphicsStreamEngine.java index cd818570e..2ff49d642 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFGraphicsStreamEngine.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFGraphicsStreamEngine.java @@ -24,14 +24,17 @@ import com.tom_roush.pdfbox.contentstream.operator.color.SetNonStrokingColor; import com.tom_roush.pdfbox.contentstream.operator.color.SetNonStrokingColorN; import com.tom_roush.pdfbox.contentstream.operator.color.SetNonStrokingColorSpace; +import com.tom_roush.pdfbox.contentstream.operator.color.SetNonStrokingDeviceCMYKColor; import com.tom_roush.pdfbox.contentstream.operator.color.SetNonStrokingDeviceGrayColor; import com.tom_roush.pdfbox.contentstream.operator.color.SetNonStrokingDeviceRGBColor; import com.tom_roush.pdfbox.contentstream.operator.color.SetStrokingColor; import com.tom_roush.pdfbox.contentstream.operator.color.SetStrokingColorN; import com.tom_roush.pdfbox.contentstream.operator.color.SetStrokingColorSpace; +import com.tom_roush.pdfbox.contentstream.operator.color.SetStrokingDeviceCMYKColor; import com.tom_roush.pdfbox.contentstream.operator.color.SetStrokingDeviceGrayColor; import com.tom_roush.pdfbox.contentstream.operator.color.SetStrokingDeviceRGBColor; import com.tom_roush.pdfbox.contentstream.operator.graphics.AppendRectangleToPath; +import com.tom_roush.pdfbox.contentstream.operator.graphics.BeginInlineImage; import com.tom_roush.pdfbox.contentstream.operator.graphics.ClipEvenOddRule; import com.tom_roush.pdfbox.contentstream.operator.graphics.ClipNonZeroRule; import com.tom_roush.pdfbox.contentstream.operator.graphics.CloseAndStrokePath; @@ -90,21 +93,23 @@ * * @author John Hewson */ -public abstract class PDFGraphicsStreamEngine extends PDFStreamEngine { +public abstract class PDFGraphicsStreamEngine extends PDFStreamEngine +{ // may be null, for example if the stream is a tiling pattern private final PDPage page; /** * Constructor. */ - protected PDFGraphicsStreamEngine(PDPage page) { + protected PDFGraphicsStreamEngine(PDPage page) + { this.page = page; addOperator(new CloseFillNonZeroAndStrokePath()); addOperator(new FillNonZeroAndStrokePath()); addOperator(new CloseFillEvenOddAndStrokePath()); addOperator(new FillEvenOddAndStrokePath()); -// addOperator(new BeginInlineImage());TODO: PdfBox-Android + addOperator(new BeginInlineImage()); addOperator(new BeginText()); addOperator(new CurveTo()); addOperator(new Concatenate()); @@ -123,8 +128,8 @@ protected PDFGraphicsStreamEngine(PDPage page) { addOperator(new SetFlatness()); addOperator(new SetLineJoinStyle()); addOperator(new SetLineCapStyle()); -// addOperator(new SetStrokingDeviceCMYKColor());TODO: PdfBox-Android -// addOperator(new SetNonStrokingDeviceCMYKColor());TODO: PdfBox-Android + addOperator(new SetStrokingDeviceCMYKColor()); + addOperator(new SetNonStrokingDeviceCMYKColor()); addOperator(new LineTo()); addOperator(new MoveTo()); addOperator(new SetLineMiterLimit()); @@ -167,7 +172,8 @@ protected PDFGraphicsStreamEngine(PDPage page) { /** * Returns the page. */ - protected final PDPage getPage() { + protected final PDPage getPage() + { return page; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFStreamEngine.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFStreamEngine.java index a293edeba..9c75f1973 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFStreamEngine.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/PDFStreamEngine.java @@ -53,6 +53,7 @@ import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColor; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; +import com.tom_roush.pdfbox.pdmodel.graphics.form.PDTransparencyGroup; import com.tom_roush.pdfbox.pdmodel.graphics.pattern.PDTilingPattern; import com.tom_roush.pdfbox.pdmodel.graphics.state.PDGraphicsState; import com.tom_roush.pdfbox.pdmodel.graphics.state.PDTextState; @@ -69,7 +70,8 @@ */ public abstract class PDFStreamEngine { - private final Map operators = new HashMap(); + private final Map operators = new HashMap( + 80); private Matrix textMatrix; private Matrix textLineMatrix; @@ -154,7 +156,7 @@ public void processPage(PDPage page) throws IOException * @param form transparency group (form) XObject * @throws IOException if the transparency group cannot be processed */ - public void showTransparencyGroup(PDFormXObject form) throws IOException + public void showTransparencyGroup(PDTransparencyGroup form) throws IOException { processTransparencyGroup(form); } @@ -178,19 +180,19 @@ public void showForm(PDFormXObject form) throws IOException /** * Processes a soft mask transparency group stream. */ - protected void processSoftMask(PDFormXObject group) throws IOException + protected void processSoftMask(PDTransparencyGroup group) throws IOException { // clear the current soft mask (this mask) to avoid recursion - Stack savedStack = saveGraphicsStack(); + saveGraphicsState(); getGraphicsState().setSoftMask(null); processTransparencyGroup(group); - restoreGraphicsStack(savedStack); + restoreGraphicsState(); } /** * Processes a transparency group stream. */ - protected void processTransparencyGroup(PDFormXObject group) throws IOException + protected void processTransparencyGroup(PDTransparencyGroup group) throws IOException { if (currentPage == null) { @@ -204,7 +206,8 @@ protected void processTransparencyGroup(PDFormXObject group) throws IOException // transform the CTM using the stream's matrix getGraphicsState().getCurrentTransformationMatrix().concatenate(group.getMatrix()); - // note: we don't clip to the BBox as it is often wrong, see PDFBOX-1917 + // clip to bounding box + clipToRect(group.getBBox()); processStreamOperators(group); @@ -217,6 +220,7 @@ protected void processTransparencyGroup(PDFormXObject group) throws IOException * * @param charProc Type 3 character procedure * @param textRenderingMatrix the Text Rendering Matrix + * @throws IOException if there is an error reading or parsing the character content stream. */ protected void processType3Stream(PDType3CharProc charProc, Matrix textRenderingMatrix) throws IOException @@ -228,7 +232,7 @@ protected void processType3Stream(PDType3CharProc charProc, Matrix textRendering } PDResources parent = pushResources(charProc); - saveGraphicsState(); + Stack savedStack = saveGraphicsStack(); // replace the CTM with the TRM getGraphicsState().setCurrentTransformationMatrix(textRenderingMatrix); @@ -250,7 +254,7 @@ protected void processType3Stream(PDType3CharProc charProc, Matrix textRendering textMatrix = textMatrixOld; textLineMatrix = textLineMatrixOld; - restoreGraphicsState(); + restoreGraphicsStack(savedStack); popResources(parent); } @@ -259,19 +263,20 @@ protected void processType3Stream(PDType3CharProc charProc, Matrix textRendering * * @param annotation The annotation containing the appearance stream to process. * @param appearance The appearance stream to process. + * @throws IOException If there is an error reading or parsing the appearance content stream. */ protected void processAnnotation(PDAnnotation annotation, PDAppearanceStream appearance) throws IOException { PDResources parent = pushResources(appearance); - saveGraphicsState(); + Stack savedStack = saveGraphicsStack(); PDRectangle bbox = appearance.getBBox(); PDRectangle rect = annotation.getRectangle(); Matrix matrix = appearance.getMatrix(); // zero-sized rectangles are not valid - if (rect.getWidth() > 0 && rect.getHeight() > 0) + if (rect != null && rect.getWidth() > 0 && rect.getHeight() > 0 && bbox != null) { // transformed appearance box fixme: may be an arbitrary shape RectF transformedBox = new RectF(); @@ -287,7 +292,10 @@ protected void processAnnotation(PDAnnotation annotation, PDAppearanceStream app // Matrix shall be concatenated with A to form a matrix AA that maps from the appearance's // coordinate system to the annotation's rectangle in default user space - Matrix aa = Matrix.concatenate(matrix, a); + // + // HOWEVER only the opposite order works for rotated pages with + // filled fields / annotations that have a matrix in the appearance stream, see PDFBOX-3083 + Matrix aa = Matrix.concatenate(a, matrix); // make matrix AA the CTM getGraphicsState().setCurrentTransformationMatrix(aa); @@ -298,7 +306,7 @@ protected void processAnnotation(PDAnnotation annotation, PDAppearanceStream app processStreamOperators(appearance); } - restoreGraphicsState(); + restoreGraphicsStack(savedStack); popResources(parent); } @@ -309,6 +317,7 @@ protected void processAnnotation(PDAnnotation annotation, PDAppearanceStream app * @param tilingPattern the tiling pattern * @param color color to use, if this is an uncoloured pattern, otherwise null. * @param colorSpace color space to use, if this is an uncoloured pattern, otherwise null. + * @throws IOException if there is an error reading or parsing the tiling pattern content stream. */ protected final void processTilingPattern(PDTilingPattern tilingPattern, PDColor color, PDColorSpace colorSpace) throws IOException @@ -324,10 +333,10 @@ protected final void processTilingPattern(PDTilingPattern tilingPattern, PDColor * @param color color to use, if this is an uncoloured pattern, otherwise null. * @param colorSpace color space to use, if this is an uncoloured pattern, otherwise null. * @param patternMatrix the pattern matrix, may be overridden for custom rendering. + * @throws IOException if there is an error reading or parsing the tiling pattern content stream. */ protected final void processTilingPattern(PDTilingPattern tilingPattern, PDColor color, - PDColorSpace colorSpace, Matrix patternMatrix) - throws IOException + PDColorSpace colorSpace, Matrix patternMatrix) throws IOException { PDResources parent = pushResources(tilingPattern); @@ -395,9 +404,10 @@ public PDAppearanceStream getAppearance(PDAnnotation annotation) } /** - * Process a child stream of the given page. Cannot be used with #processPage(PDPage). + * Process a child stream of the given page. Cannot be used with {@link #processPage(PDPage)}. * * @param contentStream the child content stream + * @param page * @throws IOException if there is an exception while processing the stream */ protected void processChildStream(PDContentStream contentStream, PDPage page) throws IOException @@ -443,6 +453,9 @@ private void processStream(PDContentStream contentStream) throws IOException /** * Processes the operators of the given content stream. + * + * @param contentStream to content stream to parse. + * @throws IOException if there is an error reading or parsing the content stream. */ private void processStreamOperators(PDContentStream contentStream) throws IOException { @@ -563,7 +576,12 @@ public void showTextStrings(COSArray array) throws IOException PDTextState textState = getGraphicsState().getTextState(); float fontSize = textState.getFontSize(); float horizontalScaling = textState.getHorizontalScaling() / 100f; - boolean isVertical = textState.getFont() != null && textState.getFont().isVertical(); + PDFont font = textState.getFont(); + boolean isVertical = false; + if (font != null) + { + isVertical = font.isVertical(); + } for (COSBase obj : array) { @@ -812,7 +830,7 @@ protected void processOperator(Operator operator, List operands) throws * Called when an unsupported operator is encountered. * * @param operator The unknown operator. - * @param operands The list of arguments. + * @param operands The list of operands. */ protected void unsupportedOperator(Operator operator, List operands) throws IOException { @@ -982,7 +1000,9 @@ public PointF transformedPoint(float x, float y) return new PointF(position[0], position[1]); } - // transforms a width using the CTM + /** + * Transforms a width using the CTM + */ protected float transformWidth(float width) { Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/DrawObject.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/DrawObject.java index a80067a9e..035d74a42 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/DrawObject.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/DrawObject.java @@ -23,7 +23,7 @@ import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.graphics.PDXObject; import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; -import com.tom_roush.pdfbox.text.PDFMarkedContentExtractor; +import com.tom_roush.pdfbox.pdmodel.graphics.form.PDTransparencyGroup; /** * Do: Draws an XObject. @@ -36,18 +36,32 @@ public class DrawObject extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { - COSName name = (COSName) arguments.get(0); + if (arguments.size() < 1) + { + throw new MissingOperandException(operator, arguments); + } + COSBase base0 = arguments.get(0); + if (!(base0 instanceof COSName)) + { + return; + } + COSName name = (COSName)base0; - PDXObject xobject = context.getResources().getXObject(name); - if (context instanceof PDFMarkedContentExtractor) + if (context.getResources().isImageXObject(name)) { - ((PDFMarkedContentExtractor) context).xobject(xobject); + // we're done here, don't decode images when doing text extraction + return; } - if(xobject instanceof PDFormXObject) + PDXObject xobject = context.getResources().getXObject(name); + + if (xobject instanceof PDTransparencyGroup) + { + context.showTransparencyGroup((PDTransparencyGroup)xobject); + } + else if (xobject instanceof PDFormXObject) { PDFormXObject form = (PDFormXObject)xobject; - context.showForm(form); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/OperatorProcessor.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/OperatorProcessor.java index a71d26cbf..a910bc826 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/OperatorProcessor.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/OperatorProcessor.java @@ -69,4 +69,24 @@ public void setContext(PDFStreamEngine context) * Returns the name of this operator, e.g. "BI". */ public abstract String getName(); + + /** + * Check whether all operands list elements are an instance of a specific class. + * + * @param operands The operands list. + * @param clazz The expected class. + * + * @return the boolean + */ + public boolean checkArrayTypesClass(List operands, Class clazz) + { + for (COSBase base : operands) + { + if (!clazz.isInstance(base)) + { + return false; + } + } + return true; + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetNonStrokingDeviceCMYKColor.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetNonStrokingDeviceCMYKColor.java new file mode 100644 index 000000000..3647c9ddc --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetNonStrokingDeviceCMYKColor.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 com.tom_roush.pdfbox.contentstream.operator.color; + +import java.io.IOException; +import java.util.List; + +import com.tom_roush.pdfbox.contentstream.operator.Operator; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; + +/** + * k: Set the non-stroking colour space to DeviceCMYK and set the colour to + * use for non-stroking operations. + * + * @author John Hewson + */ +public class SetNonStrokingDeviceCMYKColor extends SetNonStrokingColor +{ + @Override + public void process(Operator operator, List arguments) throws IOException + { + PDColorSpace cs = context.getResources().getColorSpace(COSName.DEVICECMYK); + context.getGraphicsState().setNonStrokingColorSpace(cs); + super.process(operator, arguments); + } + + @Override + public String getName() + { + return "k"; + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetStrokingDeviceCMYKColor.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetStrokingDeviceCMYKColor.java new file mode 100644 index 000000000..414d8990d --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/color/SetStrokingDeviceCMYKColor.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.contentstream.operator.color; + +import java.io.IOException; +import java.util.List; + +import com.tom_roush.pdfbox.contentstream.operator.Operator; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; + +/** + * K: Set the stroking colour space to DeviceCMYK and set the colour to use for stroking operations. + * + * @author John Hewson + */ +public class SetStrokingDeviceCMYKColor extends SetStrokingColor +{ + @Override + public void process(Operator operator, List arguments) throws IOException + { + PDColorSpace cs = context.getResources().getColorSpace(COSName.DEVICECMYK); + context.getGraphicsState().setStrokingColorSpace(cs); + super.process(operator, arguments); + } + + @Override + public String getName() + { + return "K"; + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/AppendRectangleToPath.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/AppendRectangleToPath.java index 5f979e9a4..fef54e9bc 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/AppendRectangleToPath.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/AppendRectangleToPath.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSNumber; @@ -35,6 +36,15 @@ public final class AppendRectangleToPath extends GraphicsOperatorProcessor @Override public void process(Operator operator, List operands) throws IOException { + if (operands.size() < 4) + { + throw new MissingOperandException(operator, operands); + } + if (!checkArrayTypesClass(operands, COSNumber.class)) + { + return; + } + COSNumber x = (COSNumber) operands.get(0); COSNumber y = (COSNumber) operands.get(1); COSNumber w = (COSNumber) operands.get(2); diff --git a/library/src/main/java/com/tom_roush/pdfbox/exceptions/WrappedIOException.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/BeginInlineImage.java similarity index 53% rename from library/src/main/java/com/tom_roush/pdfbox/exceptions/WrappedIOException.java rename to library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/BeginInlineImage.java index 55b1a09e7..8902ad8a9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/exceptions/WrappedIOException.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/BeginInlineImage.java @@ -14,37 +14,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.tom_roush.pdfbox.exceptions; +package com.tom_roush.pdfbox.contentstream.operator.graphics; import java.io.IOException; +import java.util.List; + +import com.tom_roush.pdfbox.contentstream.operator.Operator; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImage; +import com.tom_roush.pdfbox.pdmodel.graphics.image.PDInlineImage; /** - * An simple class that allows a sub exception to be stored. + * BI Begins an inline image. * - * @author Ben Litchfield - * @version $Revision: 1.4 $ + * @author Ben Litchfield */ -public class WrappedIOException extends IOException +public final class BeginInlineImage extends GraphicsOperatorProcessor { - /** - * constructor comment. - * - * @param e The root exception that caused this exception. - */ - public WrappedIOException( Throwable e ) + @Override + public void process(Operator operator, List operands) throws IOException { - initCause( e ); + PDImage image = new PDInlineImage(operator.getImageParameters(), operator.getImageData(), + context.getResources()); + context.drawImage(image); } - /** - * constructor comment. - * - * @param message Descriptive text for the exception. - * @param e The root exception that caused this exception. - */ - public WrappedIOException( String message, Throwable e ) + @Override + public String getName() { - super( message ); - initCause( e ); + return "BI"; } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveTo.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveTo.java index 76fdf25f5..42dc007d7 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveTo.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveTo.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSNumber; @@ -36,6 +37,15 @@ public class CurveTo extends GraphicsOperatorProcessor @Override public void process(Operator operator, List operands) throws IOException { + if (operands.size() < 6) + { + throw new MissingOperandException(operator, operands); + } + if (!checkArrayTypesClass(operands, COSNumber.class)) + { + return; + } + COSNumber x1 = (COSNumber) operands.get(0); COSNumber y1 = (COSNumber) operands.get(1); COSNumber x2 = (COSNumber) operands.get(2); diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveToReplicateFinalPoint.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveToReplicateFinalPoint.java index dd385e1ff..d8159c421 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveToReplicateFinalPoint.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveToReplicateFinalPoint.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSNumber; @@ -35,6 +36,15 @@ public final class CurveToReplicateFinalPoint extends GraphicsOperatorProcessor @Override public void process(Operator operator, List operands) throws IOException { + if (operands.size() < 4) + { + throw new MissingOperandException(operator, operands); + } + if (!checkArrayTypesClass(operands, COSNumber.class)) + { + return; + } + COSNumber x1 = (COSNumber)operands.get(0); COSNumber y1 = (COSNumber)operands.get(1); COSNumber x3 = (COSNumber)operands.get(2); diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveToReplicateInitialPoint.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveToReplicateInitialPoint.java index f91196bf8..073a11612 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveToReplicateInitialPoint.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/CurveToReplicateInitialPoint.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSNumber; @@ -36,6 +37,15 @@ public class CurveToReplicateInitialPoint extends GraphicsOperatorProcessor @Override public void process(Operator operator, List operands) throws IOException { + if (operands.size() < 4) + { + throw new MissingOperandException(operator, operands); + } + if (!checkArrayTypesClass(operands, COSNumber.class)) + { + return; + } + COSNumber x2 = (COSNumber) operands.get(0); COSNumber y2 = (COSNumber) operands.get(1); COSNumber x3 = (COSNumber) operands.get(2); diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/DrawObject.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/DrawObject.java index a639ead94..b4ebe3f27 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/DrawObject.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/DrawObject.java @@ -19,12 +19,14 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.MissingResourceException; import com.tom_roush.pdfbox.pdmodel.graphics.PDXObject; import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; +import com.tom_roush.pdfbox.pdmodel.graphics.form.PDTransparencyGroup; import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImageXObject; /** @@ -38,7 +40,16 @@ public final class DrawObject extends GraphicsOperatorProcessor @Override public void process(Operator operator, List operands) throws IOException { - COSName objectName = (COSName)operands.get(0); + if (operands.size() < 1) + { + throw new MissingOperandException(operator, operands); + } + COSBase base0 = operands.get(0); + if (!(base0 instanceof COSName)) + { + return; + } + COSName objectName = (COSName)base0; PDXObject xobject = context.getResources().getXObject(objectName); if (xobject == null) @@ -50,18 +61,13 @@ else if (xobject instanceof PDImageXObject) PDImageXObject image = (PDImageXObject)xobject; context.drawImage(image); } + else if (xobject instanceof PDTransparencyGroup) + { + getContext().showTransparencyGroup((PDTransparencyGroup)xobject); + } else if (xobject instanceof PDFormXObject) { - PDFormXObject form = (PDFormXObject) xobject; - if (form.getGroup() != null && - COSName.TRANSPARENCY.equals(form.getGroup().getSubType())) - { - getContext().showTransparencyGroup(form); - } - else - { - getContext().showForm(form); - } + getContext().showForm((PDFormXObject)xobject); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/LineTo.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/LineTo.java index 096c25634..bc0ee7579 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/LineTo.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/LineTo.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSNumber; @@ -36,9 +37,23 @@ public class LineTo extends GraphicsOperatorProcessor @Override public void process(Operator operator, List operands) throws IOException { + if (operands.size() < 2) + { + throw new MissingOperandException(operator, operands); + } + COSBase base0 = operands.get(0); + if (!(base0 instanceof COSNumber)) + { + return; + } + COSBase base1 = operands.get(1); + if (!(base1 instanceof COSNumber)) + { + return; + } // append straight line segment from the current point to the point - COSNumber x = (COSNumber) operands.get(0); - COSNumber y = (COSNumber) operands.get(1); + COSNumber x = (COSNumber)base0; + COSNumber y = (COSNumber)base1; PointF pos = context.transformedPoint(x.floatValue(), y.floatValue()); diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/MoveTo.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/MoveTo.java index 8f7f3c7d2..a252cf6be 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/MoveTo.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/MoveTo.java @@ -18,13 +18,14 @@ import android.graphics.PointF; +import java.io.IOException; +import java.util.List; + +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSNumber; -import java.io.IOException; -import java.util.List; - /** * m Begins a new subpath. * @@ -35,8 +36,22 @@ public final class MoveTo extends GraphicsOperatorProcessor @Override public void process(Operator operator, List operands) throws IOException { - COSNumber x = (COSNumber)operands.get(0); - COSNumber y = (COSNumber)operands.get(1); + if (operands.size() < 2) + { + throw new MissingOperandException(operator, operands); + } + COSBase base0 = operands.get(0); + if (!(base0 instanceof COSNumber)) + { + return; + } + COSBase base1 = operands.get(1); + if (!(base1 instanceof COSNumber)) + { + return; + } + COSNumber x = (COSNumber)base0; + COSNumber y = (COSNumber)base1; PointF pos = context.transformedPoint(x.floatValue(), y.floatValue()); context.moveTo(pos.x, pos.y); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/ShadingFill.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/ShadingFill.java index 68015a1ae..82da76bde 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/ShadingFill.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/graphics/ShadingFill.java @@ -16,13 +16,14 @@ */ package com.tom_roush.pdfbox.contentstream.operator.graphics; +import java.io.IOException; +import java.util.List; + +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSName; -import java.io.IOException; -import java.util.List; - /** * sh Fills the clipping area with the given shading pattern. * @@ -33,6 +34,10 @@ public final class ShadingFill extends GraphicsOperatorProcessor @Override public void process(Operator operator, List operands) throws IOException { + if (operands.size() < 1) + { + throw new MissingOperandException(operator, operands); + } context.shadingFill((COSName) operands.get(0)); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/markedcontent/DrawObject.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/markedcontent/DrawObject.java new file mode 100644 index 000000000..7918d31e3 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/markedcontent/DrawObject.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.contentstream.operator.markedcontent; + +import java.io.IOException; +import java.util.List; + +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; +import com.tom_roush.pdfbox.contentstream.operator.Operator; +import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.graphics.PDXObject; +import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; +import com.tom_roush.pdfbox.pdmodel.graphics.form.PDTransparencyGroup; +import com.tom_roush.pdfbox.text.PDFMarkedContentExtractor; + +/** + * Do: Draws an XObject. + * + * @author Ben Litchfield + * @author Mario Ivankovits + */ +public class DrawObject extends OperatorProcessor +{ + @Override + public void process(Operator operator, List arguments) throws IOException + { + if (arguments.size() < 1) + { + throw new MissingOperandException(operator, arguments); + } + COSBase base0 = arguments.get(0); + if (!(base0 instanceof COSName)) + { + return; + } + COSName name = (COSName)base0; + PDXObject xobject = context.getResources().getXObject(name); + ((PDFMarkedContentExtractor)context).xobject(xobject); + + if (xobject instanceof PDTransparencyGroup) + { + context.showTransparencyGroup((PDTransparencyGroup)xobject); + } + else if (xobject instanceof PDFormXObject) + { + PDFormXObject form = (PDFormXObject)xobject; + context.showForm(form); + } + } + + @Override + public String getName() + { + return "Do"; + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/Concatenate.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/Concatenate.java index 61520cef5..fe302e51e 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/Concatenate.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/Concatenate.java @@ -40,6 +40,10 @@ public void process(Operator operator, List arguments) throws IOExcepti { throw new MissingOperandException(operator, arguments); } + if (!checkArrayTypesClass(arguments, COSNumber.class)) + { + return; + } // concatenate matrix to current transformation matrix COSNumber a = (COSNumber) arguments.get(0); diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/Restore.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/Restore.java index 46737b5f8..aeed0ae80 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/Restore.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/Restore.java @@ -16,13 +16,13 @@ */ package com.tom_roush.pdfbox.contentstream.operator.state; +import java.io.IOException; +import java.util.List; + import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; -import java.io.IOException; -import java.util.List; - /** * Q: Restore the graphics state. * @@ -40,7 +40,7 @@ public void process(Operator operator, List arguments) throws IOExcepti else { // this shouldn't happen but it does, see PDFBOX-161 - throw new EmptyGraphicsStackException(); + throw new EmptyGraphicsStackException(); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetGraphicsStateParameters.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetGraphicsStateParameters.java index 4039ed59d..9b3faf219 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetGraphicsStateParameters.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetGraphicsStateParameters.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; @@ -35,9 +36,24 @@ public class SetGraphicsStateParameters extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { + if (arguments.size() < 1) + { + throw new MissingOperandException(operator, arguments); + } + COSBase base0 = arguments.get(0); + if (!(base0 instanceof COSName)) + { + return; + } + // set parameters from graphics state parameter dictionary - COSName graphicsName = (COSName)arguments.get( 0 ); - PDExtendedGraphicsState gs = context.getResources().getExtGState( graphicsName ); + COSName graphicsName = (COSName)base0; + PDExtendedGraphicsState gs = context.getResources().getExtGState(graphicsName); + if (gs == null) + { + throw new IOException( + "name for 'gs' operator not found in resources: /" + graphicsName.getName()); + } gs.copyIntoGraphicsState( context.getGraphicsState() ); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineCapStyle.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineCapStyle.java index acae87aff..10a2d31b9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineCapStyle.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineCapStyle.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; @@ -34,6 +35,11 @@ public class SetLineCapStyle extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { + if (arguments.size() < 1) + { + throw new MissingOperandException(operator, arguments); + } + Paint.Cap lineCapStyle; switch(((COSNumber)arguments.get( 0 )).intValue()) { case 0: diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineDashPattern.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineDashPattern.java index b20a54737..6f1aee41d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineDashPattern.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineDashPattern.java @@ -16,14 +16,17 @@ */ package com.tom_roush.pdfbox.contentstream.operator.state; +import android.util.Log; + +import java.util.List; + +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSNumber; -import java.util.List; - /** * d: Set the line dash pattern. * @@ -32,10 +35,49 @@ public class SetLineDashPattern extends OperatorProcessor { @Override - public void process(Operator operator, List arguments) + public void process(Operator operator, List arguments) throws MissingOperandException { - COSArray dashArray = (COSArray) arguments.get(0); - int dashPhase = ((COSNumber) arguments.get(1)).intValue(); + if (arguments.size() < 2) + { + throw new MissingOperandException(operator, arguments); + } + COSBase base0 = arguments.get(0); + if (!(base0 instanceof COSArray)) + { + return; + } + COSBase base1 = arguments.get(1); + if (!(base1 instanceof COSNumber)) + { + return; + } + COSArray dashArray = (COSArray)base0; + int dashPhase = ((COSNumber)base1).intValue(); + + boolean allZero = true; + for (COSBase base : dashArray) + { + if (base instanceof COSNumber) + { + COSNumber num = (COSNumber)base; + if (num.floatValue() != 0) + { + allZero = false; + break; + } + } + else + { + Log.e("PdfBox-Android", "dash array has non number element " + base + ", ignored"); + dashArray = new COSArray(); + break; + } + } + if (dashArray.size() > 0 && allZero) + { + Log.e("PdfBox-Android", "dash lengths all zero, ignored"); + dashArray = new COSArray(); + } context.setLineDashPattern(dashArray, dashPhase); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineJoinStyle.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineJoinStyle.java index de490788d..8b423d34b 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineJoinStyle.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineJoinStyle.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; @@ -34,6 +35,11 @@ public class SetLineJoinStyle extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { + if (arguments.size() < 1) + { + throw new MissingOperandException(operator, arguments); + } + Paint.Join lineJoinStyle; switch(((COSNumber)arguments.get( 0 )).intValue()) { case 0: diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineMiterLimit.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineMiterLimit.java index 1f6450c05..b15cdf41a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineMiterLimit.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineMiterLimit.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; @@ -32,6 +33,10 @@ public class SetLineMiterLimit extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { + if (arguments.size() < 1) + { + throw new MissingOperandException(operator, arguments); + } COSNumber miterLimit = (COSNumber)arguments.get( 0 ); context.getGraphicsState().setMiterLimit( miterLimit.floatValue() ); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineWidth.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineWidth.java index a15945446..642804233 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineWidth.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetLineWidth.java @@ -35,10 +35,10 @@ public class SetLineWidth extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { - if (arguments.size() < 1) - { - throw new MissingOperandException(operator, arguments); - } + if (arguments.size() < 1) + { + throw new MissingOperandException(operator, arguments); + } COSNumber width = (COSNumber) arguments.get(0); context.getGraphicsState().setLineWidth(width.floatValue()); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetRenderingIntent.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetRenderingIntent.java index 0aa4c3201..539e1aa61 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetRenderingIntent.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/state/SetRenderingIntent.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; @@ -35,6 +36,10 @@ public class SetRenderingIntent extends OperatorProcessor @Override public void process(Operator operator, List operands) throws IOException { + if (operands.size() < 1) + { + throw new MissingOperandException(operator, operands); + } COSName value = (COSName)operands.get(0); context.getGraphicsState().setRenderingIntent(RenderingIntent.fromString(value.getName())); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/MoveText.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/MoveText.java index bf49c2307..ea42c8e49 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/MoveText.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/MoveText.java @@ -48,8 +48,19 @@ public void process(Operator operator, List arguments) throws MissingOp return; } - COSNumber x = (COSNumber) arguments.get(0); - COSNumber y = (COSNumber) arguments.get(1); + COSBase base0 = arguments.get(0); + COSBase base1 = arguments.get(1); + if (!(base0 instanceof COSNumber)) + { + return; + } + if (!(base1 instanceof COSNumber)) + { + return; + } + COSNumber x = (COSNumber)base0; + COSNumber y = (COSNumber)base1; + Matrix matrix = new Matrix(1, 0, 0, 1, x.floatValue(), y.floatValue()); textLineMatrix.concatenate(matrix); context.setTextMatrix(textLineMatrix.clone()); diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/MoveTextSetLeading.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/MoveTextSetLeading.java index 6ebac138a..f3ee4f2e7 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/MoveTextSetLeading.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/MoveTextSetLeading.java @@ -43,7 +43,12 @@ public void process(Operator operator, List arguments) throws IOExcepti } //move text position and set leading - COSNumber y = (COSNumber) arguments.get(1); + COSBase base1 = arguments.get(1); + if (!(base1 instanceof COSNumber)) + { + return; + } + COSNumber y = (COSNumber)base1; List args = new ArrayList(); args.add(new COSFloat(-1 * y.floatValue())); diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetFontAndSize.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetFontAndSize.java index dd5de91a8..da5ada2e4 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetFontAndSize.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetFontAndSize.java @@ -16,6 +16,9 @@ */ package com.tom_roush.pdfbox.contentstream.operator.text; +import java.io.IOException; +import java.util.List; + import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; @@ -24,9 +27,6 @@ import com.tom_roush.pdfbox.cos.COSNumber; import com.tom_roush.pdfbox.pdmodel.font.PDFont; -import java.io.IOException; -import java.util.List; - /** * Tf: Set text font and size. * @@ -42,8 +42,18 @@ public void process(Operator operator, List arguments) throws IOExcepti throw new MissingOperandException(operator, arguments); } // set font and size - COSName fontName = (COSName)arguments.get(0); - float fontSize = ((COSNumber)arguments.get(1)).floatValue(); + COSBase base0 = arguments.get(0); + COSBase base1 = arguments.get(1); + if (!(base0 instanceof COSName)) + { + return; + } + if (!(base1 instanceof COSNumber)) + { + return; + } + COSName fontName = (COSName)base0; + float fontSize = ((COSNumber)base1).floatValue(); context.getGraphicsState().getTextState().setFontSize(fontSize); PDFont font = context.getResources().getFont(fontName); context.getGraphicsState().getTextState().setFont(font); diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetTextRenderingMode.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetTextRenderingMode.java index 3d2329080..e62ced6f1 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetTextRenderingMode.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetTextRenderingMode.java @@ -16,15 +16,16 @@ */ package com.tom_roush.pdfbox.contentstream.operator.text; +import java.io.IOException; +import java.util.List; + +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSNumber; import com.tom_roush.pdfbox.pdmodel.graphics.state.RenderingMode; -import java.io.IOException; -import java.util.List; - /** * Tr: Set text rendering mode. * @@ -35,7 +36,16 @@ public class SetTextRenderingMode extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { - COSNumber mode = (COSNumber)arguments.get(0); + if (arguments.size() < 1) + { + throw new MissingOperandException(operator, arguments); + } + COSBase base0 = arguments.get(0); + if (!(base0 instanceof COSNumber)) + { + return; + } + COSNumber mode = (COSNumber)base0; RenderingMode renderingMode = RenderingMode.fromInt(mode.intValue()); context.getGraphicsState().getTextState().setRenderingMode(renderingMode); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetTextRise.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetTextRise.java index f38c259a8..0cffefef6 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetTextRise.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetTextRise.java @@ -16,14 +16,14 @@ */ package com.tom_roush.pdfbox.contentstream.operator.text; +import java.io.IOException; +import java.util.List; + import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSNumber; -import java.io.IOException; -import java.util.List; - /** * Ts: Set text rise. * @@ -34,7 +34,16 @@ public class SetTextRise extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { - COSNumber rise = (COSNumber)arguments.get(0); + if (arguments.size() < 1) + { + return; + } + COSBase base = arguments.get(0); + if (!(base instanceof COSNumber)) + { + return; + } + COSNumber rise = (COSNumber)base; context.getGraphicsState().getTextState().setRise( rise.floatValue() ); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetWordSpacing.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetWordSpacing.java index cd859e425..9a84460f1 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetWordSpacing.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/SetWordSpacing.java @@ -16,13 +16,13 @@ */ package com.tom_roush.pdfbox.contentstream.operator.text; +import java.util.List; + import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSNumber; -import java.util.List; - /** * Tw: Set word spacing. * @@ -33,7 +33,16 @@ public class SetWordSpacing extends OperatorProcessor @Override public void process(Operator operator, List arguments) { - COSNumber wordSpacing = (COSNumber)arguments.get( 0 ); + if (arguments.size() < 1) + { + return; + } + COSBase base = arguments.get(0); + if (!(base instanceof COSNumber)) + { + return; + } + COSNumber wordSpacing = (COSNumber)base; context.getGraphicsState().getTextState().setWordSpacing( wordSpacing.floatValue() ); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowText.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowText.java index 4c5ce9d81..22832b965 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowText.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowText.java @@ -16,14 +16,14 @@ */ package com.tom_roush.pdfbox.contentstream.operator.text; +import java.io.IOException; +import java.util.List; + import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSString; -import java.io.IOException; -import java.util.List; - /** * Tj: Show text. * @@ -34,12 +34,23 @@ public class ShowText extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { - if (arguments.size() < 1) - { - // ignore ( )Tj - return; - } - COSString string = (COSString)arguments.get( 0 ); + if (arguments.size() < 1) + { + // ignore ( )Tj + return; + } + COSBase base = arguments.get(0); + if (!(base instanceof COSString)) + { + // ignore + return; + } + if (context.getTextMatrix() == null) + { + // ignore: outside of BT...ET + return; + } + COSString string = (COSString)base; context.showTextString(string.getBytes()); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowTextAdjusted.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowTextAdjusted.java index 2a01296db..0458a0c98 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowTextAdjusted.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowTextAdjusted.java @@ -16,14 +16,14 @@ */ package com.tom_roush.pdfbox.contentstream.operator.text; +import java.io.IOException; +import java.util.List; + import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; -import java.io.IOException; -import java.util.List; - /** * TJ: Show text, with position adjustments. * @@ -34,7 +34,21 @@ public class ShowTextAdjusted extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { - COSArray array = (COSArray)arguments.get(0); + if (arguments.size() < 1) + { + return; + } + COSBase base = arguments.get(0); + if (!(base instanceof COSArray)) + { + return; + } + if (context.getTextMatrix() == null) + { + // ignore: outside of BT...ET + return; + } + COSArray array = (COSArray)base; context.showTextStrings(array); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowTextLineAndSpace.java b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowTextLineAndSpace.java index 07abf1b53..971a0d3d3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowTextLineAndSpace.java +++ b/library/src/main/java/com/tom_roush/pdfbox/contentstream/operator/text/ShowTextLineAndSpace.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.List; +import com.tom_roush.pdfbox.contentstream.operator.MissingOperandException; import com.tom_roush.pdfbox.contentstream.operator.Operator; import com.tom_roush.pdfbox.contentstream.operator.OperatorProcessor; import com.tom_roush.pdfbox.cos.COSBase; @@ -33,6 +34,10 @@ public class ShowTextLineAndSpace extends OperatorProcessor @Override public void process(Operator operator, List arguments) throws IOException { + if (arguments.size() < 3) + { + throw new MissingOperandException(operator, arguments); + } context.processOperator("Tw", arguments.subList(0,1)); context.processOperator("Tc", arguments.subList(1,2)); context.processOperator("'", arguments.subList(2,3)); diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSArray.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSArray.java index 77d41b22b..09cb91cb8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSArray.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSArray.java @@ -29,9 +29,10 @@ * * @author Ben Litchfield */ -public class COSArray extends COSBase implements Iterable +public class COSArray extends COSBase implements Iterable, COSUpdateInfo { private final List objects = new ArrayList(); + private boolean needToBeUpdated; /** * Constructor. @@ -189,7 +190,7 @@ public COSBase getObject( int index ) { obj = ((COSObject)obj).getObject(); } - else if( obj instanceof COSNull ) + if (obj instanceof COSNull) { obj = null; } @@ -466,8 +467,8 @@ public int indexOfObject(COSBase object) } else if (item instanceof COSObject && ((COSObject) item).getObject().equals(object)) { - retval = i; - break; + retval = i; + break; } } return retval; @@ -514,6 +515,18 @@ public Object accept(ICOSVisitor visitor) throws IOException return visitor.visitFromArray(this); } + @Override + public boolean isNeedToBeUpdated() + { + return needToBeUpdated; + } + + @Override + public void setNeedToBeUpdated(boolean flag) + { + needToBeUpdated = flag; + } + /** * This will take an COSArray of numbers and convert it to a float[]. * diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSDictionary.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSDictionary.java index 156c9dad5..4ea812b93 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSDictionary.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSDictionary.java @@ -47,7 +47,7 @@ public class COSDictionary extends COSBase implements COSUpdateInfo */ public COSDictionary() { - //default constructor + // default constructor } /** @@ -119,8 +119,8 @@ public void clear() } /** - * This will get an object from this dictionary. If the object is a reference then it will - * dereference it and get it from the document. If the object is COSNull then + * This will get an object from this dictionary. If the object is a reference then it will + * dereference it and get it from the document. If the object is COSNull then * null will be returned. * * @param key The key to the object that we are getting. @@ -136,8 +136,8 @@ public COSBase getDictionaryObject( String key ) * This is a special case of getDictionaryObject that takes multiple keys, it will handle * the situation where multiple keys could get the same value, ie if either CS or ColorSpace * is used to get the colorspace. - * This will get an object from this dictionary. If the object is a reference then it will - * dereference it and get it from the document. If the object is COSNull then + * This will get an object from this dictionary. If the object is a reference then it will + * dereference it and get it from the document. If the object is COSNull then * null will be returned. * * @param firstKey The first key to try. @@ -154,12 +154,13 @@ public COSBase getDictionaryObject( COSName firstKey, COSName secondKey ) } return retval; } + /** * This is a special case of getDictionaryObject that takes multiple keys, it will handle * the situation where multiple keys could get the same value, ie if either CS or ColorSpace * is used to get the colorspace. - * This will get an object from this dictionary. If the object is a reference then it will - * dereference it and get it from the document. If the object is COSNull then + * This will get an object from this dictionary. If the object is a reference then it will + * dereference it and get it from the document. If the object is COSNull then * null will be returned. * * @param keyList The list of keys to find a value. @@ -177,8 +178,8 @@ public COSBase getDictionaryObject( String[] keyList ) } /** - * This will get an object from this dictionary. If the object is a reference then it will - * dereference it and get it from the document. If the object is COSNull then + * This will get an object from this dictionary. If the object is a reference then it will + * dereference it and get it from the document. If the object is COSNull then * null will be returned. * * @param key The key to the object that we are getting. @@ -200,7 +201,7 @@ public COSBase getDictionaryObject( COSName key ) } /** - * This will set an item in the dictionary. If value is null then the result + * This will set an item in the dictionary. If value is null then the result * will be the same as removeItem( key ). * * @param key The key to the dictionary object. @@ -219,7 +220,7 @@ public void setItem( COSName key, COSBase value ) } /** - * This will set an item in the dictionary. If value is null then the result + * This will set an item in the dictionary. If value is null then the result * will be the same as removeItem( key ). * * @param key The key to the dictionary object. @@ -236,7 +237,7 @@ public void setItem( COSName key, COSObjectable value ) } /** - * This will set an item in the dictionary. If value is null then the result + * This will set an item in the dictionary. If value is null then the result * will be the same as removeItem( key ). * * @param key The key to the dictionary object. @@ -270,7 +271,7 @@ public void setBoolean( COSName key, boolean value ) } /** - * This will set an item in the dictionary. If value is null then the result + * This will set an item in the dictionary. If value is null then the result * will be the same as removeItem( key ). * * @param key The key to the dictionary object. @@ -283,7 +284,7 @@ public void setItem( String key, COSBase value ) /** * This is a convenience method that will convert the value to a COSName - * object. If it is null then the object will be removed. + * object. If it is null then the object will be removed. * * @param key The key to the object, * @param value The string value for the name. @@ -295,7 +296,7 @@ public void setName( String key, String value ) /** * This is a convenience method that will convert the value to a COSName - * object. If it is null then the object will be removed. + * object. If it is null then the object will be removed. * * @param key The key to the object, * @param value The string value for the name. @@ -367,7 +368,7 @@ public void setEmbeddedDate( String embedded, COSName key, Calendar date ) /** * This is a convenience method that will convert the value to a COSString - * object. If it is null then the object will be removed. + * object. If it is null then the object will be removed. * * @param key The key to the object, * @param value The string value for the name. @@ -379,7 +380,7 @@ public void setString( String key, String value ) /** * This is a convenience method that will convert the value to a COSString - * object. If it is null then the object will be removed. + * object. If it is null then the object will be removed. * * @param key The key to the object, * @param value The string value for the name. @@ -396,7 +397,7 @@ public void setString( COSName key, String value ) /** * This is a convenience method that will convert the value to a COSString - * object. If it is null then the object will be removed. + * object. If it is null then the object will be removed. * * @param embedded The embedded dictionary to set the item in. * @param key The key to the object, @@ -409,7 +410,7 @@ public void setEmbeddedString( String embedded, String key, String value ) /** * This is a convenience method that will convert the value to a COSString - * object. If it is null then the object will be removed. + * object. If it is null then the object will be removed. * * @param embedded The embedded dictionary to set the item in. * @param key The key to the object, @@ -593,7 +594,7 @@ public COSName getCOSName(COSName key, COSName defaultValue) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param key The key to the item in the dictionary. @@ -606,7 +607,7 @@ public String getNameAsString( String key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param key The key to the item in the dictionary. @@ -629,7 +630,7 @@ else if ( name instanceof COSString) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param key The key to the item in the dictionary. @@ -643,7 +644,7 @@ public String getNameAsString( String key, String defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param key The key to the item in the dictionary. @@ -662,7 +663,7 @@ public String getNameAsString( COSName key, String defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param key The key to the item in the dictionary. @@ -675,7 +676,7 @@ public String getString( String key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param key The key to the item in the dictionary. @@ -694,7 +695,7 @@ public String getString( COSName key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param key The key to the item in the dictionary. @@ -708,7 +709,7 @@ public String getString( String key, String defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param key The key to the item in the dictionary. @@ -727,7 +728,7 @@ public String getString( COSName key, String defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param embedded The embedded dictionary. @@ -741,7 +742,7 @@ public String getEmbeddedString( String embedded, String key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param embedded The embedded dictionary. @@ -755,7 +756,7 @@ public String getEmbeddedString( String embedded, COSName key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param embedded The embedded dictionary. @@ -770,7 +771,7 @@ public String getEmbeddedString( String embedded, String key, String defaultValu /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param embedded The embedded dictionary. @@ -791,7 +792,7 @@ public String getEmbeddedString( String embedded, COSName key, String defaultVal /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary or if the date was invalid. * * @param key The key to the item in the dictionary. @@ -804,7 +805,7 @@ public Calendar getDate( String key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary or if the date was invalid. * * @param key The key to the item in the dictionary. @@ -818,7 +819,7 @@ public Calendar getDate( COSName key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a date. Null is returned + * is expected to be a date. Null is returned * if the entry does not exist in the dictionary or if the date was invalid. * * @param key The key to the item in the dictionary. @@ -832,7 +833,7 @@ public Calendar getDate( String key, Calendar defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a date. Null is returned + * is expected to be a date. Null is returned * if the entry does not exist in the dictionary or if the date was invalid. * * @param key The key to the item in the dictionary. @@ -851,7 +852,7 @@ public Calendar getDate( COSName key, Calendar defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param embedded The embedded dictionary to get. @@ -866,7 +867,7 @@ public Calendar getEmbeddedDate( String embedded, String key ) throws IOExceptio /** * This is a convenience method that will get the dictionary object that - * is expected to be a name and convert it to a string. Null is returned + * is expected to be a name and convert it to a string. Null is returned * if the entry does not exist in the dictionary. * * @param embedded The embedded dictionary to get. @@ -882,7 +883,7 @@ public Calendar getEmbeddedDate( String embedded, COSName key ) throws IOExcepti /** * This is a convenience method that will get the dictionary object that - * is expected to be a date. Null is returned + * is expected to be a date. Null is returned * if the entry does not exist in the dictionary. * * @param embedded The embedded dictionary to get. @@ -898,7 +899,7 @@ public Calendar getEmbeddedDate( String embedded, String key, Calendar defaultVa /** * This is a convenience method that will get the dictionary object that - * is expected to be a date. Null is returned + * is expected to be a date. Null is returned * if the entry does not exist in the dictionary. * * @param embedded The embedded dictionary to get. @@ -968,7 +969,7 @@ public boolean getBoolean( COSName firstKey, COSName secondKey, boolean defaultV } /** - * Get an integer from an embedded dictionary. Useful for 1-1 mappings. default:-1 + * Get an integer from an embedded dictionary. Useful for 1-1 mappings. default:-1 * * @param embeddedDictionary The name of the embedded dictionary. * @param key The key in the embedded dictionary. @@ -981,7 +982,7 @@ public int getEmbeddedInt( String embeddedDictionary, String key ) } /** - * Get an integer from an embedded dictionary. Useful for 1-1 mappings. default:-1 + * Get an integer from an embedded dictionary. Useful for 1-1 mappings. default:-1 * * @param embeddedDictionary The name of the embedded dictionary. * @param key The key in the embedded dictionary. @@ -994,7 +995,7 @@ public int getEmbeddedInt( String embeddedDictionary, COSName key ) } /** - * Get an integer from an embedded dictionary. Useful for 1-1 mappings. + * Get an integer from an embedded dictionary. Useful for 1-1 mappings. * * @param embeddedDictionary The name of the embedded dictionary. * @param key The key in the embedded dictionary. @@ -1009,7 +1010,7 @@ public int getEmbeddedInt( String embeddedDictionary, String key, int defaultVal /** - * Get an integer from an embedded dictionary. Useful for 1-1 mappings. + * Get an integer from an embedded dictionary. Useful for 1-1 mappings. * * @param embeddedDictionary The name of the embedded dictionary. * @param key The key in the embedded dictionary. @@ -1030,7 +1031,7 @@ public int getEmbeddedInt( String embeddedDictionary, COSName key, int defaultVa /** * This is a convenience method that will get the dictionary object that - * is expected to be an int. -1 is returned if there is no value. + * is expected to be an int. -1 is returned if there is no value. * * @param key The key to the item in the dictionary. * @return The integer value. @@ -1042,7 +1043,7 @@ public int getInt( String key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an int. -1 is returned if there is no value. + * is expected to be an int. -1 is returned if there is no value. * * @param key The key to the item in the dictionary. * @return The integer value.. @@ -1054,7 +1055,7 @@ public int getInt( COSName key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an integer. If the dictionary value is null then the + * is expected to be an integer. If the dictionary value is null then the * default Value will be returned. * * @param keyList The key to the item in the dictionary. @@ -1074,7 +1075,7 @@ public int getInt( String[] keyList, int defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an integer. If the dictionary value is null then the + * is expected to be an integer. If the dictionary value is null then the * default Value will be returned. * * @param key The key to the item in the dictionary. @@ -1088,7 +1089,7 @@ public int getInt( String key, int defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an integer. If the dictionary value is null then the + * is expected to be an integer. If the dictionary value is null then the * default Value will be returned. * * @param key The key to the item in the dictionary. @@ -1102,7 +1103,7 @@ public int getInt( COSName key, int defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an integer. If the dictionary value is null then the + * is expected to be an integer. If the dictionary value is null then the * default Value -1 will be returned. * * @param firstKey The first key to the item in the dictionary. @@ -1116,7 +1117,7 @@ public int getInt( COSName firstKey, COSName secondKey ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an integer. If the dictionary value is null then the + * is expected to be an integer. If the dictionary value is null then the * default Value will be returned. * * @param firstKey The first key to the item in the dictionary. @@ -1137,7 +1138,7 @@ public int getInt( COSName firstKey, COSName secondKey, int defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an long. -1 is returned if there is no value. + * is expected to be an long. -1 is returned if there is no value. * * @param key The key to the item in the dictionary. * @@ -1150,7 +1151,7 @@ public long getLong( String key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an long. -1 is returned if there is no value. + * is expected to be an long. -1 is returned if there is no value. * * @param key The key to the item in the dictionary. * @return The long value. @@ -1162,7 +1163,7 @@ public long getLong( COSName key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an long. If the dictionary value is null then the + * is expected to be an long. If the dictionary value is null then the * default Value will be returned. * * @param keyList The key to the item in the dictionary. @@ -1182,7 +1183,7 @@ public long getLong( String[] keyList, long defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an integer. If the dictionary value is null then the + * is expected to be an integer. If the dictionary value is null then the * default Value will be returned. * * @param key The key to the item in the dictionary. @@ -1196,7 +1197,7 @@ public long getLong( String key, long defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an integer. If the dictionary value is null then the + * is expected to be an integer. If the dictionary value is null then the * default Value will be returned. * * @param key The key to the item in the dictionary. @@ -1216,7 +1217,7 @@ public long getLong( COSName key, long defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an float. -1 is returned if there is no value. + * is expected to be an float. -1 is returned if there is no value. * * @param key The key to the item in the dictionary. * @return The float value. @@ -1228,7 +1229,7 @@ public float getFloat( String key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an float. -1 is returned if there is no value. + * is expected to be an float. -1 is returned if there is no value. * * @param key The key to the item in the dictionary. * @return The float value. @@ -1240,7 +1241,7 @@ public float getFloat( COSName key ) /** * This is a convenience method that will get the dictionary object that - * is expected to be a float. If the dictionary value is null then the + * is expected to be a float. If the dictionary value is null then the * default Value will be returned. * * @param key The key to the item in the dictionary. @@ -1254,7 +1255,7 @@ public float getFloat( String key, float defaultValue ) /** * This is a convenience method that will get the dictionary object that - * is expected to be an float. If the dictionary value is null then the + * is expected to be an float. If the dictionary value is null then the * default Value will be returned. * * @param key The key to the item in the dictionary. @@ -1287,7 +1288,7 @@ public boolean getFlag(COSName field, int bitFlag) } /** - * This will remove an item for the dictionary. This + * This will remove an item for the dictionary. This * will do nothing of the object does not exist. * * @param key The key to the item to remove from the dictionary. @@ -1364,7 +1365,7 @@ public Collection getValues() * @throws IOException If there is an error visiting this object. */ @Override - public Object accept(ICOSVisitor visitor) throws IOException + public Object accept(ICOSVisitor visitor) throws IOException { return visitor.visitFromDictionary(this); } @@ -1410,7 +1411,8 @@ public void addAll( COSDictionary dic ) * @param name The key to find in the map. * @return true if the map contains this key. */ - public boolean containsKey(COSName name) { + public boolean containsKey(COSName name) + { return this.items.containsKey(name); } @@ -1420,7 +1422,8 @@ public boolean containsKey(COSName name) { * @param name The key to find in the map. * @return true if the map contains this key. */ - public boolean containsKey(String name) { + public boolean containsKey(String name) + { return containsKey(COSName.getPDFName(name)); } @@ -1472,6 +1475,7 @@ else if (retval instanceof COSDictionary) /** * Returns an unmodifiable view of this dictionary. + * * @return an unmodifiable view of this dictionary */ public COSDictionary asUnmodifiableDictionary() @@ -1483,13 +1487,16 @@ public COSDictionary asUnmodifiableDictionary() * {@inheritDoc} */ @Override - public String toString() { - StringBuilder retVal = new StringBuilder("COSDictionary{"); - for(COSName key : items.keySet()) { + public String toString() + { + StringBuilder retVal = new StringBuilder(getClass().getSimpleName()); + retVal.append("{"); + for (COSName key : items.keySet()) + { retVal.append("("); retVal.append(key); retVal.append(":"); - if(getDictionaryObject(key) != null) + if (getDictionaryObject(key) != null) { retVal.append(getDictionaryObject(key).toString()); } @@ -1502,5 +1509,4 @@ public String toString() { retVal.append("}"); return retVal.toString(); } - } diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSDocument.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSDocument.java index 547290736..5ee0ff9ee 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSDocument.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSDocument.java @@ -19,7 +19,6 @@ import android.util.Log; import java.io.Closeable; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -53,6 +52,11 @@ public class COSDocument extends COSBase implements Closeable private final Map xrefTable = new HashMap(); + /** + * List containing all streams which are created when creating a new pdf. + */ + private final List streams = new ArrayList(); + /** * Document trailer dictionary. */ @@ -74,60 +78,24 @@ public class COSDocument extends COSBase implements Closeable private ScratchFile scratchFile; /** - * Constructor. - * - * @param useScratchFiles enables the usage of a scratch file if set to true - * - */ - public COSDocument(boolean useScratchFiles) - { - this((File) null, useScratchFiles); - } - - /** - * Constructor that will use a temporary file in the given directory - * for storage of the PDF streams. The temporary file is automatically - * removed when this document gets closed. - * - * @param scratchDir directory for the temporary file, - * or null to use the system default - * @param useScratchFiles enables the usage of a scratch file if set to true - * + * Constructor. Uses main memory to buffer PDF streams. */ - public COSDocument(File scratchDir, boolean useScratchFiles) + public COSDocument() { - if (useScratchFiles) - { - try - { - scratchFile = new ScratchFile(scratchDir); - } - catch (IOException e) - { - Log.e("PdfBox-Android", "Can't create temp file, using memory buffer instead", e); - } - } + this(ScratchFile.getMainMemoryOnlyInstance()); } /** * Constructor that will use the provide memory handler for storage of the * PDF streams. * - * @param scratchFile memory handler for storage of PDF streams + * @param scratchFile memory handler for buffering of PDF streams */ public COSDocument(ScratchFile scratchFile) { this.scratchFile = scratchFile; } - /** - * Constructor. Uses memory to store stream. - */ - public COSDocument() - { - this(false); - } - /** * Creates a new COSStream using the current configuration for scratch files. * @@ -135,7 +103,12 @@ public COSDocument() */ public COSStream createCOSStream() { - return new COSStream(scratchFile); + COSStream stream = new COSStream(scratchFile); + // collect all COSStreams so that they can be closed when closing the COSDocument. + // This is limited to newly created pdfs as all COSStreams of an existing pdf are + // collected within the map objectPool + streams.add(stream); + return stream; } /** @@ -288,7 +261,6 @@ public void print() */ public void setVersion( float versionValue ) { - // update header string version = versionValue; } @@ -464,6 +436,14 @@ public void close() throws IOException } } + if (streams != null) + { + for (COSStream stream : streams) + { + stream.close(); + } + } + if (scratchFile != null) { scratchFile.close(); diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSFloat.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSFloat.java index e3f0631ea..fee023139 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSFloat.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSFloat.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tom_roush.pdfbox.cos; import java.io.IOException; @@ -21,10 +37,10 @@ public class COSFloat extends COSNumber */ public COSFloat( float aFloat ) { - // use a BigDecimal as intermediate state to avoid - // a floating point string representation of the float value - value = new BigDecimal(String.valueOf(aFloat)); - valueAsString = removeNullDigits(value.toPlainString()); + // use a BigDecimal as intermediate state to avoid + // a floating point string representation of the float value + value = new BigDecimal(String.valueOf(aFloat)); + valueAsString = removeNullDigits(value.toPlainString()); } /** @@ -38,12 +54,65 @@ public COSFloat( String aFloat ) throws IOException { try { - valueAsString = aFloat; + valueAsString = aFloat; value = new BigDecimal( valueAsString ); + checkMinMaxValues(); } catch( NumberFormatException e ) { - throw new IOException( "Error expected floating point number actual='" +aFloat + "'", e ); + if (aFloat.startsWith("0.00000-")) + { + // PDFBOX-2990 has 0.00000-33917698 + // Let's wait what other floats will be coming before doing a more general workaround. + try + { + valueAsString = "-0.00000" + aFloat.substring(8); + value = new BigDecimal(valueAsString); + checkMinMaxValues(); + } + catch (NumberFormatException e2) + { + throw new IOException( + "Error expected floating point number actual='" + aFloat + "'", e2); + } + } + else + { + throw new IOException( + "Error expected floating point number actual='" + aFloat + "'", e); + } + } + } + + private void checkMinMaxValues() + { + float floatValue = value.floatValue(); + double doubleValue = value.doubleValue(); + boolean valueReplaced = false; + // check for huge values + if (floatValue == Float.NEGATIVE_INFINITY || floatValue == Float.POSITIVE_INFINITY) + { + + if (Math.abs(doubleValue) > Float.MAX_VALUE) + { + floatValue = Float.MAX_VALUE * (floatValue == Float.POSITIVE_INFINITY ? 1 : -1); + valueReplaced = true; + } + } + // check for very small values + else if (floatValue == 0 && doubleValue != 0) + { + if (Math.abs(doubleValue) < Float.MIN_NORMAL) + { + floatValue = Float.MIN_NORMAL; + floatValue *= doubleValue >= 0 ? 1 : -1; + valueReplaced = true; + } + } + if (valueReplaced) + { + value = new BigDecimal(floatValue); + valueAsString = removeNullDigits(value.toPlainString()); } } @@ -110,8 +179,8 @@ public int intValue() @Override public boolean equals( Object o ) { - return o instanceof COSFloat && - Float.floatToIntBits(((COSFloat)o).value.floatValue()) == Float.floatToIntBits(value.floatValue()); + return o instanceof COSFloat && Float.floatToIntBits(((COSFloat)o).value.floatValue()) == + Float.floatToIntBits(value.floatValue()); } /** @@ -155,4 +224,4 @@ public void writePDF( OutputStream output ) throws IOException { output.write(valueAsString.getBytes("ISO-8859-1")); } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSName.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSName.java index fa32e4c41..2e76d9ea2 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSName.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSName.java @@ -34,8 +34,8 @@ public final class COSName extends COSBase implements Comparable private static Map nameMap = new ConcurrentHashMap(8192); // all common COSName values are stored in this HashMap - // hey are already defined as static constants and don't need to be synchronized - private static Map commonNameMap = new HashMap(); + // they are already defined as static constants and don't need to be synchronized + private static Map commonNameMap = new HashMap(768); // // IMPORTANT: this list is *alphabetized* and does not need any JavaDoc @@ -50,6 +50,7 @@ public final class COSName extends COSBase implements Comparable public static final COSName ADBE_PKCS7_SHA1 = new COSName("adbe.pkcs7.sha1"); public static final COSName ADBE_X509_RSA_SHA1 = new COSName("adbe.x509.rsa_sha1"); public static final COSName ADOBE_PPKLITE = new COSName("Adobe.PPKLite"); + public static final COSName AESV2 = new COSName("AESV2"); public static final COSName AESV3 = new COSName("AESV3"); public static final COSName AFTER = new COSName("After"); public static final COSName AIS = new COSName("AIS"); @@ -92,6 +93,7 @@ public final class COSName extends COSBase implements Comparable public static final COSName BLACK_POINT = new COSName("BlackPoint"); public static final COSName BLEED_BOX = new COSName("BleedBox"); public static final COSName BM = new COSName("BM"); + public static final COSName BORDER = new COSName("Border"); public static final COSName BOUNDS = new COSName("Bounds"); public static final COSName BPC = new COSName("BPC"); public static final COSName BS = new COSName("BS"); @@ -124,6 +126,7 @@ public final class COSName extends COSBase implements Comparable public static final COSName CID_TO_GID_MAP = new COSName("CIDToGIDMap"); public static final COSName CID_SET = new COSName("CIDSet"); public static final COSName CIDSYSTEMINFO = new COSName("CIDSystemInfo"); + public static final COSName CL = new COSName("CL"); public static final COSName CLR_F = new COSName("ClrF"); public static final COSName CLR_FF = new COSName("ClrFf"); public static final COSName CMAP = new COSName("CMap"); @@ -274,6 +277,7 @@ public final class COSName extends COSBase implements Comparable public static final COSName ID_TREE = new COSName("IDTree"); public static final COSName IDENTITY = new COSName("Identity"); public static final COSName IDENTITY_H = new COSName("Identity-H"); + public static final COSName IDENTITY_V = new COSName("Identity-V"); public static final COSName IF = new COSName("IF"); public static final COSName IM = new COSName("IM"); public static final COSName IMAGE = new COSName("Image"); @@ -370,7 +374,7 @@ public final class COSName extends COSBase implements Comparable * "Off", to be used for Acroform, not for OCGs */ public static final COSName Off = new COSName("Off"); - ; + public static final COSName ON = new COSName("ON"); public static final COSName OP = new COSName("OP"); public static final COSName OP_NS = new COSName("op"); @@ -497,6 +501,7 @@ public final class COSName extends COSBase implements Comparable public static final COSName TM = new COSName("TM"); public static final COSName TO_UNICODE = new COSName("ToUnicode"); public static final COSName TR = new COSName("TR"); + public static final COSName TR2 = new COSName("TR2"); public static final COSName TRAPPED = new COSName("Trapped"); public static final COSName TRANS = new COSName("Trans"); public static final COSName TRANSPARENCY = new COSName("Transparency"); diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSNull.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSNull.java index 5d3081dea..eac05f075 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSNull.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSNull.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tom_roush.pdfbox.cos; import java.io.IOException; diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSNumber.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSNumber.java index 5c95136ac..078ec47a9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSNumber.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSNumber.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tom_roush.pdfbox.cos; import java.io.IOException; @@ -13,13 +29,13 @@ public abstract class COSNumber extends COSBase /** * @deprecated Use the {@link COSInteger#ZERO} constant instead */ - @Deprecated + @Deprecated public static final COSInteger ZERO = COSInteger.ZERO; /** * @deprecated Use the {@link COSInteger#ONE} constant instead */ - @Deprecated + @Deprecated public static final COSInteger ONE = COSInteger.ONE; /** @@ -61,39 +77,40 @@ public abstract class COSNumber extends COSBase */ public static COSNumber get( String number ) throws IOException { - if (number.length() == 1) + if (number.length() == 1) { char digit = number.charAt(0); - if ('0' <= digit && digit <= '9') + if ('0' <= digit && digit <= '9') { return COSInteger.get(digit - '0'); - } - else if (digit == '-' || digit == '.') + } + else if (digit == '-' || digit == '.') { // See https://issues.apache.org/jira/browse/PDFBOX-592 return COSInteger.ZERO; - } - else + } + else { throw new IOException("Not a number: " + number); } - } - else if (number.indexOf('.') == -1 && (number.toLowerCase().indexOf('e') == -1)) + } + else if (number.indexOf('.') == -1 && (number.toLowerCase().indexOf('e') == -1)) { - try - { - if (number.charAt(0) == '+') - { - return COSInteger.get(Long.parseLong(number.substring(1))); - } - return COSInteger.get(Long.parseLong(number)); - } - catch( NumberFormatException e ) - { - throw new IOException( "Value is not an integer: " + number, e ); + try + { + if (number.charAt(0) == '+') + { + return COSInteger.get(Long.parseLong(number.substring(1))); + } + return COSInteger.get(Long.parseLong(number)); } - } - else + catch (NumberFormatException e) + { + // might be a huge number, see PDFBOX-3116 + return new COSFloat(number); + } + } + else { return new COSFloat(number); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSObject.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSObject.java index 0ea695b6c..64a0146d4 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSObject.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSObject.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tom_roush.pdfbox.cos; import java.io.IOException; @@ -89,10 +105,11 @@ public final void setObject( COSBase object ) throws IOException @Override public String toString() { - return "COSObject{" + Long.toString(objectNumber) + ", " + Integer.toString(generationNumber) + "}"; + return "COSObject{" + Long.toString(objectNumber) + ", " + Integer.toString( + generationNumber) + "}"; } - /** + /** * Getter for property objectNumber. * @return Value of property objectNumber. */ @@ -101,7 +118,7 @@ public long getObjectNumber() return objectNumber; } - /** + /** * Setter for property objectNumber. * @param objectNum New value of property objectNumber. */ @@ -110,7 +127,7 @@ public void setObjectNumber(long objectNum) objectNumber = objectNum; } - /** + /** * Getter for property generationNumber. * @return Value of property generationNumber. */ @@ -119,7 +136,7 @@ public int getGenerationNumber() return generationNumber; } - /** + /** * Setter for property generationNumber. * @param generationNumberValue New value of property generationNumber. */ @@ -140,7 +157,7 @@ public Object accept( ICOSVisitor visitor ) throws IOException { return getObject() != null ? getObject().accept( visitor ) : COSNull.NULL.accept( visitor ); } - + /** * Get the update state for the COSWriter. * @@ -149,7 +166,7 @@ public Object accept( ICOSVisitor visitor ) throws IOException @Override public boolean isNeedToBeUpdated() { - return needToBeUpdated; + return needToBeUpdated; } /** @@ -160,6 +177,6 @@ public boolean isNeedToBeUpdated() @Override public void setNeedToBeUpdated(boolean flag) { - needToBeUpdated = flag; + needToBeUpdated = flag; } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSObjectKey.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSObjectKey.java index bd08b8a4c..f01991956 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSObjectKey.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSObjectKey.java @@ -1,6 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tom_roush.pdfbox.cos; - /** * Object representing the physical reference to an indirect pdf object. * @@ -18,7 +33,7 @@ public class COSObjectKey implements Comparable */ public COSObjectKey(COSObject object) { - this(object.getObjectNumber(), object.getGenerationNumber()); + this(object.getObjectNumber(), object.getGenerationNumber()); } /** @@ -39,10 +54,9 @@ public COSObjectKey(long num, int gen) @Override public boolean equals(Object obj) { - COSObjectKey objToBeCompared = obj instanceof COSObjectKey ? (COSObjectKey)obj : null; - return objToBeCompared != null && - objToBeCompared.getNumber() == getNumber() && - objToBeCompared.getGeneration() == getGeneration(); + COSObjectKey objToBeCompared = obj instanceof COSObjectKey ? (COSObjectKey)obj : null; + return objToBeCompared != null && objToBeCompared.getNumber() == getNumber() && + objToBeCompared.getGeneration() == getGeneration(); } /** @@ -54,6 +68,7 @@ public int getGeneration() { return generation; } + /** * This will get the objects id. * @@ -70,13 +85,13 @@ public long getNumber() @Override public int hashCode() { - return Long.valueOf(number+generation).hashCode(); + return Long.valueOf(number + generation).hashCode(); } @Override public String toString() { - return Long.toString(number) + " " + Integer.toString(generation) + " R"; + return Long.toString(number) + " " + Integer.toString(generation) + " R"; } @Override @@ -106,4 +121,4 @@ else if (getGeneration() > other.getGeneration()) } } } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/COSStream.java b/library/src/main/java/com/tom_roush/pdfbox/cos/COSStream.java index 596f10da4..04f672313 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/COSStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/COSStream.java @@ -16,6 +16,8 @@ */ package com.tom_roush.pdfbox.cos; +import android.util.Log; + import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.FilterOutputStream; @@ -29,7 +31,6 @@ import com.tom_roush.pdfbox.filter.FilterFactory; import com.tom_roush.pdfbox.io.IOUtils; import com.tom_roush.pdfbox.io.RandomAccess; -import com.tom_roush.pdfbox.io.RandomAccessBuffer; import com.tom_roush.pdfbox.io.RandomAccessInputStream; import com.tom_roush.pdfbox.io.RandomAccessOutputStream; import com.tom_roush.pdfbox.io.ScratchFile; @@ -50,8 +51,7 @@ public class COSStream extends COSDictionary implements Closeable */ public COSStream() { - this.randomAccess = new RandomAccessBuffer(); - this.scratchFile = null; + this(ScratchFile.getMainMemoryOnlyInstance()); } /** @@ -62,31 +62,8 @@ public COSStream() public COSStream(ScratchFile scratchFile) { super(); - this.randomAccess = createRandomAccess(scratchFile); - this.scratchFile = scratchFile; - } - - /** - * Creates a buffer for writing stream data, either in-memory or on-disk. - */ - private RandomAccess createRandomAccess(ScratchFile scratchFile) - { - if (scratchFile != null) - { - try - { - return scratchFile.createBuffer(); - } - catch (IOException e) - { - // user can't recover from this exception anyway - throw new RuntimeException(e); - } - } - else - { - return new RandomAccessBuffer(); - } + this.scratchFile = + scratchFile != null ? scratchFile : ScratchFile.getMainMemoryOnlyInstance(); } /** @@ -95,7 +72,7 @@ private RandomAccess createRandomAccess(ScratchFile scratchFile) */ private void checkClosed() throws IOException { - if (randomAccess.isClosed()) + if ((randomAccess != null) && randomAccess.isClosed()) { throw new IOException("COSStream has been closed and cannot be read. " + "Perhaps its enclosing PDDocument has been closed?"); @@ -115,6 +92,29 @@ public InputStream getFilteredStream() throws IOException return createRawInputStream(); } + /** + * Ensures {@link #randomAccess} is not null by creating a + * buffer from {@link #scratchFile} if needed. + * + * @param forInputStream if true and {@link #randomAccess} is null + * a debug message is logged - input stream should be retrieved after + * data being written to stream + * @throws IOException + */ + private void ensureRandomAccessExists(boolean forInputStream) throws IOException + { + if (randomAccess == null) + { + if (forInputStream) + { + // no data written to stream - maybe this should be an exception + Log.d("PdfBox-Android", + "Create InputStream called without data being written before to stream."); + } + randomAccess = scratchFile.createBuffer(); + } + } + /** * Returns a new InputStream which reads the encoded PDF stream data. Experts only! * @@ -128,6 +128,7 @@ public InputStream createRawInputStream() throws IOException { throw new IllegalStateException("Cannot read while there is an open stream writer"); } + ensureRandomAccessExists(true); return new RandomAccessInputStream(randomAccess); } @@ -147,7 +148,7 @@ public InputStream getUnfilteredStream() throws IOException /** * Returns a new InputStream which reads the decoded stream data. * - * @return InputStream containing raw, decoded stream data. + * @return InputStream containing decoded stream data. * @throws IOException If the stream could not be read. */ public COSInputStream createInputStream() throws IOException @@ -157,6 +158,7 @@ public COSInputStream createInputStream() throws IOException { throw new IllegalStateException("Cannot read while there is an open stream writer"); } + ensureRandomAccessExists(true); InputStream input = new RandomAccessInputStream(randomAccess); return COSInputStream.create(getFilterList(), this, input, scratchFile); } @@ -204,7 +206,8 @@ public OutputStream createOutputStream(COSBase filters) throws IOException { setItem(COSName.FILTER, filters); } - randomAccess = createRandomAccess(scratchFile); // discards old data + randomAccess = + scratchFile.createBuffer(); // discards old data - TODO: close existing buffer? OutputStream randomOut = new RandomAccessOutputStream(randomAccess); OutputStream cosOut = new COSOutputStream(getFilterList(), this, randomOut, scratchFile); isWriting = true; @@ -254,7 +257,8 @@ public OutputStream createRawOutputStream() throws IOException { throw new IllegalStateException("Cannot have more than one open stream writer."); } - randomAccess = createRandomAccess(scratchFile); // discards old data + randomAccess = + scratchFile.createBuffer(); // discards old data - TODO: close existing buffer? OutputStream out = new RandomAccessOutputStream(randomAccess); isWriting = true; return new FilterOutputStream(out) @@ -386,6 +390,9 @@ public Object accept(ICOSVisitor visitor) throws IOException public void close() throws IOException { // marks the scratch file pages as free - randomAccess.close(); + if (randomAccess != null) + { + randomAccess.close(); + } } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/ICOSVisitor.java b/library/src/main/java/com/tom_roush/pdfbox/cos/ICOSVisitor.java index b7e147f8b..3899b957b 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/ICOSVisitor.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/ICOSVisitor.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tom_roush.pdfbox.cos; import java.io.IOException; diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/PDFDocEncoding.java b/library/src/main/java/com/tom_roush/pdfbox/cos/PDFDocEncoding.java index 70f1dbd13..13fca2553 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/PDFDocEncoding.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/PDFDocEncoding.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.tom_roush.pdfbox.cos; import java.io.ByteArrayOutputStream; @@ -73,7 +89,7 @@ final class PDFDocEncoding set(0xA0, '\u20AC'); // EURO SIGN // end of deviations } - + private PDFDocEncoding() { } @@ -134,4 +150,4 @@ public static boolean containsChar(char character) { return UNI_TO_CODE.containsKey(character); } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/cos/UnmodifiableCOSDictionary.java b/library/src/main/java/com/tom_roush/pdfbox/cos/UnmodifiableCOSDictionary.java index fd83da1b1..f5aed9f68 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/cos/UnmodifiableCOSDictionary.java +++ b/library/src/main/java/com/tom_roush/pdfbox/cos/UnmodifiableCOSDictionary.java @@ -287,4 +287,4 @@ public void mergeInto(COSDictionary dic) { throw new UnsupportedOperationException(); } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/exceptions/CryptographyException.java b/library/src/main/java/com/tom_roush/pdfbox/exceptions/CryptographyException.java deleted file mode 100644 index b880c02da..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/exceptions/CryptographyException.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.exceptions; - -/** - * An exception that indicates that something has gone wrong during a - * cryptography operation. - * - * @author Ben Litchfield - * @version $Revision: 1.4 $ - */ -public class CryptographyException extends Exception -{ - private Exception embedded; - - /** - * Constructor. - * - * @param msg A msg to go with this exception. - */ - public CryptographyException( String msg ) - { - super( msg ); - } - - /** - * Constructor. - * - * @param e The root exception that caused this exception. - */ - public CryptographyException( Exception e ) - { - super( e.getMessage() ); - setEmbedded( e ); - } - /** - * This will get the exception that caused this exception. - * - * @return The embedded exception if one exists. - */ - public Exception getEmbedded() - { - return embedded; - } - /** - * This will set the exception that caused this exception. - * - * @param e The sub exception. - */ - private void setEmbedded( Exception e ) - { - embedded = e; - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/exceptions/InvalidPasswordException.java b/library/src/main/java/com/tom_roush/pdfbox/exceptions/InvalidPasswordException.java deleted file mode 100644 index b9ce7c3a3..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/exceptions/InvalidPasswordException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.exceptions; - -/** - * An exception that indicates an invalid password was supplied. - * - * @author Ben Litchfield - * @version $Revision: 1.4 $ - */ -public class InvalidPasswordException extends Exception -{ - - /** - * Constructor. - * - * @param msg A msg to go with this exception. - */ - public InvalidPasswordException( String msg ) - { - super( msg ); - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/exceptions/SignatureException.java b/library/src/main/java/com/tom_roush/pdfbox/exceptions/SignatureException.java deleted file mode 100644 index 399e6b5c3..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/exceptions/SignatureException.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.exceptions; - -/** - * An exception that indicates a problem during the signing process. - * - * @author Thomas Chojecki - * @version $Revision: $ - */ -public class SignatureException extends Exception -{ - - public final static int WRONG_PASSWORD = 1; - - public final static int UNSUPPORTED_OPERATION = 2; - - public final static int CERT_PATH_CHECK_INVALID = 3; - - public final static int NO_SUCH_ALGORITHM = 4; - - public final static int INVALID_PAGE_FOR_SIGNATURE = 5; - - public final static int VISUAL_SIGNATURE_INVALID = 6; - - private int no; - - /** - * Constructor. - * - * @param msg A msg to go with this exception. - */ - public SignatureException( String msg ) - { - super( msg ); - } - - /** - * Constructor. - * - * @param errno A error number to fulfill this exception - * @param msg A msg to go with this exception. - */ - public SignatureException( int errno , String msg ) - { - super( msg ); - no = errno; - } - - /** - * Constructor. - * - * @param e The exception that should be encapsulate. - */ - public SignatureException(Throwable e) - { - super(e); - } - - /** - * Constructor. - * - * @param errno A error number to fulfill this exception - * @param e The exception that should be encapsulate. - */ - public SignatureException( int errno, Throwable e) - { - super(e); - } - - /** - * A error number to fulfill this exception - * - * @return the error number if available, otherwise 0 - */ - public int getErrNo() - { - return no; - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/ASCII85OutputStream.java b/library/src/main/java/com/tom_roush/pdfbox/filter/ASCII85OutputStream.java index 85664ef86..febae3d1e 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/ASCII85OutputStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/ASCII85OutputStream.java @@ -216,6 +216,7 @@ public void flush() throws IOException out.write(NEWLINE); } out.write(terminator); + out.write('>'); out.write(NEWLINE); count = 0; lineBreak = maxline; diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/ASCIIHexFilter.java b/library/src/main/java/com/tom_roush/pdfbox/filter/ASCIIHexFilter.java index bd1ef3d02..d11f21f23 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/ASCIIHexFilter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/ASCIIHexFilter.java @@ -18,13 +18,13 @@ import android.util.Log; -import com.tom_roush.pdfbox.cos.COSDictionary; -import com.tom_roush.pdfbox.util.Hex; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.util.Hex; + /** * Decodes data encoded in an ASCII hexadecimal form, reproducing the original binary data. * @@ -114,7 +114,7 @@ public void encode(InputStream input, OutputStream encoded, COSDictionary parame int byteRead; while ((byteRead = input.read()) != -1) { - encoded.write(Hex.getBytes((byte)byteRead)); + encoded.write(Hex.getBytes((byte)byteRead)); } encoded.flush(); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/CCITTFaxFilter.java b/library/src/main/java/com/tom_roush/pdfbox/filter/CCITTFaxFilter.java index 1ad3da80c..be889c1ff 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/CCITTFaxFilter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/CCITTFaxFilter.java @@ -16,7 +16,10 @@ */ package com.tom_roush.pdfbox.filter; -import android.util.Log; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; @@ -25,10 +28,6 @@ import com.tom_roush.pdfbox.filter.ccitt.TIFFFaxDecoder; import com.tom_roush.pdfbox.io.IOUtils; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - /** * Decodes image data that has been encoded using either Group 3 or Group 4 * CCITT facsimile (fax) encoding. @@ -127,6 +126,7 @@ private void invertBitmap(byte[] bufferData) protected void encode(InputStream input, OutputStream encoded, COSDictionary parameters) throws IOException { - Log.w("PdfBox-Android", "CCITTFaxDecode.encode is not implemented yet, skipping this stream."); + throw new UnsupportedEncodingException( + "CCITTFaxDecode encoding is not implemented, use the CCITTFactory methods instead."); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/DCTFilter.java b/library/src/main/java/com/tom_roush/pdfbox/filter/DCTFilter.java index 8308ffe77..f04a83c47 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/DCTFilter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/DCTFilter.java @@ -16,15 +16,13 @@ */ package com.tom_roush.pdfbox.filter; -import android.util.Log; - -import com.tom_roush.pdfbox.cos.COSDictionary; -import com.tom_roush.pdfbox.io.IOUtils; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.io.IOUtils; + /** * Decompresses data encoded using a DCT (discrete cosine transform) * technique based on the JPEG standard. @@ -38,13 +36,6 @@ public DecodeResult decode(InputStream encoded, OutputStream decoded, COSDictionary parameters, int index) throws IOException { // Already ready, just read it back out -// byte[] buffer = new byte[1024]; -// int bytesRead; -// while ((bytesRead = encoded.read(buffer)) != -1) -// { -// decoded.write(buffer, 0, bytesRead); -// } - IOUtils.copy(encoded, decoded); return new DecodeResult(parameters); @@ -141,6 +132,7 @@ private int clamp(float value) protected void encode(InputStream input, OutputStream encoded, COSDictionary parameters) throws IOException { - Log.w("PdfBox-Android", "DCTFilter#encode is not implemented yet, skipping this stream."); + throw new UnsupportedOperationException( + "DCTFilter encoding is not implemented, use the JPEGFactory methods instead"); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/Filter.java b/library/src/main/java/com/tom_roush/pdfbox/filter/Filter.java index 830a9ad7a..4b7a0deb7 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/Filter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/Filter.java @@ -18,15 +18,15 @@ import android.util.Log; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - /** * A filter for stream data. * @@ -74,7 +74,7 @@ protected abstract void encode(InputStream input, OutputStream encoded, // gets the decode params for a specific filter index, this is used to // normalise the DecodeParams entry so that it is always a dictionary - protected static COSDictionary getDecodeParams(COSDictionary dictionary, int index) + protected COSDictionary getDecodeParams(COSDictionary dictionary, int index) { COSBase obj = dictionary.getDictionaryObject(COSName.DECODE_PARMS, COSName.DP); if (obj instanceof COSDictionary) diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/FlateFilter.java b/library/src/main/java/com/tom_roush/pdfbox/filter/FlateFilter.java index 756428f4f..ad0dd4e3a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/FlateFilter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/FlateFilter.java @@ -18,9 +18,6 @@ import android.util.Log; -import com.tom_roush.pdfbox.cos.COSDictionary; -import com.tom_roush.pdfbox.cos.COSName; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -30,6 +27,9 @@ import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; + /** * Decompresses data encoded using the zlib/deflate compression method, * reproducing the original text or binary data. @@ -86,21 +86,46 @@ public DecodeResult decode(InputStream encoded, OutputStream decoded, // Use Inflater instead of InflateInputStream to avoid an EOFException due to a probably // missing Z_STREAM_END, see PDFBOX-1232 for details - private static void decompress(InputStream in, OutputStream out) throws IOException, DataFormatException - { - byte[] buf = new byte[2048]; - int read = in.read(buf); - if (read > 0) - { - Inflater inflater = new Inflater(); - inflater.setInput(buf,0,read); - byte[] res = new byte[2048]; - while (true) - { - int resRead = inflater.inflate(res); + private void decompress(InputStream in, OutputStream out) + throws IOException, DataFormatException + { + byte[] buf = new byte[2048]; + // skip zlib header + in.read(buf, 0, 2); + int read = in.read(buf); + if (read > 0) + { + // use nowrap mode to bypass zlib-header and checksum to avoid a DataFormatException + Inflater inflater = new Inflater(true); + inflater.setInput(buf,0,read); + byte[] res = new byte[2048]; + boolean dataWritten = false; + while (true) + { + int resRead = 0; + try + { + resRead = inflater.inflate(res); + } + catch (DataFormatException exception) + { + if (dataWritten) + { + // some data could be read -> don't throw an exception + Log.w("PdfBox-Android", + "FlateFilter: premature end of stream due to a DataFormatException"); + break; + } + else + { + // nothing could be read -> re-throw exception + throw exception; + } + } if (resRead != 0) - { - out.write(res,0,resRead); + { + out.write(res, 0, resRead); + dataWritten = true; continue; } if (inflater.finished() || inflater.needsDictionary() || in.available() == 0) @@ -108,7 +133,7 @@ private static void decompress(InputStream in, OutputStream out) throws IOExcept break; } read = in.read(buf); - inflater.setInput(buf,0,read); + inflater.setInput(buf,0,read); } inflater.end(); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/LZWFilter.java b/library/src/main/java/com/tom_roush/pdfbox/filter/LZWFilter.java index fb2c2b80a..d32961644 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/LZWFilter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/LZWFilter.java @@ -17,11 +17,6 @@ import android.util.Log; -import com.tom_roush.harmony.javax.imageio.stream.MemoryCacheImageInputStream; -import com.tom_roush.harmony.javax.imageio.stream.MemoryCacheImageOutputStream; -import com.tom_roush.pdfbox.cos.COSDictionary; -import com.tom_roush.pdfbox.cos.COSName; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; @@ -32,6 +27,11 @@ import java.util.Arrays; import java.util.List; +import com.tom_roush.harmony.javax.imageio.stream.MemoryCacheImageInputStream; +import com.tom_roush.harmony.javax.imageio.stream.MemoryCacheImageOutputStream; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; + /** * * This is the filter used for the LZWDecode filter. @@ -76,6 +76,7 @@ public DecodeResult decode(InputStream encoded, OutputStream decoded, } if (predictor > 1) { + @SuppressWarnings("null") int colors = Math.min(decodeParams.getInt(COSName.COLORS, 1), 32); int bitsPerPixel = decodeParams.getInt(COSName.BITS_PER_COMPONENT, 8); int columns = decodeParams.getInt(COSName.COLUMNS, 1); @@ -121,6 +122,7 @@ private void doLZWDecode(InputStream encoded, OutputStream decoded, int earlyCha decoded.write(data); if (prevCommand != -1) { + checkIndexBounds(codeTable, prevCommand, in); data = codeTable.get((int) prevCommand); byte[] newData = Arrays.copyOf(data, data.length + 1); newData[data.length] = firstByte; @@ -129,6 +131,7 @@ private void doLZWDecode(InputStream encoded, OutputStream decoded, int earlyCha } else { + checkIndexBounds(codeTable, prevCommand, in); byte[] data = codeTable.get((int) prevCommand); byte[] newData = Arrays.copyOf(data, data.length + 1); newData[data.length] = data[0]; @@ -143,11 +146,27 @@ private void doLZWDecode(InputStream encoded, OutputStream decoded, int earlyCha } catch (EOFException ex) { - Log.w("PdfBox-Android", "Premature EOF in LZW stream, EOD code missing"); + Log.w("PdfBox-Android", "Premature EOF in LZW stream, EOD code missing"); } decoded.flush(); } + private void checkIndexBounds(List codeTable, long index, MemoryCacheImageInputStream in) + throws IOException + { + if (index < 0) + { + throw new IOException( + "negative array index: " + index + " near offset " + in.getStreamPosition()); + } + if (index >= codeTable.size()) + { + throw new IOException( + "array index overflow: " + index + " >= " + codeTable.size() + " near offset " + + in.getStreamPosition()); + } + } + /** * {@inheritDoc} */ @@ -168,7 +187,7 @@ protected void encode(InputStream rawData, OutputStream encoded, COSDictionary p byte by = (byte) r; if (inputPattern == null) { - inputPattern = new byte[] { by }; + inputPattern = new byte[] { by }; foundCode = by & 0xff; } else @@ -214,6 +233,7 @@ protected void encode(InputStream rawData, OutputStream encoded, COSDictionary p chunk = calculateChunk(codeTable.size(), 1); out.writeBits(EOD, chunk); + // pad with 0 out.writeBits(0, 7); @@ -242,19 +262,19 @@ private int findPatternCode(List codeTable, byte[] pattern) if (foundCode != -1) { // we already found pattern with size > 1 - return foundCode; + return foundCode; } else if (pattern.length > 1) { // we won't find anything here anyway - return -1; + return -1; } } byte[] tryPattern = codeTable.get(i); if ((foundCode != -1 || tryPattern.length > foundLen) && Arrays.equals(tryPattern, pattern)) { - foundCode = i; - foundLen = tryPattern.length; + foundCode = i; + foundLen = tryPattern.length; } } return foundCode; @@ -269,7 +289,7 @@ private List createCodeTable() List codeTable = new ArrayList(4096); for (int i = 0; i < 256; ++i) { - codeTable.add(new byte[] { (byte) (i & 0xFF) }); + codeTable.add(new byte[] { (byte)(i & 0xFF) }); } codeTable.add(null); // 256 EOD codeTable.add(null); // 257 CLEAR_TABLE diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/MissingImageReaderException.java b/library/src/main/java/com/tom_roush/pdfbox/filter/MissingImageReaderException.java index 2e8748ee9..98b7afd5b 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/MissingImageReaderException.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/MissingImageReaderException.java @@ -25,10 +25,10 @@ */ public class MissingImageReaderException extends IOException { - /** - * - */ - private static final long serialVersionUID = 1L; + /** + * + */ + private static final long serialVersionUID = 1L; public MissingImageReaderException(String message) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/Predictor.java b/library/src/main/java/com/tom_roush/pdfbox/filter/Predictor.java index b2d34dbe8..443914180 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/Predictor.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/Predictor.java @@ -79,33 +79,81 @@ static void decodePredictor(int predictor, int colors, int bitsPerComponent, int { case 2: // PRED TIFF SUB - // TODO decode tiff with bpc smaller 8 - // e.g. for 4 bpc each nibble must be subtracted separately + if (bitsPerComponent == 8) + { + // for 8 bits per component it is the same algorithm as PRED SUB of PNG format + for (int p = bytesPerPixel; p < rowlength; p++) + { + int sub = actline[p] & 0xff; + int left = actline[p - bytesPerPixel] & 0xff; + actline[p] = (byte) (sub + left); + } + break; + } if (bitsPerComponent == 16) { - for (int p = 0; p < rowlength; p += 2) + for (int p = bytesPerPixel; p < rowlength; p += 2) { int sub = ((actline[p] & 0xff) << 8) + (actline[p + 1] & 0xff); - int left = p - bytesPerPixel >= 0 - ? (((actline[p - bytesPerPixel] & 0xff) << 8) - + (actline[p - bytesPerPixel + 1] & 0xff)) - : 0; + int left = (((actline[p - bytesPerPixel] & 0xff) << 8) + + (actline[p - bytesPerPixel + 1] & 0xff)); actline[p] = (byte) (((sub + left) >> 8) & 0xff); actline[p + 1] = (byte) ((sub + left) & 0xff); } break; } - if (bitsPerComponent < 8) + if (bitsPerComponent == 1 && colors == 1) { - throw new IOException("TIFF-Predictor with " + bitsPerComponent - + " bits per component not supported; please open JIRA issue with sample PDF"); + // bytesPerPixel cannot be used: + // "A row shall occupy a whole number of bytes, rounded up if necessary. + // Samples and their components shall be packed into bytes + // from high-order to low-order bits." + for (int p = 0; p < rowlength; p++) + { + for (int bit = 7; bit >= 0; --bit) + { + int sub = (actline[p] >> bit) & 1; + if (p == 0 && bit == 7) + { + continue; + } + int left; + if (bit == 7) + { + // use bit #0 from previous byte + left = actline[p - 1] & 1; + } + else + { + // use "previous" bit + left = (actline[p] >> (bit + 1)) & 1; + } + if (((sub + left) & 1) == 0) + { + // reset bit + actline[p] = (byte) (actline[p] & ~(1 << bit)); + } + else + { + // set bit + actline[p] = (byte) (actline[p] | (1 << bit)); + } + } + } + break; } - // for 8 bits per component it is the same algorithm as PRED SUB of PNG format - for (int p = 0; p < rowlength; p++) + // everything else, i.e. bpc 2 and 4, but has been tested for bpc 1 and 8 too + int elements = columns * colors; + for (int p = colors; p < elements; ++p) { - int sub = actline[p] & 0xff; - int left = p - bytesPerPixel >= 0 ? actline[p - bytesPerPixel] & 0xff : 0; - actline[p] = (byte) (sub + left); + int bytePosSub = p * bitsPerComponent / 8; + int bitPosSub = 8 - p * bitsPerComponent % 8 - bitsPerComponent; + int bytePosLeft = (p - colors) * bitsPerComponent / 8; + int bitPosLeft = 8 - (p - colors) * bitsPerComponent % 8 - bitsPerComponent; + + int sub = getBitSeq(actline[bytePosSub], bitPosSub, bitsPerComponent); + int left = getBitSeq(actline[bytePosLeft], bitPosLeft, bitsPerComponent); + actline[bytePosSub] = (byte) calcSetBitSeq(actline[bytePosSub], bitPosSub, bitsPerComponent, sub + left); } break; case 10: @@ -114,10 +162,10 @@ static void decodePredictor(int predictor, int colors, int bitsPerComponent, int break; case 11: // PRED SUB - for (int p = 0; p < rowlength; p++) + for (int p = bytesPerPixel; p < rowlength; p++) { int sub = actline[p]; - int left = p - bytesPerPixel >= 0 ? actline[p - bytesPerPixel] : 0; + int left = actline[p - bytesPerPixel]; actline[p] = (byte) (sub + left); } break; @@ -137,7 +185,7 @@ static void decodePredictor(int predictor, int colors, int bitsPerComponent, int int avg = actline[p] & 0xff; int left = p - bytesPerPixel >= 0 ? actline[p - bytesPerPixel] & 0xff : 0; int up = lastline[p] & 0xff; - actline[p] = (byte) ((avg + ((left + up) / 2)) & 0xff); + actline[p] = (byte) ((avg + (left + up) / 2) & 0xff); } break; case 14: @@ -176,4 +224,19 @@ else if (absb <= absc) } } + // get value from bit interval from a byte + static int getBitSeq(int by, int startBit, int bitSize) + { + int mask = ((1 << bitSize) - 1); + return (by >>> startBit) & mask; + } + + // set value in a bit interval and return that value + static int calcSetBitSeq(int by, int startBit, int bitSize, int val) + { + int mask = ((1 << bitSize) - 1); + int truncatedVal = val & mask; + mask = ~(mask << startBit); + return (by & mask) | (truncatedVal << startBit); + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/ccitt/CCITTFaxG31DDecodeInputStream.java b/library/src/main/java/com/tom_roush/pdfbox/filter/ccitt/CCITTFaxG31DDecodeInputStream.java index 60fc491d9..05713ac60 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/ccitt/CCITTFaxG31DDecodeInputStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/ccitt/CCITTFaxG31DDecodeInputStream.java @@ -119,14 +119,14 @@ private boolean decodeLine() throws IOException } if (this.bits < 0) { - //Shortcut after EOD + //Shortcut after EOD return false; } this.y++; int x = 0; if (this.rows > 0 && this.y >= this.rows) { - //All rows decoded, ignore further bits + //All rows decoded, ignore further bits return false; } this.decodedLine.clear(); @@ -157,12 +157,12 @@ else if (code.getType() == SIGNAL_EOL) expectRTC--; if (expectRTC == 0) { - //Return to Control = End Of Data + //Return to Control = End Of Data return false; } if (x == 0) { - //Ignore leading EOL + //Ignore leading EOL continue; } } @@ -307,10 +307,8 @@ public abstract CodeWord getNextCodeWord(CCITTFaxG31DDecodeInputStream decoder) /** Interface for code words. */ private interface CodeWord { - int getType(); int execute(CCITTFaxG31DDecodeInputStream decoder) throws IOException; - } /** Non-leaf nodes that hold a child node for both the 0 and 1 cases for the lookup tree. */ @@ -434,8 +432,8 @@ public CodeWord getNextCodeWord(CCITTFaxG31DDecodeInputStream decoder) throws IO int bit; do { - bit = decoder.readBit(); - //bit 1 finishes the EOL, any number of bit 0 allowed as fillers + bit = decoder.readBit(); + //bit 1 finishes the EOL, any number of bit 0 allowed as fillers } while (bit == 0); if (bit < 0) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/filter/ccitt/TIFFFaxDecoder.java b/library/src/main/java/com/tom_roush/pdfbox/filter/ccitt/TIFFFaxDecoder.java index ec34cbf9a..d204286c4 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/filter/ccitt/TIFFFaxDecoder.java +++ b/library/src/main/java/com/tom_roush/pdfbox/filter/ccitt/TIFFFaxDecoder.java @@ -1593,4 +1593,4 @@ private boolean advancePointer() return true; } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/io/IOUtils.java b/library/src/main/java/com/tom_roush/pdfbox/io/IOUtils.java index d6ed71aee..cfaea3613 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/io/IOUtils.java +++ b/library/src/main/java/com/tom_roush/pdfbox/io/IOUtils.java @@ -92,7 +92,7 @@ public static long populateBuffer(InputStream in, byte[] buffer) throws IOExcept } return buffer.length - remaining; } - + /** * Null safe close of the given {@link Closeable} suppressing any exception. * @@ -100,16 +100,16 @@ public static long populateBuffer(InputStream in, byte[] buffer) throws IOExcept */ public static void closeQuietly(Closeable closeable) { - try - { - if (closeable != null) - { - closeable.close(); - } - } - catch (IOException ioe) - { - // ignore - } + try + { + if (closeable != null) + { + closeable.close(); + } + } + catch (IOException ioe) + { + // ignore + } } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/io/MemoryUsageSetting.java b/library/src/main/java/com/tom_roush/pdfbox/io/MemoryUsageSetting.java index 0d55ae5b2..306c7076b 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/io/MemoryUsageSetting.java +++ b/library/src/main/java/com/tom_roush/pdfbox/io/MemoryUsageSetting.java @@ -91,7 +91,6 @@ private MemoryUsageSetting(boolean useMainMemory, boolean useTempFile, locMaxStorageBytes = locMaxMainMemoryBytes; } - this.useMainMemory = locUseMainMemory; this.useTempFile = useTempFile; this.maxMainMemoryBytes = locMaxMainMemoryBytes; @@ -154,6 +153,29 @@ public static MemoryUsageSetting setupMixed(long maxMainMemoryBytes) return setupMixed(maxMainMemoryBytes, -1); } + /** + * Returns a copy of this instance with the maximum memory/storage restriction + * divided by the provided number of parallel uses. + * + * @param parallelUseCount specifies the number of parallel usages for the setting to + * be returned + * @return a copy from this instance with the maximum memory/storage restrictions + * adjusted to the multiple usage + */ + public MemoryUsageSetting getPartitionedCopy(int parallelUseCount) + { + long newMaxMainMemoryBytes = + maxMainMemoryBytes <= 0 ? maxMainMemoryBytes : maxMainMemoryBytes / parallelUseCount; + long newMaxStorageBytes = + maxStorageBytes <= 0 ? maxStorageBytes : maxStorageBytes / parallelUseCount; + + MemoryUsageSetting copy = new MemoryUsageSetting(useMainMemory, useTempFile, + newMaxMainMemoryBytes, newMaxStorageBytes); + copy.tempDir = tempDir; + + return copy; + } + /** * Setups buffering memory usage to use a portion of main-memory and additionally * temporary file(s) in case the specified portion is exceeded. diff --git a/library/src/main/java/com/tom_roush/pdfbox/io/RandomAccessBuffer.java b/library/src/main/java/com/tom_roush/pdfbox/io/RandomAccessBuffer.java index 169a8abb3..98e4a2994 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/io/RandomAccessBuffer.java +++ b/library/src/main/java/com/tom_roush/pdfbox/io/RandomAccessBuffer.java @@ -21,8 +21,6 @@ import java.util.ArrayList; import java.util.List; -import static java.lang.System.arraycopy; - /** * An implementation of the RandomAccess interface to store data in memory. * The data will be stored in chunks organized in an ArrayList. @@ -53,9 +51,18 @@ public class RandomAccessBuffer implements RandomAccess, Cloneable * Default constructor. */ public RandomAccessBuffer() + { + this(DEFAULT_CHUNK_SIZE); + } + + /** + * Default constructor. + */ + private RandomAccessBuffer(int definedChunkSize) { // starting with one chunk bufferList = new ArrayList(); + chunkSize = definedChunkSize; currentBuffer = new byte[chunkSize]; bufferList.add(currentBuffer); pointer = 0; @@ -106,13 +113,13 @@ public RandomAccessBuffer(InputStream input) throws IOException @Override public RandomAccessBuffer clone() { - RandomAccessBuffer copy = new RandomAccessBuffer(); + RandomAccessBuffer copy = new RandomAccessBuffer(chunkSize); copy.bufferList = new ArrayList(bufferList.size()); for (byte [] buffer : bufferList) { byte [] newBuffer = new byte [buffer.length]; - arraycopy(buffer, 0, newBuffer, 0, buffer.length); + System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); copy.bufferList.add(newBuffer); } if (currentBuffer!=null) @@ -267,7 +274,7 @@ private int readRemainingBytes(byte[] b, int offset, int length) throws IOExcept if (maxLength >= remainingBytes) { // copy the remaining bytes from the current buffer - arraycopy(currentBuffer, currentBufferPointer, b, offset, remainingBytes); + System.arraycopy(currentBuffer, currentBufferPointer, b, offset, remainingBytes); // end of file reached currentBufferPointer += remainingBytes; pointer += remainingBytes; @@ -276,7 +283,7 @@ private int readRemainingBytes(byte[] b, int offset, int length) throws IOExcept else { // copy the remaining bytes from the whole buffer - arraycopy(currentBuffer, currentBufferPointer, b, offset, maxLength); + System.arraycopy(currentBuffer, currentBufferPointer, b, offset, maxLength); // end of file reached currentBufferPointer += maxLength; pointer += maxLength; @@ -327,6 +334,15 @@ public void write(int b) throws IOException } } + /** + * {@inheritDoc} + */ + @Override + public void write(byte[] b) throws IOException + { + write(b, 0, b.length); + } + /** * {@inheritDoc} */ @@ -369,7 +385,7 @@ public void write(byte[] b, int offset, int length) throws IOException } else { - arraycopy(b, offset, currentBuffer, (int) currentBufferPointer, length); + System.arraycopy(b, offset, currentBuffer, currentBufferPointer, length); currentBufferPointer += length; } pointer += length; @@ -427,22 +443,13 @@ private void checkClosed () throws IOException } - /** - * {@inheritDoc} - */ - @Override - public void write(byte[] b) throws IOException - { - write(b, 0, b.length); - } - /** * {@inheritDoc} */ @Override public boolean isClosed() { - return currentBuffer == null; + return currentBuffer == null; } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/io/RandomAccessBufferedFileInputStream.java b/library/src/main/java/com/tom_roush/pdfbox/io/RandomAccessBufferedFileInputStream.java index 54f19b697..9aba35317 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/io/RandomAccessBufferedFileInputStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/io/RandomAccessBufferedFileInputStream.java @@ -105,7 +105,7 @@ public RandomAccessBufferedFileInputStream(File file) throws IOException * Create a random access input stream for the given input stream by copying the data to a * temporary file. * - * @param input the input stream to be read. + * @param input the input stream to be read. It will be closed by this method. * @throws IOException if something went wrong while creating the temporary file. */ public RandomAccessBufferedFileInputStream(InputStream input) throws IOException @@ -313,7 +313,7 @@ public void close() throws IOException @Override public boolean isClosed() { - return isClosed; + return isClosed; } @Override diff --git a/library/src/main/java/com/tom_roush/pdfbox/io/ScratchFile.java b/library/src/main/java/com/tom_roush/pdfbox/io/ScratchFile.java index 9452aa122..ce24f8f0b 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/io/ScratchFile.java +++ b/library/src/main/java/com/tom_roush/pdfbox/io/ScratchFile.java @@ -139,6 +139,28 @@ public ScratchFile(MemoryUsageSetting memUsageSetting) throws IOException freePages.set(0, inMemoryPages.length); } + /** + * Getter for an instance using only unrestricted main memory for buffering + * (same as new ScratchFile(MemoryUsageSetting.setupMainMemoryOnly())). + * + * @return instance configured to only use main memory with no size restriction + */ + public static ScratchFile getMainMemoryOnlyInstance() + { + try + { + return new ScratchFile(MemoryUsageSetting.setupMainMemoryOnly()); + } + catch (IOException ioe) + { + // cannot happen for main memory setup + Log.e("PdfBox-Android", + "Unexpected exception occurred creating main memory scratch file instance: " + + ioe.getMessage()); + return null; + } + } + /** * Returns a new free page, either from free page pool * or by enlarging scratch file (may be created). @@ -271,6 +293,7 @@ int getPageSize() * Reads the page with specified index. * * @param pageIdx index of page to read + * * @return byte array of size {@link #PAGE_SIZE} filled with page data read from file * @throws IOException */ @@ -307,6 +330,7 @@ byte[] readPage(int pageIdx) throws IOException throw new IOException( "Missing scratch file to read page with index " + pageIdx + " from."); } + byte[] page = new byte[PAGE_SIZE]; raf.seek(((long) pageIdx - inMemoryMaxPageCount) * PAGE_SIZE); raf.readFully(page); @@ -324,6 +348,7 @@ byte[] readPage(int pageIdx) throws IOException * * @param pageIdx index of page to write * @param page page to write (length has to be {@value #PAGE_SIZE}) + * * @throws IOException in case page index is out of range or page has wrong length * or writing to file failed */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/io/ScratchFileBuffer.java b/library/src/main/java/com/tom_roush/pdfbox/io/ScratchFileBuffer.java index cb2aeb5f4..7c437d313 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/io/ScratchFileBuffer.java +++ b/library/src/main/java/com/tom_roush/pdfbox/io/ScratchFileBuffer.java @@ -16,9 +16,13 @@ */ package com.tom_roush.pdfbox.io; +import android.util.Log; + import java.io.EOFException; import java.io.IOException; +import com.tom_roush.pdfbox.cos.COSStream; + /** * Implementation of {@link RandomAccess} as sequence of multiple fixed size pages handled * by {@link ScratchFile}. @@ -496,4 +500,31 @@ public void close() throws IOException size = 0; } } + + /** + * While calling finalize is normally discouraged we will have to + * use it here as long as closing a scratch file buffer is not + * done in every case. Currently {@link COSStream} creates new + * buffers without closing the old one - which might still be + * used. + * + *

Enabling debugging one will see if there are still cases + * where the buffer is not closed.

+ */ + @Override + protected void finalize() throws Throwable + { + try + { + if ((pageHandler != null)) + { + Log.d("PdfBox-Android", "ScratchFileBuffer not closed!"); + } + close(); + } + finally + { + super.finalize(); + } + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/multipdf/LayerUtility.java b/library/src/main/java/com/tom_roush/pdfbox/multipdf/LayerUtility.java index 65b5736c4..22f06af90 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/multipdf/LayerUtility.java +++ b/library/src/main/java/com/tom_roush/pdfbox/multipdf/LayerUtility.java @@ -155,7 +155,7 @@ public PDFormXObject importPageAsForm(PDDocument sourceDoc, PDPage page) throws form.setResources(formRes); //Transfer some values from page to form - transferDict(page.getCOSObject(), form.getCOSStream(), PAGE_TO_FORM_FILTER, true); + transferDict(page.getCOSObject(), form.getCOSObject(), PAGE_TO_FORM_FILTER, true); Matrix matrix = form.getMatrix(); AffineTransform at = matrix.createAffineTransform(); @@ -175,16 +175,16 @@ public PDFormXObject importPageAsForm(PDDocument sourceDoc, PDPage page) throws case 90: at.scale(viewBox.getWidth() / viewBox.getHeight(), viewBox.getHeight() / viewBox.getWidth()); at.translate(0, viewBox.getWidth()); - at.rotate((float)(-Math.PI / 2.0)); + at.rotate(-Math.PI / 2.0); break; case 180: at.translate(viewBox.getWidth(), viewBox.getHeight()); - at.rotate((float)-Math.PI); + at.rotate(-Math.PI); break; case 270: at.scale(viewBox.getWidth() / viewBox.getHeight(), viewBox.getHeight() / viewBox.getWidth()); at.translate(viewBox.getHeight(), 0); - at.rotate((float)(-Math.PI * 1.5)); + at.rotate(-Math.PI * 1.5); default: //no additional transformations necessary } @@ -237,7 +237,7 @@ public PDOptionalContentGroup appendFormAsLayer(PDPage targetPage, ocprops.addGroup(layer); PDPageContentStream contentStream = new PDPageContentStream( - targetDoc, targetPage, true, !DEBUG); + targetDoc, targetPage, PDPageContentStream.AppendMode.APPEND, !DEBUG); contentStream.beginMarkedContent(COSName.OC, layer); contentStream.saveGraphicsState(); contentStream.transform(new Matrix(transform)); diff --git a/library/src/main/java/com/tom_roush/pdfbox/multipdf/Overlay.java b/library/src/main/java/com/tom_roush/pdfbox/multipdf/Overlay.java index ee77ede84..74e07a51d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/multipdf/Overlay.java +++ b/library/src/main/java/com/tom_roush/pdfbox/multipdf/Overlay.java @@ -55,6 +55,8 @@ public enum Position FOREGROUND, BACKGROUND } + ; + private LayoutPage defaultOverlayPage; private LayoutPage firstPageOverlayPage; private LayoutPage lastPageOverlayPage; @@ -69,8 +71,6 @@ public enum Position private String inputFileName = null; private PDDocument inputPDFDocument = null; - private String outputFilename = null; - private String defaultOverlayFilename = null; private PDDocument defaultOverlay = null; @@ -96,54 +96,55 @@ public enum Position * This will add overlays to a documents. * * @param specificPageOverlayFile map of overlay files for specific pages + * @return the resulting pdf, which has to be saved and closed be the caller * @throws IOException if something went wrong */ - public void overlay(Map specificPageOverlayFile) - throws IOException + public PDDocument overlay(Map specificPageOverlayFile) throws IOException { - try + loadPDFs(); + for (Map.Entry e : specificPageOverlayFile.entrySet()) { - loadPDFs(); - for (Map.Entry e : specificPageOverlayFile.entrySet()) - { - PDDocument doc = loadPDF(e.getValue()); - specificPageOverlay.put(e.getKey(), doc); - specificPageOverlayPage.put(e.getKey(), getLayoutPage(doc)); - } - processPages(inputPDFDocument); + PDDocument doc = loadPDF(e.getValue()); + specificPageOverlay.put(e.getKey(), doc); + specificPageOverlayPage.put(e.getKey(), getLayoutPage(doc)); + } + processPages(inputPDFDocument); + return inputPDFDocument; + } - inputPDFDocument.save(outputFilename); + /** + * Close all input pdfs which were used for the overlay. + * + * @throws IOException if something went wrong + */ + public void close() throws IOException + { + if (defaultOverlay != null) + { + defaultOverlay.close(); } - finally + if (firstPageOverlay != null) + { + firstPageOverlay.close(); + } + if (lastPageOverlay != null) + { + lastPageOverlay.close(); + } + if (allPagesOverlay != null) + { + allPagesOverlay.close(); + } + if (oddPageOverlay != null) + { + oddPageOverlay.close(); + } + if (evenPageOverlay != null) + { + evenPageOverlay.close(); + } + if (specificPageOverlay != null) { - if (inputPDFDocument != null) - { - inputPDFDocument.close(); - } - if (defaultOverlay != null) - { - defaultOverlay.close(); - } - if (firstPageOverlay != null) - { - firstPageOverlay.close(); - } - if (lastPageOverlay != null) - { - lastPageOverlay.close(); - } - if (allPagesOverlay != null) - { - allPagesOverlay.close(); - } - if (oddPageOverlay != null) - { - oddPageOverlay.close(); - } - if (evenPageOverlay != null) - { - evenPageOverlay.close(); - } for (Map.Entry e : specificPageOverlay.entrySet()) { e.getValue().close(); @@ -508,26 +509,6 @@ public String getInputFile() return inputFileName; } - /** - * Sets the output file. - * - * @param outputFile the output file - */ - public void setOutputFile(String outputFile) - { - outputFilename = outputFile; - } - - /** - * Returns the output file. - * - * @return the output file - */ - public String getOutputFile() - { - return outputFilename; - } - /** * Sets the default overlay file. * diff --git a/library/src/main/java/com/tom_roush/pdfbox/multipdf/PDFCloneUtility.java b/library/src/main/java/com/tom_roush/pdfbox/multipdf/PDFCloneUtility.java index 4617f053c..38803af89 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/multipdf/PDFCloneUtility.java +++ b/library/src/main/java/com/tom_roush/pdfbox/multipdf/PDFCloneUtility.java @@ -16,6 +16,13 @@ */ package com.tom_roush.pdfbox.multipdf; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; @@ -26,12 +33,6 @@ import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; -import java.io.IOException; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - /** * Utility class used to clone PDF objects. It keeps track of objects it has already cloned. */ @@ -112,8 +113,10 @@ else if( base instanceof COSStream ) { COSStream originalStream = (COSStream)base; COSStream stream = destination.getDocument().createCOSStream(); - OutputStream output = stream.createOutputStream(originalStream.getFilters()); - IOUtils.copy(originalStream.createInputStream(), output); + OutputStream output = stream.createRawOutputStream(); + InputStream input = originalStream.createRawInputStream(); + IOUtils.copy(input, output); + input.close(); output.close(); clonedVersion.put(base, stream); for( Map.Entry entry : originalStream.entrySet() ) diff --git a/library/src/main/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtility.java b/library/src/main/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtility.java index 61d40d4aa..65ce60a45 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtility.java +++ b/library/src/main/java/com/tom_roush/pdfbox/multipdf/PDFMergerUtility.java @@ -16,6 +16,18 @@ */ package com.tom_roush.pdfbox.multipdf; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; @@ -24,6 +36,7 @@ import com.tom_roush.pdfbox.cos.COSNumber; import com.tom_roush.pdfbox.cos.COSStream; import com.tom_roush.pdfbox.cos.COSString; +import com.tom_roush.pdfbox.io.MemoryUsageSetting; import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.pdmodel.PDDocumentCatalog; import com.tom_roush.pdfbox.pdmodel.PDDocumentInformation; @@ -42,18 +55,6 @@ import com.tom_roush.pdfbox.pdmodel.interactive.form.PDAcroForm; import com.tom_roush.pdfbox.pdmodel.interactive.form.PDField; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - /** * This class will take a list of pdf documents and merge them, saving the * result in a new document. @@ -166,14 +167,27 @@ public void addSources(List 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. + * @deprecated use {@link #mergeDocuments(com.tom_roush.pdfbox.io.MemoryUsageSetting) } + */ + @Deprecated + public void mergeDocuments() throws IOException + { + mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly()); + } + /** * Merge the list of source documents, saving the result in the destination * file. * - * @param useScratchFiles enables the usage of a scratch file if set to true + * @param memUsageSetting defines how memory is used for buffering PDF streams; + * in case of null unrestricted main memory is used * @throws IOException If there is an error saving the document. */ - public void mergeDocuments(boolean useScratchFiles) throws IOException + public void mergeDocuments(MemoryUsageSetting memUsageSetting) throws IOException { PDDocument destination = null; InputStream sourceFile; @@ -184,13 +198,16 @@ public void mergeDocuments(boolean useScratchFiles) throws IOException try { + MemoryUsageSetting partitionedMemSetting = + memUsageSetting != null ? memUsageSetting.getPartitionedCopy( + sources.size() + 1) : MemoryUsageSetting.setupMainMemoryOnly(); Iterator sit = sources.iterator(); - destination = new PDDocument(useScratchFiles); + destination = new PDDocument(partitionedMemSetting); while (sit.hasNext()) { sourceFile = sit.next(); - source = PDDocument.load(sourceFile, useScratchFiles); + source = PDDocument.load(sourceFile, partitionedMemSetting); tobeclosed.add(source); appendDocument(destination, source); } @@ -232,13 +249,13 @@ public void mergeDocuments(boolean useScratchFiles) throws IOException */ public void appendDocument(PDDocument destination, PDDocument source) throws IOException { - if (destination.isEncrypted()) + if (source.getDocument().isClosed()) { - throw new IOException("Error: destination PDF is encrypted, can't append encrypted PDF documents."); + throw new IOException("Error: source PDF is closed."); } - if (source.isEncrypted()) + if (destination.getDocument().isClosed()) { - throw new IOException("Error: source PDF is encrypted, can't append encrypted PDF documents."); + throw new IOException("Error: destination PDF is closed."); } PDDocumentCatalog destCatalog = destination.getDocumentCatalog(); @@ -404,8 +421,8 @@ public void appendDocument(PDDocument destination, PDDocument source) throws IOE if (destMetadata == null && srcMetadata != null) { PDStream newStream = new PDStream(destination, srcMetadata.createInputStream(), - COSName.FLATE_DECODE); - newStream.getStream().mergeInto(srcMetadata); + (COSName)null); + newStream.getCOSObject().mergeInto(srcMetadata); destCatalog.getCOSObject().setItem(COSName.METADATA, newStream); } @@ -535,20 +552,30 @@ private void mergeAcroForm(PDFCloneUtility cloner, PDAcroForm destAcroForm, PDAc List srcFields = srcAcroForm.getFields(); if (srcFields != null) { - // keep fields from previous merged parts - COSArray destFields = (COSArray) destAcroForm.getCOSObject().getItem(COSName.FIELDS); - if ( destFields == null ) { - destFields = new COSArray(); + // if a form is merged multiple times using PDFBox the newly generated + // fields starting with dummyFieldName may already exist. We need to determine the last unique + // number used and increment that. + final String prefix = "dummyFieldName"; + final int prefixLength = prefix.length(); + + for (PDField destField : destAcroForm.getFieldTree()) + { + String fieldName = destField.getPartialName(); + if (fieldName.startsWith(prefix)) + { + nextFieldNum = Math.max(nextFieldNum, Integer.parseInt(fieldName.substring(prefixLength, fieldName.length()))+1); + } } - // fixme: we're only iterating over the root fields, names of kids aren't being checked - for (PDField srcField : srcFields) + + COSArray destFields = (COSArray) destAcroForm.getCOSObject().getItem(COSName.FIELDS); + for (PDField srcField : srcAcroForm.getFieldTree()) { COSDictionary dstField = (COSDictionary) cloner.cloneForNewDocument(srcField.getCOSObject()); // if the form already has a field with this name then we need to rename this field // to prevent merge conflicts. if (destAcroForm.getField(srcField.getFullyQualifiedName()) != null) { - dstField.setString(COSName.T, "dummyFieldName" + nextFieldNum++); + dstField.setString(COSName.T, prefix + nextFieldNum++); } destFields.add(dstField); } @@ -591,7 +618,7 @@ private void updatePageReferences(COSDictionary parentTreeEntry, Map * protected void splitAtPage() * { - * // will split at pages with prime numbers only - * return isPrime(pageNumber); + * // will split at pages with prime numbers only + * return isPrime(pageNumber); * } * * @@ -174,7 +174,7 @@ private void createNewDocumentIfNecessary() throws IOException */ protected boolean splitAtPage(int pageNumber) { - return pageNumber % splitLength == 0; + return pageNumber % splitLength == 0; } /** @@ -213,33 +213,33 @@ protected void processPage(PDPage page) throws IOException // remove page links to avoid copying not needed resources processAnnotations(imported); } - + private void processAnnotations(PDPage imported) throws IOException { - List annotations = imported.getAnnotations(); - for (PDAnnotation annotation : annotations) - { - if (annotation instanceof PDAnnotationLink) - { - PDAnnotationLink link = (PDAnnotationLink)annotation; - PDDestination destination = link.getDestination(); - if (destination == null && link.getAction() != null) - { - PDAction action = link.getAction(); - if (action instanceof PDActionGoTo) - { - destination = ((PDActionGoTo)action).getDestination(); - } - } - if (destination instanceof PDPageDestination) - { - // TODO preserve links to pages within the splitted result - ((PDPageDestination) destination).setPage(null); - } - } + List annotations = imported.getAnnotations(); + for (PDAnnotation annotation : annotations) + { + if (annotation instanceof PDAnnotationLink) + { + PDAnnotationLink link = (PDAnnotationLink)annotation; + PDDestination destination = link.getDestination(); + if (destination == null && link.getAction() != null) + { + PDAction action = link.getAction(); + if (action instanceof PDActionGoTo) + { + destination = ((PDActionGoTo)action).getDestination(); + } + } + if (destination instanceof PDPageDestination) + { + // TODO preserve links to pages within the splitted result + ((PDPageDestination)destination).setPage(null); + } + } // TODO preserve links to pages within the splitted result annotation.setPage(null); - } + } } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/BaseParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/BaseParser.java index 7580e2560..f8de707ff 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/BaseParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/BaseParser.java @@ -46,9 +46,11 @@ */ public abstract class BaseParser { - private static final long OBJECT_NUMBER_THRESHOLD = 10000000000L; + private static final long OBJECT_NUMBER_THRESHOLD = 10000000000L; - private static final long GENERATION_NUMBER_THRESHOLD = 65535; + private static final long GENERATION_NUMBER_THRESHOLD = 65535; + + static final int MAX_LENGTH_LONG = Long.toString(Long.MAX_VALUE).length(); protected static final int E = 'e'; protected static final int N = 'n'; @@ -64,56 +66,56 @@ public abstract class BaseParser protected static final int B = 'b'; protected static final int J = 'j'; - /** - * This is a string constant that will be used for comparisons. - */ - public static final String DEF = "def"; - /** - * This is a string constant that will be used for comparisons. - */ - protected static final String ENDOBJ_STRING = "endobj"; - /** - * This is a string constant that will be used for comparisons. - */ - protected static final String ENDSTREAM_STRING = "endstream"; - /** - * This is a string constant that will be used for comparisons. - */ - protected static final String STREAM_STRING = "stream"; - /** - * This is a string constant that will be used for comparisons. - */ - private static final String TRUE = "true"; - /** - * This is a string constant that will be used for comparisons. - */ - private static final String FALSE = "false"; - /** - * This is a string constant that will be used for comparisons. - */ - private static final String NULL = "null"; - - /** - * ASCII code for line feed. - */ - protected static final byte ASCII_LF = 10; - /** - * ASCII code for carriage return. - */ - protected static final byte ASCII_CR = 13; - private static final byte ASCII_ZERO = 48; - private static final byte ASCII_NINE = 57; - private static final byte ASCII_SPACE = 32; - - /** - * This is the stream that will be read from. - */ + /** + * This is a string constant that will be used for comparisons. + */ + public static final String DEF = "def"; + /** + * This is a string constant that will be used for comparisons. + */ + protected static final String ENDOBJ_STRING = "endobj"; + /** + * This is a string constant that will be used for comparisons. + */ + protected static final String ENDSTREAM_STRING = "endstream"; + /** + * This is a string constant that will be used for comparisons. + */ + protected static final String STREAM_STRING = "stream"; + /** + * This is a string constant that will be used for comparisons. + */ + private static final String TRUE = "true"; + /** + * This is a string constant that will be used for comparisons. + */ + private static final String FALSE = "false"; + /** + * This is a string constant that will be used for comparisons. + */ + private static final String NULL = "null"; + + /** + * ASCII code for line feed. + */ + protected static final byte ASCII_LF = 10; + /** + * ASCII code for carriage return. + */ + protected static final byte ASCII_CR = 13; + private static final byte ASCII_ZERO = 48; + private static final byte ASCII_NINE = 57; + private static final byte ASCII_SPACE = 32; + + /** + * This is the stream that will be read from. + */ protected final SequentialSource seqSource; - /** - * This is the document that will be parsed. - */ - protected COSDocument document; + /** + * This is the document that will be parsed. + */ + protected COSDocument document; /** * Default constructor. @@ -123,20 +125,19 @@ public BaseParser(SequentialSource pdfSource) this.seqSource = pdfSource; } - private static boolean isHexDigit(char ch) - { - return isDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); - } - - /** - * This will parse a PDF dictionary value. - * - * @return The parsed Dictionary object. - * - * @throws IOException If there is an error parsing the dictionary object. - */ - private COSBase parseCOSDictionaryValue() throws IOException - { + private static boolean isHexDigit(char ch) + { + return isDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + } + + /** + * This will parse a PDF dictionary value. + * + * @return The parsed Dictionary object. + * @throws IOException If there is an error parsing the dictionary object. + */ + private COSBase parseCOSDictionaryValue() throws IOException + { long numOffset = seqSource.getPosition(); COSBase number = parseDirObject(); skipSpaces(); @@ -151,20 +152,20 @@ private COSBase parseCOSDictionaryValue() throws IOException if (!(number instanceof COSInteger)) { throw new IOException("expected number, actual=" + number + " at offset " + numOffset); - } - if (!(generationNumber instanceof COSInteger)) - { - throw new IOException("expected number, actual=" + number + " at offset " + genOffset); - } - COSObjectKey key = new COSObjectKey(((COSInteger) number).longValue(), - ((COSInteger) generationNumber).intValue()); - return getObjectFromPool(key); - } - - private COSBase getObjectFromPool(COSObjectKey key) throws IOException - { - if (document == null) - { + } + if (!(generationNumber instanceof COSInteger)) + { + throw new IOException("expected number, actual=" + number + " at offset " + genOffset); + } + COSObjectKey key = new COSObjectKey(((COSInteger)number).longValue(), + ((COSInteger)generationNumber).intValue()); + return getObjectFromPool(key); + } + + private COSBase getObjectFromPool(COSObjectKey key) throws IOException + { + if (document == null) + { throw new IOException( "object reference " + key + " at offset " + seqSource.getPosition() + " in content stream"); @@ -172,23 +173,23 @@ private COSBase getObjectFromPool(COSObjectKey key) throws IOException return document.getObjectFromPool(key); } - /** - * This will parse a PDF dictionary. - * - * @return The parsed dictionary. - * - * @throws IOException If there is an error reading the stream. - */ - protected COSDictionary parseCOSDictionary() throws IOException - { - readExpectedChar('<'); - readExpectedChar('<'); - skipSpaces(); - COSDictionary obj = new COSDictionary(); - boolean done = false; - while (!done) - { - skipSpaces(); + /** + * This will parse a PDF dictionary. + * + * @return The parsed dictionary. + * + * @throws IOException If there is an error reading the stream. + */ + protected COSDictionary parseCOSDictionary() throws IOException + { + readExpectedChar('<'); + readExpectedChar('<'); + skipSpaces(); + COSDictionary obj = new COSDictionary(); + boolean done = false; + while (!done) + { + skipSpaces(); char c = (char) seqSource.peek(); if (c == '>') { @@ -214,16 +215,16 @@ else if (c == '/') return obj; } - /** - * Keep reading until the end of the dictionary object or the file has been hit, or until a '/' - * has been found. - * - * @return true if the end of the object or the file has been found, false if not, i.e. that the - * caller can continue to parse the dictionary at the current position. - * @throws IOException if there is a reading error. - */ - private boolean readUntilEndOfCOSDictionary() throws IOException - { + /** + * Keep reading until the end of the dictionary object or the file has been hit, or until a '/' + * has been found. + * + * @return true if the end of the object or the file has been found, false if not, i.e. that the + * caller can continue to parse the dictionary at the current position. + * @throws IOException if there is a reading error. + */ + private boolean readUntilEndOfCOSDictionary() throws IOException + { int c = seqSource.read(); while (c != -1 && c != '/' && c != '>') { @@ -261,11 +262,11 @@ private boolean readUntilEndOfCOSDictionary() throws IOException return false; } - private void parseCOSDictionaryNameValuePair(COSDictionary obj) throws IOException - { - COSName key = parseCOSName(); - COSBase value = parseCOSDictionaryValue(); - skipSpaces(); + private void parseCOSDictionaryNameValuePair(COSDictionary obj) throws IOException + { + COSName key = parseCOSName(); + COSBase value = parseCOSDictionaryValue(); + skipSpaces(); if (((char) seqSource.peek()) == 'd') { // if the next string is 'def' then we are parsing a cmap stream @@ -281,18 +282,19 @@ private void parseCOSDictionaryNameValuePair(COSDictionary obj) throws IOExcepti } } - if (value == null) - { + if (value == null) + { Log.w("PdfBox-Android", "Bad Dictionary Declaration " + seqSource); } else { + // label this item as direct, to avoid signature problems. value.setDirect(true); obj.setItem(key, value); } } - protected void skipWhiteSpace() throws IOException + protected void skipWhiteSpaces() throws IOException { //PDF Ref 3.2.7 A stream must be followed by either //a CRLF or LF but nothing else. @@ -317,11 +319,7 @@ protected void skipWhiteSpace() throws IOException //world so we must support it. } } - else if (ASCII_LF == whitespace) - { - //that is fine - } - else + else if (ASCII_LF != whitespace) { //we are in an error. //but again we will do a lenient parsing and just assume that everything @@ -330,74 +328,73 @@ else if (ASCII_LF == whitespace) } } - /** - * This is really a bug in the Document creators code, but it caused a crash - * in PDFBox, the first bug was in this format: - * /Title ( (5) - * /Creator which was patched in 1 place. - * However it missed the case where the Close Paren was escaped - * - * The second bug was in this format - * /Title (c:\) - * /Producer - * - * This patch moves this code out of the parseCOSString method, so it can be used twice. - * - * - * @param bracesParameter the number of braces currently open. - * - * @return the corrected value of the brace counter - * @throws IOException - */ - private int checkForMissingCloseParen(final int bracesParameter) throws IOException - { - int braces = bracesParameter; - byte[] nextThreeBytes = new byte[3]; + /** + * This is really a bug in the Document creators code, but it caused a crash + * in PDFBox, the first bug was in this format: + * /Title ( (5) + * /Creator which was patched in 1 place. + * However it missed the case where the Close Paren was escaped + * + * The second bug was in this format + * /Title (c:\) + * /Producer + * + * This patch moves this code out of the parseCOSString method, so it can be used twice. + * + * + * @param bracesParameter the number of braces currently open. + * + * @return the corrected value of the brace counter + * @throws IOException + */ + private int checkForMissingCloseParen(final int bracesParameter) throws IOException + { + int braces = bracesParameter; + byte[] nextThreeBytes = new byte[3]; int amountRead = seqSource.read(nextThreeBytes); - //lets handle the special case seen in Bull River Rules and Regulations.pdf - //The dictionary looks like this - // 2 0 obj - // << - // /Type /Info - // /Creator (PaperPort http://www.scansoft.com) - // /Producer (sspdflib 1.0 http://www.scansoft.com) - // /Title ( (5) - // /Author () - // /Subject () - // - // Notice the /Title, the braces are not even but they should - // be. So lets assume that if we encounter an this scenario - // then that - // means that there is an error in the pdf and assume that - // was the end of the document. - // - if (amountRead == 3 && - ( nextThreeBytes[0] == ASCII_CR // Look for a carriage return - && nextThreeBytes[1] == ASCII_LF // Look for a new line - && nextThreeBytes[2] == 0x2f ) // Look for a slash / - // Add a second case without a new line - || (nextThreeBytes[0] == ASCII_CR // Look for a carriage return - && nextThreeBytes[1] == 0x2f )) // Look for a slash / - { - braces = 0; - } - if (amountRead > 0) - { + //lets handle the special case seen in Bull River Rules and Regulations.pdf + //The dictionary looks like this + // 2 0 obj + // << + // /Type /Info + // /Creator (PaperPort http://www.scansoft.com) + // /Producer (sspdflib 1.0 http://www.scansoft.com) + // /Title ( (5) + // /Author () + // /Subject () + // + // Notice the /Title, the braces are not even but they should + // be. So lets assume that if we encounter an this scenario + // then that + // means that there is an error in the pdf and assume that + // was the end of the document. + // + if (amountRead == 3 && ((nextThreeBytes[0] == ASCII_CR // Look for a carriage return + && nextThreeBytes[1] == ASCII_LF // Look for a new line + && nextThreeBytes[2] == 0x2f) // Look for a slash / + // Add a second case without a new line + || (nextThreeBytes[0] == ASCII_CR // Look for a carriage return + && nextThreeBytes[1] == 0x2f))) // Look for a slash / + { + braces = 0; + } + if (amountRead > 0) + { seqSource.unread(Arrays.copyOfRange(nextThreeBytes, 0, amountRead)); } return braces; } - /** - * This will parse a PDF string. - * - * @return The parsed PDF string. - * - * @throws IOException If there is an error reading from the stream. - */ - protected COSString parseCOSString() throws IOException - { + /** + * This will parse a PDF string. + * + * @return The parsed PDF string. + * + * @throws IOException If there is an error reading from the stream. + */ + protected COSString parseCOSString() throws IOException + { char nextChar = (char) seqSource.read(); char openBrace; char closeBrace; @@ -416,35 +413,35 @@ else if (nextChar == '<') nextChar + "' " + seqSource); } - ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); - //This is the number of braces read - // - int braces = 1; + //This is the number of braces read + // + int braces = 1; int c = seqSource.read(); while (braces > 0 && c != -1) { char ch = (char) c; int nextc = -2; // not yet read - if(ch == closeBrace) - { - - braces--; - braces = checkForMissingCloseParen(braces); - if( braces != 0 ) - { - out.write(ch); - } - } - else if( ch == openBrace ) - { - braces++; - out.write(ch); - } - else if( ch == '\\' ) - { - //patched by ram + if (ch == closeBrace) + { + + braces--; + braces = checkForMissingCloseParen(braces); + if (braces != 0) + { + out.write(ch); + } + } + else if (ch == openBrace) + { + braces++; + out.write(ch); + } + else if (ch == '\\') + { + //patched by ram char next = (char) seqSource.read(); switch (next) { @@ -521,36 +518,37 @@ else if( ch == '\\' ) nextc = c; } - int character = 0; - try - { - character = Integer.parseInt( octal.toString(), 8 ); - } - catch( NumberFormatException e ) - { - throw new IOException( "Error: Expected octal character, actual='" + octal + "'", e ); - } - out.write(character); - break; - } - default: - { - // dropping the backslash - // see 7.3.4.2 Literal Strings for further information - out.write(next); - } - } - } - else - { - out.write(ch); - } - if (nextc != -2) - { - c = nextc; - } - else - { + int character = 0; + try + { + character = Integer.parseInt(octal.toString(), 8); + } + catch (NumberFormatException e) + { + throw new IOException( + "Error: Expected octal character, actual='" + octal + "'", e); + } + out.write(character); + break; + } + default: + { + // dropping the backslash + // see 7.3.4.2 Literal Strings for further information + out.write(next); + } + } + } + else + { + out.write(ch); + } + if (nextc != -2) + { + c = nextc; + } + else + { c = seqSource.read(); } } @@ -561,23 +559,23 @@ else if( ch == '\\' ) return new COSString(out.toByteArray()); } - /** - * This will parse a PDF HEX string with fail fast semantic - * meaning that we stop if a not allowed character is found. - * This is necessary in order to detect malformed input and - * be able to skip to next object start. - * - * We assume starting '<' was already read. - * - * @return The parsed PDF string. - * - * @throws IOException If there is an error reading from the stream. - */ - private COSString parseCOSHexString() throws IOException - { - final StringBuilder sBuf = new StringBuilder(); - while( true ) - { + /** + * This will parse a PDF HEX string with fail fast semantic + * meaning that we stop if a not allowed character is found. + * This is necessary in order to detect malformed input and + * be able to skip to next object start. + * + * We assume starting '<' was already read. + * + * @return The parsed PDF string. + * + * @throws IOException If there is an error reading from the stream. + */ + private COSString parseCOSHexString() throws IOException + { + final StringBuilder sBuf = new StringBuilder(); + while (true) + { int c = seqSource.read(); if (isHexDigit((char) c)) { @@ -606,42 +604,41 @@ else if ((c == ' ') || (c == '\n') || sBuf.deleteCharAt(sBuf.length() - 1); } - // read till the closing bracket was found - do - { + // read till the closing bracket was found + do + { c = seqSource.read(); } 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; - } - } - return COSString.parseHex(sBuf.toString()); - } - - /** - * This will parse a PDF array object. - * - * @return The parsed PDF array. - * - * @throws IOException If there is an error parsing the stream. - */ - protected COSArray parseCOSArray() throws IOException - { - readExpectedChar('['); - COSArray po = new COSArray(); - COSBase pbo; - skipSpaces(); - int i; + // 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; + } + } + return COSString.parseHex(sBuf.toString()); + } + + /** + * This will parse a PDF array object. + * + * @return The parsed PDF array. + * @throws IOException If there is an error parsing the stream. + */ + protected COSArray parseCOSArray() throws IOException + { + readExpectedChar('['); + COSArray po = new COSArray(); + COSBase pbo; + skipSpaces(); + int i; while (((i = seqSource.peek()) > 0) && ((char) i != ']')) { pbo = parseDirObject(); @@ -679,9 +676,9 @@ protected COSArray parseCOSArray() throws IOException Log.w("PdfBox-Android", "Corrupt object reference at offset " + seqSource.getPosition()); - // This could also be an "endobj" or "endstream" which means we can assume that - // the array has ended. - String isThisTheEnd = readString(); + // This could also be an "endobj" or "endstream" which means we can assume that + // the array has ended. + String isThisTheEnd = readString(); seqSource.unread(isThisTheEnd.getBytes(ISO_8859_1)); if (ENDOBJ_STRING.equals(isThisTheEnd) || ENDSTREAM_STRING.equals(isThisTheEnd)) { @@ -689,17 +686,17 @@ protected COSArray parseCOSArray() throws IOException } } skipSpaces(); - } - //read ']' + } + //read ']' seqSource.read(); skipSpaces(); return po; } - /** - * Determine if a character terminates a PDF name. - * - * @param ch The character + /** + * Determine if a character terminates a PDF name. + * + * @param ch The character * @return true if the character terminates a PDF name, otherwise false. */ protected boolean isEndOfName(int ch) @@ -708,15 +705,15 @@ protected boolean isEndOfName(int ch) ch == '<' || ch == '[' || ch == '/' || ch == ']' || ch == ')' || ch == '('; } - /** - * This will parse a PDF name from the stream. - * - * @return The parsed PDF name. - * @throws IOException If there is an error reading from the stream. - */ - protected COSName parseCOSName() throws IOException - { - readExpectedChar('/'); + /** + * This will parse a PDF name from the stream. + * + * @return The parsed PDF name. + * @throws IOException If there is an error reading from the stream. + */ + protected COSName parseCOSName() throws IOException + { + readExpectedChar('/'); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int c = seqSource.read(); while (c != -1) @@ -724,21 +721,21 @@ protected COSName parseCOSName() throws IOException int ch = c; if (ch == '#') { - char ch1 = (char) seqSource.read(); - char ch2 = (char) seqSource.read(); - - // Prior to PDF v1.2, the # was not a special character. Also, - // it has been observed that various PDF tools do not follow the - // spec with respect to the # escape, even though they report - // PDF versions of 1.2 or later. The solution here is that we - // interpret the # as an escape only when it is followed by two - // valid hex digits. - // - if (isHexDigit(ch1) && isHexDigit(ch2)) - { - String hex = "" + ch1 + ch2; - try - { + int ch1 = seqSource.read(); + int ch2 = seqSource.read(); + + // Prior to PDF v1.2, the # was not a special character. Also, + // it has been observed that various PDF tools do not follow the + // spec with respect to the # escape, even though they report + // PDF versions of 1.2 or later. The solution here is that we + // interpret the # as an escape only when it is followed by two + // valid hex digits. + // + if (isHexDigit((char)ch1) && isHexDigit((char)ch2)) + { + String hex = "" + (char)ch1 + (char)ch2; + try + { buffer.write(Integer.parseInt(hex, 16)); } catch (NumberFormatException e) @@ -749,6 +746,13 @@ protected COSName parseCOSName() throws IOException } else { + // check for premature EOF + if (ch2 == -1 || ch1 == -1) + { + Log.e("PdfBox-Android", "Premature EOF in BaseParser#parseCOSName"); + c = -1; + break; + } seqSource.unread(ch2); c = ch1; buffer.write(ch); @@ -756,10 +760,10 @@ protected COSName parseCOSName() throws IOException } else if (isEndOfName(ch)) { - break; - } - else - { + break; + } + else + { buffer.write(ch); c = seqSource.read(); } @@ -772,16 +776,16 @@ else if (isEndOfName(ch)) return COSName.getPDFName(string); } - /** - * This will parse a boolean object from the stream. - * - * @return The parsed boolean object. - * - * @throws IOException If an IO error occurs during parsing. - */ - protected COSBoolean parseBoolean() throws IOException - { - COSBoolean retval = null; + /** + * This will parse a boolean object from the stream. + * + * @return The parsed boolean object. + * + * @throws IOException If an IO error occurs during parsing. + */ + protected COSBoolean parseBoolean() throws IOException + { + COSBoolean retval = null; char c = (char) seqSource.peek(); if (c == 't') { @@ -796,8 +800,8 @@ protected COSBoolean parseBoolean() throws IOException retval = COSBoolean.TRUE; } } - else if( c == 'f' ) - { + else if (c == 'f') + { String falseString = new String(seqSource.readFully(5), ISO_8859_1); if (!falseString.equals(FALSE)) { @@ -810,26 +814,26 @@ else if( c == 'f' ) retval = COSBoolean.FALSE; } } - else - { - throw new IOException( "Error parsing boolean expected='t or f' actual='" + c - + "' at offset " + seqSource.getPosition()); + else + { + throw new IOException( + "Error parsing boolean expected='t or f' actual='" + c + "' at offset " + seqSource.getPosition()); } return retval; } - /** - * This will parse a directory object from the stream. - * - * @return The parsed object. - * - * @throws IOException If there is an error during parsing. - */ - protected COSBase parseDirObject() throws IOException - { - COSBase retval = null; - - skipSpaces(); + /** + * This will parse a directory object from the stream. + * + * @return The parsed object. + * + * @throws IOException If there is an error during parsing. + */ + protected COSBase parseDirObject() throws IOException + { + COSBase retval = null; + + skipSpaces(); int nextByte = seqSource.peek(); char c = (char) nextByte; switch (c) @@ -844,38 +848,38 @@ protected COSBase parseDirObject() throws IOException if (c == '<') { - retval = parseCOSDictionary(); - skipSpaces(); - } - else - { - retval = parseCOSString(); - } - break; - } - case '[': - { - // array - retval = parseCOSArray(); - break; - } - case '(': - retval = parseCOSString(); - break; - case '/': - // name - retval = parseCOSName(); - break; - case 'n': - { - // null - readExpectedString(NULL); - retval = COSNull.NULL; - break; - } - case 't': - { - String trueString = new String(seqSource.readFully(4), ISO_8859_1); + retval = parseCOSDictionary(); + skipSpaces(); + } + else + { + retval = parseCOSString(); + } + break; + } + case '[': + { + // array + retval = parseCOSArray(); + break; + } + case '(': + retval = parseCOSString(); + break; + case '/': + // name + retval = parseCOSName(); + break; + case 'n': + { + // null + readExpectedString(NULL); + retval = COSNull.NULL; + break; + } + case 't': + { + String trueString = new String(seqSource.readFully(4), ISO_8859_1); if (trueString.equals(TRUE)) { retval = COSBoolean.TRUE; @@ -935,11 +939,11 @@ protected COSBase parseDirObject() throws IOException else { //This is not suppose to happen, but we will allow for it - //so we are more compatible with POS writers that don't - //follow the spec - String badString = readString(); - if( badString == null || badString.length() == 0 ) - { + //so we are more compatible with POS writers that don't + //follow the spec + String badString = readString(); + if (badString == null || badString.length() == 0) + { int peek = seqSource.peek(); // we can end up in an infinite loop otherwise throw new IOException("Unknown dir object c='" + c + @@ -947,9 +951,9 @@ protected COSBase parseDirObject() throws IOException + "' peekInt=" + peek + " " + seqSource.getPosition()); } - // if it's an endstream/endobj, we want to put it back so the caller will see it - if(ENDOBJ_STRING.equals(badString) || ENDSTREAM_STRING.equals(badString)) - { + // if it's an endstream/endobj, we want to put it back so the caller will see it + if (ENDOBJ_STRING.equals(badString) || ENDSTREAM_STRING.equals(badString)) + { seqSource.unread(badString.getBytes(ISO_8859_1)); } } @@ -958,17 +962,17 @@ protected COSBase parseDirObject() throws IOException return retval; } - /** - * This will read the next string from the stream. - * - * @return The string that was read from the stream. - * - * @throws IOException If there is an error reading from the stream. - */ - protected String readString() throws IOException - { - skipSpaces(); - StringBuilder buffer = new StringBuilder(); + /** + * This will read the next string from the stream. + * + * @return The string that was read from the stream. + * + * @throws IOException If there is an error reading from the stream. + */ + protected String readString() throws IOException + { + skipSpaces(); + StringBuilder buffer = new StringBuilder(); int c = seqSource.read(); while (!isEndOfName((char) c) && c != -1) { @@ -982,19 +986,19 @@ protected String readString() throws IOException return buffer.toString(); } - /** - * Read one String and throw an exception if it is not the expected value. - * - * @param expectedString the String value that is expected. - * @throws IOException if the String char is not the expected value or if an - * I/O error occurs. - */ - protected void readExpectedString(String expectedString) throws IOException - { - readExpectedString(expectedString.toCharArray(), false); - } - - /** + /** + * Read one String and throw an exception if it is not the expected value. + * + * @param expectedString the String value that is expected. + * @throws IOException if the String char is not the expected value or if an + * I/O error occurs. + */ + protected void readExpectedString(String expectedString) throws IOException + { + readExpectedString(expectedString.toCharArray(), false); + } + + /** * Reads given pattern from {@link #seqSource}. Skipping whitespace at start and end if wanted. * * @param expectedString pattern to be skipped @@ -1017,15 +1021,15 @@ protected final void readExpectedString(final char[] expectedString, boolean ski skipSpaces(); } - /** - * Read one char and throw an exception if it is not the expected value. - * - * @param ec the char value that is expected. - * @throws IOException if the read char is not the expected value or if an - * I/O error occurs. - */ - protected void readExpectedChar(char ec) throws IOException - { + /** + * Read one char and throw an exception if it is not the expected value. + * + * @param ec the char value that is expected. + * @throws IOException if the read char is not the expected value or if an + * I/O error occurs. + */ + protected void readExpectedChar(char ec) throws IOException + { char c = (char) seqSource.read(); if (c != ec) { @@ -1034,31 +1038,28 @@ protected void readExpectedChar(char ec) throws IOException } } - /** - * This will read the next string from the stream up to a certain length. - * - * @param length The length to stop reading at. - * - * @return The string that was read from the stream of length 0 to length. - * - * @throws IOException If there is an error reading from the stream. - */ - protected String readString( int length ) throws IOException - { - skipSpaces(); + /** + * This will read the next string from the stream up to a certain length. + * + * @param length The length to stop reading at. + * + * @return The string that was read from the stream of length 0 to length. + * + * @throws IOException If there is an error reading from the stream. + */ + protected String readString(int length) throws IOException + { + skipSpaces(); int c = seqSource.read(); - //average string size is around 2 and the normal string buffer size is - //about 16 so lets save some space. - StringBuilder buffer = new StringBuilder(length); - while( !isWhitespace(c) && !isClosing(c) && c != -1 && buffer.length() < length && - c != '[' && - c != '<' && - c != '(' && - c != '/' ) - { - buffer.append( (char)c ); + //average string size is around 2 and the normal string buffer size is + //about 16 so lets save some space. + StringBuilder buffer = new StringBuilder(length); + while (!isWhitespace(c) && !isClosing(c) && c != -1 && buffer.length() < length && + c != '[' && c != '<' && c != '(' && c != '/') + { + buffer.append((char)c); c = seqSource.read(); } if (c != -1) @@ -1068,48 +1069,47 @@ protected String readString( int length ) throws IOException return buffer.toString(); } - /** - * This will tell if the next character is a closing brace( close of PDF array ). - * - * @return true if the next byte is ']', false otherwise. - * - * @throws IOException If an IO error occurs. - */ - protected boolean isClosing() throws IOException - { + /** + * This will tell if the next character is a closing brace( close of PDF array ). + * + * @return true if the next byte is ']', false otherwise. + * + * @throws IOException If an IO error occurs. + */ + protected boolean isClosing() throws IOException + { return isClosing(seqSource.peek()); } - /** - * This will tell if the next character is a closing brace( close of PDF array ). - * - * @param c The character to check against end of line - * @return true if the next byte is ']', false otherwise. - */ - protected boolean isClosing(int c) - { - return c == ']'; - } - - /** - * This will read bytes until the first end of line marker occurs. - * NOTE: The EOL marker may consists of 1 (CR or LF) or 2 (CR and CL) bytes - * which is an important detail if one wants to unread the line. - * - * @return The characters between the current position and the end of the line. - * - * @throws IOException If there is an error reading from the stream. - */ - protected String readLine() throws IOException - { + /** + * This will tell if the next character is a closing brace( close of PDF array ). + * + * @param c The character to check against end of line + * @return true if the next byte is ']', false otherwise. + */ + protected boolean isClosing(int c) + { + return c == ']'; + } + + /** + * This will read bytes until the first end of line marker occurs. + * NOTE: The EOL marker may consists of 1 (CR or LF) or 2 (CR and CL) bytes + * which is an important detail if one wants to unread the line. + * + * @return The characters between the current position and the end of the line. + * @throws IOException If there is an error reading from the stream. + */ + protected String readLine() throws IOException + { if (seqSource.isEOF()) { throw new IOException("Error: End-of-File, expected line"); } - StringBuilder buffer = new StringBuilder( 11 ); + StringBuilder buffer = new StringBuilder(11); - int c; + int c; while ((c = seqSource.read()) != -1) { // CR and LF are valid EOLs @@ -1127,116 +1127,112 @@ protected String readLine() throws IOException return buffer.toString(); } - /** - * This will tell if the next byte to be read is an end of line byte. - * - * @return true if the next byte is 0x0A or 0x0D. - * - * @throws IOException If there is an error reading from the stream. - */ - protected boolean isEOL() throws IOException - { + /** + * This will tell if the next byte to be read is an end of line byte. + * + * @return true if the next byte is 0x0A or 0x0D. + * + * @throws IOException If there is an error reading from the stream. + */ + protected boolean isEOL() throws IOException + { return isEOL(seqSource.peek()); } - /** - * This will tell if the next byte to be read is an end of line byte. - * - * @param c The character to check against end of line - * @return true if the next byte is 0x0A or 0x0D. - */ - protected boolean isEOL(int c) - { - return isLF(c) || isCR(c); - } - - private boolean isLF(int c) - { - return ASCII_LF == c; - } - - private boolean isCR(int c) - { - return ASCII_CR == c; - } - - /** - * This will tell if the next byte is whitespace or not. - * - * @return true if the next byte in the stream is a whitespace character. - * - * @throws IOException If there is an error reading from the stream. - */ - protected boolean isWhitespace() throws IOException - { + /** + * This will tell if the next byte to be read is an end of line byte. + * + * @param c The character to check against end of line + * @return true if the next byte is 0x0A or 0x0D. + */ + protected boolean isEOL(int c) + { + return isLF(c) || isCR(c); + } + + private boolean isLF(int c) + { + return ASCII_LF == c; + } + + private boolean isCR(int c) + { + return ASCII_CR == c; + } + + /** + * This will tell if the next byte is whitespace or not. + * + * @return true if the next byte in the stream is a whitespace character. + * @throws IOException If there is an error reading from the stream. + */ + protected boolean isWhitespace() throws IOException + { return isWhitespace(seqSource.peek()); } - /** - * This will tell if a character is whitespace or not. These values are - * specified in table 1 (page 12) of ISO 32000-1:2008. - * @param c The character to check against whitespace - * @return true if the character is a whitespace character. - */ - protected boolean isWhitespace( int c ) - { - return c == 0 || c == 9 || c == 12 || c == ASCII_LF - || c == ASCII_CR || c == ASCII_SPACE; - } - - /** - * This will tell if the next byte is a space or not. - * - * @return true if the next byte in the stream is a space character. - * - * @throws IOException If there is an error reading from the stream. - */ - protected boolean isSpace() throws IOException - { + /** + * This will tell if a character is whitespace or not. These values are + * specified in table 1 (page 12) of ISO 32000-1:2008. + * @param c The character to check against whitespace + * @return true if the character is a whitespace character. + */ + protected boolean isWhitespace(int c) + { + return c == 0 || c == 9 || c == 12 || c == ASCII_LF || c == ASCII_CR || c == ASCII_SPACE; + } + + /** + * This will tell if the next byte is a space or not. + * + * @return true if the next byte in the stream is a space character. + * @throws IOException If there is an error reading from the stream. + */ + protected boolean isSpace() throws IOException + { return isSpace(seqSource.peek()); } - /** - * This will tell if the given value is a space or not. - * - * @param c The character to check against space - * @return true if the next byte in the stream is a space character. - */ - protected boolean isSpace(int c) - { - return ASCII_SPACE == c; - } - - /** - * This will tell if the next byte is a digit or not. - * - * @return true if the next byte in the stream is a digit. - * - * @throws IOException If there is an error reading from the stream. - */ - protected boolean isDigit() throws IOException - { + /** + * This will tell if the given value is a space or not. + * + * @param c The character to check against space + * @return true if the next byte in the stream is a space character. + */ + protected boolean isSpace(int c) + { + return ASCII_SPACE == c; + } + + /** + * This will tell if the next byte is a digit or not. + * + * @return true if the next byte in the stream is a digit. + * @throws IOException If there is an error reading from the stream. + */ + protected boolean isDigit() throws IOException + { return isDigit(seqSource.peek()); } - /** - * This will tell if the given value is a digit or not. - * - * @param c The character to be checked - * @return true if the next byte in the stream is a digit. - */ - protected static boolean isDigit(int c) - { - return c >= ASCII_ZERO && c <= ASCII_NINE; - } - - /** - * This will skip all spaces and comments that are present. - * - * @throws IOException If there is an error reading from the stream. - */ - protected void skipSpaces() throws IOException - { + /** + * This will tell if the given value is a digit or not. + * + * @param c The character to be checked + * @return true if the next byte in the stream is a digit. + */ + protected static boolean isDigit(int c) + { + return c >= ASCII_ZERO && c <= ASCII_NINE; + } + + /** + * This will skip all spaces and comments that are present. + * + * @throws IOException If there is an error reading from the stream. + */ + protected void skipSpaces() throws IOException + { int c = seqSource.read(); // 37 is the % character, a comment while (isWhitespace(c) || c == 37) @@ -1259,62 +1255,63 @@ protected void skipSpaces() throws IOException { seqSource.unread(c); } - //log( "skipSpaces() done peek='" + (char)seqSource.peek() + "'" ); } - /** - * This will read a long from the Stream and throw an {@link IOException} if - * the long value is negative or has more than 10 digits (i.e. : bigger than - * {@link #OBJECT_NUMBER_THRESHOLD}) - * @return the object number being read. - * @throws IOException if an I/O error occurs - */ - protected int readObjectNumber() throws IOException - { - int retval = readInt(); - if (retval < 0 || retval >= 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 the generation number being read. - * @throws IOException if an I/O error occurs - */ - 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. - * - * @return The integer that was read from the stream. - * - * @throws IOException If there is an error reading from the stream. - */ - protected int readInt() throws IOException - { - skipSpaces(); - int retval = 0; - - StringBuilder intBuffer = readStringNumber(); - - try - { - retval = Integer.parseInt( intBuffer.toString() ); - } - catch( NumberFormatException e ) - { + /** + * This will read a long from the Stream and throw an {@link IOException} if + * the long value is negative or has more than 10 digits (i.e. : bigger than + * {@link #OBJECT_NUMBER_THRESHOLD}) + * + * @return the object number being read. + * @throws IOException if an I/O error occurs + */ + protected long readObjectNumber() throws IOException + { + long retval = readLong(); + if (retval < 0 || retval >= 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 the generation number being read. + * @throws IOException if an I/O error occurs + */ + 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. + * + * @return The integer that was read from the stream. + * @throws IOException If there is an error reading from the stream. + */ + protected int readInt() throws IOException + { + skipSpaces(); + int retval = 0; + + StringBuilder intBuffer = readStringNumber(); + + try + { + retval = Integer.parseInt(intBuffer.toString()); + } + catch (NumberFormatException e) + { seqSource.unread(intBuffer.toString().getBytes(ISO_8859_1)); throw new IOException( "Error: Expected an integer type at offset " + seqSource.getPosition(), e); @@ -1323,26 +1320,26 @@ protected int readInt() throws IOException } - /** - * 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 ) - { + /** + * 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) + { seqSource.unread(longBuffer.toString().getBytes(ISO_8859_1)); throw new IOException("Error: Expected a long type at offset " + seqSource.getPosition() + ", instead got '" + longBuffer + "'", e); @@ -1350,11 +1347,11 @@ protected long readLong() throws IOException 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. + /** + * 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 #seqSource} methods. */ protected final StringBuilder readStringNumber() throws IOException @@ -1371,6 +1368,12 @@ protected final StringBuilder readStringNumber() throws IOException lastByte != -1) { buffer.append((char) lastByte); + if (buffer.length() > MAX_LENGTH_LONG) + { + throw new IOException( + "Number '" + buffer + "' is getting too long, stop reading at offset " + + seqSource.getPosition()); + } } if (lastByte != -1) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java index 7dcb307c5..47b2ec65a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/COSParser.java @@ -18,20 +18,6 @@ import android.util.Log; -import com.tom_roush.pdfbox.cos.COSArray; -import com.tom_roush.pdfbox.cos.COSBase; -import com.tom_roush.pdfbox.cos.COSDictionary; -import com.tom_roush.pdfbox.cos.COSDocument; -import com.tom_roush.pdfbox.cos.COSName; -import com.tom_roush.pdfbox.cos.COSNull; -import com.tom_roush.pdfbox.cos.COSNumber; -import com.tom_roush.pdfbox.cos.COSObject; -import com.tom_roush.pdfbox.cos.COSObjectKey; -import com.tom_roush.pdfbox.cos.COSStream; -import com.tom_roush.pdfbox.io.RandomAccessRead; -import com.tom_roush.pdfbox.pdfparser.XrefTrailerResolver.XRefType; -import com.tom_roush.pdfbox.pdmodel.encryption.SecurityHandler; - import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; @@ -50,6 +36,20 @@ import java.util.TreeMap; import java.util.Vector; +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSDocument; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.cos.COSNull; +import com.tom_roush.pdfbox.cos.COSNumber; +import com.tom_roush.pdfbox.cos.COSObject; +import com.tom_roush.pdfbox.cos.COSObjectKey; +import com.tom_roush.pdfbox.cos.COSStream; +import com.tom_roush.pdfbox.io.RandomAccessRead; +import com.tom_roush.pdfbox.pdfparser.XrefTrailerResolver.XRefType; +import com.tom_roush.pdfbox.pdmodel.encryption.SecurityHandler; + import static com.tom_roush.pdfbox.util.Charsets.ISO_8859_1; /** @@ -74,9 +74,9 @@ public class COSParser extends BaseParser private static final char[] XREF_STREAM = new char[] { '/', 'X', 'R', 'e', 'f' }; private static final char[] STARTXREF = new char[] { 's','t','a','r','t','x','r','e','f' }; - public static final byte[] ENDSTREAM = new byte[]{E, N, D, S, T, R, E, A, M}; + private static final byte[] ENDSTREAM = new byte[] { E, N, D, S, T, R, E, A, M }; - public static final byte[] ENDOBJ = new byte[]{E, N, D, O, B, J}; + private static final byte[] ENDOBJ = new byte[] { E, N, D, O, B, J }; private static final long MINIMUM_SEARCH_OFFSET = 6; @@ -145,16 +145,9 @@ public class COSParser extends BaseParser */ private int readTrailBytes = DEFAULT_TRAIL_BYTECOUNT; - /** - * 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 final boolean parseMinimalCatalog = "true".equals(System.getProperty(SYSPROP_PARSEMINIMAL)); - /** * Collects all Xref/trailer objects and resolves them into single - * object using startxref reference. + * object using startxref reference. */ protected XrefTrailerResolver xrefTrailerResolver = new XrefTrailerResolver(); @@ -376,7 +369,7 @@ protected final long getStartxrefOffset() throws IOException { source.seek(0); } - // ---- find last '%%EOF' + // find last '%%EOF' int bufOff = lastIndexOf(EOF_MARKER, buf, buf.length); if (bufOff < 0) { @@ -422,9 +415,11 @@ protected final long getStartxrefOffset() throws IOException 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) { if (buf[bufOff] == lookupCh) @@ -615,11 +610,12 @@ else if (baseObj instanceof COSObject) for (COSObject obj : objToBeParsed.remove(objToBeParsed.firstKey())) { COSBase parsedObj = parseObjectDynamically(obj, false); - - obj.setObject(parsedObj); - addNewToList(toBeParsedList, parsedObj, addedObjects); - - parsedObjects.add(getObjectId(obj)); + if (parsedObj != null) + { + obj.setObject(parsedObj); + addNewToList(toBeParsedList, parsedObj, addedObjects); + parsedObjects.add(getObjectId(obj)); + } } } } @@ -827,7 +823,26 @@ private void parseObjectStream(int objstmObjNr) throws IOException { // parse object stream PDFObjectStreamParser parser = new PDFObjectStreamParser((COSStream) objstmBaseObj, document); - parser.parse(); + try + { + parser.parse(); + } + catch (IOException exception) + { + if (isLenient) + { + Log.d("PdfBox-Android", + "Stop reading object stream " + objstmObjNr + " due to an exception", + exception); + // the error is handled in parseDictObjects + return; + } + else + { + throw exception; + } + } + // get set of object numbers referenced for this object stream final Set refObjNrs = xrefTrailerResolver.getContainedObjectNumbers(objstmObjNr); @@ -916,7 +931,7 @@ protected COSStream parseCOSStream(COSDictionary dic) throws IOException // read 'stream'; this was already tested in parseObjectsDynamically() readString(); - skipWhiteSpace(); + skipWhiteSpaces(); /* * This needs to be dic.getItem because when we are parsing, the underlying object might still be null. @@ -1203,22 +1218,26 @@ private long checkXRefStreamOffset(long startXRefOffset, boolean checkOnly) thro source.seek(startXRefOffset - 1); int nextValue = source.read(); // the first character has to be a whitespace, and then a digit - if (isWhitespace(nextValue) && isDigit()) + if (isWhitespace(nextValue)) { - try - { - // it's a XRef stream - readObjectNumber(); - readGenerationNumber(); - readExpectedString(OBJ_MARKER, true); - source.seek(startXRefOffset); - return startXRefOffset; - } - catch (IOException exception) + skipSpaces(); + if (isDigit()) { - // there wasn't an object of a xref stream - // try to repair the offset - source.seek(startXRefOffset); + try + { + // it's a XRef stream + readObjectNumber(); + readGenerationNumber(); + readExpectedString(OBJ_MARKER, true); + source.seek(startXRefOffset); + return startXRefOffset; + } + catch (IOException exception) + { + // there wasn't an object of a xref stream + // try to repair the offset + source.seek(startXRefOffset); + } } } // try to find a fixed offset @@ -1287,6 +1306,35 @@ private void checkXrefOffsets() throws IOException bfSearchForObjects(); if (bfSearchCOSObjectKeyOffsets != null && !bfSearchCOSObjectKeyOffsets.isEmpty()) { + List objStreams = new ArrayList(); + // find all object streams + for (COSObjectKey key : xrefOffset.keySet()) + { + Long offset = xrefOffset.get(key); + if (offset != null && offset < 0) + { + COSObjectKey objStream = new COSObjectKey(-offset, 0); + if (!objStreams.contains(objStream)) + { + objStreams.add(new COSObjectKey(-offset, 0)); + } + } + } + // remove all found object streams + for (COSObjectKey key : bfSearchCOSObjectKeyOffsets.keySet()) + { + objStreams.remove(key); + } + // remove all objects which are part of an object stream which wasn't found + for (COSObjectKey key : objStreams) + { + Set objects = xrefTrailerResolver.getContainedObjectNumbers( + (int)(key.getNumber())); + for (Long objNr : objects) + { + xrefOffset.remove(new COSObjectKey(objNr, 0)); + } + } Log.d("PdfBox-Android", "Replaced read xref table with the results of a brute force search"); xrefOffset.putAll(bfSearchCOSObjectKeyOffsets); } @@ -1299,7 +1347,7 @@ private void checkXrefOffsets() throws IOException * * @param objectKey the object we are looking for * @param offset the offset where to look - * returns true if the given object can be dereferenced at the given offset + * @return true if the given object can be dereferenced at the given offset * @throws IOException if something went wrong */ private boolean checkObjectKeys(COSObjectKey objectKey, long offset) throws IOException @@ -1379,7 +1427,7 @@ private void bfSearchForObjects() throws IOException source.seek(tempOffset); int genID = source.peek(); // is the next char a digit? - if (isDigit()) + if (isDigit(genID)) { genID -= 48; tempOffset--; @@ -1461,12 +1509,12 @@ private long bfSearchForXRef(long xrefOffset, boolean streamsOnly) throws IOExce long differenceStream = xrefOffset - newOffsetStream; if (Math.abs(differenceTable) > Math.abs(differenceStream)) { - newOffset = differenceStream; + newOffset = newOffsetStream; bfSearchXRefStreamsOffsets.remove(newOffsetStream); } else { - newOffset = differenceTable; + newOffset = newOffsetTable; bfSearchXRefTablesOffsets.remove(newOffsetTable); } } @@ -1666,13 +1714,13 @@ protected final COSDictionary rebuildTrailer() throws IOException document.getObjectFromPool(entry.getKey())); } // info dictionary - else if (dictionary.containsKey(COSName.TITLE) + else if (dictionary.containsKey(COSName.MOD_DATE) && + (dictionary.containsKey(COSName.TITLE) || dictionary.containsKey(COSName.AUTHOR) || dictionary.containsKey(COSName.SUBJECT) || dictionary.containsKey(COSName.KEYWORDS) - || dictionary.containsKey(COSName.CREATOR) - || dictionary.containsKey(COSName.PRODUCER) - || dictionary.containsKey(COSName.CREATION_DATE)) + || dictionary.containsKey(COSName.CREATOR) || dictionary.containsKey( + COSName.PRODUCER) || dictionary.containsKey(COSName.CREATION_DATE))) { trailer.setItem(COSName.INFO, document.getObjectFromPool(entry.getKey())); @@ -1694,10 +1742,10 @@ else if (dictionary.containsKey(COSName.TITLE) * This will parse the startxref section from the stream. * The startxref value is ignored. * - * @return the startxref value or -1 on parsing error on parsing error + * @return the startxref value or -1 on parsing error * @throws IOException If an IO error occurs. */ - protected long parseStartXref() throws IOException + private long parseStartXref() throws IOException { long startXref = -1; if (isString(STARTXREF)) @@ -1809,7 +1857,7 @@ private long findString(char[] string) throws IOException * @return false on parsing error * @throws IOException If an IO error occurs. */ - protected boolean parseTrailer() throws IOException + private boolean parseTrailer() throws IOException { if (source.peek() != 't') { @@ -2049,7 +2097,8 @@ else if(!splitString[2].equals("f")) * @param isStandalone should be set to true if the stream is not part of a hybrid xref table * @throws IOException if there is an error parsing the stream */ - public void parseXrefStream( COSStream stream, long objByteOffset, boolean isStandalone ) throws IOException + private void parseXrefStream(COSStream stream, long objByteOffset, boolean isStandalone) + throws IOException { // the cross reference stream of a hybrid xref table will be added to the existing one // and we must not override the offset and the trailer @@ -2081,12 +2130,12 @@ public COSDocument getDocument() throws IOException } /** - * Parse the values of the trailer dictionary and return the root object + * Parse the values of the trailer dictionary and return the root object. * * @param trailer The trailer dictionary. - * @return The parsed root object + * @return The parsed root object. * @throws IOException If an IO error occurs or if the root object is - * missing in the trailer dictionary + * missing in the trailer dictionary */ protected COSBase parseTrailerValuesDynamically(COSDictionary trailer) throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/FDFParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/FDFParser.java index 7e91812b1..5f74ba685 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/FDFParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/FDFParser.java @@ -87,7 +87,7 @@ private void init() throws IOException + " does not contain an integer value, but: '" + eofLookupRangeStr + "'"); } } - document = new COSDocument(false); + document = new COSDocument(); } /** @@ -119,13 +119,11 @@ private void initialParse() throws IOException { parseDictObjects((COSDictionary) rootObject, (COSName[]) null); } - initialParseDone = true; } /** - * This will parse the stream and populate the COSDocument object. This will close - * the stream when it is done parsing. + * This will parse the stream and populate the COSDocument object. * * @throws IOException If there is an error reading from the stream or corrupt data * is found. diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFObjectStreamParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFObjectStreamParser.java index cef71b870..94cd2a3c8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFObjectStreamParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFObjectStreamParser.java @@ -47,8 +47,8 @@ public class PDFObjectStreamParser extends BaseParser public PDFObjectStreamParser(COSStream stream, COSDocument document) throws IOException { super(new InputStreamSource(stream.createInputStream())); - this.document = document; this.stream = stream; + this.document = document; } /** @@ -68,7 +68,7 @@ public void parse() throws IOException for( int i=0; i= objectNumbers.size()) { - Log.e("PdfBox-Android", "/ObjStm (object stream) has more objects than /N " + numberOfObjects); + Log.e("PdfBox-Android", + "/ObjStm (object stream) has more objects than /N " + numberOfObjects); break; } object.setObjectNumber( objectNumbers.get( objectCounter) ); @@ -92,7 +93,7 @@ public void parse() throws IOException // skip endobject marker if present if (!seqSource.isEOF() && seqSource.peek() == 'e') { - readLine(); + readLine(); } objectCounter++; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFParser.java index c9c6bed93..870009817 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFParser.java @@ -44,34 +44,37 @@ public class PDFParser extends COSParser private InputStream keyStoreInputStream = null; private String keyAlias = null; + private PDEncryption encryption = null; private AccessPermission accessPermission; /** * Constructor. + * Unrestricted main memory will be used for buffering PDF streams. * - * @param source input representing the pdf. + * @param source source representing the pdf. * @throws IOException If something went wrong. */ public PDFParser(RandomAccessRead source) throws IOException { - this(source, "", false); + this(source, "", ScratchFile.getMainMemoryOnlyInstance()); } /** * Constructor. * * @param source input representing the pdf. - * @param useScratchFiles use a fiel based buffer for temporary storage. + * @param scratchFile use a {@link ScratchFile} for temporary storage. * * @throws IOException If something went wrong. */ - public PDFParser(RandomAccessRead source, boolean useScratchFiles) throws IOException + public PDFParser(RandomAccessRead source, ScratchFile scratchFile) throws IOException { - this(source, "", useScratchFiles); + this(source, "", scratchFile); } /** * Constructor. + * Unrestricted main memory will be used for buffering PDF streams. * * @param source input representing the pdf. * @param decryptionPassword password to be used for decryption. @@ -79,7 +82,7 @@ public PDFParser(RandomAccessRead source, boolean useScratchFiles) throws IOExce */ public PDFParser(RandomAccessRead source, String decryptionPassword) throws IOException { - this(source, decryptionPassword, false); + this(source, decryptionPassword, ScratchFile.getMainMemoryOnlyInstance()); } /** @@ -87,18 +90,19 @@ public PDFParser(RandomAccessRead source, String decryptionPassword) throws IOEx * * @param source input representing the pdf. * @param decryptionPassword password to be used for decryption. - * @param useScratchFiles use a buffer for temporary storage. + * @param scratchFile use a {@link ScratchFile} for temporary storage. * * @throws IOException If something went wrong. */ - public PDFParser(RandomAccessRead source, String decryptionPassword, boolean useScratchFiles) + public PDFParser(RandomAccessRead source, String decryptionPassword, ScratchFile scratchFile) throws IOException { - this(source, decryptionPassword, null, null, useScratchFiles); + this(source, decryptionPassword, null, null, scratchFile); } /** * Constructor. + * Unrestricted main memory will be used for buffering PDF streams. * * @param source input representing the pdf. * @param decryptionPassword password to be used for decryption. @@ -110,7 +114,7 @@ public PDFParser(RandomAccessRead source, String decryptionPassword, boolean use public PDFParser(RandomAccessRead source, String decryptionPassword, InputStream keyStore, String alias) throws IOException { - this(source, decryptionPassword, keyStore, alias, false); + this(source, decryptionPassword, keyStore, alias, ScratchFile.getMainMemoryOnlyInstance()); } /** @@ -120,28 +124,6 @@ public PDFParser(RandomAccessRead source, String decryptionPassword, InputStream * @param decryptionPassword password to be used for decryption. * @param keyStore key store to be used for decryption when using public key security * @param alias alias to be used for decryption when using public key security - * @param useScratchFiles use a buffer for temporary storage. - * - * @throws IOException If something went wrong. - */ - public PDFParser(RandomAccessRead source, String decryptionPassword, InputStream keyStore, - String alias, boolean useScratchFiles) throws IOException - { - super(source); - fileLen = source.length(); - password = decryptionPassword; - keyStoreInputStream = keyStore; - keyAlias = alias; - init(useScratchFiles); - } - - /** - * Constructor. - * - * @param source input representing the pdf. - * @param decryptionPassword password to be used for decryption. - * @param keyStore key store to be used for decryption when using public key security - * @param alias alias to be used for decryption when using public key security * @param scratchFile buffer handler for temporary storage; it will be closed on * {@link COSDocument#close()} * @throws IOException If something went wrong. @@ -168,31 +150,13 @@ private void init(ScratchFile scratchFile) throws IOException } catch (NumberFormatException nfe) { - Log.w("PdfBox-Android", "System property " + SYSPROP_EOFLOOKUPRANGE - + " does not contain an integer value, but: '" + eofLookupRangeStr + "'"); + Log.w("PdfBox-Android", "System property " + SYSPROP_EOFLOOKUPRANGE + + " does not contain an integer value, but: '" + eofLookupRangeStr + "'"); } } document = new COSDocument(scratchFile); } - private void init(boolean useScratchFiles) throws IOException - { - String eofLookupRangeStr = System.getProperty(SYSPROP_EOFLOOKUPRANGE); - if (eofLookupRangeStr != null) - { - try - { - setEOFLookupRange(Integer.parseInt(eofLookupRangeStr)); - } - catch (NumberFormatException nfe) - { - Log.w("PdfBox-Android", "System property " + SYSPROP_EOFLOOKUPRANGE - + " does not contain an integer value, but: '" + eofLookupRangeStr + "'"); - } - } - document = new COSDocument(useScratchFiles); - } - /** * 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. @@ -203,7 +167,9 @@ private void init(boolean useScratchFiles) throws IOException */ public PDDocument getPDDocument() throws IOException { - return new PDDocument(getDocument(), source, accessPermission); + PDDocument doc = new PDDocument(getDocument(), source, accessPermission); + doc.setEncryptionDictionary(encryption); + return doc; } /** @@ -228,13 +194,29 @@ else if (isLenient()) } // prepare decryption if necessary prepareDecryption(); - - parseTrailerValuesDynamically(trailer); - + + COSBase base = parseTrailerValuesDynamically(trailer); + if (!(base instanceof COSDictionary)) + { + throw new IOException("Expected root dictionary, but got this: " + base); + } + COSDictionary root = (COSDictionary)base; + // in some pdfs the type value "Catalog" is missing in the root object + if (isLenient() && !root.containsKey(COSName.TYPE)) + { + root.setItem(COSName.TYPE, COSName.CATALOG); + } COSObject catalogObj = document.getCatalog(); if (catalogObj != null && catalogObj.getObject() instanceof COSDictionary) { parseDictObjects((COSDictionary) catalogObj.getObject(), (COSName[]) null); + + COSBase infoBase = trailer.getDictionaryObject(COSName.INFO); + if (infoBase instanceof COSDictionary) + { + parseDictObjects((COSDictionary)infoBase, (COSName[])null); + } + document.setDecrypted(); } initialParseDone = true; @@ -242,7 +224,7 @@ else if (isLenient()) /** * This will parse the stream and populate the COSDocument object. This will close - * the stream when it is done parsing. + * the keystore stream when it is done parsing. * * @throws IOException If there is an error reading from the stream or corrupt data * is found. @@ -294,7 +276,7 @@ private void prepareDecryption() throws IOException } try { - PDEncryption encryption = new PDEncryption(document.getEncryptionDictionary()); + encryption = new PDEncryption(document.getEncryptionDictionary()); DecryptionMaterial decryptionMaterial; if (keyStoreInputStream != null) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFStreamParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFStreamParser.java index bdb24bfcc..477fa5fd8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFStreamParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFStreamParser.java @@ -72,7 +72,7 @@ public PDFStreamParser(PDStream stream) throws IOException @Deprecated public PDFStreamParser(COSStream stream) throws IOException { - super(new InputStreamSource(stream.getUnfilteredStream())); + super(new InputStreamSource(stream.createInputStream())); } /** @@ -143,13 +143,13 @@ public Object parseNextToken() throws IOException { case '<': { - //pull off first left bracket + // pull off first left bracket int leftBracket = seqSource.read(); - //check for second left bracket + // check for second left bracket c = (char) seqSource.peek(); - //put back first bracket + // put back first bracket seqSource.unread(leftBracket); if (c == '<') @@ -236,8 +236,8 @@ else if (next.equals("false")) case '+': case '.': { - /* We will be filling buf with the rest of the number. Only - * allow 1 "." and "-" and "+" at start of number. */ + /* We will be filling buf with the rest of the number. Only + * allow 1 "." and "-" and "+" at start of number. */ StringBuffer buf = new StringBuffer(); buf.append(c); seqSource.read(); @@ -362,7 +362,7 @@ private boolean hasNoFollowingBinData(SequentialSource pdfSource) throws IOExcep for (int bIdx = 0; bIdx < readBytes; bIdx++) { final byte b = binCharTestArr[bIdx]; - if ((b < 0x09) || ((b > 0x0a) && (b < 0x20) && (b != 0x0d))) + if (b < 0x09 || b > 0x0a && b < 0x20 && b != 0x0d) { // control character or > 0x7f -> we have binary data noBinData = false; @@ -404,32 +404,6 @@ else if (startOpIdx != -1 && endOpIdx == -1 && return noBinData; } - /** - * Check whether the output stream ends with 70 ASCII85 data bytes - * (33..117). This method is to be called when "EI" and then space/LF/CR - * are detected. - * - * @param imageData output data stream without the "EI" - * @return true if this is an ASCII85 line so the "EI" is to be considered - * part of the data stream, false if not - */ - private boolean hasPrecedingAscii85Data(ByteArrayOutputStream imageData) - { - if (imageData.size() < 70) - { - return false; - } - byte[] tab = imageData.toByteArray(); - for (int i = tab.length - 1; i >= tab.length - 70; --i) - { - if (tab[i] < 33 || tab[i] > 117) - { - return false; - } - } - return true; - } - /** * This will read an operator from the stream. * diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXRefStream.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXRefStream.java index a4ac1caba..5370ce4d8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXRefStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXRefStream.java @@ -77,7 +77,8 @@ public COSStream getStream() throws IOException { throw new IllegalArgumentException("size is not set in xrefstream"); } - stream.setLong(COSName.SIZE, getSizeEntry()); + // add one for object number 0 + stream.setLong(COSName.SIZE, streamData.size() + 1); List indexEntry = getIndexEntry(); COSArray indexAsArray = new COSArray(); @@ -104,6 +105,13 @@ public COSStream getStream() throws IOException Set keySet = this.stream.keySet(); for ( COSName cosName : keySet ) { + // "Other cross-reference stream entries not listed in Table 17 may be indirect; in fact, + // some (such as Root in Table 15) shall be indirect." + if (COSName.ROOT.equals(cosName) || COSName.INFO.equals(cosName) || COSName.PREV.equals( + cosName)) + { + continue; + } COSBase dictionaryObject = this.stream.getDictionaryObject(cosName); dictionaryObject.setDirect(true); } @@ -206,11 +214,6 @@ else if (entry instanceof ObjectStreamReference) return w; } - private long getSizeEntry() - { - return size; - } - /** * Set the size of the XRef stream. * @@ -226,8 +229,11 @@ private List getIndexEntry() LinkedList linkedList = new LinkedList(); Long first = null; Long length = null; - - for ( Long objNumber : objectNumbers ) + Set objNumbers = new TreeSet(); + // add object number 0 to the set + objNumbers.add(0L); + objNumbers.addAll(objectNumbers); + for (Long objNumber : objNumbers) { if (first == null) { @@ -269,6 +275,10 @@ private void writeNumber(OutputStream os, long number, int bytes) throws IOExcep private void writeStreamData(OutputStream os, int[] w) throws IOException { + // write dummy entry for object number 0 + writeNumber(os, ENTRY_FREE, w[0]); + writeNumber(os, ENTRY_FREE, w[1]); + writeNumber(os, 0xFFFF, w[2]); // iterate over all streamData and write it in the required format for ( Object entry : streamData.values() ) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXrefStreamParser.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXrefStreamParser.java index 209cd04be..059220b72 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXrefStreamParser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/PDFXrefStreamParser.java @@ -111,14 +111,24 @@ public void parse() throws IOException byte[] currLine = new byte[lineSize]; seqSource.read(currLine); - int type = 0; - /* - * Grabs the number of bytes specified for the first column in - * the W array and stores it. - */ - for(int i = 0; i < w0; i++) + int type; + if (w0 == 0) + { + // "If the first element is zero, + // the type field shall not be present, and shall default to type 1" + type = 1; + } + else { - type += (currLine[i] & 0x00ff) << ((w0 - i - 1)* 8); + type = 0; + /* + * Grabs the number of bytes specified for the first column in + * the W array and stores it. + */ + for (int i = 0; i < w0; i++) + { + type += (currLine[i] & 0x00ff) << ((w0 - i - 1) * 8); + } } //Need to remember the current objID Long objID = objIter.next(); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/XrefTrailerResolver.java b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/XrefTrailerResolver.java index 0e4bbbad8..674fae657 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfparser/XrefTrailerResolver.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfparser/XrefTrailerResolver.java @@ -72,7 +72,7 @@ private class XrefTrailerObj */ private XrefTrailerObj() { - xrefType = XRefType.TABLE; + xrefType = XRefType.TABLE; } } @@ -88,7 +88,7 @@ public enum XRefType /** * XRef stream type. */ - STREAM + STREAM; } private final Map bytePosToXrefMap = new HashMap(); @@ -159,7 +159,8 @@ public void setXRef( COSObjectKey objKey, long offset ) if ( curXrefTrailerObj == null ) { // should not happen... - Log.w("PdfBox-Android", "Cannot add XRef entry for '" + objKey.getNumber() + "' because XRef start was not signalled." ); + Log.w("PdfBox-Android", "Cannot add XRef entry for '" + objKey.getNumber() + + "' because XRef start was not signalled."); return; } curXrefTrailerObj.xrefTable.put( objKey, offset ); @@ -175,7 +176,7 @@ public void setTrailer( COSDictionary trailer ) if ( curXrefTrailerObj == null ) { // should not happen... - Log.w("PdfBox-Android", "Cannot add trailer because XRef start was not signalled." ); + Log.w("PdfBox-Android", "Cannot add trailer because XRef start was not signalled."); return; } curXrefTrailerObj.trailer = trailer; @@ -210,7 +211,7 @@ public void setStartxref( long startxrefBytePosValue ) { if ( resolvedXrefTrailer != null ) { - Log.w("PdfBox-Android", "Method must be called only ones with last startxref value." ); + Log.w("PdfBox-Android", "Method must be called only ones with last startxref value."); return; } @@ -223,7 +224,8 @@ public void setStartxref( long startxrefBytePosValue ) if ( curObj == null ) { // no XRef at given position - Log.w("PdfBox-Android", "Did not found XRef object at specified startxref position " + startxrefBytePosValue ); + Log.w("PdfBox-Android", "Did not found XRef object at specified startxref position " + + startxrefBytePosValue); // use all objects in byte position order (last entries overwrite previous ones) xrefSeqBytePos.addAll( bytePosToXrefMap.keySet() ); @@ -231,9 +233,9 @@ public void setStartxref( long startxrefBytePosValue ) } else { - // copy xref type - resolvedXrefTrailer.xrefType = curObj.xrefType; - // found starting Xref object + // copy xref type + resolvedXrefTrailer.xrefType = curObj.xrefType; + // found starting Xref object // add this and follow chain defined by 'Prev' keys xrefSeqBytePos.add( startxrefBytePosValue ); while ( curObj.trailer != null ) @@ -247,7 +249,9 @@ public void setStartxref( long startxrefBytePosValue ) curObj = bytePosToXrefMap.get( prevBytePos ); if ( curObj == null ) { - Log.w("PdfBox-Android", "Did not found XRef object pointed to by 'Prev' key at position " + prevBytePos ); + Log.w("PdfBox-Android", + "Did not found XRef object pointed to by 'Prev' key at position " + + prevBytePos); break; } xrefSeqBytePos.add( prevBytePos ); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/COSWriter.java b/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/COSWriter.java index 69475ac73..f9de40086 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/COSWriter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/COSWriter.java @@ -59,6 +59,9 @@ import com.tom_roush.pdfbox.cos.COSUpdateInfo; import com.tom_roush.pdfbox.cos.ICOSVisitor; import com.tom_roush.pdfbox.io.IOUtils; +import com.tom_roush.pdfbox.io.RandomAccessBuffer; +import com.tom_roush.pdfbox.io.RandomAccessInputStream; +import com.tom_roush.pdfbox.io.RandomAccessRead; import com.tom_roush.pdfbox.pdfparser.PDFXRefStream; import com.tom_roush.pdfbox.pdmodel.PDDocument; import com.tom_roush.pdfbox.pdmodel.encryption.SecurityHandler; @@ -211,7 +214,8 @@ public class COSWriter implements ICOSVisitor, Closeable private boolean reachedSignature = false; private long signatureOffset, signatureLength; private long byteRangeOffset, byteRangeLength; - private InputStream incrementalInput; + private RandomAccessRead incrementalInput; + private RandomAccessRead tempIncInput; private OutputStream incrementalOutput; private SignatureInterface signatureInterface; @@ -236,16 +240,36 @@ public COSWriter(OutputStream os) * @param inputStream input stream containing source PDF data * * @throws IOException if something went wrong + * @deprecated Use {@link #COSWriter(OutputStream, RandomAccessRead)} instead */ public COSWriter(OutputStream outputStream, InputStream inputStream) throws IOException { super(); + tempIncInput = new RandomAccessBuffer(inputStream); + initWriter(outputStream, tempIncInput); + } + /** + * COSWriter constructor for incremental updates. + * + * @param outputStream output stream where the new PDF data will be written + * @param inputData random access read containing source PDF data + * @throws IOException if something went wrong + */ + public COSWriter(OutputStream outputStream, RandomAccessRead inputData) throws IOException + { + super(); + initWriter(outputStream, inputData); + } + + private void initWriter(OutputStream outputStream, RandomAccessRead inputData) + throws IOException + { // write to buffer instead of output setOutput(new ByteArrayOutputStream()); - setStandardOutput(new COSStandardOutputStream(output, inputStream.available())); + setStandardOutput(new COSStandardOutputStream(output, (int)inputData.length())); - incrementalInput = inputStream; + incrementalInput = inputData; incrementalOutput = outputStream; incrementalUpdate = true; @@ -321,6 +345,10 @@ public void close() throws IOException { incrementalOutput.close(); } + if (tempIncInput != null) + { + incrementalOutput.close(); + } } /** @@ -514,15 +542,10 @@ public void doWriteObject( COSBase obj ) throws IOException writtenObjects.add( obj ); if(obj instanceof COSDictionary) { - COSDictionary dict = (COSDictionary)obj; - COSBase itemType = dict.getItem(COSName.TYPE); - if (itemType instanceof COSName) + COSBase itemType = ((COSDictionary)obj).getItem(COSName.TYPE); + if (COSName.SIG.equals(itemType) || COSName.DOC_TIME_STAMP.equals(itemType)) { - COSName item = (COSName) itemType; - if (COSName.SIG.equals(item) || COSName.DOC_TIME_STAMP.equals(item)) - { - reachedSignature = true; - } + reachedSignature = true; } } @@ -687,15 +710,26 @@ private void doWriteXRefTable() throws IOException } } - private void doWriteSignature() throws IOException + /** + * Write an incremental update for a non signature case. This can be used for e.g. augmenting signatures. + * + * @throws IOException + */ + private void doWriteIncrement() throws IOException { - if (signatureOffset == 0 || byteRangeOffset == 0) - { - return; - } + ByteArrayOutputStream byteOut = (ByteArrayOutputStream)output; + byteOut.flush(); + byte[] buffer = byteOut.toByteArray(); + SequenceInputStream signStream = new SequenceInputStream( + new RandomAccessInputStream(incrementalInput), new ByteArrayInputStream(buffer)); + // write the data to the incremental output stream + IOUtils.copy(signStream, incrementalOutput); + } + private void doWriteSignature() throws IOException + { // calculate the ByteRange values - long inLength = incrementalInput.available(); + long inLength = incrementalInput.length(); long beforeLength = signatureOffset; long afterOffset = signatureOffset + signatureLength; long afterLength = getStandardOutput().getPos() - (inLength + signatureLength) - (signatureOffset - inLength); @@ -712,7 +746,7 @@ private void doWriteSignature() throws IOException byte[] buffer = byteOut.toByteArray(); // overwrite the ByteRange in the buffer - byte[] byteRangeBytes = byteRange.getBytes(); + byte[] byteRangeBytes = byteRange.getBytes(Charsets.ISO_8859_1); for (int i = 0; i < byteRangeLength; i++) { if (i >= byteRangeBytes.length) @@ -725,9 +759,6 @@ private void doWriteSignature() throws IOException } } - // get the input PDF bytes - byte[] inputBytes = IOUtils.toByteArray(incrementalInput); - // get only the incremental bytes to be signed (includes /ByteRange but not /Contents) byte[] signBuffer = new byte[buffer.length - (int)signatureLength]; int bufSignatureOffset = (int)(signatureOffset - inLength); @@ -735,7 +766,8 @@ private void doWriteSignature() throws IOException System.arraycopy(buffer, bufSignatureOffset + (int)signatureLength, signBuffer, bufSignatureOffset, buffer.length - bufSignatureOffset - (int)signatureLength); - SequenceInputStream signStream = new SequenceInputStream(new ByteArrayInputStream(inputBytes), + SequenceInputStream signStream = new SequenceInputStream( + new RandomAccessInputStream(incrementalInput), new ByteArrayInputStream(signBuffer)); // sign the bytes @@ -748,11 +780,11 @@ private void doWriteSignature() throws IOException } // overwrite the signature Contents in the buffer - byte[] signatureBytes = signature.getBytes(); + byte[] signatureBytes = signature.getBytes(Charsets.ISO_8859_1); System.arraycopy(signatureBytes, 0, buffer, bufSignatureOffset + 1, signatureBytes.length); // write the data to the incremental output stream - incrementalOutput.write(inputBytes); + IOUtils.copy(new RandomAccessInputStream(incrementalInput), incrementalOutput); incrementalOutput.write(buffer); } @@ -888,7 +920,7 @@ public Object visitFromArray( COSArray obj ) throws IOException else if( current instanceof COSObject ) { COSBase subValue = ((COSObject)current).getObject(); - if( subValue instanceof COSDictionary || subValue == null ) + if (incrementalUpdate || subValue instanceof COSDictionary || subValue == null) { addObjectToWrite( current ); writeReference( current ); @@ -947,16 +979,19 @@ public Object visitFromDictionary(COSDictionary obj) throws IOException { COSDictionary dict = (COSDictionary)value; - // write all XObjects as direct objects, this will save some size - COSBase item = dict.getItem(COSName.XOBJECT); - if(item!=null) + if (!incrementalUpdate) { - item.setDirect(true); - } - item = dict.getItem(COSName.RESOURCES); - if(item!=null) - { - item.setDirect(true); + // write all XObjects as direct objects, this will save some size + COSBase item = dict.getItem(COSName.XOBJECT); + if (item != null) + { + item.setDirect(true); + } + item = dict.getItem(COSName.RESOURCES); + if (item != null) + { + item.setDirect(true); + } } if(dict.isDirect()) @@ -974,7 +1009,7 @@ public Object visitFromDictionary(COSDictionary obj) throws IOException else if( value instanceof COSObject ) { COSBase subValue = ((COSObject)value).getObject(); - if( subValue instanceof COSDictionary || subValue == null ) + if (incrementalUpdate || subValue instanceof COSDictionary || subValue == null) { addObjectToWrite( value ); writeReference( value ); @@ -1069,7 +1104,14 @@ public Object visitFromDocument(COSDocument doc) throws IOException if(incrementalUpdate) { - doWriteSignature(); + if (signatureOffset == 0 || byteRangeOffset == 0) + { + doWriteIncrement(); + } + else + { + doWriteSignature(); + } } return null; @@ -1210,6 +1252,7 @@ public void write(PDDocument doc, SignatureInterface signInterface) throws IOExc pdDocument = doc; signatureInterface = signInterface; + if(incrementalUpdate) { prepareIncrement(doc); @@ -1229,13 +1272,18 @@ public void write(PDDocument doc, SignatureInterface signInterface) throws IOExc { if (pdDocument.getEncryption() != null) { - SecurityHandler securityHandler = pdDocument.getEncryption().getSecurityHandler(); - if (!securityHandler.hasProtectionPolicy()) + if (!incrementalUpdate) { - throw new IllegalStateException("PDF contains an encryption dictionary, please remove it with " - + "setAllSecurityToBeRemoved() or set a protection policy with protect()"); + SecurityHandler securityHandler = + pdDocument.getEncryption().getSecurityHandler(); + if (!securityHandler.hasProtectionPolicy()) + { + throw new IllegalStateException( + "PDF contains an encryption dictionary, please remove it with " + + "setAllSecurityToBeRemoved() or set a protection policy with protect()"); + } + securityHandler.prepareDocumentForEncryption(pdDocument); } - securityHandler.prepareDocumentForEncryption(pdDocument); willEncrypt = true; } else @@ -1349,6 +1397,12 @@ private static void writeString(byte[] bytes, boolean forceHex, OutputStream out isASCII = false; break; } + // PDFBOX-3107 EOL markers within a string are troublesome + if (b == 0x0d || b == 0x0a) + { + isASCII = false; + break; + } } if (isASCII && !forceHex) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/COSWriterXRefEntry.java b/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/COSWriterXRefEntry.java index d47a3a748..9a6fe499e 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/COSWriterXRefEntry.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/COSWriterXRefEntry.java @@ -35,8 +35,8 @@ public class COSWriterXRefEntry implements Comparable static { - NULLENTRY = new COSWriterXRefEntry(0, null, new COSObjectKey(0, 65535)); - NULLENTRY.setFree(true); + NULLENTRY = new COSWriterXRefEntry(0, null, new COSObjectKey(0, 65535)); + NULLENTRY.setFree(true); } /** @@ -45,19 +45,19 @@ public class COSWriterXRefEntry implements Comparable @Override public int compareTo(COSWriterXRefEntry obj) { - if (obj != null) - { - if (getKey().getNumber() < obj.getKey().getNumber()) - { - return -1; - } - else if (getKey().getNumber() > obj.getKey().getNumber()) - { - return 1; - } - return 0; - } - return -1; + if (obj != null) + { + if (getKey().getNumber() < obj.getKey().getNumber()) + { + return -1; + } + else if (getKey().getNumber() > obj.getKey().getNumber()) + { + return 1; + } + return 0; + } + return -1; } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/ContentStreamWriter.java b/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/ContentStreamWriter.java index 28de7e153..8afb3f5b5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/ContentStreamWriter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdfwriter/ContentStreamWriter.java @@ -115,7 +115,7 @@ private void writeObject( Object o ) throws IOException { if( o instanceof COSString ) { - COSWriter.writeString((COSString)o, output); + COSWriter.writeString((COSString)o, output); output.write(SPACE); } else if( o instanceof COSFloat ) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDestinationNameTreeNode.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDestinationNameTreeNode.java index d0cc1cc4e..1299ca235 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDestinationNameTreeNode.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDestinationNameTreeNode.java @@ -61,7 +61,7 @@ protected PDPageDestination convertCOSToPD(COSBase base) throws IOException //it for now destination = ((COSDictionary)base).getDictionaryObject( COSName.D ); } - return (PDPageDestination) PDDestination.create(destination); + return (PDPageDestination)PDDestination.create(destination); } @Override diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocument.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocument.java index a5fa44e52..da21a9818 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocument.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocument.java @@ -18,6 +18,7 @@ import android.util.Log; +import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; @@ -36,12 +37,10 @@ import com.tom_roush.pdfbox.cos.COSInteger; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSObject; -import com.tom_roush.pdfbox.cos.COSStream; import com.tom_roush.pdfbox.io.IOUtils; import com.tom_roush.pdfbox.io.MemoryUsageSetting; import com.tom_roush.pdfbox.io.RandomAccessBuffer; import com.tom_roush.pdfbox.io.RandomAccessBufferedFileInputStream; -import com.tom_roush.pdfbox.io.RandomAccessInputStream; import com.tom_roush.pdfbox.io.RandomAccessRead; import com.tom_roush.pdfbox.io.ScratchFile; import com.tom_roush.pdfbox.pdfparser.PDFParser; @@ -57,7 +56,6 @@ import com.tom_roush.pdfbox.pdmodel.font.PDFont; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; -import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; import com.tom_roush.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; import com.tom_roush.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; import com.tom_roush.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions; @@ -111,18 +109,7 @@ public class PDDocument implements Closeable */ public PDDocument() { - this(false); - } - - /** - * Creates an empty PDF document. - * You need to add at least one page for the document to be valid. - * - * @param useScratchFiles enables the usage of a scratch file if set to true - */ - public PDDocument(boolean useScratchFiles) - { - this(useScratchFiles, null); + this(MemoryUsageSetting.setupMainMemoryOnly()); } /** @@ -132,46 +119,27 @@ public PDDocument(boolean useScratchFiles) * @param memUsageSetting defines how memory is used for buffering PDF streams */ public PDDocument(MemoryUsageSetting memUsageSetting) - { - this(true, memUsageSetting); - } - - /** - * Internal constructor which support setting scratch file usage - * via boolean parameter or directly (new). This will be only needed - * as long as the new ScratchFile handling is tested. - * - *

You need to add at least one page for the document to be valid.

- * - * @param useScratchFiles enables the usage of a scratch file if set to true - * @param memUsageSetting defines how memory is used for buffering PDF streams - */ - private PDDocument(boolean useScratchFiles, MemoryUsageSetting memUsageSetting) { ScratchFile scratchFile = null; - if (memUsageSetting != null) + try { + scratchFile = new ScratchFile(memUsageSetting); + } + catch (IOException ioe) + { + Log.w("PdfBox-Android", "Error initializing scratch file: " + ioe.getMessage() + + ". Fall back to main memory usage only."); try { - scratchFile = new ScratchFile(memUsageSetting); + scratchFile = new ScratchFile(MemoryUsageSetting.setupMainMemoryOnly()); } - catch (IOException ioe) + catch (IOException ioe2) { - Log.w("PdfBox-Android", "Error initializing scratch file: " + ioe.getMessage() + - ". Fall back to main memory usage only."); - try - { - scratchFile = new ScratchFile(MemoryUsageSetting.setupMainMemoryOnly()); - } - catch (IOException ioe2) - { - } } } - document = scratchFile != null ? new COSDocument(scratchFile) : - new COSDocument(useScratchFiles); + document = new COSDocument(scratchFile); pdfSource = null; // First we need a trailer @@ -208,7 +176,7 @@ public void addPage(PDPage page) * Add a signature. * * @param sigObject is the PDSignatureField model - * @param signatureInterface is a interface which provides signing capabilities + * @param signatureInterface is an interface which provides signing capabilities * @throws IOException if there is an error creating required fields */ public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface) throws IOException @@ -217,10 +185,12 @@ public void addSignature(PDSignature sigObject, SignatureInterface signatureInte } /** - * This will add a signature to the document. + * This will add a signature to the document. If the 0-based page number in the options + * parameter is smaller than 0 or larger than max, the nearest valid page number will be used + * (i.e. 0 or max) and no exception will be thrown. * * @param sigObject is the PDSignatureField model - * @param signatureInterface is a interface which provides signing capabilities + * @param signatureInterface is an interface which provides signing capabilities * @param options signature options * @throws IOException if there is an error creating required fields */ @@ -230,14 +200,14 @@ public void addSignature(PDSignature sigObject, SignatureInterface signatureInte // Reserve content // We need to reserve some space for the signature. Some signatures including // big certificate chain and we need enough space to store it. - int preferedSignatureSize = options.getPreferedSignatureSize(); - if (preferedSignatureSize > 0) + int preferredSignatureSize = options.getPreferredSignatureSize(); + if (preferredSignatureSize > 0) { - sigObject.setContents(new byte[preferedSignatureSize]); + sigObject.setContents(new byte[preferredSignatureSize]); } else { - sigObject.setContents(new byte[0x2500]); + sigObject.setContents(new byte[SignatureOptions.DEFAULT_SIGNATURE_SIZE]); } // Reserve ByteRange @@ -257,6 +227,7 @@ public void addSignature(PDSignature sigObject, SignatureInterface signatureInte { throw new IllegalStateException("Cannot sign an empty document"); } + int startIndex = Math.min(Math.max(options.getPage(), 0), pageCount - 1); PDPage page = catalog.getPages().get(startIndex); @@ -274,14 +245,6 @@ public void addSignature(PDSignature sigObject, SignatureInterface signatureInte acroForm.getCOSObject().setNeedToBeUpdated(true); } - // For invisible signatures, the annotation has a rectangle array with values [ 0 0 0 0 ]. This annotation is - // usually attached to the viewed page when the signature is created. Despite not having an appearance, the - // annotation AP and N dictionaries may be present in some versions of Acrobat. If present, N references the - // DSBlankXObj (blank) XObject. - - // Create Annotation / Field for signature - List annotations = page.getAnnotations(); - List fields = acroForm.getFields(); if (fields == null) { @@ -298,7 +261,8 @@ public void addSignature(PDSignature sigObject, SignatureInterface signatureInte signatureField.getWidgets().get(0).setPage(page); } // to conform PDF/A-1 requirement: - // The /F key's Print flag bit shall be set to 1 and its Hidden, Invisible and NoView flag bits shall be set to 0 + // The /F key's Print flag bit shall be set to 1 and + // its Hidden, Invisible and NoView flag bits shall be set to 0 signatureField.getWidgets().get(0).setPrinted(true); // Set the AcroForm Fields @@ -315,25 +279,33 @@ public void addSignature(PDSignature sigObject, SignatureInterface signatureInte // Distinction of case for visual and non-visual signature if (visualSignature == null) { - prepareNonVisibleSignature(signatureField, acroForm); - } - else - { - prepareVisibleSignature(signatureField, acroForm, visualSignature); + prepareNonVisibleSignature(signatureField); + return; } + prepareVisibleSignature(signatureField, acroForm, visualSignature); + + // Create Annotation / Field for signature + List annotations = page.getAnnotations(); + + // Make /Annots a direct object to avoid problem if it is an existing indirect object: + // it would not be updated in incremental save, and if we'd set the /Annots array "to be updated" + // while keeping it indirect, Adobe Reader would claim that the document had been modified. + page.setAnnotations(annotations); + // Get the annotations of the page and append the signature-annotation to it // take care that page and acroforms do not share the same array (if so, we don't need to add it twice) if (!(annotations instanceof COSArrayList && acroFormFields instanceof COSArrayList && - ((COSArrayList) annotations).toList().equals(((COSArrayList) acroFormFields).toList()) && - checkFields)) + ((COSArrayList)annotations).toList().equals( + ((COSArrayList)acroFormFields).toList()) && checkFields)) { annotations.add(signatureField.getWidgets().get(0)); } page.getCOSObject().setNeedToBeUpdated(true); } + // search acroform field list for signature field with specific signature dictionary private PDSignatureField findSignatureField(List fields, PDSignature sigObject) { PDSignatureField signatureField = null; @@ -351,7 +323,7 @@ private PDSignatureField findSignatureField(List fields, PDSignature si return signatureField; } - //return true if the field already existed in the field list, in that case, it is marked for update + // return true if the field already existed in the field list, in that case, it is marked for update private boolean checkSignatureField(List acroFormFields, PDSignatureField signatureField) { boolean checkFields = false; @@ -399,12 +371,13 @@ private void prepareVisibleSignature(PDSignatureField signatureField, PDAcroForm annotNotFound = false; } - // Search for Signature-Field - COSBase ft = cosBaseDict.getDictionaryObject(COSName.FT); + // Search for signature Field + COSBase fieldType = cosBaseDict.getDictionaryObject(COSName.FT); COSBase apDict = cosBaseDict.getDictionaryObject(COSName.AP); - if (sigFieldNotFound && COSName.SIG.equals(ft) && apDict != null) + if (sigFieldNotFound && COSName.SIG.equals(fieldType) && + apDict instanceof COSDictionary) { - assignAppearanceDictionary(signatureField, cosBaseDict); + assignAppearanceDictionary(signatureField, (COSDictionary)apDict); assignAcroFormDefaultResource(acroForm, cosBaseDict); sigFieldNotFound = false; } @@ -417,56 +390,41 @@ private void prepareVisibleSignature(PDSignatureField signatureField, PDAcroForm } } - private void assignSignatureRectangle(PDSignatureField signatureField, COSDictionary cosBaseDict) + private void assignSignatureRectangle(PDSignatureField signatureField, COSDictionary annotDict) { - // Read and set the Rectangle for visual signature - COSArray rectAry = (COSArray) cosBaseDict.getDictionaryObject(COSName.RECT); - PDRectangle rect = new PDRectangle(rectAry); + // Read and set the rectangle for visual signature + COSArray rectArray = (COSArray)annotDict.getDictionaryObject(COSName.RECT); + PDRectangle rect = new PDRectangle(rectArray); signatureField.getWidgets().get(0).setRectangle(rect); } - private void assignAppearanceDictionary(PDSignatureField signatureField, COSDictionary dict) + private void assignAppearanceDictionary(PDSignatureField signatureField, COSDictionary apDict) { // read and set Appearance Dictionary - PDAppearanceDictionary ap = - new PDAppearanceDictionary((COSDictionary) dict.getDictionaryObject(COSName.AP)); - ap.getCOSObject().setDirect(true); + PDAppearanceDictionary ap = new PDAppearanceDictionary(apDict); + apDict.setDirect(true); signatureField.getWidgets().get(0).setAppearance(ap); } private void assignAcroFormDefaultResource(PDAcroForm acroForm, COSDictionary dict) { - // read and set AcroForm DefaultResource - COSDictionary dr = (COSDictionary) dict.getDictionaryObject(COSName.DR); - if (dr != null) + // read and set AcroForm default resource dictionary /DR if available + COSBase base = dict.getDictionaryObject(COSName.DR); + if (base instanceof COSDictionary) { + COSDictionary dr = (COSDictionary)base; dr.setDirect(true); dr.setNeedToBeUpdated(true); - COSDictionary acroFormDict = acroForm.getCOSObject(); - acroFormDict.setItem(COSName.DR, dr); + acroForm.getCOSObject().setItem(COSName.DR, dr); } } - private void prepareNonVisibleSignature(PDSignatureField signatureField, PDAcroForm acroForm) throws IOException + private void prepareNonVisibleSignature(PDSignatureField signatureField) throws IOException { + // "Signature fields that are not intended to be visible shall + // have an annotation rectangle that has zero height and width." // Set rectangle for non-visual signature to rectangle array [ 0 0 0 0 ] signatureField.getWidgets().get(0).setRectangle(new PDRectangle()); - // Clear AcroForm / Set DefaultRessource - acroForm.setDefaultResources(null); - // Set empty Appearance-Dictionary - PDAppearanceDictionary ap = new PDAppearanceDictionary(); - - // Create empty visual appearance stream - COSStream apsStream = getDocument().createCOSStream(); - apsStream.createOutputStream().close(); - PDAppearanceStream aps = new PDAppearanceStream(apsStream); - COSDictionary cosObject = (COSDictionary) aps.getCOSObject(); - cosObject.setItem(COSName.SUBTYPE, COSName.FORM); - cosObject.setItem(COSName.BBOX, new PDRectangle()); - - ap.setNormalAppearance(aps); - ap.getCOSObject().setDirect(true); - signatureField.getWidgets().get(0).setAppearance(ap); } /** @@ -545,7 +503,12 @@ public void removePage(int pageNumber) * This will import and copy the contents from another location. Currently the content stream is stored in a scratch * file. The scratch file is associated with the document. If you are adding a page to this document from another * document and want to copy the contents to this document's scratch file then use this method otherwise just use - * the addPage method. + * the {@link #addPage} method. + * + * Unlike {@link #addPage}, this method does a deep copy. If your page has annotations, and if + * these link to pages not in the target document, then the target document might become huge. + * What you need to do is to delete page references of such annotations. See + * here for how to do this. * * @param page The page to import. * @return The page that was imported. @@ -783,7 +746,7 @@ Set getFontsToSubset() } /** - * Parses a PDF. + * Parses a PDF. Unrestricted main memory will be used for buffering PDF streams. * * @param file file to be loaded * @@ -793,22 +756,7 @@ Set getFontsToSubset() */ public static PDDocument load(File file) throws IOException { - return load(file, "", false); - } - - /** - * Parses a PDF. - * - * @param file file to be loaded - * @param useScratchFiles enables the usage of a scratch file if set to true - * - * @return loaded document - * - * @throws IOException in case of a file reading or parsing error - */ - public static PDDocument load(File file, boolean useScratchFiles) throws IOException - { - return load(file, "", null, null, useScratchFiles); + return load(file, "", MemoryUsageSetting.setupMainMemoryOnly()); } /** @@ -825,7 +773,7 @@ public static PDDocument load(File file, MemoryUsageSetting memUsageSetting) thr } /** - * Parses a PDF. + * Parses a PDF. Unrestricted main memory will be used for buffering PDF streams. * * @param file file to be loaded * @param password password to be used for decryption @@ -836,23 +784,7 @@ public static PDDocument load(File file, MemoryUsageSetting memUsageSetting) thr */ public static PDDocument load(File file, String password) throws IOException { - return load(file, password, null, null, false); - } - - /** - * Parses a PDF. - * - * @param file file to be loaded - * @param password password to be used for decryption - * @param useScratchFiles enables the usage of a scratch file if set to true - * - * @return loaded document - * - * @throws IOException in case of a file reading or parsing error - */ - public static PDDocument load(File file, String password, boolean useScratchFiles) throws IOException - { - return load(file, password, null, null, useScratchFiles); + return load(file, password, null, null, MemoryUsageSetting.setupMainMemoryOnly()); } /** @@ -871,7 +803,7 @@ public static PDDocument load(File file, String password, MemoryUsageSetting mem } /** - * Parses a PDF. + * Parses a PDF. Unrestricted main memory will be used for buffering PDF streams. * * @param file file to be loaded * @param password password to be used for decryption @@ -885,29 +817,7 @@ public static PDDocument load(File file, String password, MemoryUsageSetting mem public static PDDocument load(File file, String password, InputStream keyStore, String alias) throws IOException { - return load(file, password, keyStore, alias, false); - } - - /** - * Parses a PDF. - * - * @param file file to be loaded - * @param password password to be used for decryption - * @param keyStore key store to be used for decryption when using public key security - * @param alias alias to be used for decryption when using public key security - * @param useScratchFiles enables the usage of a scratch file if set to true - * - * @return loaded document - * - * @throws IOException in case of a file reading or parsing error - */ - public static PDDocument load(File file, String password, InputStream keyStore, String alias, - boolean useScratchFiles) throws IOException - { - RandomAccessBufferedFileInputStream raFile = new RandomAccessBufferedFileInputStream(file); - PDFParser parser = new PDFParser(raFile, password, keyStore, alias, useScratchFiles); - parser.parse(); - return parser.getPDDocument(); + return load(file, password, keyStore, alias, MemoryUsageSetting.setupMainMemoryOnly()); } /** @@ -925,14 +835,31 @@ public static PDDocument load(File file, String password, InputStream keyStore, MemoryUsageSetting memUsageSetting) throws IOException { RandomAccessBufferedFileInputStream raFile = new RandomAccessBufferedFileInputStream(file); - PDFParser parser = new PDFParser(raFile, password, keyStore, alias, - new ScratchFile(memUsageSetting)); - parser.parse(); - return parser.getPDDocument(); + try + { + ScratchFile scratchFile = new ScratchFile(memUsageSetting); + try + { + PDFParser parser = new PDFParser(raFile, password, keyStore, alias, scratchFile); + parser.parse(); + return parser.getPDDocument(); + } + catch (IOException ioe) + { + IOUtils.closeQuietly(scratchFile); + throw ioe; + } + } + catch (IOException ioe) + { + IOUtils.closeQuietly(raFile); + throw ioe; + } } /** * Parses a PDF. The given input stream is copied to the memory to enable random access to the pdf. + * Unrestricted main memory will be used for buffering PDF streams. * * @param input stream that contains the document. * @@ -942,36 +869,21 @@ public static PDDocument load(File file, String password, InputStream keyStore, */ public static PDDocument load(InputStream input) throws IOException { - return load(input, "", null, null, false); + return load(input, "", null, null, MemoryUsageSetting.setupMainMemoryOnly()); } /** - * Parses a PDF. Depending on the parameter useScratchFiles the given input - * stream is either copied to the memory or to a temporary file to enable + * Parses a PDF. Depending on the memory settings parameter the given input + * stream is either copied to main memory or to a temporary file to enable * random access to the pdf. * * @param input stream that contains the document. - * @param useScratchFiles enables the usage of a scratch file if set to true + * @param memUsageSetting defines how memory is used for buffering input stream and PDF streams * * @return loaded document * * @throws IOException in case of a file reading or parsing error */ - public static PDDocument load(InputStream input, boolean useScratchFiles) throws IOException - { - return load(input, "", null, null, useScratchFiles); - } - - /** - * Parses a PDF. Depending on the parameter useScratchFiles the given input - * stream is either copied to the memory or to a temporary file to enable - * random access to the pdf. - * - * @param input stream that contains the document. - * @param memUsageSetting defines how memory is used for buffering input stream and PDF streams - * @return loaded document - * @throws IOException in case of a file reading or parsing error - */ public static PDDocument load(InputStream input, MemoryUsageSetting memUsageSetting) throws IOException { @@ -980,6 +892,7 @@ public static PDDocument load(InputStream input, MemoryUsageSetting memUsageSett /** * Parses a PDF. The given input stream is copied to the memory to enable random access to the pdf. + * Unrestricted main memory will be used for buffering PDF streams. * * @param input stream that contains the document. * @param password password to be used for decryption @@ -991,11 +904,12 @@ public static PDDocument load(InputStream input, MemoryUsageSetting memUsageSett public static PDDocument load(InputStream input, String password) throws IOException { - return load(input, password, false); + return load(input, password, null, null, MemoryUsageSetting.setupMainMemoryOnly()); } /** * Parses a PDF. The given input stream is copied to the memory to enable random access to the pdf. + * Unrestricted main memory will be used for buffering PDF streams. * * @param input stream that contains the document. * @param password password to be used for decryption @@ -1009,30 +923,12 @@ public static PDDocument load(InputStream input, String password) public static PDDocument load(InputStream input, String password, InputStream keyStore, String alias) throws IOException { - return load(input, password, keyStore, alias, false); - } - - /** - * Parses a PDF. Depending on the parameter useScratchFiles the given input - * stream is either copied to the memory or to a temporary file to enable - * random access to the pdf. - * - * @param input stream that contains the document. - * @param password password to be used for decryption - * @param useScratchFiles enables the usage of a scratch file if set to true - * - * @return loaded document - * - * @throws IOException in case of a file reading or parsing error - */ - public static PDDocument load(InputStream input, String password, boolean useScratchFiles) throws IOException - { - return load(input, password, null, null, useScratchFiles); + return load(input, password, keyStore, alias, MemoryUsageSetting.setupMainMemoryOnly()); } /** - * Parses a PDF. Depending on the parameter useScratchFiles the given input - * stream is either copied to the memory or to a temporary file to enable + * Parses a PDF. Depending on the memory settings parameter the given input + * stream is either copied to main memory or to a temporary file to enable * random access to the pdf. * * @param input stream that contains the document. @@ -1048,83 +944,83 @@ public static PDDocument load(InputStream input, String password, } /** - * Parses a PDF. Depending on the parameter useScratchFiles the given input - * stream is either copied to the memory or to a temporary file to enable + * Parses a PDF. Depending on the memory settings parameter the given input + * stream is either copied to main memory or to a temporary file to enable * random access to the pdf. * * @param input stream that contains the document. * @param password password to be used for decryption * @param keyStore key store to be used for decryption when using public key security * @param alias alias to be used for decryption when using public key security - * @param useScratchFiles enables the usage of a scratch file if set to true + * @param memUsageSetting defines how memory is used for buffering input stream and PDF streams * * @return loaded document * * @throws IOException in case of a file reading or parsing error */ public static PDDocument load(InputStream input, String password, InputStream keyStore, - String alias, boolean useScratchFiles) throws IOException + String alias, MemoryUsageSetting memUsageSetting) throws IOException { - RandomAccessRead source; - if (useScratchFiles) + ScratchFile scratchFile = new ScratchFile(memUsageSetting); + try { - source = new RandomAccessBufferedFileInputStream(input); + RandomAccessRead source = scratchFile.createBuffer(input); + PDFParser parser = new PDFParser(source, password, keyStore, alias, scratchFile); + parser.parse(); + return parser.getPDDocument(); } - else + catch (IOException ioe) { - source = new RandomAccessBuffer(input); + IOUtils.closeQuietly(scratchFile); + throw ioe; } - PDFParser parser = new PDFParser(source, password, keyStore, alias, useScratchFiles); - parser.parse(); - return parser.getPDDocument(); } /** - * Parses a PDF. Depending on the parameter useScratchFiles the given input - * stream is either copied to the memory or to a temporary file to enable - * random access to the pdf. + * Parses a PDF. Unrestricted main memory will be used for buffering PDF streams. + * + * @param input byte array that contains the document. * - * @param input stream that contains the document. - * @param password password to be used for decryption - * @param keyStore key store to be used for decryption when using public key security - * @param alias alias to be used for decryption when using public key security - * @param memUsageSetting defines how memory is used for buffering input stream and PDF streams * @return loaded document + * * @throws IOException in case of a file reading or parsing error */ - public static PDDocument load(InputStream input, String password, InputStream keyStore, - String alias, MemoryUsageSetting memUsageSetting) throws IOException + public static PDDocument load(byte[] input) throws IOException { - ScratchFile scratchFile = new ScratchFile(memUsageSetting); - RandomAccessRead source = scratchFile.createBuffer(input); - PDFParser parser = new PDFParser(source, password, keyStore, alias, scratchFile); - parser.parse(); - return parser.getPDDocument(); + return load(input, ""); } /** - * Parses a PDF. + * Parses a PDF. Unrestricted main memory will be used for buffering PDF streams. * * @param input byte array that contains the 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 load(byte[] input) throws IOException + public static PDDocument load(byte[] input, String password) throws IOException { - return load(input, ""); + return load(input, password, null, null); } /** - * Parses a PDF. + * Parses a PDF. Unrestricted main memory will be used for buffering PDF streams. * * @param input byte array that contains the document. * @param password password to be used for decryption + * @param keyStore key store to be used for decryption when using public key security + * @param alias alias to be used for decryption when using public key security + * * @return loaded document + * * @throws IOException in case of a file reading or parsing error */ - public static PDDocument load(byte[] input, String password) throws IOException + public static PDDocument load(byte[] input, String password, InputStream keyStore, + String alias) throws IOException { - return load(input, password, null, null); + return load(input, password, keyStore, alias, MemoryUsageSetting.setupMainMemoryOnly()); } /** @@ -1134,14 +1030,17 @@ public static PDDocument load(byte[] input, String password) throws IOException * @param password password to be used for decryption * @param keyStore key store to be used for decryption when using public key security * @param alias alias to be used for decryption when using public key security + * @param memUsageSetting defines how memory is used for buffering input stream and PDF streams + * * @return loaded document * @throws IOException in case of a file reading or parsing error */ - public static PDDocument load(byte[] input, String password, InputStream keyStore, - String alias) throws IOException + public static PDDocument load(byte[] input, String password, InputStream keyStore, String alias, + MemoryUsageSetting memUsageSetting) throws IOException { + ScratchFile scratchFile = new ScratchFile(memUsageSetting); RandomAccessRead source = new RandomAccessBuffer(input); - PDFParser parser = new PDFParser(source, password, keyStore, alias, false); + PDFParser parser = new PDFParser(source, password, keyStore, alias, scratchFile); parser.parse(); return parser.getPDDocument(); } @@ -1167,7 +1066,7 @@ public void save(String fileName) throws IOException */ public void save(File file) throws IOException { - save(new FileOutputStream(file)); + save(new BufferedOutputStream(new FileOutputStream(file))); } /** @@ -1183,6 +1082,7 @@ public void save(OutputStream output) throws IOException { throw new IOException("Cannot save a document which has been closed"); } + // subset designated fonts for (PDFont font : fontsToSubset) { @@ -1195,7 +1095,6 @@ public void save(OutputStream output) throws IOException try { writer.write(this); - writer.close(); } finally { @@ -1206,19 +1105,16 @@ public void save(OutputStream output) throws IOException /** * Save the PDF as an incremental update. This is only possible if the PDF was loaded from a file. * - * Use of this method is discouraged, use {@link #saveIncremental(OutputStream)} instead. - * * @param output stream to write * @throws IOException if the output could not be written * @throws IllegalStateException if the document was not loaded from a file. */ public void saveIncremental(OutputStream output) throws IOException { - InputStream input = new RandomAccessInputStream(pdfSource); COSWriter writer = null; try { - writer = new COSWriter(output, input); + writer = new COSWriter(output, pdfSource); writer.write(this, signInterface); writer.close(); } @@ -1242,8 +1138,13 @@ public PDPage getPage(int pageIndex) // todo: REPLACE most calls to this method return getDocumentCatalog().getPages().get(pageIndex); } - // TODO new - public PDPageTree getPages() { + /** + * Returns the page tree. + * + * @return the page tree + */ + public PDPageTree getPages() + { return getDocumentCatalog().getPages(); } @@ -1324,7 +1225,8 @@ public void protect(ProtectionPolicy policy) throws IOException */ public AccessPermission getCurrentAccessPermission() { - if(accessPermission == null) { + if (accessPermission == null) + { accessPermission = AccessPermission.getOwnerAccessPermission(); } return accessPermission; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentCatalog.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentCatalog.java index 9f884d6c4..9a0dcd7fc 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentCatalog.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentCatalog.java @@ -1,10 +1,10 @@ /* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -39,6 +39,8 @@ import com.tom_roush.pdfbox.pdmodel.interactive.action.PDDocumentCatalogAdditionalActions; import com.tom_roush.pdfbox.pdmodel.interactive.action.PDURIDictionary; import com.tom_roush.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination; +import com.tom_roush.pdfbox.pdmodel.interactive.documentnavigation.destination.PDNamedDestination; +import com.tom_roush.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination; import com.tom_roush.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline; import com.tom_roush.pdfbox.pdmodel.interactive.form.PDAcroForm; import com.tom_roush.pdfbox.pdmodel.interactive.pagenavigation.PDThread; @@ -56,7 +58,7 @@ public class PDDocumentCatalog implements COSObjectable private PDAcroForm cachedAcroForm; /** - * Constructor. Acroform. + * Constructor. AcroForm. * * @param doc The document that this catalog is part of. */ @@ -85,6 +87,7 @@ public PDDocumentCatalog(PDDocument doc, COSDictionary rootDictionary) * * @return The cos object that matches this Java object. */ + @Override public COSDictionary getCOSObject() { return root; @@ -93,7 +96,7 @@ public COSDictionary getCOSObject() /** * Get the documents AcroForm. This will return null if no AcroForm is part of the document. * - * @return The documents acroform. + * @return The document's AcroForm. */ public PDAcroForm getAcroForm() { @@ -106,9 +109,9 @@ public PDAcroForm getAcroForm() } /** - * Sets the Acroform for this catalog. + * Sets the AcroForm for this catalog. * - * @param acroForm The new Acroform. + * @param acroForm The new AcroForm. */ public void setAcroForm(PDAcroForm acroForm) { @@ -197,7 +200,7 @@ public void setThreads(List threads) } /** - * Get the metadata that is part of the document catalog. This will return null if there is no + * 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. @@ -213,7 +216,7 @@ public PDMetadata getMetadata() } /** - * Sets the metadata for this object. This can be null. + * Sets the metadata for this object. This can be null. * * @param meta The meta data for this object. */ @@ -236,7 +239,6 @@ public void setOpenAction(PDDestinationOrAction action) * Get the Document Open Action for this object. * * @return The action to perform when the document is opened. - * * @throws IOException If there is an error creating the destination or action. */ public PDDestinationOrAction getOpenAction() throws IOException @@ -307,6 +309,40 @@ public PDDocumentNameDestinationDictionary getDests() return nameDic; } + /** + * Find the page destination from a named destination. + * + * @param namedDest the named destination. + * + * @return a PDPageDestination object or null if not found. + * @throws IOException if there is an error creating the PDPageDestination object. + */ + public PDPageDestination findNamedDestinationPage(PDNamedDestination namedDest) + throws IOException + { + PDPageDestination pageDestination = null; + PDDocumentNameDictionary namesDict = getNames(); + if (namesDict != null) + { + PDDestinationNameTreeNode destsTree = namesDict.getDests(); + if (destsTree != null) + { + pageDestination = destsTree.getValue(namedDest.getNamedDestination()); + } + } + if (pageDestination == null) + { + // Look up /Dests dictionary from catalog + PDDocumentNameDestinationDictionary nameDestDict = getDests(); + if (nameDestDict != null) + { + String name = namedDest.getNamedDestination(); + pageDestination = (PDPageDestination)nameDestDict.getDestination(name); + } + } + return pageDestination; + } + /** * Sets the names dictionary for the document. * @@ -318,7 +354,7 @@ public void setNames(PDDocumentNameDictionary names) } /** - * Get info about doc's usage of tagged features. This will return null if there is no + * Get info about doc's usage of tagged features. This will return null if there is no * information. * * @return The new mark info. @@ -330,7 +366,7 @@ public PDMarkInfo getMarkInfo() } /** - * Sets information about the doc's usage of tagged features. + * Set information about the doc's usage of tagged features. * * @param markInfo The new MarkInfo data. */ @@ -458,9 +494,9 @@ public PDURIDictionary getURI() } /** - * Sets the document level uri. + * Sets the document level URI. * - * @param uri The new document level uri. + * @param uri The new document level URI. */ public void setURI(PDURIDictionary uri) { @@ -550,7 +586,6 @@ public void setPageLabels(PDPageLabels labels) * Get the optional content properties dictionary associated with this document. * * @return the optional properties dictionary or null if it is not present - * @since PDF 1.5 */ public PDOptionalContentProperties getOCProperties() { @@ -562,7 +597,6 @@ public PDOptionalContentProperties getOCProperties() * Sets the optional content properties dictionary. * * @param ocProperties the optional properties dictionary - * @since PDF 1.5 */ public void setOCProperties(PDOptionalContentProperties ocProperties) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentInformation.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentInformation.java index 3d62ec755..86ea6c6ee 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentInformation.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentInformation.java @@ -30,11 +30,11 @@ * method then it will clear the value. * * @author Ben Litchfield - * @author Gerardo Ortiz + * @author Gerardo Ortiz */ -public final class PDDocumentInformation implements COSObjectable +public class PDDocumentInformation implements COSObjectable { - private COSDictionary info; + private final COSDictionary info; /** * Default Constructor. @@ -77,7 +77,7 @@ public COSDictionary getCOSObject() */ public Object getPropertyStringValue(String propertyKey) { - return info.getString(propertyKey); + return info.getString(propertyKey); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentNameDestinationDictionary.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentNameDestinationDictionary.java index 6483f4d31..a0b884ac3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentNameDestinationDictionary.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDDocumentNameDestinationDictionary.java @@ -17,14 +17,16 @@ import java.io.IOException; +import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; import com.tom_roush.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination; /** - * This encapsulates the "dictionary of names and corresponding destinations" for the /Dest entry in - * the document catalog. + * This encapsulates the "dictionary of names and corresponding destinations" for the /Dest entry + * in the document catalog. * * @author Tilman Hausherr */ @@ -63,6 +65,22 @@ public COSDictionary getCOSObject() public PDDestination getDestination(String name) throws IOException { COSBase item = nameDictionary.getDictionaryObject(name); - return PDDestination.create(item); + + // "The value of this entry shall be a dictionary in which each key is a destination name + // and the corresponding value is either an array defining the destination (...) + // or a dictionary with a D entry whose value is such an array." + if (item instanceof COSArray) + { + return PDDestination.create(item); + } + else if (item instanceof COSDictionary) + { + COSDictionary dict = (COSDictionary)item; + if (dict.containsKey(COSName.D)) + { + return PDDestination.create(dict.getDictionaryObject(COSName.D)); + } + } + return null; } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPage.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPage.java index 8bd6e1a76..373c7980a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPage.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPage.java @@ -126,7 +126,7 @@ public Iterator getContentStreams() else if (base instanceof COSArray && ((COSArray) base).size() > 0) { COSArray array = (COSArray) base; - for (int i = 0; i < streams.size(); i++) + for (int i = 0; i < array.size(); i++) { COSStream stream = (COSStream) array.getObject(i); streams.add(new PDStream(stream)); @@ -141,7 +141,7 @@ public InputStream getContents() throws IOException COSBase base = page.getDictionaryObject(COSName.CONTENTS); if (base instanceof COSStream) { - return ((COSStream) base).createInputStream(); + return ((COSStream)base).createInputStream(); } else if (base instanceof COSArray && ((COSArray)base).size() > 0) { @@ -176,7 +176,6 @@ else if (contents instanceof COSArray) return false; } - /** * A dictionary containing any resources required by the page. */ @@ -225,12 +224,6 @@ public int getStructParents() return page.getInt(COSName.STRUCT_PARENTS, 0); } - @Override - public PDRectangle getBBox() - { - return getCropBox(); - } - /** * This will set the key for this page in the structural parent tree. * @@ -241,6 +234,19 @@ public void setStructParents(int structParents) page.setInt(COSName.STRUCT_PARENTS, structParents); } + @Override + public PDRectangle getBBox() + { + return getCropBox(); + } + + @Override + public Matrix getMatrix() + { + // todo: take into account user-space unit redefinition as scale? + return new Matrix(); + } + /** * A rectangle, expressed in default user space units, defining the boundaries of the physical * medium on which the page is intended to be displayed or printed. @@ -277,7 +283,7 @@ public void setMediaBox(PDRectangle mediaBox) } else { - page.setItem(COSName.MEDIA_BOX, mediaBox.getCOSArray()); + page.setItem(COSName.MEDIA_BOX, mediaBox); } } @@ -299,12 +305,6 @@ public PDRectangle getCropBox() } } - @Override - public Matrix getMatrix() { - // todo: take into account user-space unit redefinition as scale? - return new Matrix(); - } - /** * This will set the CropBox for this page. * @@ -335,11 +335,11 @@ public PDRectangle getBleedBox() COSArray array = (COSArray) page.getDictionaryObject(COSName.BLEED_BOX); if (array != null) { - retval = new PDRectangle(array); + retval = clipToMediaBox(new PDRectangle(array)); } else { - retval = clipToMediaBox(new PDRectangle(array)); + retval = getCropBox(); } return retval; } @@ -512,7 +512,7 @@ public void setContents(List contents) /** * This will get a list of PDThreadBead objects, which are article threads in the document. - * This will return an empty list of there are no thread beads. + * This will return an empty list if there are no thread beads. * * @return A list of article threads on this page. */ @@ -550,8 +550,8 @@ public void setThreadBeads(List beads) } /** - * 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. */ @@ -651,7 +651,6 @@ public List getAnnotations() throws IOException else { List actuals = new ArrayList(); - for (int i = 0; i < annots.size(); i++) { COSBase item = annots.getObject(i); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPageContentStream.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPageContentStream.java index 467a8e2f3..8d388c6a6 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPageContentStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPageContentStream.java @@ -57,29 +57,58 @@ */ public final class PDPageContentStream implements Closeable { - // instance variables - private final PDDocument document; - private OutputStream output; - private PDResources resources; - private boolean inTextMode = false; - private final Stack fontStack = new Stack(); - - private final Stack nonStrokingColorSpaceStack = new Stack(); - private Stack strokingColorSpaceStack = new Stack(); - - // number format - private final NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US); - - /** - * Create a new PDPage content stream. - * - * @param document The document the page is part of. - * @param sourcePage The page to write the contents to. - * @throws IOException If there is an error writing to the page contents. + /** + * This is to choose what to do with the stream: overwrite, append or prepend. + */ + public static enum AppendMode + { + /** + * Overwrite the existing page content streams. + */ + OVERWRITE, + /** + * Append the content stream after all existing page content streams. + */ + APPEND, + /** + * Insert before all other page content streams. + */ + PREPEND; + + public boolean isOverwrite() + { + return this == OVERWRITE; + } + + public boolean isPrepend() + { + return this == PREPEND; + } + } + + private final PDDocument document; + private OutputStream output; + private PDResources resources; + + private boolean inTextMode = false; + private final Stack fontStack = new Stack(); + + private final Stack nonStrokingColorSpaceStack = new Stack(); + private final Stack strokingColorSpaceStack = new Stack(); + + // number format + private final NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US); + + /** + * Create a new PDPage content stream. + * + * @param document The document the page is part of. + * @param sourcePage The page to write the contents to. + * @throws IOException If there is an error writing to the page contents. */ public PDPageContentStream(PDDocument document, PDPage sourcePage) throws IOException { - this(document, sourcePage, false, true); + this(document, sourcePage, AppendMode.OVERWRITE, true, false); } /** @@ -91,13 +120,31 @@ public PDPageContentStream(PDDocument document, PDPage sourcePage) throws IOExce * content is deleted. * @param compress Tell if the content stream should compress the page contents. * @throws IOException If there is an error writing to the page contents. + * @deprecated use {@link #PDPageContentStream(PDDocument, PDPage, PDPageContentStream.AppendMode, boolean)} */ + @Deprecated public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appendContent, boolean compress) throws IOException { this(document, sourcePage, appendContent, compress, false); } + /** + * Create a new PDPage content stream. + * + * @param document The document the page is part of. + * @param sourcePage The page to write the contents to. + * @param appendContent Indicates whether content will be overwritten, appended or prepended. + * @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, AppendMode appendContent, + boolean compress) throws IOException + { + this(document, sourcePage, appendContent, compress, false); + } + /** * Create a new PDPage content stream. * @@ -106,17 +153,42 @@ public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appen * @param appendContent Indicates whether content will be overwritten. If false all previous * content is deleted. * @param compress Tell if the content stream should compress the page contents. - * @param resetContext Tell if the graphic context should be reseted. + * @param resetContext Tell if the graphic context should be reset. You should use this when + * appending to an existing stream, because the existing stream may have changed graphic + * properties (e.g. scaling, rotation). * @throws IOException If there is an error writing to the page contents. + * @deprecated use {@link #PDPageContentStream(PDDocument, PDPage, PDPageContentStream.AppendMode, boolean, boolean) } */ + @Deprecated public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appendContent, boolean compress, boolean resetContext) throws IOException { - this.document = document; + this(document, sourcePage, appendContent ? AppendMode.APPEND : AppendMode.OVERWRITE, + compress, resetContext); + } + + /** + * Create a new PDPage content stream. + * + * @param document The document the page is part of. + * @param sourcePage The page to write the contents to. + * @param appendContent Indicates whether content will be overwritten, appended or prepended. + * @param compress Tell if the content stream should compress the page contents. + * @param resetContext Tell if the graphic context should be reset. This is only relevant when + * the appendContent parameter is set to {@link AppendMode#APPEND}. You should use this when + * appending to an existing stream, because the existing stream may have changed graphic + * properties (e.g. scaling, rotation). + * + * @throws IOException If there is an error writing to the page contents. + */ + public PDPageContentStream(PDDocument document, PDPage sourcePage, AppendMode appendContent, + boolean compress, boolean resetContext) throws IOException + { + this.document = document; COSName filter = compress ? COSName.FLATE_DECODE : null; - // If request specifies the need to append to the document - if (appendContent && sourcePage.hasContents()) + // If request specifies the need to append/prepend to the document + if (!appendContent.isOverwrite() && sourcePage.hasContents()) { // Create a stream to append new content PDStream contentsToAppend = new PDStream(document); @@ -128,13 +200,19 @@ public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appen { // If contents is already an array, a new stream is simply appended to it array = (COSArray) contents; - array.add(contentsToAppend); } else { // Creates a new array and adds the current stream plus a new one to it array = new COSArray(); array.add(contents); + } + if (appendContent.isPrepend()) + { + array.add(0, contentsToAppend.getCOSObject()); + } + else + { array.add(contentsToAppend); } @@ -150,7 +228,7 @@ public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appen close(); // insert the new stream at the beginning - array.add(0, saveGraphics.getStream()); + array.add(0, saveGraphics.getCOSObject()); } // Sets the compoundStream as page contents @@ -167,12 +245,14 @@ public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appen { if (sourcePage.hasContents()) { - Log.w("PdfBox-Android", "You are overwriting an existing content, you should use the append mode"); + Log.w("PdfBox-Android", + "You are overwriting an existing content, you should use the append mode"); } PDStream contents = new PDStream(document); sourcePage.setContents(contents); output = contents.createOutputStream(filter); } + // this has to be done here, as the resources will be set to null when resetting the content // stream resources = sourcePage.getResources(); @@ -196,20 +276,14 @@ public PDPageContentStream(PDDocument document, PDPage sourcePage, boolean appen */ public PDPageContentStream(PDDocument doc, PDAppearanceStream appearance) throws IOException { - this.document = doc; - - output = appearance.getStream().createOutputStream(); - this.resources = appearance.getResources(); - - formatDecimal.setMaximumFractionDigits(4); - formatDecimal.setGroupingUsed(false); + this(doc, appearance, appearance.getStream().createOutputStream()); } /** * Create a new appearance stream. Note that this is not actually a "page" content stream. * * @param doc The document the appearance is part of. - * @param doc The document the appearance is part of. + * @param appearance The appearance stream to add to. * @param outputStream The appearances output stream to write to. * @throws IOException If there is an error writing to the page contents. */ @@ -260,7 +334,7 @@ public void endText() throws IOException } /** - * Set the font to draw text with. + * Set the font and font size to draw text with. * * @param font The font to use. * @param fontSize The font size to draw the text. @@ -268,25 +342,25 @@ public void endText() throws IOException */ public void setFont(PDFont font, float fontSize) throws IOException { - if (fontStack.isEmpty()) - { - fontStack.add(font); - } - else - { - fontStack.setElementAt(font, fontStack.size() - 1); - } + if (fontStack.isEmpty()) + { + fontStack.add(font); + } + else + { + fontStack.setElementAt(font, fontStack.size() - 1); + } - if (font.willBeSubset() && !document.getFontsToSubset().contains(font)) - { - document.getFontsToSubset().add(font); - } + if (font.willBeSubset()) + { + document.getFontsToSubset().add(font); + } - writeOperand(resources.add(font)); - writeOperand(fontSize); - writeOperator("Tf"); + writeOperand(resources.add(font)); + writeOperand(fontSize); + writeOperator("Tf"); } - + /** * This will draw a string at the current location on the screen. * @@ -329,15 +403,14 @@ public void showText(String text) throws IOException font.addToSubset(codePoint); offset += Character.charCount(codePoint); } - } COSWriter.writeString(font.encode(text), output); write(" "); - + writeOperator("Tj"); - } - + } + /** * Sets the text leading. * @@ -346,22 +419,23 @@ public void showText(String text) throws IOException */ public void setLeading(double leading) throws IOException { - writeOperand((float) leading); - writeOperator("TL"); + writeOperand((float)leading); + writeOperator("TL"); } /** - * Move to the start of the next line of text. Requires the leading to have been set. + * Move to the start of the next line of text. Requires the leading (see {@link #setLeading}) + * to have been set. * * @throws IOException If there is an error writing to the stream. */ public void newLine() throws IOException { - if (!inTextMode) - { - throw new IllegalStateException("Must call beginText() before newLine()"); - } - writeOperator("T*"); + if (!inTextMode) + { + throw new IllegalStateException("Must call beginText() before newLine()"); + } + writeOperator("T*"); } /** @@ -375,7 +449,7 @@ public void newLine() throws IOException @Deprecated public void moveTextPositionByAmount(float tx, float ty) throws IOException { - newLineAtOffset(tx, ty); + newLineAtOffset(tx, ty); } /** @@ -413,22 +487,22 @@ public void newLineAtOffset(float tx, float ty) throws IOException @Deprecated public void setTextMatrix(double a, double b, double c, double d, double e, double f) throws IOException { - setTextMatrix(new Matrix((float) a, (float) b, (float) c, (float) d, (float) e, (float) f)); + setTextMatrix(new Matrix((float)a, (float)b, (float)c, (float)d, (float)e, (float)f)); } /** - * The Tm operator. Sets the text matrix to the given values. - * A current text matrix will be replaced with the new one. - * @param matrix the transformation matrix - * @throws IOException If there is an error writing to the stream. - * @deprecated Use {@link #setTextMatrix(Matrix)} instead. - */ + * The Tm operator. Sets the text matrix to the given values. + * A current text matrix will be replaced with the new one. + * @param matrix the transformation matrix + * @throws IOException If there is an error writing to the stream. + * @deprecated Use {@link #setTextMatrix(Matrix)} instead. + */ @Deprecated public void setTextMatrix(AffineTransform matrix) throws IOException { - setTextMatrix(new Matrix(matrix)); + setTextMatrix(new Matrix(matrix)); } - + /** * The Tm operator. Sets the text matrix to the given values. * A current text matrix will be replaced with the new one. @@ -439,12 +513,12 @@ public void setTextMatrix(AffineTransform matrix) throws IOException */ public void setTextMatrix(Matrix matrix) throws IOException { - if (!inTextMode) - { - throw new IllegalStateException("Error: must call beginText() before setTextMatrix"); - } - writeAffineTransform(matrix.createAffineTransform()); - writeOperator("Tm"); + if (!inTextMode) + { + throw new IllegalStateException("Error: must call beginText() before setTextMatrix"); + } + writeAffineTransform(matrix.createAffineTransform()); + writeOperator("Tm"); } /** @@ -460,7 +534,7 @@ public void setTextMatrix(Matrix matrix) throws IOException @Deprecated public void setTextScaling(double sx, double sy, double tx, double ty) throws IOException { - setTextMatrix(new Matrix((float) sx, 0f, 0f, (float) sy, (float) tx, (float) ty)); + setTextMatrix(new Matrix((float)sx, 0f, 0f, (float)sy, (float)tx, (float)ty)); } /** @@ -474,9 +548,9 @@ public void setTextScaling(double sx, double sy, double tx, double ty) throws IO @Deprecated public void setTextTranslation(double tx, double ty) throws IOException { - setTextMatrix(Matrix.getTranslateInstance((float) tx, (float) ty)); + setTextMatrix(Matrix.getTranslateInstance((float)tx, (float)ty)); } - + /** * The Tm operator. Sets the text matrix to the given rotation and translation values. * A current text matrix will be replaced with the new one. @@ -489,9 +563,9 @@ public void setTextTranslation(double tx, double ty) throws IOException @Deprecated public void setTextRotation(double angle, double tx, double ty) throws IOException { - setTextMatrix(Matrix.getRotateInstance(angle, (float) tx, (float) ty)); + setTextMatrix(Matrix.getRotateInstance(angle, (float)tx, (float)ty)); } - + /** * Draw an image at the x,y coordinates, with the default size of the image. * @@ -503,9 +577,9 @@ public void setTextRotation(double angle, double tx, double ty) throws IOExcepti */ public void drawImage(PDImageXObject image, float x, float y) throws IOException { - drawImage(image, x, y, image.getWidth(), image.getHeight()); + drawImage(image, x, y, image.getWidth(), image.getHeight()); } - + /** * Draw an image at the x,y coordinates, with the given size. * @@ -520,18 +594,20 @@ public void drawImage(PDImageXObject image, float x, float y) throws IOException */ public void drawImage(PDImageXObject image, float x, float y, float width, float height) throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: drawImage is not allowed within a text block."); - } - saveGraphicsState(); + if (inTextMode) + { + throw new IllegalStateException("Error: drawImage is not allowed within a text block."); + } - AffineTransform transform = new AffineTransform(width, 0, 0, height, x, y); - transform(new Matrix(transform)); - writeOperand(resources.add(image)); - writeOperator("Do"); - - restoreGraphicsState(); + saveGraphicsState(); + + AffineTransform transform = new AffineTransform(width, 0, 0, height, x, y); + transform(new Matrix(transform)); + + writeOperand(resources.add(image)); + writeOperator("Do"); + + restoreGraphicsState(); } /** @@ -668,8 +744,8 @@ public void drawImage(PDInlineImage inlineImage, float x, float y, float width, @Deprecated 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); + AffineTransform transform = new AffineTransform(width, 0, 0, height, x, y); + drawXObject(xobject, transform); } /** @@ -685,27 +761,29 @@ public void drawXObject(PDXObject xobject, float x, float y, float width, float @Deprecated public void drawXObject(PDXObject xobject, AffineTransform transform) throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: drawXObject is not allowed within a text block."); - } - String xObjectPrefix; - if (xobject instanceof PDImageXObject) - { - xObjectPrefix = "Im"; - } - else - { - xObjectPrefix = "Form"; - } - COSName objMapping = resources.add(xobject, xObjectPrefix); - - saveGraphicsState(); - transform(new Matrix(transform)); - writeOperand(objMapping); - writeOperator("Do"); - - restoreGraphicsState(); + if (inTextMode) + { + throw new IllegalStateException( + "Error: drawXObject is not allowed within a text block."); + } + String xObjectPrefix; + if (xobject instanceof PDImageXObject) + { + xObjectPrefix = "Im"; + } + else + { + xObjectPrefix = "Form"; + } + COSName objMapping = resources.add(xobject, xObjectPrefix); + + saveGraphicsState(); + transform(new Matrix(transform)); + + writeOperand(objMapping); + writeOperator("Do"); + + restoreGraphicsState(); } /** @@ -717,12 +795,13 @@ public void drawXObject(PDXObject xobject, AffineTransform transform) throws IOE */ public void drawForm(PDFormXObject form) throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: drawForm is not allowed within a text block."); - } - writeOperand(resources.add(form)); - writeOperator("Do"); + if (inTextMode) + { + throw new IllegalStateException("Error: drawForm is not allowed within a text block."); + } + + writeOperand(resources.add(form)); + writeOperator("Do"); } /** @@ -739,7 +818,7 @@ public void drawForm(PDFormXObject form) throws IOException @Deprecated public void concatenate2CTM(double a, double b, double c, double d, double e, double f) throws IOException { - transform(new Matrix((float) a, (float) b, (float) c, (float) d, (float) e, (float) f)); + transform(new Matrix((float)a, (float)b, (float)c, (float)d, (float)e, (float)f)); } /** @@ -752,9 +831,9 @@ public void concatenate2CTM(double a, double b, double c, double d, double e, do @Deprecated public void concatenate2CTM(AffineTransform at) throws IOException { - transform(new Matrix(at)); + transform(new Matrix(at)); } - + /** * The cm operator. Concatenates the given matrix with the CTM. * @@ -763,29 +842,29 @@ public void concatenate2CTM(AffineTransform at) throws IOException */ public void transform(Matrix matrix) throws IOException { - writeAffineTransform(matrix.createAffineTransform()); - writeOperator("cm"); + writeAffineTransform(matrix.createAffineTransform()); + writeOperator("cm"); } - + /** * q operator. Saves the current graphics state. * @throws IOException If an error occurs while writing to the stream. */ public void saveGraphicsState() throws IOException { - if (!fontStack.isEmpty()) - { - fontStack.push(fontStack.peek()); - } - if (!strokingColorSpaceStack.isEmpty()) - { - strokingColorSpaceStack.push(strokingColorSpaceStack.peek()); - } - if (!nonStrokingColorSpaceStack.isEmpty()) - { - nonStrokingColorSpaceStack.push(nonStrokingColorSpaceStack.peek()); - } - writeOperator("q"); + if (!fontStack.isEmpty()) + { + fontStack.push(fontStack.peek()); + } + if (!strokingColorSpaceStack.isEmpty()) + { + strokingColorSpaceStack.push(strokingColorSpaceStack.peek()); + } + if (!nonStrokingColorSpaceStack.isEmpty()) + { + nonStrokingColorSpaceStack.push(nonStrokingColorSpaceStack.peek()); + } + writeOperator("q"); } /** @@ -794,19 +873,19 @@ public void saveGraphicsState() throws IOException */ public void restoreGraphicsState() throws IOException { - if (!fontStack.isEmpty()) - { - fontStack.pop(); - } - if (!strokingColorSpaceStack.isEmpty()) - { - strokingColorSpaceStack.pop(); - } - if (!nonStrokingColorSpaceStack.isEmpty()) - { - nonStrokingColorSpaceStack.pop(); - } - writeOperator("Q"); + if (!fontStack.isEmpty()) + { + fontStack.pop(); + } + if (!strokingColorSpaceStack.isEmpty()) + { + strokingColorSpaceStack.pop(); + } + if (!nonStrokingColorSpaceStack.isEmpty()) + { + nonStrokingColorSpaceStack.pop(); + } + writeOperator("Q"); } /** @@ -820,17 +899,9 @@ public void restoreGraphicsState() throws IOException @Deprecated public void setStrokingColorSpace(PDColorSpace colorSpace) throws IOException { - if (strokingColorSpaceStack.isEmpty()) - { - strokingColorSpaceStack.add(colorSpace); - } - else - { - strokingColorSpaceStack.setElementAt(colorSpace, nonStrokingColorSpaceStack.size() - 1); - } - - writeOperand(getName(colorSpace)); - writeOperator("CS"); + setStrokingColorSpaceStack(colorSpace); + writeOperand(getName(colorSpace)); + writeOperator("CS"); } /** @@ -844,32 +915,23 @@ public void setStrokingColorSpace(PDColorSpace colorSpace) throws IOException @Deprecated public void setNonStrokingColorSpace(PDColorSpace colorSpace) throws IOException { - if (nonStrokingColorSpaceStack.isEmpty()) - { - nonStrokingColorSpaceStack.add(colorSpace); - } - else - { - nonStrokingColorSpaceStack.setElementAt(colorSpace, nonStrokingColorSpaceStack.size() - 1); - } + setNonStrokingColorSpaceStack(colorSpace); - writeOperand(getName(colorSpace)); - writeOperator("cs"); + writeOperand(getName(colorSpace)); + writeOperator("cs"); } private COSName getName(PDColorSpace colorSpace) throws IOException { - COSName key; - if (colorSpace instanceof PDDeviceGray || - colorSpace instanceof PDDeviceRGB /*|| TODO: PdfBox-Android - colorSpace instanceof PDDeviceCMYK*/) - { - return COSName.getPDFName(colorSpace.getName()); - } - else - { - return resources.add(colorSpace); - } + if (colorSpace instanceof PDDeviceGray || colorSpace instanceof PDDeviceRGB /*|| TODO: PdfBox-Android + colorSpace instanceof PDDeviceCMYK*/) + { + return COSName.getPDFName(colorSpace.getName()); + } + else + { + return resources.add(colorSpace); + } } /** @@ -880,27 +942,18 @@ private COSName getName(PDColorSpace colorSpace) throws IOException */ public void setStrokingColor(PDColor color) throws IOException { - if (strokingColorSpaceStack.isEmpty() || - strokingColorSpaceStack.peek() != color.getColorSpace()) + if (strokingColorSpaceStack.isEmpty() || + strokingColorSpaceStack.peek() != color.getColorSpace()) { - writeOperand(getName(color.getColorSpace())); - writeOperator("CS"); - - if (strokingColorSpaceStack.isEmpty()) - { - strokingColorSpaceStack.add(color.getColorSpace()); - } - else - { - strokingColorSpaceStack.setElementAt(color.getColorSpace(), - nonStrokingColorSpaceStack.size() - 1); - } + writeOperand(getName(color.getColorSpace())); + writeOperator("CS"); + setStrokingColorSpaceStack(color.getColorSpace()); } - for (float value : color.getComponents()) - { - writeOperand(value); - } + for (float value : color.getComponents()) + { + writeOperand(value); + } // if (color.getColorSpace() instanceof PDPattern) // { @@ -928,10 +981,10 @@ public void setStrokingColor(PDColor color) throws IOException */ public void setStrokingColor(AWTColor color) throws IOException { - float[] components = new float[] { - color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f }; - PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE); - setStrokingColor(pdColor); + float[] components = + new float[] { color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f }; + PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE); + setStrokingColor(pdColor); } /** @@ -944,17 +997,17 @@ public void setStrokingColor(AWTColor color) throws IOException @Deprecated public void setStrokingColor(float[] components) throws IOException { - if (strokingColorSpaceStack.isEmpty()) - { - throw new IllegalStateException("The color space must be set before setting a color"); - } + if (strokingColorSpaceStack.isEmpty()) + { + throw new IllegalStateException("The color space must be set before setting a color"); + } - for (int i = 0; i < components.length; i++) - { - writeOperand(components[i]); - } + for (int i = 0; i < components.length; i++) + { + writeOperand(components[i]); + } - PDColorSpace currentStrokingColorSpace = strokingColorSpaceStack.peek(); + PDColorSpace currentStrokingColorSpace = strokingColorSpaceStack.peek(); // if (currentStrokingColorSpace instanceof PDSeparation || // currentStrokingColorSpace instanceof PDPattern || @@ -964,7 +1017,7 @@ public void setStrokingColor(float[] components) throws IOException // } // else // { - writeOperator("SC"); + writeOperator("SC"); // } TODO: PdfBox-Android } @@ -988,6 +1041,7 @@ public void setStrokingColor(int r, int g, int b) throws IOException writeOperand(g / 255f); writeOperand(b / 255f); writeOperator("RG"); + setStrokingColorSpaceStack(PDDeviceRGB.INSTANCE); } /** @@ -1009,7 +1063,7 @@ public void setStrokingColor(int c, int m, int y, int k) throws IOException throw new IllegalArgumentException("Parameters must be within 0..255, but are " + String.format("(%d,%d,%d,%d)", c, m, y, k)); } - setStrokingColor(c / 255f, m / 255f, y / 255f, k / 255f); + setStrokingColor(c / 255f, m / 255f, y / 255f, k / 255f); } /** @@ -1034,6 +1088,7 @@ public void setStrokingColor(float c, float m, float y, float k) throws IOExcept writeOperand(y); writeOperand(k); writeOperator("K"); + // setStrokingColorSpaceStack(PDDeviceCMYK.INSTANCE); TODO: PdfBox-Android } /** @@ -1051,7 +1106,7 @@ public void setStrokingColor(int g) throws IOException { throw new IllegalArgumentException("Parameter must be within 0..255, but is " + g); } - setStrokingColor(g / 255f); + setStrokingColor(g / 255f); } /** @@ -1079,27 +1134,18 @@ public void setStrokingColor(double g) throws IOException */ public void setNonStrokingColor(PDColor color) throws IOException { - if (nonStrokingColorSpaceStack.isEmpty() || - nonStrokingColorSpaceStack.peek() != color.getColorSpace()) - { - writeOperand(getName(color.getColorSpace())); - writeOperator("cs"); - - if (nonStrokingColorSpaceStack.isEmpty()) - { - nonStrokingColorSpaceStack.add(color.getColorSpace()); - } - else - { - nonStrokingColorSpaceStack.setElementAt(color.getColorSpace(), - nonStrokingColorSpaceStack.size() - 1); - } - } - - for (float value : color.getComponents()) - { - writeOperand(value); - } + if (nonStrokingColorSpaceStack.isEmpty() || + nonStrokingColorSpaceStack.peek() != color.getColorSpace()) + { + writeOperand(getName(color.getColorSpace())); + writeOperator("cs"); + setNonStrokingColorSpaceStack(color.getColorSpace()); + } + + for (float value : color.getComponents()) + { + writeOperand(value); + } // if (color.getColorSpace() instanceof PDPattern) // { @@ -1115,7 +1161,7 @@ public void setNonStrokingColor(PDColor color) throws IOException // } // else // { - writeOperator("sc"); + writeOperator("sc"); // } TODO: PdfBox-Android } @@ -1127,10 +1173,10 @@ public void setNonStrokingColor(PDColor color) throws IOException */ public void setNonStrokingColor(AWTColor color) throws IOException { - float[] components = new float[] { - color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f }; - PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE); - setNonStrokingColor(pdColor); + float[] components = + new float[] { color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f }; + PDColor pdColor = new PDColor(components, PDDeviceRGB.INSTANCE); + setNonStrokingColor(pdColor); } /** @@ -1143,15 +1189,15 @@ public void setNonStrokingColor(AWTColor color) throws IOException @Deprecated public void setNonStrokingColor(float[] components) throws IOException { - if (nonStrokingColorSpaceStack.isEmpty()) - { - throw new IllegalStateException("The color space must be set before setting a color"); - } + if (nonStrokingColorSpaceStack.isEmpty()) + { + throw new IllegalStateException("The color space must be set before setting a color"); + } -// for (int i = 0; i < components.length; i++) -// { -// writeOperator(components[i]); -// } + for (int i = 0; i < components.length; i++) + { + writeOperand(components[i]); + } PDColorSpace currentNonStrokingColorSpace = nonStrokingColorSpaceStack.peek(); // if (currentNonStrokingColorSpace instanceof PDSeparation || @@ -1186,6 +1232,7 @@ public void setNonStrokingColor(int r, int g, int b) throws IOException writeOperand(g / 255f); writeOperand(b / 255f); writeOperator("rg"); + setNonStrokingColorSpaceStack(PDDeviceRGB.INSTANCE); } /** @@ -1205,7 +1252,7 @@ public void setNonStrokingColor(int c, int m, int y, int k) throws IOException throw new IllegalArgumentException("Parameters must be within 0..255, but are " + String.format("(%d,%d,%d,%d)", c, m, y, k)); } - setNonStrokingColor(c / 255f, m / 255f, y / 255f, k / 255f); + setNonStrokingColor(c / 255f, m / 255f, y / 255f, k / 255f); } /** @@ -1216,7 +1263,6 @@ public void setNonStrokingColor(int c, int m, int y, int k) throws IOException * @param y The yellow value. * @param k The black value. * @throws IOException If an IO error occurs while writing to the stream. - * @throws IllegalArgumentException If the parameters are invalid. */ public void setNonStrokingColor(double c, double m, double y, double k) throws IOException { @@ -1230,6 +1276,7 @@ public void setNonStrokingColor(double c, double m, double y, double k) throws I writeOperand((float) y); writeOperand((float) k); writeOperator("k"); + // setNonStrokingColorSpaceStack(PDDeviceCMYK.INSTANCE); TODO: PdfBox-Android } /** @@ -1245,7 +1292,7 @@ public void setNonStrokingColor(int g) throws IOException { throw new IllegalArgumentException("Parameter must be within 0..255, but is " + g); } - setNonStrokingColor(g / 255f); + setNonStrokingColor(g / 255f); } /** @@ -1263,6 +1310,7 @@ public void setNonStrokingColor(double g) throws IOException } writeOperand((float) g); writeOperator("g"); + setNonStrokingColorSpaceStack(PDDeviceGray.INSTANCE); } /** @@ -1325,7 +1373,7 @@ public void fillRect(float x, float y, float width, float height) throws IOExcep @Deprecated public void addBezier312(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException { - curveTo(x1, y1, x2, y2, x3, y3); + curveTo(x1, y1, x2, y2, x3, y3); } /** @@ -1343,17 +1391,17 @@ public void addBezier312(float x1, float y1, float x2, float y2, float x3, float */ public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: curveTo is not allowed within a text block."); - } - writeOperand(x1); - writeOperand(y1); - writeOperand(x2); - writeOperand(y2); - writeOperand(x3); - writeOperand(y3); - writeOperator("c"); + if (inTextMode) + { + throw new IllegalStateException("Error: curveTo is not allowed within a text block."); + } + writeOperand(x1); + writeOperand(y1); + writeOperand(x2); + writeOperand(y2); + writeOperand(x3); + writeOperand(y3); + writeOperator("c"); } /** @@ -1369,7 +1417,7 @@ public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) @Deprecated public void addBezier32(float x2, float y2, float x3, float y3) throws IOException { - curveTo2(x2, y2, x3, y3); + curveTo2(x2, y2, x3, y3); } /** @@ -1380,13 +1428,13 @@ public void addBezier32(float x2, float y2, float x3, float y3) throws IOExcepti * @param y2 y coordinate of the point 2 * @param x3 x coordinate of the point 3 * @param y3 y coordinate of the point 3 - * @throws IOException If the content stream could not be written. * @throws IllegalStateException If the method was called within a text block. + * @throws IOException If the content stream could not be written. */ public void curveTo2(float x2, float y2, float x3, float y3) throws IOException { - if (inTextMode) - { + if (inTextMode) + { throw new IllegalStateException("Error: curveTo2 is not allowed within a text block."); } writeOperand(x2); @@ -1409,7 +1457,7 @@ public void curveTo2(float x2, float y2, float x3, float y3) throws IOException @Deprecated public void addBezier31(float x1, float y1, float x3, float y3) throws IOException { - curveTo1(x1, y1, x3, y3); + curveTo1(x1, y1, x3, y3); } /** @@ -1425,9 +1473,9 @@ public void addBezier31(float x1, float y1, float x3, float y3) throws IOExcepti */ public void curveTo1(float x1, float y1, float x3, float y3) throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: curveTo1 is not allowed within a text block."); + if (inTextMode) + { + throw new IllegalStateException("Error: curveTo1 is not allowed within a text block."); } writeOperand(x1); writeOperand(y1); @@ -1634,31 +1682,31 @@ public void fill(Path.FillType windingRule) throws IOException { if (windingRule == Path.FillType.WINDING) { - fill(); + fill(); } else if (windingRule == Path.FillType.EVEN_ODD) { - fillEvenOdd(); + fillEvenOdd(); } else { throw new IllegalArgumentException("Error: unknown value for winding rule"); } } - + /** - * Fills the path using the nonzero winding rule. + * Fills the path using the nonzero winding number rule. * * @throws IOException If the content stream could not be written * @throws IllegalStateException If the method was called within a text block. */ public void fill() throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: fill is not allowed within a text block."); - } - writeOperator("f"); + if (inTextMode) + { + throw new IllegalStateException("Error: fill is not allowed within a text block."); + } + writeOperator("f"); } /** @@ -1669,11 +1717,84 @@ public void fill() throws IOException */ public void fillEvenOdd() throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: fill is not allowed within a text block."); - } - writeOperator("f*"); + if (inTextMode) + { + throw new IllegalStateException( + "Error: fillEvenOdd is not allowed within a text block."); + } + writeOperator("f*"); + } + + /** + * Fill and then stroke the path, using the nonzero winding number rule to determine the region + * to fill. This shall produce the same result as constructing two identical path objects, + * painting the first with {@link #fill() } and the second with {@link #stroke() }. + * + * @throws IOException If the content stream could not be written + * @throws IllegalStateException If the method was called within a text block. + */ + public void fillAndStroke() throws IOException + { + if (inTextMode) + { + throw new IllegalStateException( + "Error: fillAndStroke is not allowed within a text block."); + } + writeOperator("B"); + } + + /** + * Fill and then stroke the path, using the even-odd rule to determine the region to + * fill. This shall produce the same result as constructing two identical path objects, painting + * the first with {@link #fillEvenOdd() } and the second with {@link #stroke() }. + * + * @throws IOException If the content stream could not be written + * @throws IllegalStateException If the method was called within a text block. + */ + public void fillAndStrokeEvenOdd() throws IOException + { + if (inTextMode) + { + throw new IllegalStateException( + "Error: fillAndStrokeEvenOdd is not allowed within a text block."); + } + writeOperator("B*"); + } + + /** + * Close, fill, and then stroke the path, using the nonzero winding number rule to determine the + * region to fill. This shall have the same effect as the sequence {@link #closePath() } + * and then {@link #fillAndStroke() }. + * + * @throws IOException If the content stream could not be written + * @throws IllegalStateException If the method was called within a text block. + */ + public void closeAndFillAndStroke() throws IOException + { + if (inTextMode) + { + throw new IllegalStateException( + "Error: closeAndFillAndStroke is not allowed within a text block."); + } + writeOperator("b"); + } + + /** + * Close, fill, and then stroke the path, using the even-odd rule to determine the region to + * fill. This shall have the same effect as the sequence {@link #closePath() } + * and then {@link #fillAndStrokeEvenOdd() }. + * + * @throws IOException If the content stream could not be written + * @throws IllegalStateException If the method was called within a text block. + */ + public void closeAndFillAndStrokeEvenOdd() throws IOException + { + if (inTextMode) + { + throw new IllegalStateException( + "Error: closeAndFillAndStrokeEvenOdd is not allowed within a text block."); + } + writeOperator("b*"); } /** @@ -1685,13 +1806,14 @@ public void fillEvenOdd() throws IOException */ public void shadingFill(PDShading shading) throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: shadingFill is not allowed within a text block."); - } + if (inTextMode) + { + throw new IllegalStateException( + "Error: shadingFill is not allowed within a text block."); + } - writeOperand(resources.add(shading)); - writeOperator("sh"); + writeOperand(resources.add(shading)); + writeOperator("sh"); } /** @@ -1703,7 +1825,7 @@ public void shadingFill(PDShading shading) throws IOException @Deprecated public void closeSubPath() throws IOException { - closePath(); + closePath(); } /** @@ -1714,11 +1836,11 @@ public void closeSubPath() throws IOException */ public void closePath() throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: closePath is not allowed within a text block."); - } - writeOperator("h"); + if (inTextMode) + { + throw new IllegalStateException("Error: closePath is not allowed within a text block."); + } + writeOperator("h"); } /** @@ -1739,11 +1861,11 @@ public void clipPath(Path.FillType windingRule) throws IOException } if (windingRule == Path.FillType.WINDING) { - writeOperator("W"); + writeOperator("W"); } else if (windingRule == Path.FillType.EVEN_ODD) { - writeOperator("W"); + writeOperator("W"); } else { @@ -1760,13 +1882,14 @@ else if (windingRule == Path.FillType.EVEN_ODD) */ public void clip() throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: clip is not allowed within a text block."); - } - writeOperator("W"); - // end path without filling or stroking - writeOperator("n"); + if (inTextMode) + { + throw new IllegalStateException("Error: clip is not allowed within a text block."); + } + writeOperator("W"); + + // end path without filling or stroking + writeOperator("n"); } /** @@ -1777,18 +1900,19 @@ public void clip() throws IOException */ public void clipEvenOdd() throws IOException { - if (inTextMode) - { - throw new IllegalStateException("Error: clipEvenOdd is not allowed within a text block."); - } - writeOperator("W*"); - - // end path without filling or stroking - writeOperator("n"); + if (inTextMode) + { + throw new IllegalStateException( + "Error: clipEvenOdd is not allowed within a text block."); + } + writeOperator("W*"); + + // end path without filling or stroking + writeOperator("n"); } /** - * Set line width to the given value. + * Set line width to the given value. * * @param lineWidth The width which is used for drwaing. * @throws IOException If the content stream could not be written @@ -1806,6 +1930,7 @@ public void setLineWidth(float lineWidth) throws IOException /** * Set the line join style. + * * @param lineJoinStyle 0 for miter join, 1 for round join, and 2 for bevel join. * @throws IOException If the content stream could not be written. * @throws IllegalStateException If the method was called within a text block. @@ -1830,6 +1955,7 @@ public void setLineJoinStyle(int lineJoinStyle) throws IOException /** * Set the line cap style. + * * @param lineCapStyle 0 for butt cap, 1 for round cap, and 2 for projecting square cap. * @throws IOException If the content stream could not be written. * @throws IllegalStateException If the method was called within a text block. @@ -1854,6 +1980,7 @@ public void setLineCapStyle(int lineCapStyle) throws IOException /** * Set the line dash pattern. + * * @param pattern The pattern array * @param phase The phase of the pattern * @throws IOException If the content stream could not be written. @@ -1877,7 +2004,7 @@ public void setLineDashPattern(float[] pattern, float phase) throws IOException /** * Begin a marked content sequence. - * + * * @param tag the tag * @throws IOException if an I/O error occurs * @deprecated Use {@link #beginMarkedContent} instead. @@ -1885,7 +2012,7 @@ public void setLineDashPattern(float[] pattern, float phase) throws IOException @Deprecated public void beginMarkedContentSequence(COSName tag) throws IOException { - beginMarkedContent(tag); + beginMarkedContent(tag); } /** @@ -1903,7 +2030,7 @@ public void beginMarkedContent(COSName tag) throws IOException /** * Begin a marked content sequence with a reference to an entry in the page resources' * Properties dictionary. - * + * * @param tag the tag * @param propsName the properties reference * @throws IOException if an I/O error occurs @@ -1927,21 +2054,21 @@ public void beginMarkedContentSequence(COSName tag, COSName propsName) throws IO */ public void beginMarkedContent(COSName tag, PDPropertyList propertyList) throws IOException { - writeOperand(tag); - writeOperand(resources.add(propertyList)); - writeOperator("BDC"); + writeOperand(tag); + writeOperand(resources.add(propertyList)); + writeOperator("BDC"); } /** * End a marked content sequence. - * + * * @throws IOException If the content stream could not be written * @deprecated Use {@link #endMarkedContent} instead. */ @Deprecated public void endMarkedContentSequence() throws IOException { - endMarkedContent(); + endMarkedContent(); } /** @@ -1951,7 +2078,7 @@ public void endMarkedContentSequence() throws IOException */ public void endMarkedContent() throws IOException { - writeOperator("EMC"); + writeOperator("EMC"); } /** @@ -2003,7 +2130,7 @@ public void appendRawCommands(int data) throws IOException @Deprecated public void appendRawCommands(double data) throws IOException { - output.write(formatDecimal.format(data).getBytes(Charsets.US_ASCII)); + output.write(formatDecimal.format(data).getBytes(Charsets.US_ASCII)); } /** @@ -2016,12 +2143,12 @@ public void appendRawCommands(double data) throws IOException @Deprecated public void appendRawCommands(float data) throws IOException { - output.write(formatDecimal.format(data).getBytes(Charsets.US_ASCII)); + output.write(formatDecimal.format(data).getBytes(Charsets.US_ASCII)); } /** * This will append a {@link COSName} to the content stream. - * + * * @param name the name * @throws IOException If an error occurs while writing to the stream. * @deprecated This method will be removed in a future release. @@ -2033,7 +2160,7 @@ public void appendCOSName(COSName name) throws IOException } /** - * Set an extended graphics state, + * Set an extended graphics state. * * @param state The extended graphics state. * @throws IOException If the content stream could not be written. @@ -2049,8 +2176,8 @@ public void setGraphicsStateParameters(PDExtendedGraphicsState state) throws IOE */ private void writeOperand(float real) throws IOException { - writeOperator(formatDecimal.format(real)); - output.write(' '); + write(formatDecimal.format(real)); + output.write(' '); } /** @@ -2058,17 +2185,17 @@ private void writeOperand(float real) throws IOException */ private void writeOperand(int integer) throws IOException { - writeOperator(formatDecimal.format(integer)); - output.write(' '); + write(formatDecimal.format(integer)); + output.write(' '); } - + /** * Writes a COSName to the content stream. */ private void writeOperand(COSName name) throws IOException { - name.writePDF(output); - output.write(' '); + name.writePDF(output); + output.write(' '); } /** @@ -2076,8 +2203,8 @@ private void writeOperand(COSName name) throws IOException */ private void writeOperator(String text) throws IOException { - output.write(text.getBytes(Charsets.US_ASCII)); - output.write('\n'); + output.write(text.getBytes(Charsets.US_ASCII)); + output.write('\n'); } /** @@ -2085,14 +2212,15 @@ private void writeOperator(String text) throws IOException */ private void write(String text) throws IOException { - output.write(text.getBytes(Charsets.US_ASCII)); + output.write(text.getBytes(Charsets.US_ASCII)); } + /** * Writes a string to the content stream as ASCII. */ private void writeLine() throws IOException { - output.write('\n'); + output.write('\n'); } /** @@ -2100,7 +2228,7 @@ private void writeLine() throws IOException */ private void writeBytes(byte[] data) throws IOException { - output.write(data); + output.write(data); } /** @@ -2108,22 +2236,23 @@ private void writeBytes(byte[] data) throws IOException */ private void writeAffineTransform(AffineTransform transform) throws IOException { - double[] values = new double[6]; - transform.getMatrix(values); - for (double v : values) - { - writeOperand((float) v); - } + double[] values = new double[6]; + transform.getMatrix(values); + for (double v : values) + { + writeOperand((float)v); + } } /** * Close the content stream. This must be called when you are done with this object. + * * @throws IOException If the underlying stream has a problem being written to. */ @Override public void close() throws IOException { - output.close(); + output.close(); } private boolean isOutside255Interval(int val) @@ -2135,4 +2264,29 @@ private boolean isOutsideOneInterval(double val) { return val < 0 || val > 1; } + + private void setStrokingColorSpaceStack(PDColorSpace colorSpace) + { + if (strokingColorSpaceStack.isEmpty()) + { + strokingColorSpaceStack.add(colorSpace); + } + else + { + strokingColorSpaceStack.setElementAt(colorSpace, strokingColorSpaceStack.size() - 1); + } + } + + private void setNonStrokingColorSpaceStack(PDColorSpace colorSpace) + { + if (nonStrokingColorSpaceStack.isEmpty()) + { + nonStrokingColorSpaceStack.add(colorSpace); + } + else + { + nonStrokingColorSpaceStack.setElementAt(colorSpace, + nonStrokingColorSpaceStack.size() - 1); + } + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPageTree.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPageTree.java index 2613dbf31..ae0fd4984 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPageTree.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDPageTree.java @@ -58,12 +58,7 @@ public PDPageTree() */ public PDPageTree(COSDictionary root) { - if (root == null) - { - throw new IllegalArgumentException("root cannot be null"); - } - this.root = root; - document = null; + this(root, null); } /** @@ -78,7 +73,19 @@ public PDPageTree(COSDictionary root) { throw new IllegalArgumentException("root cannot be null"); } - this.root = root; + // repair bad PDFs which contain a Page dict instead of a page tree, see PDFBOX-3154 + if (COSName.PAGE.equals(root.getCOSName(COSName.TYPE))) + { + COSArray kids = new COSArray(); + kids.add(root); + this.root = new COSDictionary(); + this.root.setItem(COSName.KIDS, kids); + this.root.setInt(COSName.COUNT, 1); + } + else + { + this.root = root; + } this.document = document; } @@ -178,11 +185,7 @@ public PDPage next() { COSDictionary next = queue.poll(); - // sanity check - if (next.getCOSName(COSName.TYPE) != COSName.PAGE) - { - throw new IllegalStateException("Expected Page but got " + next); - } + sanitizeType(next); ResourceCache resourceCache = document != null ? document.getResourceCache() : null; return new PDPage(next, resourceCache); @@ -204,16 +207,26 @@ public PDPage get(int index) { COSDictionary dict = get(index + 1, root, 0); - // sanity check - if (dict.getCOSName(COSName.TYPE) != COSName.PAGE) - { - throw new IllegalStateException("Expected Page but got " + dict); - } + sanitizeType(dict); ResourceCache resourceCache = document != null ? document.getResourceCache() : null; return new PDPage(dict, resourceCache); } + private static void sanitizeType(COSDictionary dictionary) + { + COSName type = dictionary.getCOSName(COSName.TYPE); + if (type == null) + { + dictionary.setItem(COSName.TYPE, COSName.PAGE); + return; + } + if (!COSName.PAGE.equals(type)) + { + throw new IllegalStateException("Expected 'Page' but found " + type); + } + } + /** * Returns the given COS page using a depth-first search. * @@ -394,16 +407,16 @@ private void remove(COSDictionary node) if (kids.removeObject(node)) { - // update ancestor counts - do - { - node = (COSDictionary) node.getDictionaryObject(COSName.PARENT, COSName.P); - if (node != null) - { - node.setInt(COSName.COUNT, node.getInt(COSName.COUNT) - 1); - } - } - while (node != null); + // update ancestor counts + do + { + node = (COSDictionary)node.getDictionaryObject(COSName.PARENT, COSName.P); + if (node != null) + { + node.setInt(COSName.COUNT, node.getInt(COSName.COUNT) - 1); + } + } + while (node != null); } } @@ -423,16 +436,93 @@ public void add(PDPage page) // add to parent's kids COSArray kids = (COSArray)root.getDictionaryObject(COSName.KIDS); kids.add(node); - + // update ancestor counts do { - node = (COSDictionary) node.getDictionaryObject(COSName.PARENT, COSName.P); - if (node != null) - { - node.setInt(COSName.COUNT, node.getInt(COSName.COUNT) + 1); - } + node = (COSDictionary)node.getDictionaryObject(COSName.PARENT, COSName.P); + if (node != null) + { + node.setInt(COSName.COUNT, node.getInt(COSName.COUNT) + 1); + } } while (node != null); } + + /** + * Insert a page before another page within a page tree. + * + * @param newPage the page to be inserted. + * @param nextPage the page that is to be after the new page. + * + * @throws IllegalArgumentException if one attempts to insert a page that isn't part of a page + * tree. + */ + public void insertBefore(PDPage newPage, PDPage nextPage) + { + COSDictionary nextPageDict = nextPage.getCOSObject(); + COSDictionary parentDict = (COSDictionary)nextPageDict.getDictionaryObject(COSName.PARENT); + COSArray kids = (COSArray)parentDict.getDictionaryObject(COSName.KIDS); + boolean found = false; + for (int i = 0; i < kids.size(); ++i) + { + COSDictionary pageDict = (COSDictionary)kids.getObject(i); + if (pageDict.equals(nextPage.getCOSObject())) + { + kids.add(i, newPage.getCOSObject()); + newPage.getCOSObject().setItem(COSName.PARENT, parentDict); + found = true; + break; + } + } + if (!found) + { + throw new IllegalArgumentException("attempted to insert before orphan page"); + } + increaseParents(parentDict); + } + + /** + * Insert a page after another page within a page tree. + * + * @param newPage the page to be inserted. + * @param prevPage the page that is to be before the new page. + * + * @throws IllegalArgumentException if one attempts to insert a page that isn't part of a page + * tree. + */ + public void insertAfter(PDPage newPage, PDPage prevPage) + { + COSDictionary prevPageDict = prevPage.getCOSObject(); + COSDictionary parentDict = (COSDictionary)prevPageDict.getDictionaryObject(COSName.PARENT); + COSArray kids = (COSArray)parentDict.getDictionaryObject(COSName.KIDS); + boolean found = false; + for (int i = 0; i < kids.size(); ++i) + { + COSDictionary pageDict = (COSDictionary)kids.getObject(i); + if (pageDict.equals(prevPage.getCOSObject())) + { + kids.add(i + 1, newPage.getCOSObject()); + newPage.getCOSObject().setItem(COSName.PARENT, parentDict); + found = true; + break; + } + } + if (!found) + { + throw new IllegalArgumentException("attempted to insert before orphan page"); + } + increaseParents(parentDict); + } + + private void increaseParents(COSDictionary parentDict) + { + do + { + int cnt = parentDict.getInt(COSName.COUNT); + parentDict.setInt(COSName.COUNT, cnt + 1); + parentDict = (COSDictionary)parentDict.getDictionaryObject(COSName.PARENT); + } + while (parentDict != null); + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDResources.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDResources.java index 00603cd1c..c0a2a3cc0 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDResources.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/PDResources.java @@ -23,6 +23,7 @@ import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSObject; +import com.tom_roush.pdfbox.cos.COSStream; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; import com.tom_roush.pdfbox.pdmodel.documentinterchange.markedcontent.PDPropertyList; import com.tom_roush.pdfbox.pdmodel.font.PDFont; @@ -90,6 +91,7 @@ public PDResources(COSDictionary resourceDictionary, ResourceCache resourceCache /** * Returns the underlying dictionary. */ + @Override public COSDictionary getCOSObject() { return resources; @@ -131,9 +133,26 @@ public PDFont getFont(COSName name) throws IOException * Returns the color space resource with the given name, or null if none exists. * * @param name Name of the color space resource. - * @throws java.io.IOException if something went wrong. + * @return a new color space. + * @throws IOException if something went wrong. */ public PDColorSpace getColorSpace(COSName name) throws IOException + { + return getColorSpace(name, false); + } + + /** + * Returns the color space resource with the given name, or null if none exists. This method is + * for PDFBox internal use only, others should use {@link #getColorSpace(COSName)}. + * + * @param name Name of the color space resource. + * @param wasDefault if current color space was used by a default color space. This parameter is + * to + * + * @return a new color space. + * @throws IOException if something went wrong. + */ + public PDColorSpace getColorSpace(COSName name, boolean wasDefault) throws IOException { COSObject indirect = getIndirect(COSName.FONT, name); if (cache != null && indirect != null) @@ -150,14 +169,15 @@ public PDColorSpace getColorSpace(COSName name) throws IOException COSBase object = get(COSName.COLORSPACE, name); if (object != null) { - colorSpace = PDColorSpace.create(object, this); + colorSpace = PDColorSpace.create(object, this, wasDefault); } else { - colorSpace = PDColorSpace.create(name, this); + colorSpace = PDColorSpace.create(name, this, wasDefault); } - if (cache != null) + // we can't cache PDPattern, because it holds page resources, see PDFBOX-2370 + if (cache != null /*&& !(colorSpace instanceof PDPattern)*/) // TODO: PdfBox-Android { cache.put(indirect, colorSpace); } @@ -175,14 +195,14 @@ public boolean hasColorSpace(COSName name) } /** - * Returns the external graphics state resource with the given name, or null + * Returns the extended graphics state resource with the given name, or null * if none exists. * * @param name Name of the graphics state resource. */ public PDExtendedGraphicsState getExtGState(COSName name) { - COSObject indirect = getIndirect(COSName.FONT, name); + COSObject indirect = getIndirect(COSName.EXT_G_STATE, name); if (cache != null && indirect != null) { PDExtendedGraphicsState cached = cache.getExtGState(indirect); @@ -215,7 +235,7 @@ public PDExtendedGraphicsState getExtGState(COSName name) */ public PDShading getShading(COSName name) throws IOException { - COSObject indirect = getIndirect(COSName.FONT, name); + COSObject indirect = getIndirect(COSName.SHADING, name); if (cache != null && indirect != null) { PDShading cached = cache.getShading(indirect); @@ -248,7 +268,7 @@ public PDShading getShading(COSName name) throws IOException */ public PDAbstractPattern getPattern(COSName name) throws IOException { - COSObject indirect = getIndirect(COSName.FONT, name); + COSObject indirect = getIndirect(COSName.PATTERN, name); if (cache != null && indirect != null) { PDAbstractPattern cached = cache.getPattern(indirect); @@ -280,7 +300,7 @@ public PDAbstractPattern getPattern(COSName name) throws IOException */ public PDPropertyList getProperties(COSName name) { - COSObject indirect = getIndirect(COSName.FONT, name); + COSObject indirect = getIndirect(COSName.PROPERTIES, name); if (cache != null && indirect != null) { PDPropertyList cached = cache.getProperties(indirect); @@ -305,6 +325,33 @@ public PDPropertyList getProperties(COSName name) return propertyList; } + /** + * Tells whether the XObject resource with the given name is an image. + * + * @param name Name of the XObject resource. + * + * @return true if it is an image XObject, false if not. + */ + public boolean isImageXObject(COSName name) + { + // get the instance + COSBase value = get(COSName.XOBJECT, name); + if (value == null) + { + return false; + } + else if (value instanceof COSObject) + { + value = ((COSObject)value).getObject(); + } + if (!(value instanceof COSStream)) + { + return false; + } + COSStream stream = (COSStream)value; + return COSName.IMAGE.equals(stream.getCOSName(COSName.SUBTYPE)); + } + /** * Returns the XObject resource with the given name, or null if none exists. * @@ -313,7 +360,7 @@ public PDPropertyList getProperties(COSName name) */ public PDXObject getXObject(COSName name) throws IOException { - COSObject indirect = getIndirect(COSName.FONT, name); + COSObject indirect = getIndirect(COSName.XOBJECT, name); if (cache != null && indirect != null) { PDXObject cached = cache.getXObject(indirect); @@ -339,7 +386,8 @@ else if (value instanceof COSObject) xobject = PDXObject.createXObject(value, this); } - if (cache != null) + // we can't cache PDImageXObject, because it holds page resources, see PDFBOX-2370 + if (cache != null && !(xobject instanceof PDImageXObject)) { cache.put(indirect, xobject); } @@ -634,7 +682,7 @@ public void put(COSName name, PDFont font) * @param name the name of the resource * @param colorSpace the color space to be added */ - public void put(COSName name, PDColorSpace colorSpace) throws IOException + public void put(COSName name, PDColorSpace colorSpace) { put(COSName.COLORSPACE, name, colorSpace); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/COSArrayList.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/COSArrayList.java index 19b64eb9a..39e04ded7 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/COSArrayList.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/COSArrayList.java @@ -294,7 +294,7 @@ public static List convertFloatCOSArrayToList( COSArray floatArray ) List numbers = new ArrayList(); for( int i=0; i( numbers, floatArray ); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/COSDictionaryMap.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/COSDictionaryMap.java index 1c8a38efa..c689abaed 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/COSDictionaryMap.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/COSDictionaryMap.java @@ -20,7 +20,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -211,12 +210,11 @@ public int hashCode() */ public static COSDictionary convert(Map someMap) { - Iterator iter = someMap.keySet().iterator(); COSDictionary dic = new COSDictionary(); for (Entry entry : someMap.entrySet()) { - String name = entry.getKey(); - COSObjectable object = (COSObjectable) entry.getValue(); + String name = entry.getKey(); + COSObjectable object = (COSObjectable)entry.getValue(); dic.setItem( COSName.getPDFName( name ), object.getCOSObject() ); } return dic; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDDictionaryWrapper.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDDictionaryWrapper.java index 85b37d9f0..cba623248 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDDictionaryWrapper.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDDictionaryWrapper.java @@ -21,7 +21,7 @@ /** * A wrapper for a COS dictionary. * - * @author Ben Litchfield + * @author Johannes Koch * */ public class PDDictionaryWrapper implements COSObjectable diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDMatrix.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDMatrix.java deleted file mode 100644 index e522c03cd..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDMatrix.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.common; - -import com.tom_roush.pdfbox.cos.COSArray; -import com.tom_roush.pdfbox.cos.COSBase; -import com.tom_roush.pdfbox.cos.COSFloat; -import com.tom_roush.pdfbox.cos.COSNumber; - -/** - * This class will be used for matrix manipulation. - * - * @author Ben Litchfield - * @version $Revision: 1.3 $ - */ -public class PDMatrix implements COSObjectable -{ - private COSArray matrix; - // the number of row elements depends on the number of elements - // within the given matrix - // 3x3 e.g. Matrix of a CalRGB colorspace dictionary - // 3x2 e.g. FontMatrix of a type 3 font - private int numberOfRowElements = 3; - - /** - * Constructor. - */ - public PDMatrix() - { - matrix = new COSArray(); - matrix.add( new COSFloat( 1.0f ) ); - matrix.add( new COSFloat( 0.0f ) ); - matrix.add( new COSFloat( 0.0f ) ); - matrix.add( new COSFloat( 0.0f ) ); - matrix.add( new COSFloat( 1.0f ) ); - matrix.add( new COSFloat( 0.0f ) ); - matrix.add( new COSFloat( 0.0f ) ); - matrix.add( new COSFloat( 0.0f ) ); - matrix.add( new COSFloat( 1.0f ) ); - } - - /** - * Constructor. - * - * @param array The array that describes the matrix. - */ - public PDMatrix( COSArray array ) - { - if ( array.size() == 6) - { - numberOfRowElements = 2; - } - matrix = array; - } - - /** - * This will get the underlying array value. - * - * @return The cos object that this object wraps. - */ - public COSArray getCOSArray() - { - return matrix; - } - - /** - * Convert this standard java object to a COS object. - * - * @return The cos object that matches this Java object. - */ - public COSBase getCOSObject() - { - return matrix; - } - - - /** - * This will get a matrix value at some point. - * - * @param row The row to get the value from. - * @param column The column to get the value from. - * - * @return The value at the row/column position. - */ - public float getValue( int row, int column ) - { - return ((COSNumber)matrix.get( row*numberOfRowElements + column )).floatValue(); - } - - /** - * This will set a value at a position. - * - * @param row The row to set the value at. - * @param column the column to set the value at. - * @param value The value to set at the position. - */ - public void setValue( int row, int column, float value ) - { - matrix.set( row*numberOfRowElements+column, new COSFloat( value ) ); - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDMetadata.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDMetadata.java index 0fb8505f0..362731919 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDMetadata.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDMetadata.java @@ -40,8 +40,8 @@ public class PDMetadata extends PDStream public PDMetadata( PDDocument document ) { super( document ); - getStream().setName( COSName.TYPE, "Metadata" ); - getStream().setName( COSName.SUBTYPE, "XML" ); + getCOSObject().setName(COSName.TYPE, "Metadata"); + getCOSObject().setName(COSName.SUBTYPE, "XML"); } /** @@ -55,8 +55,8 @@ public PDMetadata( PDDocument document ) public PDMetadata(PDDocument doc, InputStream str) throws IOException { super(doc, str); - getStream().setName( COSName.TYPE, "Metadata" ); - getStream().setName( COSName.SUBTYPE, "XML" ); + getCOSObject().setName(COSName.TYPE, "Metadata"); + getCOSObject().setName(COSName.SUBTYPE, "XML"); } /** @@ -92,8 +92,8 @@ public InputStream exportXMPMetadata() throws IOException public void importXMPMetadata( byte[] xmp ) throws IOException { - OutputStream os = createOutputStream(); - os.write(xmp); - os.close(); + OutputStream os = createOutputStream(); + os.write(xmp); + os.close(); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDNameTreeNode.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDNameTreeNode.java index 25c23385b..e7c7433a8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDNameTreeNode.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDNameTreeNode.java @@ -101,6 +101,7 @@ public boolean isRootNode() { return parent == null; } + /** * Return the children of this node. This list will contain PDNameTreeNode objects. * @@ -108,7 +109,6 @@ public boolean isRootNode() */ public List> getKids() { - List> retval = null; COSArray kids = (COSArray)node.getDictionaryObject( COSName.KIDS ); if( kids != null ) @@ -232,7 +232,8 @@ public T getValue(String name) throws IOException } else { - Log.e("PdfBox-Android", "NameTreeNode does not have \"names\" nor \"kids\" objects."); + Log.w("PdfBox-Android", + "NameTreeNode does not have \"names\" nor \"kids\" objects."); } } return retval; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDNumberTreeNode.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDNumberTreeNode.java index b50f487eb..5bd72a77a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDNumberTreeNode.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDNumberTreeNode.java @@ -41,7 +41,7 @@ */ public class PDNumberTreeNode implements COSObjectable { - private final COSDictionary node; + private final COSDictionary node; private Class valueType = null; /** @@ -158,7 +158,8 @@ public Object getValue( Integer index ) throws IOException } else { - Log.w("PdfBox-Android", "NumberTreeNode does not have \"nums\" nor \"kids\" objects."); + Log.w("PdfBox-Android", + "NumberTreeNode does not have \"nums\" nor \"kids\" objects."); } } return retval; @@ -199,7 +200,6 @@ public Map getNumbers() throws IOException * * @param base The COS object to convert. * @return The converted PD Model object. - * @throws IOException If there is an error during creation. */ protected COSObjectable convertCOSToPD( COSBase base ) throws IOException { @@ -212,7 +212,6 @@ protected COSObjectable convertCOSToPD( COSBase base ) throws IOException catch( Throwable t ) { throw new IOException( "Error while trying to create value in number tree:" + t.getMessage(), t); - } return retval; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDObjectStream.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDObjectStream.java index 959a02c5b..6cb76b6f8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDObjectStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDObjectStream.java @@ -18,7 +18,6 @@ import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSStream; - import com.tom_roush.pdfbox.pdmodel.PDDocument; /** @@ -50,7 +49,7 @@ public static PDObjectStream createStream( PDDocument document ) { COSStream cosStream = document.getDocument().createCOSStream(); PDObjectStream strm = new PDObjectStream( cosStream ); - strm.getStream().setItem( COSName.TYPE, COSName.OBJ_STM ); + strm.getCOSObject().setItem(COSName.TYPE, COSName.OBJ_STM); return strm; } @@ -61,7 +60,7 @@ public static PDObjectStream createStream( PDDocument document ) */ public String getType() { - return getStream().getNameAsString( COSName.TYPE ); + return getCOSObject().getNameAsString(COSName.TYPE); } /** @@ -71,7 +70,7 @@ public String getType() */ public int getNumberOfObjects() { - return getStream().getInt( COSName.N, 0 ); + return getCOSObject().getInt(COSName.N, 0); } /** @@ -81,7 +80,7 @@ public int getNumberOfObjects() */ public void setNumberOfObjects( int n ) { - getStream().setInt( COSName.N, n ); + getCOSObject().setInt(COSName.N, n); } /** @@ -91,7 +90,7 @@ public void setNumberOfObjects( int n ) */ public int getFirstByteOffset() { - return getStream().getInt( COSName.FIRST, 0 ); + return getCOSObject().getInt(COSName.FIRST, 0); } /** @@ -101,7 +100,7 @@ public int getFirstByteOffset() */ public void setFirstByteOffset( int n ) { - getStream().setInt( COSName.FIRST, n ); + getCOSObject().setInt(COSName.FIRST, n); } /** @@ -113,7 +112,7 @@ public void setFirstByteOffset( int n ) public PDObjectStream getExtends() { PDObjectStream retval = null; - COSStream stream = (COSStream)getStream().getDictionaryObject( COSName.EXTENDS ); + COSStream stream = (COSStream)getCOSObject().getDictionaryObject(COSName.EXTENDS); if( stream != null ) { retval = new PDObjectStream( stream ); @@ -130,6 +129,6 @@ public PDObjectStream getExtends() */ public void setExtends( PDObjectStream stream ) { - getStream().setItem( COSName.EXTENDS, stream ); + getCOSObject().setItem(COSName.EXTENDS, stream); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDPageLabels.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDPageLabels.java index b173f5f7c..21cea35da 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDPageLabels.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDPageLabels.java @@ -162,10 +162,11 @@ public PDPageLabelRange getPageLabelRange(int startPage) */ public void setLabelItem(int startPage, PDPageLabelRange item) { - if (startPage < 0) - { - throw new IllegalArgumentException("startPage parameter of setLabelItem may not be < 0"); - } + if (startPage < 0) + { + throw new IllegalArgumentException( + "startPage parameter of setLabelItem may not be < 0"); + } labels.put(startPage, item); } @@ -206,7 +207,7 @@ public Map getPageIndicesByLabels() new HashMap(doc.getNumberOfPages()); computeLabels(new LabelHandler() { - @Override + @Override public void newLabel(int pageIndex, String label) { labelMap.put(label, pageIndex); @@ -227,7 +228,7 @@ public String[] getLabelsByPageIndices() final String[] map = new String[doc.getNumberOfPages()]; computeLabels(new LabelHandler() { - @Override + @Override public void newLabel(int pageIndex, String label) { if(pageIndex < doc.getNumberOfPages()) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDRectangle.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDRectangle.java index de93aa641..b7ff47886 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDRectangle.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDRectangle.java @@ -30,7 +30,6 @@ * A rectangle in a PDF document. * * @author Ben Litchfield - * */ public class PDRectangle implements COSObjectable { @@ -280,21 +279,8 @@ public float getHeight() } /** - * This will move the rectangle the given relative amount. - * - * @param horizontalAmount positive values will move rectangle to the right, negative's to the left. - * @param verticalAmount positive values will move the rectangle up, negative's down. - */ - public void move(float horizontalAmount, float verticalAmount) - { - setUpperRightX(getUpperRightX() + horizontalAmount); - setLowerLeftX(getLowerLeftX() + horizontalAmount); - setUpperRightY(getUpperRightY() + verticalAmount); - setLowerLeftY(getLowerLeftY() + verticalAmount); - } - - /** - * Returns a copy of this rectangle which has been transformed using the given matrix. + * Returns a path which represents this rectangle having been transformed by the given matrix. + * Note that the resulting path need not be rectangular. */ public Path transform(Matrix matrix) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDStream.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDStream.java index 0c278da20..a5ad3deb9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDStream.java @@ -191,7 +191,7 @@ public void addCompression() } /** - * Convert this standard java object to a COS object. + * Get the cos stream associated with this object. * * @return The cos object that matches this Java object. */ @@ -205,7 +205,6 @@ public COSStream getCOSObject() * This will get a stream that can be written to. * * @return An output stream to write data to. - * * @throws IOException If an IO error occurs during writing. */ public OutputStream createOutputStream() throws IOException @@ -216,6 +215,7 @@ public OutputStream createOutputStream() throws IOException /** * This will get a stream that can be written to, with the given filter. * + * @param filter the filter to be used. * @return An output stream to write data to. * @throws IOException If an IO error occurs during writing. */ @@ -249,20 +249,23 @@ public InputStream createInputStream(List stopFilters) throws IOExceptio InputStream is = stream.createRawInputStream(); ByteArrayOutputStream os = new ByteArrayOutputStream(); List filters = getFilters(); - for (int i = 0; i < filters.size(); i++) + if (filters != null) { - COSName nextFilter = filters.get(i); - if (stopFilters.contains(nextFilter.getName())) - { - break; - } - else + for (int i = 0; i < filters.size(); i++) { - Filter filter = FilterFactory.INSTANCE.getFilter(nextFilter); - filter.decode(is, os, stream, i); - IOUtils.closeQuietly(is); - is = new ByteArrayInputStream(os.toByteArray()); - os.reset(); + COSName nextFilter = filters.get(i); + if ((stopFilters != null) && stopFilters.contains(nextFilter.getName())) + { + break; + } + else + { + Filter filter = FilterFactory.INSTANCE.getFilter(nextFilter); + filter.decode(is, os, stream, i); + IOUtils.closeQuietly(is); + is = new ByteArrayInputStream(os.toByteArray()); + os.reset(); + } } } return is; @@ -270,9 +273,11 @@ public InputStream createInputStream(List stopFilters) throws IOExceptio /** * Get the cos stream associated with this object. - * + * + * @deprecated use {@link #getCOSObject() } * @return The cos object that matches this Java object. */ + @Deprecated public COSStream getStream() { return stream; @@ -327,7 +332,6 @@ public void setFilters(List filters) * an entry in the filters list. * * @return The list of decode parameters. - * * @throws IOException if there is an error retrieving the parameters. */ public List getDecodeParms() throws IOException @@ -380,7 +384,6 @@ public void setDecodeParms(List decodeParams) * required for external files. * * @return The file specification. - * * @throws IOException If there is an error creating the file spec. */ public PDFileSpecification getFile() throws IOException @@ -439,7 +442,6 @@ public void setFileFilters(List filters) * an entry in the filters list. * * @return The list of decode parameters. - * * @throws IOException if there is an error retrieving the parameters. */ public List getFileDecodeParams() throws IOException @@ -483,9 +485,9 @@ public void setFileDecodeParams(List decodeParams) /** * This will copy the stream into a byte array. - * - * @return The byte array of the filteredStream - * @throws IOException When getFilteredStream did not work + * + * @return The byte array of the filteredStream. + * @throws IOException if an I/O error occurs. */ public byte[] toByteArray() throws IOException { @@ -572,5 +574,4 @@ public void setDecodedStreamLength(int decodedStreamLength) { this.stream.setInt(COSName.DL, decodedStreamLength); } - } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDTextStream.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDTextStream.java deleted file mode 100644 index 4250742a0..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/PDTextStream.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.common; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import com.tom_roush.pdfbox.cos.COSBase; -import com.tom_roush.pdfbox.cos.COSStream; -import com.tom_roush.pdfbox.cos.COSString; -import com.tom_roush.pdfbox.io.IOUtils; - -/** - * A PDTextStream class is used when the PDF specification supports either - * a string or a stream for the value of an object. This is usually when - * a value could be large or small, for example a JavaScript method. This - * class will help abstract that and give a single unified interface to - * those types of fields. - * - * @author Ben Litchfield - */ -public class PDTextStream implements COSObjectable -{ - private COSString string; - private COSStream stream; - - /** - * Constructor. - * - * @param str The string parameter. - */ - public PDTextStream( COSString str ) - { - string = str; - } - - /** - * Constructor. - * - * @param str The string parameter. - */ - public PDTextStream( String str ) - { - string = new COSString( str ); - } - - /** - * Constructor. - * - * @param str The stream parameter. - */ - public PDTextStream( COSStream str ) - { - stream = str; - } - - /** - * This will create the text stream object. base must either be a string - * or a stream. - * - * @param base The COS text stream object. - * - * @return A PDTextStream that wraps the base object. - */ - public static PDTextStream createTextStream( COSBase base ) - { - PDTextStream retval = null; - if( base instanceof COSString ) - { - retval = new PDTextStream( (COSString) base ); - } - else if( base instanceof COSStream ) - { - retval = new PDTextStream( (COSStream)base ); - } - return retval; - } - - /** - * Convert this standard java object to a COS object. - * - * @return The cos object that matches this Java object. - */ - @Override - public COSBase getCOSObject() - { - return string == null ? stream : string; - } - - /** - * This will get this value as a string. If this is a stream then it - * will load the entire stream into memory, so you should only do this when - * the stream is a manageable size. - * - * @return This value as a string. - * - * @throws IOException If an IO error occurs while accessing the stream. - */ - public String getAsString() throws IOException - { - if (string != null) - { - return string.getString(); - } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - InputStream is = stream.createInputStream(); - IOUtils.copy(is, out); - IOUtils.closeQuietly(is); - return new String(out.toByteArray(), "ISO-8859-1"); - } - - /** - * This is the preferred way of getting data with this class as it uses - * a stream object. - * - * @return The stream object. - * - * @throws IOException If an IO error occurs while accessing the stream. - */ - public InputStream getAsStream() throws IOException - { - InputStream retval; - if( string != null ) - { - retval = new ByteArrayInputStream( string.getBytes() ); - } - else - { - retval = stream.createInputStream(); - } - return retval; - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/XrefEntry.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/XrefEntry.java deleted file mode 100644 index 640120839..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/XrefEntry.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.common; - -/** -* -* @author adam -*/ -public class XrefEntry { - private int objectNumber = 0; - private int byteOffset = 0; - private int generation = 0; - private boolean inUse = true; - - public XrefEntry() { - } - - public XrefEntry(int objectNumber, int byteOffset, int generation, String inUse) { - this.objectNumber = objectNumber; - this.byteOffset = byteOffset; - this.generation = generation; - this.inUse = "n".equals(inUse); - } - - public int getByteOffset() { - return byteOffset; - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/filespecification/PDEmbeddedFile.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/filespecification/PDEmbeddedFile.java index 97d6210fb..cf699b8b3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/filespecification/PDEmbeddedFile.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/filespecification/PDEmbeddedFile.java @@ -42,7 +42,7 @@ public class PDEmbeddedFile extends PDStream public PDEmbeddedFile( PDDocument document ) { super( document ); - getStream().setName(COSName.TYPE, "EmbeddedFile" ); + getCOSObject().setName(COSName.TYPE, "EmbeddedFile"); } @@ -53,7 +53,7 @@ public PDEmbeddedFile( PDDocument document ) */ public PDEmbeddedFile( COSStream str ) { - super( str ); + super(str); } /** @@ -66,8 +66,23 @@ public PDEmbeddedFile( COSStream str ) */ public PDEmbeddedFile( PDDocument doc, InputStream str ) throws IOException { - super( doc, str ); - getStream().setName(COSName.TYPE, "EmbeddedFile" ); + super(doc, str); + getCOSObject().setName(COSName.TYPE, "EmbeddedFile"); + } + + /** + * Constructor. + * + * @param doc {@inheritDoc} + * @param input {@inheritDoc} + * @param filter Filter to apply to the stream. + * + * @throws IOException {@inheritDoc} + */ + public PDEmbeddedFile(PDDocument doc, InputStream input, COSName filter) throws IOException + { + super(doc, input, filter); + getCOSObject().setName(COSName.TYPE, "EmbeddedFile"); } /** @@ -77,7 +92,7 @@ public PDEmbeddedFile( PDDocument doc, InputStream str ) throws IOException */ public void setSubtype( String mimeType ) { - getStream().setName(COSName.SUBTYPE, mimeType ); + getCOSObject().setName(COSName.SUBTYPE, mimeType); } /** @@ -87,7 +102,7 @@ public void setSubtype( String mimeType ) */ public String getSubtype() { - return getStream().getNameAsString(COSName.SUBTYPE ); + return getCOSObject().getNameAsString(COSName.SUBTYPE); } /** @@ -97,7 +112,7 @@ public String getSubtype() */ public int getSize() { - return getStream().getEmbeddedInt( "Params", "Size" ); + return getCOSObject().getEmbeddedInt("Params", "Size"); } /** @@ -107,7 +122,7 @@ public int getSize() */ public void setSize( int size ) { - getStream().setEmbeddedInt( "Params", "Size", size ); + getCOSObject().setEmbeddedInt("Params", "Size", size); } /** @@ -118,7 +133,7 @@ public void setSize( int size ) */ public Calendar getCreationDate() throws IOException { - return getStream().getEmbeddedDate( "Params", "CreationDate" ); + return getCOSObject().getEmbeddedDate("Params", "CreationDate"); } /** @@ -128,7 +143,7 @@ public Calendar getCreationDate() throws IOException */ public void setCreationDate( Calendar creation ) { - getStream().setEmbeddedDate( "Params", "CreationDate", creation ); + getCOSObject().setEmbeddedDate("Params", "CreationDate", creation); } /** @@ -139,7 +154,7 @@ public void setCreationDate( Calendar creation ) */ public Calendar getModDate() throws IOException { - return getStream().getEmbeddedDate( "Params", "ModDate" ); + return getCOSObject().getEmbeddedDate("Params", "ModDate"); } /** @@ -149,7 +164,7 @@ public Calendar getModDate() throws IOException */ public void setModDate( Calendar mod ) { - getStream().setEmbeddedDate( "Params", "ModDate", mod ); + getCOSObject().setEmbeddedDate("Params", "ModDate", mod); } /** @@ -159,7 +174,7 @@ public void setModDate( Calendar mod ) */ public String getCheckSum() { - return getStream().getEmbeddedString( "Params", "CheckSum" ); + return getCOSObject().getEmbeddedString("Params", "CheckSum"); } /** @@ -169,7 +184,7 @@ public String getCheckSum() */ public void setCheckSum( String checksum ) { - getStream().setEmbeddedString( "Params", "CheckSum", checksum ); + getCOSObject().setEmbeddedString("Params", "CheckSum", checksum); } /** @@ -180,7 +195,7 @@ public void setCheckSum( String checksum ) public String getMacSubtype() { String retval = null; - COSDictionary params = (COSDictionary)getStream().getDictionaryObject( COSName.PARAMS ); + COSDictionary params = (COSDictionary)getCOSObject().getDictionaryObject(COSName.PARAMS); if( params != null ) { retval = params.getEmbeddedString( "Mac", "Subtype" ); @@ -195,11 +210,11 @@ public String getMacSubtype() */ public void setMacSubtype( String macSubtype ) { - COSDictionary params = (COSDictionary)getStream().getDictionaryObject( COSName.PARAMS ); + COSDictionary params = (COSDictionary)getCOSObject().getDictionaryObject(COSName.PARAMS); if( params == null && macSubtype != null ) { params = new COSDictionary(); - getStream().setItem( COSName.PARAMS, params ); + getCOSObject().setItem(COSName.PARAMS, params); } if( params != null ) { @@ -215,7 +230,7 @@ public void setMacSubtype( String macSubtype ) public String getMacCreator() { String retval = null; - COSDictionary params = (COSDictionary)getStream().getDictionaryObject( COSName.PARAMS ); + COSDictionary params = (COSDictionary)getCOSObject().getDictionaryObject(COSName.PARAMS); if( params != null ) { retval = params.getEmbeddedString( "Mac", "Creator" ); @@ -230,11 +245,11 @@ public String getMacCreator() */ public void setMacCreator( String macCreator ) { - COSDictionary params = (COSDictionary)getStream().getDictionaryObject( COSName.PARAMS ); + COSDictionary params = (COSDictionary)getCOSObject().getDictionaryObject(COSName.PARAMS); if( params == null && macCreator != null ) { params = new COSDictionary(); - getStream().setItem( COSName.PARAMS, params ); + getCOSObject().setItem(COSName.PARAMS, params); } if( params != null ) { @@ -250,7 +265,7 @@ public void setMacCreator( String macCreator ) public String getMacResFork() { String retval = null; - COSDictionary params = (COSDictionary)getStream().getDictionaryObject( COSName.PARAMS ); + COSDictionary params = (COSDictionary)getCOSObject().getDictionaryObject(COSName.PARAMS); if( params != null ) { retval = params.getEmbeddedString( "Mac", "ResFork" ); @@ -265,11 +280,11 @@ public String getMacResFork() */ public void setMacResFork( String macResFork ) { - COSDictionary params = (COSDictionary)getStream().getDictionaryObject( COSName.PARAMS ); + COSDictionary params = (COSDictionary)getCOSObject().getDictionaryObject(COSName.PARAMS); if( params == null && macResFork != null ) { params = new COSDictionary(); - getStream().setItem( COSName.PARAMS, params ); + getCOSObject().setItem(COSName.PARAMS, params); } if( params != null ) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunction.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunction.java index eafe17e30..0ea74c087 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunction.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunction.java @@ -54,7 +54,7 @@ public PDFunction( COSBase function ) if (function instanceof COSStream) { functionStream = new PDStream( (COSStream)function ); - functionStream.getStream().setItem( COSName.TYPE, COSName.FUNCTION ); + functionStream.getCOSObject().setItem(COSName.TYPE, COSName.FUNCTION); } else if (function instanceof COSDictionary) { @@ -85,7 +85,7 @@ public COSDictionary getCOSObject() { if (functionStream != null) { - return functionStream.getStream(); + return functionStream.getCOSObject(); } else { @@ -126,7 +126,7 @@ public static PDFunction create( COSBase function ) throws IOException int functionType = functionDictionary.getInt( COSName.FUNCTION_TYPE ); if( functionType == 0 ) { -// retval = new PDFunctionType0(functionDictionary);TODO: PdfBox-Android + retval = new PDFunctionType0(functionDictionary); } else if( functionType == 2 ) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunctionType0.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunctionType0.java new file mode 100644 index 000000000..9f13080fc --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunctionType0.java @@ -0,0 +1,460 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.common.function; + +import android.util.Log; + +import java.io.IOException; + +import com.tom_roush.harmony.javax.imageio.stream.ImageInputStream; +import com.tom_roush.harmony.javax.imageio.stream.MemoryCacheImageInputStream; +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSInteger; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.common.PDRange; + +/** + * This class represents a type 0 function in a PDF document. + * + * @author Ben Litchfield + * @author Tilman Hausherr + */ +public class PDFunctionType0 extends PDFunction +{ + /** + * 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 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; + /** + * An array of m positive integers specifying the number of samples in each + * input dimension of the sample table. + */ + private COSArray size = null; + /** + * The samples of the function. + */ + private int[][] samples = null; + + /** + * Constructor. + * + * @param function The function. + */ + public PDFunctionType0(COSBase function) + { + super(function); + } + + /** + * {@inheritDoc} + */ + @Override + public int getFunctionType() + { + return 0; + } + + /** + * The "Size" entry, which is the number of samples in each input dimension + * of the sample table. + * + * @return A List of java.lang.Integer objects. + */ + public COSArray getSize() + { + if (size == null) + { + size = (COSArray)getCOSObject().getDictionaryObject(COSName.SIZE); + } + return size; + } + + /** + * Get all sample values of this function. + * + * @return an array with all samples. + */ + private int[][] getSamples() + { + if (samples == null) + { + int arraySize = 1; + int numberOfInputValues = getNumberOfInputParameters(); + int numberOfOutputValues = getNumberOfOutputParameters(); + COSArray sizes = getSize(); + for (int i = 0; i < numberOfInputValues; i++) + { + arraySize *= sizes.getInt(i); + } + samples = new int[arraySize][numberOfOutputValues]; + int bitsPerSample = getBitsPerSample(); + int index = 0; + try + { + // PDF spec 1.7 p.171: + // Each sample value is represented as a sequence of BitsPerSample bits. + // Successive values are adjacent in the bit stream; there is no padding at byte boundaries. + ImageInputStream mciis = new MemoryCacheImageInputStream( + getPDStream().createInputStream()); + for (int i = 0; i < arraySize; i++) + { + for (int k = 0; k < numberOfOutputValues; k++) + { + // TODO will this cast work properly for 32 bitsPerSample or should we use long[]? + samples[index][k] = (int)mciis.readBits(bitsPerSample); + } + index++; + } + mciis.close(); + } + catch (IOException exception) + { + Log.e("PdfBox-Android", + "IOException while reading the sample values of this function.", exception); + } + } + return samples; + } + + /** + * Get the number of bits that the output value will take up. + * + * Valid values are 1,2,4,8,12,16,24,32. + * + * @return Number of bits for each output value. + */ + public int getBitsPerSample() + { + return getCOSObject().getInt(COSName.BITS_PER_SAMPLE); + } + + /** + * Get the order of interpolation between samples. Valid values are 1 and 3, + * specifying linear and cubic spline interpolation, respectively. Default + * is 1. See p.170 in PDF spec 1.7. + * + * @return order of interpolation. + */ + public int getOrder() + { + return getCOSObject().getInt(COSName.ORDER, 1); + } + + /** + * Set the number of bits that the output value will take up. Valid values + * are 1,2,4,8,12,16,24,32. + * + * @param bps The number of bits for each output value. + */ + public void setBitsPerSample(int bps) + { + getCOSObject().setInt(COSName.BITS_PER_SAMPLE, bps); + } + + /** + * Returns all encode values as COSArray. + * + * @return the encode array. + */ + private COSArray getEncodeValues() + { + if (encode == null) + { + encode = (COSArray)getCOSObject().getDictionaryObject(COSName.ENCODE); + // the default value is [0 (size[0]-1) 0 (size[1]-1) ...] + if (encode == null) + { + encode = new COSArray(); + COSArray sizeValues = getSize(); + int sizeValuesSize = sizeValues.size(); + for (int i = 0; i < sizeValuesSize; i++) + { + encode.add(COSInteger.ZERO); + encode.add(COSInteger.get(sizeValues.getInt(i) - 1)); + } + } + } + return encode; + } + + /** + * Returns all decode values as COSArray. + * + * @return the decode array. + */ + private COSArray getDecodeValues() + { + if (decode == null) + { + decode = (COSArray)getCOSObject().getDictionaryObject(COSName.DECODE); + // if decode is null, the default values are the range values + if (decode == null) + { + decode = getRangeValues(); + } + } + return decode; + } + + /** + * Get the encode for the input parameter. + * + * @param paramNum The function parameter number. + * + * @return The encode parameter range or null if none is set. + */ + public PDRange getEncodeForParameter(int paramNum) + { + PDRange retval = null; + COSArray encodeValues = getEncodeValues(); + if (encodeValues != null && encodeValues.size() >= paramNum * 2 + 1) + { + retval = new PDRange(encodeValues, paramNum); + } + return retval; + } + + /** + * This will set the encode values. + * + * @param encodeValues The new encode values. + */ + public void setEncodeValues(COSArray encodeValues) + { + encode = encodeValues; + getCOSObject().setItem(COSName.ENCODE, encodeValues); + } + + /** + * Get the decode for the input parameter. + * + * @param paramNum The function parameter number. + * + * @return The decode parameter range or null if none is set. + */ + public PDRange getDecodeForParameter(int paramNum) + { + PDRange retval = null; + COSArray decodeValues = getDecodeValues(); + if (decodeValues != null && decodeValues.size() >= paramNum * 2 + 1) + { + retval = new PDRange(decodeValues, paramNum); + } + return retval; + } + + /** + * This will set the decode values. + * + * @param decodeValues The new decode values. + */ + public void setDecodeValues(COSArray decodeValues) + { + decode = decodeValues; + getCOSObject().setItem(COSName.DECODE, decodeValues); + } + + /** + * calculate array index (structure described in p.171 PDF spec 1.7) in + * multiple dimensions. + * + * @param vector with coordinates + * + * @return index in flat array + */ + private int calcSampleIndex(int[] vector) + { + // inspiration: http://stackoverflow.com/a/12113479/535646 + // but used in reverse + float[] sizeValues = getSize().toFloatArray(); + int index = 0; + int sizeProduct = 1; + int dimension = vector.length; + for (int i = dimension - 2; i >= 0; --i) + { + sizeProduct *= sizeValues[i]; + } + for (int i = dimension - 1; i >= 0; --i) + { + index += sizeProduct * vector[i]; + if (i - 1 >= 0) + { + sizeProduct /= sizeValues[i - 1]; + } + } + return index; + } + + /** + * Inner class do to an interpolation in the Nth dimension by comparing the + * content size of N-1 dimensional objects. This is done with the help of + * recursive calls. To understand the algorithm without recursion, here is a + * bilinear + * interpolation and here's a trilinear + * interpolation (external links). + */ + private class Rinterpol + { + // coordinate that is to be interpolated + private final float[] in; + // coordinate of the "ceil" point + private final int[] inPrev; + // coordinate of the "floor" point + private final int[] inNext; + private final int numberOfInputValues; + private final int numberOfOutputValues = getNumberOfOutputParameters(); + + /** + * Constructor. + * + * @param input the input coordinates + * @param inputPrev coordinate of the "ceil" point + * @param inputNext coordinate of the "floor" point + */ + Rinterpol(float[] input, int[] inputPrev, int[] inputNext) + { + in = input; + inPrev = inputPrev; + inNext = inputNext; + numberOfInputValues = input.length; + } + + /** + * Calculate the interpolation. + * + * @return interpolated result sample + */ + float[] rinterpolate() + { + return rinterpol(new int[numberOfInputValues], 0); + } + + /** + * Do a linear interpolation if the two coordinates can be known, or + * call itself recursively twice. + * + * @param coord coord partially set coordinate (not set from step + * upwards); gets fully filled in the last call ("leaf"), where it is + * used to get the correct sample + * @param step between 0 (first call) and dimension - 1 + * + * @return interpolated result sample + */ + private float[] rinterpol(int[] coord, int step) + { + float[] resultSample = new float[numberOfOutputValues]; + if (step == in.length - 1) + { + // leaf + if (inPrev[step] == inNext[step]) + { + coord[step] = inPrev[step]; + int[] tmpSample = getSamples()[calcSampleIndex(coord)]; + for (int i = 0; i < numberOfOutputValues; ++i) + { + resultSample[i] = tmpSample[i]; + } + return resultSample; + } + coord[step] = inPrev[step]; + int[] sample1 = getSamples()[calcSampleIndex(coord)]; + coord[step] = inNext[step]; + int[] sample2 = getSamples()[calcSampleIndex(coord)]; + for (int i = 0; i < numberOfOutputValues; ++i) + { + resultSample[i] = interpolate(in[step], inPrev[step], inNext[step], sample1[i], + sample2[i]); + } + return resultSample; + } + else + { + // branch + if (inPrev[step] == inNext[step]) + { + coord[step] = inPrev[step]; + return rinterpol(coord, step + 1); + } + coord[step] = inPrev[step]; + float[] sample1 = rinterpol(coord, step + 1); + coord[step] = inNext[step]; + float[] sample2 = rinterpol(coord, step + 1); + for (int i = 0; i < numberOfOutputValues; ++i) + { + resultSample[i] = interpolate(in[step], inPrev[step], inNext[step], sample1[i], + sample2[i]); + } + return resultSample; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public float[] eval(float[] input) throws IOException + { + //This involves linear interpolation based on a set of sample points. + //Theoretically it's not that difficult ... see section 3.9.1 of the PDF Reference. + + float[] sizeValues = getSize().toFloatArray(); + int bitsPerSample = getBitsPerSample(); + float maxSample = (float)(Math.pow(2, bitsPerSample) - 1.0); + int numberOfInputValues = input.length; + int numberOfOutputValues = getNumberOfOutputParameters(); + + int[] inputPrev = new int[numberOfInputValues]; + int[] inputNext = new int[numberOfInputValues]; + + for (int i = 0; i < numberOfInputValues; i++) + { + PDRange domain = getDomainForInput(i); + PDRange encodeValues = getEncodeForParameter(i); + input[i] = clipToRange(input[i], domain.getMin(), domain.getMax()); + input[i] = interpolate(input[i], domain.getMin(), domain.getMax(), + encodeValues.getMin(), encodeValues.getMax()); + input[i] = clipToRange(input[i], 0, sizeValues[i] - 1); + inputPrev[i] = (int)Math.floor(input[i]); + inputNext[i] = (int)Math.ceil(input[i]); + } + + float[] outputValues = new Rinterpol(input, inputPrev, inputNext).rinterpolate(); + + for (int i = 0; i < numberOfOutputValues; i++) + { + PDRange range = getRangeForOutput(i); + PDRange decodeValues = getDecodeForParameter(i); + outputValues[i] = interpolate(outputValues[i], 0, maxSample, decodeValues.getMin(), + decodeValues.getMax()); + outputValues[i] = clipToRange(outputValues[i], range.getMin(), range.getMax()); + } + + return outputValues; + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunctionType3.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunctionType3.java index 5b60dd8d2..e92dca8fa 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunctionType3.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/PDFunctionType3.java @@ -104,7 +104,7 @@ public float[] eval(float[] input) throws IOException } if (function == null) { - throw new IOException("partition not found in type 3 function"); + throw new IOException("partition not found in type 3 function"); } float[] functionValues = new float[]{x}; // calculate the output values using the chosen function diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/type4/Parser.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/type4/Parser.java index afe565580..9a1ed4ca2 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/type4/Parser.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/common/function/type4/Parser.java @@ -24,7 +24,7 @@ public final class Parser { /** Used to indicate the parsers current state. */ - private enum State + private static enum State { NEWLINE, WHITESPACE, COMMENT, TOKEN } @@ -86,21 +86,21 @@ public abstract static class AbstractSyntaxHandler implements SyntaxHandler { /** {@inheritDoc} */ - @Override + @Override public void comment(CharSequence text) { //nop } /** {@inheritDoc} */ - @Override + @Override public void newLine(CharSequence text) { //nop } /** {@inheritDoc} */ - @Override + @Override public void whitespace(CharSequence text) { //nop @@ -223,8 +223,8 @@ private void scanNewLine() buffer.append(ch); if (ch == CR && peek() == LF) { - //CRLF is treated as one newline - buffer.append(nextChar()); + //CRLF is treated as one newline + buffer.append(nextChar()); } handler.newLine(buffer); nextChar(); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDMarkedContentReference.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDMarkedContentReference.java index bd04d6bd9..720f263ea 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDMarkedContentReference.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDMarkedContentReference.java @@ -23,8 +23,8 @@ /** * A marked-content reference. - * - * @author Ben Litchfield + * + * @author Johannes Koch */ public class PDMarkedContentReference implements COSObjectable { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDObjectReference.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDObjectReference.java index 807e7d42f..a11d5d3f5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDObjectReference.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDObjectReference.java @@ -28,8 +28,8 @@ /** * An object reference. - * - * @author Ben Litchfield + * + * @author Johannes Koch */ public class PDObjectReference implements COSObjectable { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureElement.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureElement.java index 8d64fadd8..bf39a5a18 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureElement.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureElement.java @@ -220,7 +220,7 @@ public void setAttributes(Revisions attributes) int revisionNumber = attributes.getRevisionNumber(i); if (revisionNumber < 0) { - throw new IllegalArgumentException("The revision number shall be > -1"); + throw new IllegalArgumentException("The revision number shall be > -1"); } array.add(attributeObject); array.add(COSInteger.get(revisionNumber)); @@ -386,7 +386,7 @@ public void setClassNames(Revisions classNames) int revisionNumber = classNames.getRevisionNumber(i); if (revisionNumber < 0) { - throw new IllegalArgumentException("The revision number shall be > -1"); + throw new IllegalArgumentException("The revision number shall be > -1"); } array.add(COSName.getPDFName(className)); array.add(COSInteger.get(revisionNumber)); @@ -482,7 +482,7 @@ public void setRevisionNumber(int revisionNumber) { if (revisionNumber < 0) { - throw new IllegalArgumentException("The revision number shall be > -1"); + throw new IllegalArgumentException("The revision number shall be > -1"); } this.getCOSObject().setInt(COSName.R, revisionNumber); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureTreeRoot.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureTreeRoot.java index a98ca2e81..177d7c16a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureTreeRoot.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureTreeRoot.java @@ -193,7 +193,7 @@ public Map getRoleMap() } catch (IOException e) { - Log.e("PdfBox-Android", e.getMessage(),e); + Log.e("PdfBox-Android", e.getMessage(), e); } } return new Hashtable(); @@ -209,7 +209,7 @@ public void setRoleMap(Map roleMap) COSDictionary rmDic = new COSDictionary(); for (Map.Entry entry : roleMap.entrySet()) { - rmDic.setName(entry.getKey(), entry.getValue()); + rmDic.setName(entry.getKey(), entry.getValue()); } this.getCOSObject().setItem(COSName.ROLE_MAP, rmDic); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDUserProperty.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDUserProperty.java index 6896327ae..ce2b6adbe 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDUserProperty.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/logicalstructure/PDUserProperty.java @@ -186,38 +186,40 @@ private boolean isEntryChanged(Object oldEntry, Object newEntry) @Override public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result - + ((userAttributeObject == null) ? 0 : userAttributeObject.hashCode()); - return result; + final int prime = 31; + int result = super.hashCode(); + result = + prime * result + ((userAttributeObject == null) ? 0 : userAttributeObject.hashCode()); + return result; } @Override public boolean equals(Object obj) { - if (this == obj) - { - return true; - } - if (!super.equals(obj)) - { - return false; - } - if (getClass() != obj.getClass()) - { - return false; - } - PDUserProperty other = (PDUserProperty) obj; - if (userAttributeObject == null) - { - if (other.userAttributeObject != null) - return false; - } - else if (!userAttributeObject.equals(other.userAttributeObject)) - { - return false; - } - return true; + if (this == obj) + { + return true; + } + if (!super.equals(obj)) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + PDUserProperty other = (PDUserProperty)obj; + if (userAttributeObject == null) + { + if (other.userAttributeObject != null) + { + return false; + } + } + else if (!userAttributeObject.equals(other.userAttributeObject)) + { + return false; + } + return true; } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/markedcontent/PDMarkedContent.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/markedcontent/PDMarkedContent.java index 01824d717..5140f5e28 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/markedcontent/PDMarkedContent.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/markedcontent/PDMarkedContent.java @@ -91,12 +91,12 @@ public COSDictionary getProperties() /** * Gets the marked-content identifier. - * - * @return the marked-content identifier + * + * @return the marked-content identifier, or -1 if it doesn't exist. */ public int getMCID() { - return this.getProperties() == null ? null : + return this.getProperties() == null ? -1 : this.getProperties().getInt(COSName.MCID); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/prepress/PDBoxStyle.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/prepress/PDBoxStyle.java index 6db3effe7..da8704bbc 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/prepress/PDBoxStyle.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/documentinterchange/prepress/PDBoxStyle.java @@ -30,7 +30,7 @@ * * @author Ben Litchfield */ -public final class PDBoxStyle implements COSObjectable +public class PDBoxStyle implements COSObjectable { /** * Style for guideline. @@ -41,7 +41,7 @@ public final class PDBoxStyle implements COSObjectable */ public static final String GUIDELINE_STYLE_DASHED = "D"; - private COSDictionary dictionary; + private final COSDictionary dictionary; /** * Default Constructor. @@ -88,7 +88,7 @@ public PDColor getGuidelineColor() colorValues.add( COSInteger.ZERO ); colorValues.add( COSInteger.ZERO ); colorValues.add( COSInteger.ZERO ); - dictionary.setItem( "C", colorValues ); + dictionary.setItem(COSName.C, colorValues); } PDColor color = new PDColor(colorValues.toFloatArray(), PDDeviceRGB.INSTANCE); return color; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java index e64ee75a2..7ae2539a9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java @@ -16,8 +16,6 @@ */ package com.tom_roush.pdfbox.pdmodel.encryption; -import java.io.IOException; - import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; @@ -87,10 +85,8 @@ public int getLength() * Allowed values are: NONE, V2, AESV2, AESV3 * * @param cfm name of the crypt filter method. - * - * @throws IOException If there is an error setting the data. */ - public void setCryptFilterMethod(COSName cfm) throws IOException + public void setCryptFilterMethod(COSName cfm) { cryptFilterDictionary.setItem( COSName.CFM, cfm ); } @@ -100,10 +96,8 @@ public void setCryptFilterMethod(COSName cfm) throws IOException * Allowed values are: NONE, V2, AESV2, AESV3 * * @return the name of the crypt filter method. - * - * @throws IOException If there is an error accessing the data. */ - public COSName getCryptFilterMethod() throws IOException + public COSName getCryptFilterMethod() { return (COSName)cryptFilterDictionary.getDictionaryObject( COSName.CFM ); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PDEncryption.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PDEncryption.java index 7a6bb50de..3225abde5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PDEncryption.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PDEncryption.java @@ -17,6 +17,8 @@ package com.tom_roush.pdfbox.pdmodel.encryption; +import java.io.IOException; + import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSBoolean; @@ -24,8 +26,6 @@ import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSString; -import java.io.IOException; - /** * This class is a specialized view of the encryption dictionary of a PDF document. * It contains a low level dictionary (COSDictionary) and provides the methods to @@ -258,7 +258,7 @@ public int getRevision() */ public void setOwnerKey(byte[] o) throws IOException { - dictionary.setItem(COSName.O, new COSString(o)); + dictionary.setItem(COSName.O, new COSString(o)); } /** @@ -288,7 +288,7 @@ public byte[] getOwnerKey() throws IOException */ public void setUserKey(byte[] u) throws IOException { - dictionary.setItem(COSName.U, new COSString(u)); + dictionary.setItem(COSName.U, new COSString(u)); } /** @@ -318,7 +318,7 @@ public byte[] getUserKey() throws IOException */ public void setOwnerEncryptionKey(byte[] oe) throws IOException { - dictionary.setItem( COSName.OE, new COSString(oe) ); + dictionary.setItem(COSName.OE, new COSString(oe)); } /** @@ -348,7 +348,7 @@ public byte[] getOwnerEncryptionKey() throws IOException */ public void setUserEncryptionKey(byte[] ue) throws IOException { - dictionary.setItem( COSName.UE, new COSString(ue) ); + dictionary.setItem(COSName.UE, new COSString(ue)); } /** @@ -420,7 +420,7 @@ public void setRecipients(byte[][] recipients) throws IOException COSArray array = new COSArray(); for (byte[] recipient : recipients) { - COSString recip = new COSString(recipient); + COSString recip = new COSString(recipient); array.add(recip); } dictionary.setItem(COSName.RECIPIENTS, array); @@ -570,7 +570,7 @@ public void setStringFilterName(COSName stringFilterName) */ public void setPerms(byte[] perms) throws IOException { - dictionary.setItem( COSName.PERMS, new COSString(perms) ); + dictionary.setItem(COSName.PERMS, new COSString(perms)); } /** @@ -600,4 +600,4 @@ public void removeV45filters() dictionary.setItem(COSName.STM_F, null); dictionary.setItem(COSName.STR_F, null); } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeyDecryptionMaterial.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeyDecryptionMaterial.java index f7ab0ed50..6d1a57232 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeyDecryptionMaterial.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeyDecryptionMaterial.java @@ -35,21 +35,6 @@ *
  • the password to decrypt the private key if necessary
  • * * - * Objects of this class can be used with the openProtection method of PDDocument. - * - * The following example shows how to decrypt a document using a PKCS#12 certificate - * (typically files with a pfx extension). - * - *
    - * PDDocument doc = PDDocument.load(document_path);
    - * KeyStore ks = KeyStore.getInstance("PKCS12");
    - * ks.load(new FileInputStream(certificate_path), password.toCharArray());
    - * PublicKeyDecryptionMaterial dm = new PublicKeyDecryptionMaterial(ks, null, password);
    - * doc.openProtection(dm);
    - * 
    - * - * In this code sample certificate_path contains the path to the PKCS#12 certificate. - * * @author Benoit Guillon */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java index fbdf11031..74a4c2859 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java @@ -174,7 +174,8 @@ public void prepareForDecryption(PDEncryption encryption, COSArray documentIDArr { foundRecipient = true; PrivateKey privateKey = (PrivateKey) material.getPrivateKey(); - envelopedData = ri.getContent(new JceKeyTransEnvelopedRecipient(privateKey).setProvider(BouncyCastleProvider.PROVIDER_NAME)); + envelopedData = ri.getContent(new JceKeyTransEnvelopedRecipient(privateKey).setProvider( + BouncyCastleProvider.PROVIDER_NAME)); break; } j++; @@ -325,9 +326,7 @@ public void prepareDocumentForEncryption(PDDocument doc) throws IOException SecretKey sk = key.generateKey(); System.arraycopy(sk.getEncoded(), 0, seed, 0, 20); // create the 20 bytes seed - byte[][] recipientsField = computeRecipientsField(seed); - dictionary.setRecipients(recipientsField); int sha1InputLength = seed.length; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandler.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandler.java index b74858794..3f02c4e51 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandler.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandler.java @@ -18,15 +18,6 @@ import android.util.Log; -import com.tom_roush.pdfbox.cos.COSArray; -import com.tom_roush.pdfbox.cos.COSBase; -import com.tom_roush.pdfbox.cos.COSDictionary; -import com.tom_roush.pdfbox.cos.COSName; -import com.tom_roush.pdfbox.cos.COSStream; -import com.tom_roush.pdfbox.cos.COSString; -import com.tom_roush.pdfbox.io.IOUtils; -import com.tom_roush.pdfbox.pdmodel.PDDocument; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -38,6 +29,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -51,6 +43,16 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.cos.COSStream; +import com.tom_roush.pdfbox.cos.COSString; +import com.tom_roush.pdfbox.io.IOUtils; +import com.tom_roush.pdfbox.pdmodel.PDDocument; +import com.tom_roush.pdfbox.util.Charsets; + /** * A security handler as described in the PDF specifications. * A security handler is responsible of documents protection. @@ -66,7 +68,9 @@ public abstract class SecurityHandler // see 7.6.2, page 58, PDF 32000-1:2008 private static final byte[] AES_SALT = { (byte) 0x73, (byte) 0x41, (byte) 0x6c, (byte) 0x54 }; - /** The length of the secret key used to encrypt the document. */ + /** + * The length in bits of the secret key used to encrypt the document. + */ protected int keyLength = DEFAULT_KEY_LENGTH; /** The encryption key that will used to encrypt / decrypt.*/ @@ -140,11 +144,6 @@ private void encryptData(long objectNumber, long genNumber, InputStream data, } else { - if (useAES && !decrypt) - { - throw new IllegalArgumentException("AES encryption with key length other than 256 bits is not yet implemented."); - } - byte[] finalKey = calcFinalKey(objectNumber, genNumber); if (useAES) @@ -200,6 +199,7 @@ private byte[] calcFinalKey(long objectNumber, long genNumber) * @param finalKey The final key obtained with via {@link #calcFinalKey(long, long)}. * @param input The data to encrypt. * @param output The output to write the encrypted data to. + * * @throws IOException If there is an error reading the data. */ protected void encryptDataRC4(byte[] finalKey, InputStream input, OutputStream output) @@ -215,6 +215,7 @@ protected void encryptDataRC4(byte[] finalKey, InputStream input, OutputStream o * @param finalKey The final key obtained with via {@link #calcFinalKey(long, long)}. * @param input The data to encrypt. * @param output The output to write the encrypted data to. + * * @throws IOException If there is an error reading the data. */ protected void encryptDataRC4(byte[] finalKey, byte[] input, OutputStream output) @@ -239,12 +240,9 @@ private void encryptDataAESother(byte[] finalKey, InputStream data, OutputStream { byte[] iv = new byte[16]; - int ivSize = data.read(iv); - if (ivSize != iv.length) + if (!prepareAESInitializationVector(decrypt, iv, data, output)) { - throw new IOException( - "AES initialization vector not fully read: only " - + ivSize + " bytes read instead of " + iv.length); + return; } try @@ -306,17 +304,9 @@ private void encryptDataAES256(InputStream data, OutputStream output, boolean de { byte[] iv = new byte[16]; - if (decrypt) - { - // read IV from stream - data.read(iv); - } - else + if (!prepareAESInitializationVector(decrypt, iv, data, output)) { - // generate random IV and write to stream - SecureRandom rnd = new SecureRandom(); - rnd.nextBytes(iv); - output.write(iv); + return; } Cipher cipher; @@ -353,6 +343,33 @@ private void encryptDataAES256(InputStream data, OutputStream output, boolean de } } + private boolean prepareAESInitializationVector(boolean decrypt, byte[] iv, InputStream data, + OutputStream output) throws IOException + { + if (decrypt) + { + // read IV from stream + int ivSize = data.read(iv); + if (ivSize == -1) + { + return false; + } + if (ivSize != iv.length) + { + throw new IOException("AES initialization vector not fully read: only " + ivSize + + " bytes read instead of " + iv.length); + } + } + else + { + // generate random IV and write to stream + SecureRandom rnd = new SecureRandom(); + rnd.nextBytes(iv); + output.write(iv); + } + return true; + } + /** * This will dispatch to the correct method. * @@ -398,15 +415,31 @@ else if (obj instanceof COSArray) */ public void decryptStream(COSStream stream, long objNum, long genNum) throws IOException { - if (!decryptMetadata && COSName.METADATA.equals(stream.getCOSName(COSName.TYPE))) + COSBase type = stream.getCOSName(COSName.TYPE); + if (!decryptMetadata && COSName.METADATA.equals(type)) { return; } // "The cross-reference stream shall not be encrypted" - if (COSName.XREF.equals(stream.getCOSName(COSName.TYPE))) + if (COSName.XREF.equals(type)) { return; } + if (COSName.METADATA.equals(type)) + { + // PDFBOX-3229 check case where metadata is not encrypted despite /EncryptMetadata missing + InputStream is = stream.createRawInputStream(); + byte buf[] = new byte[10]; + is.read(buf); + is.close(); + if (Arrays.equals(buf, " entry : dictionary.entrySet()) { - for (Map.Entry entry : dictionary.entrySet()) + if (isSignature && COSName.CONTENTS.equals(entry.getKey())) + { + // do not decrypt the signature contents string + continue; + } + COSBase value = entry.getValue(); + // within a dictionary only the following kind of COS objects have to be decrypted + if (value instanceof COSString || value instanceof COSArray || + value instanceof COSDictionary) { - COSBase value = entry.getValue(); - // within a dictionary only the following kind of COS objects have to be decrypted - if (value instanceof COSString || value instanceof COSArray || value instanceof COSDictionary) - { - decrypt(value, objNum, genNum); - } + decrypt(value, objNum, genNum); } } } @@ -491,9 +527,18 @@ private void decryptDictionary(COSDictionary dictionary, long objNum, long genNu private void decryptString(COSString string, long objNum, long genNum) throws IOException { ByteArrayInputStream data = new ByteArrayInputStream(string.getBytes()); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - encryptData(objNum, genNum, data, buffer, true /* decrypt */); - string.setValue(buffer.toByteArray()); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try + { + encryptData(objNum, genNum, data, outputStream, true /* decrypt */); + string.setValue(outputStream.toByteArray()); + } + catch (IOException ex) + { + Log.e("PdfBox-Android", + "Failed to decrypt COSString of length " + string.getBytes().length + + " in object " + objNum + ": " + ex.getMessage()); + } } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandlerFactory.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandlerFactory.java index 128b64f40..8a8dbd86a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandlerFactory.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/SecurityHandlerFactory.java @@ -115,7 +115,7 @@ public SecurityHandler newSecurityHandlerForFilter(String name) Class handlerClass = nameToHandler.get(name); if (handlerClass == null) { - return null; + return null; } Class[] argsClasses = { }; @@ -129,14 +129,13 @@ public SecurityHandler newSecurityHandlerForFilter(String name) * @param argsClasses the parameter array. * @param args array of objects to be passed as arguments to the constructor call. * @return a new SecurityHandler instance, or null if none is available. - * @throws RuntimeException */ private SecurityHandler newSecurityHandler(Class handlerClass, - Class[] argsClasses, Object[] args) + Class[] argsClasses, Object[] args) { - try - { - Constructor ctor = + try + { + Constructor ctor = handlerClass.getDeclaredConstructor(argsClasses); return ctor.newInstance(args); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/StandardDecryptionMaterial.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/StandardDecryptionMaterial.java index 6344b01c4..d8408b1b8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/StandardDecryptionMaterial.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/StandardDecryptionMaterial.java @@ -23,15 +23,6 @@ * * This is only composed of a password. * - * The following example shows how to decrypt a document protected with - * the standard security handler: - * - *
    - *  PDDocument doc = PDDocument.load(in);
    - *  StandardDecryptionMaterial dm = new StandardDecryptionMaterial("password");
    - *  doc.openProtection(dm);
    - *  
    - * * @author Benoit Guillon */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/StandardSecurityHandler.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/StandardSecurityHandler.java index d0fcbbbf9..a202bc4cd 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/StandardSecurityHandler.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/StandardSecurityHandler.java @@ -103,6 +103,7 @@ private int computeVersionNumber() { return DEFAULT_VERSION; } + //TODO return 4 if keyLength is 128 to enable AES128 functionality else if(keyLength == 256) { return 5; @@ -131,6 +132,10 @@ private int computeRevisionNumber(int version) // note about revision 5: "Shall not be used. This value was used by a deprecated Adobe extension." return 6; } + if (version == 4) + { + return 4; + } if ( version == 2 || version == 3 || policy.getPermissions().hasAnyRevision3PermissionSet()) { return 3; @@ -159,7 +164,6 @@ public void prepareForDecryption(PDEncryption encryption, COSArray documentIDArr throw new IOException("Decryption material is not compatible with the document"); } setDecryptMetadata(encryption.isEncryptMetaData()); - StandardDecryptionMaterial material = (StandardDecryptionMaterial)decryptionMaterial; String password = material.getPassword(); @@ -225,6 +229,7 @@ else if( isUserPassword(password.getBytes(passwordCharset), userKey, ownerKey, { currentAccessPermission = new AccessPermission(dicPermissions); setCurrentAccessPermission(currentAccessPermission); + encryptionKey = computeEncryptedKey( password.getBytes(passwordCharset), @@ -255,11 +260,8 @@ else if( isUserPassword(password.getBytes(passwordCharset), userKey, ownerKey, if (stdCryptFilterDictionary != null) { COSName cryptFilterMethod = stdCryptFilterDictionary.getCryptFilterMethod(); - if (cryptFilterMethod != null) - { - setAES("AESV2".equalsIgnoreCase(cryptFilterMethod.getName()) - || "AESV3".equalsIgnoreCase(cryptFilterMethod.getName())); - } + setAES(COSName.AESV2.equals(cryptFilterMethod) || + COSName.AESV3.equals(cryptFilterMethod)); } } } @@ -282,26 +284,33 @@ private byte[] getDocumentIDBytes(COSArray documentIDArray) } // Algorithm 13: validate permissions ("Perms" field). Relaxed to accomodate buggy encoders + // https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/adobe_supplement_iso32000.pdf private void validatePerms(PDEncryption encryption, int dicPermissions, boolean encryptMetadata) throws IOException { try { + // "Decrypt the 16-byte Perms string using AES-256 in ECB mode with an + // initialization vector of zero and the file encryption key as the key." Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(encryptionKey, "AES")); byte[] perms = cipher.doFinal(encryption.getPerms()); + // "Verify that bytes 9-11 of the result are the characters ‘a’, ‘d’, ‘b’." if (perms[9] != 'a' || perms[10] != 'd' || perms[11] != 'b') { Log.w("PdfBox-Android", "Verification of permissions failed (constant)"); } - int permsP = perms[0] & 0xFF | perms[1] & 0xFF << 8 | perms[2] & 0xFF << 16 | - perms[3] & 0xFF << 24; + // "Bytes 0-3 of the decrypted Perms entry, treated as a little-endian integer, + // are the user permissions. They should match the value in the P key." + int permsP = perms[0] & 0xFF | (perms[1] & 0xFF) << 8 | (perms[2] & 0xFF) << 16 | + (perms[3] & 0xFF) << 24; if (permsP != dicPermissions) { - Log.w("PdfBox-Android", "Verification of permissions failed (" + permsP + - " != " + dicPermissions + ")"); + Log.w("PdfBox-Android", + "Verification of permissions failed (" + String.format("%08X", permsP) + + " != " + String.format("%08X", dicPermissions) + ")"); } if (encryptMetadata && perms[8] != 'T' || !encryptMetadata && perms[8] != 'F') @@ -323,6 +332,7 @@ private void validatePerms(PDEncryption encryption, int dicPermissions, boolean * * @throws IOException If there is an error accessing data. */ + @Override public void prepareDocumentForEncryption(PDDocument document) throws IOException { PDEncryption encryptionDictionary = document.getEncryption(); @@ -432,13 +442,7 @@ private void prepareEncryptionDictRev6(String ownerPassword, String userPassword encryptionDictionary.setOwnerKey(o); encryptionDictionary.setOwnerEncryptionKey(oe); - PDCryptFilterDictionary cryptFilterDictionary = new PDCryptFilterDictionary(); - cryptFilterDictionary.setCryptFilterMethod(COSName.AESV3); - cryptFilterDictionary.setLength(keyLength); - encryptionDictionary.setStdCryptFilterDictionary(cryptFilterDictionary); - encryptionDictionary.setStreamFilterName(COSName.STD_CF); - encryptionDictionary.setStringFilterName(COSName.STD_CF); - setAES(true); + prepareEncryptionDictAES(encryptionDictionary, COSName.AESV3); // Algorithm 10: compute "Perms" value byte[] perms = new byte[16]; @@ -458,6 +462,13 @@ private void prepareEncryptionDictRev6(String ownerPassword, String userPassword { perms[i] = (byte) rnd.nextInt(); } + + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKey, "AES"), + new IvParameterSpec(new byte[16])); + + byte[] permsEnc = cipher.doFinal(perms); + + encryptionDictionary.setPerms(permsEnc); } catch (GeneralSecurityException e) { @@ -471,6 +482,7 @@ private void prepareEncryptionDictRev2345(String ownerPassword, String userPassw int revision, int length) throws IOException { COSArray idArray = document.getDocument().getDocumentID(); + //check if the document has an id yet. If it does not then generate one if (idArray == null || idArray.size() < 2) { @@ -492,12 +504,10 @@ private void prepareEncryptionDictRev2345(String ownerPassword, String userPassw COSString id = (COSString) idArray.getObject(0); - byte[] ownerBytes = computeOwnerPassword( - ownerPassword.getBytes(Charsets.ISO_8859_1), + byte[] ownerBytes = computeOwnerPassword(ownerPassword.getBytes(Charsets.ISO_8859_1), userPassword.getBytes(Charsets.ISO_8859_1), revision, length); - byte[] userBytes = computeUserPassword( - userPassword.getBytes(Charsets.ISO_8859_1), + byte[] userBytes = computeUserPassword(userPassword.getBytes(Charsets.ISO_8859_1), ownerBytes, permissionInt, id.getBytes(), revision, length, true); encryptionKey = computeEncryptedKey(userPassword.getBytes(Charsets.ISO_8859_1), ownerBytes, @@ -505,6 +515,22 @@ private void prepareEncryptionDictRev2345(String ownerPassword, String userPassw encryptionDictionary.setOwnerKey(ownerBytes); encryptionDictionary.setUserKey(userBytes); + + if (revision == 4) + { + prepareEncryptionDictAES(encryptionDictionary, COSName.AESV2); + } + } + + private void prepareEncryptionDictAES(PDEncryption encryptionDictionary, COSName aesVName) + { + PDCryptFilterDictionary cryptFilterDictionary = new PDCryptFilterDictionary(); + cryptFilterDictionary.setCryptFilterMethod(aesVName); + cryptFilterDictionary.setLength(keyLength); + encryptionDictionary.setStdCryptFilterDictionary(cryptFilterDictionary); + encryptionDictionary.setStreamFilterName(COSName.STD_CF); + encryptionDictionary.setStringFilterName(COSName.STD_CF); + setAES(true); } /** @@ -572,7 +598,6 @@ public byte[] getUserPassword( byte[] ownerPassword, byte[] owner, int encRevis int length ) throws IOException { ByteArrayOutputStream result = new ByteArrayOutputStream(); - byte[] rc4Key = computeRC4key(ownerPassword, encRevision, length); if( encRevision == 2 ) @@ -624,7 +649,6 @@ public byte[] computeEncryptedKey(byte[] password, byte[] o, byte[] u, byte[] oe boolean encryptMetadata, boolean isOwnerPassword) throws IOException { - if (encRevision == 6 || encRevision == 5) { return computeEncryptedKeyRev56(password, isOwnerPassword, o, u, oe, ue, encRevision); @@ -807,7 +831,6 @@ public byte[] computeOwnerPassword(byte[] ownerPassword, byte[] userPassword, } byte[] rc4Key = computeRC4key(ownerPassword, encRevision, length); - byte[] paddedUser = truncateOrPad( userPassword ); ByteArrayOutputStream encrypted = new ByteArrayOutputStream(); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotation.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotation.java index d6714ecdd..a6fdec2b9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotation.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotation.java @@ -90,103 +90,103 @@ public abstract class FDFAnnotation implements COSObjectable */ private static final int FLAG_TOGGLE_NO_VIEW = 1 << 8; - /** - * Annotation dictionary. - */ - protected COSDictionary annot; - - /** - * Default constructor. - */ - public FDFAnnotation() - { - annot = new COSDictionary(); - annot.setItem( COSName.TYPE, COSName.ANNOT ); - } - - /** - * Constructor. - * - * @param a The FDF annotation. - */ - public FDFAnnotation( COSDictionary a ) - { - annot = a; - } - - /** - * Constructor. - * - * @param element An XFDF element. - * - * @throws IOException If there is an error extracting data from the element. - */ - public FDFAnnotation( Element element ) throws IOException - { - this(); - - String page = element.getAttribute( "page" ); + /** + * Annotation dictionary. + */ + protected COSDictionary annot; + + /** + * Default constructor. + */ + public FDFAnnotation() + { + annot = new COSDictionary(); + annot.setItem(COSName.TYPE, COSName.ANNOT); + } + + /** + * Constructor. + * + * @param a The FDF annotation. + */ + public FDFAnnotation(COSDictionary a) + { + annot = a; + } + + /** + * Constructor. + * + * @param element An XFDF element. + * + * @throws IOException If there is an error extracting data from the element. + */ + public FDFAnnotation(Element element) throws IOException + { + this(); + + String page = element.getAttribute("page"); if (page == null || page.isEmpty()) { throw new IOException("Error: missing required attribute 'page'"); } setPage(Integer.parseInt(page)); - String color = element.getAttribute( "color" ); - if( color != null && color.length() == 7 && color.charAt( 0 ) == '#' ) - { - int colorValue = Integer.parseInt(color.substring(1,7), 16); + String color = element.getAttribute("color"); + if (color != null && color.length() == 7 && color.charAt(0) == '#') + { + int colorValue = Integer.parseInt(color.substring(1, 7), 16); setColor(new AWTColor(colorValue)); } - setDate( element.getAttribute( "date" ) ); - - String flags = element.getAttribute( "flags" ); - if( flags != null ) - { - String[] flagTokens = flags.split( "," ); - for (String flagToken : flagTokens) - { - if (flagToken.equals("invisible")) - { - setInvisible( true ); - } - else if (flagToken.equals("hidden")) - { - setHidden( true ); - } - else if (flagToken.equals("print")) - { - setPrinted( true ); - } - else if (flagToken.equals("nozoom")) - { - setNoZoom( true ); - } - else if (flagToken.equals("norotate")) - { - setNoRotate( true ); - } - else if (flagToken.equals("noview")) - { - setNoView( true ); - } - else if (flagToken.equals("readonly")) - { - setReadOnly( true ); - } - else if (flagToken.equals("locked")) - { - setLocked( true ); - } - else if (flagToken.equals("togglenoview")) - { - setToggleNoView( true ); - } - } - } - - setName( element.getAttribute( "name" ) ); + setDate(element.getAttribute("date")); + + String flags = element.getAttribute("flags"); + if (flags != null) + { + String[] flagTokens = flags.split(","); + for (String flagToken : flagTokens) + { + if (flagToken.equals("invisible")) + { + setInvisible(true); + } + else if (flagToken.equals("hidden")) + { + setHidden(true); + } + else if (flagToken.equals("print")) + { + setPrinted(true); + } + else if (flagToken.equals("nozoom")) + { + setNoZoom(true); + } + else if (flagToken.equals("norotate")) + { + setNoRotate(true); + } + else if (flagToken.equals("noview")) + { + setNoView(true); + } + else if (flagToken.equals("readonly")) + { + setReadOnly(true); + } + else if (flagToken.equals("locked")) + { + setLocked(true); + } + else if (flagToken.equals("togglenoview")) + { + setToggleNoView(true); + } + } + } + + setName(element.getAttribute("name")); String rect = element.getAttribute("rect"); if (rect == null) @@ -219,7 +219,14 @@ else if (flagToken.equals("togglenoview")) setOpacity(Float.parseFloat(opac)); } setSubject(element.getAttribute("subject")); - setIntent(element.getAttribute("intent")); + + String intent = element.getAttribute("intent"); + if (intent.isEmpty()) + { + // not conforming to spec, but qoppa produces it and Adobe accepts it + intent = element.getAttribute("IT"); + } + setIntent(intent); XPath xpath = XPathFactory.newInstance().newXPath(); try @@ -306,26 +313,25 @@ else if (style.equals("cloudy")) } setBorderStyle(borderStyle); } - } - - /** - * Create the correct FDFAnnotation. - * - * @param fdfDic The FDF dictionary. - * - * @return A newly created FDFAnnotation - * - * @throws IOException If there is an error accessing the FDF information. - */ - public static FDFAnnotation create( COSDictionary fdfDic ) throws IOException - { - FDFAnnotation retval = null; + } + + /** + * Create the correct FDFAnnotation. + * + * @param fdfDic The FDF dictionary. + * + * @return A newly created FDFAnnotation + * @throws IOException If there is an error accessing the FDF information. + */ + public static FDFAnnotation create(COSDictionary fdfDic) throws IOException + { + FDFAnnotation retval = null; if (fdfDic != null) { if (FDFAnnotationText.SUBTYPE.equals(fdfDic.getNameAsString(COSName.SUBTYPE))) { - retval = new FDFAnnotationText( fdfDic ); - } + retval = new FDFAnnotationText(fdfDic); + } else if (FDFAnnotationCaret.SUBTYPE.equals(fdfDic.getNameAsString(COSName.SUBTYPE))) { retval = new FDFAnnotationCaret(fdfDic); @@ -350,11 +356,11 @@ else if (FDFAnnotationLine.SUBTYPE.equals(fdfDic.getNameAsString(COSName.SUBTYPE { retval = new FDFAnnotationLine(fdfDic); } - else if (FDFAnnotationLink.SUBTYPE.equals(fdfDic.getNameAsString(COSName.SUBTYPE))) - { - retval = new FDFAnnotationLink(fdfDic); - } - else if (FDFAnnotationCircle.SUBTYPE.equals(fdfDic.getNameAsString(COSName.SUBTYPE))) + else if (FDFAnnotationLink.SUBTYPE.equals(fdfDic.getNameAsString(COSName.SUBTYPE))) + { + retval = new FDFAnnotationLink(fdfDic); + } + else if (FDFAnnotationCircle.SUBTYPE.equals(fdfDic.getNameAsString(COSName.SUBTYPE))) { retval = new FDFAnnotationCircle(fdfDic); } @@ -391,55 +397,55 @@ else if (FDFAnnotationUnderline.SUBTYPE.equals(fdfDic.getNameAsString(COSName.SU retval = new FDFAnnotationUnderline(fdfDic); } else - { + { Log.w("PdfBox-Android", "Unknown or unsupported annotation type '" + fdfDic.getNameAsString(COSName.SUBTYPE) + "'"); } - } - return retval; - } - - /** - * Convert this standard java object to a COS object. - * - * @return The cos object that matches this Java object. - */ + } + return retval; + } + + /** + * Convert this standard java object to a COS object. + * + * @return The cos object that matches this Java object. + */ @Override public COSDictionary getCOSObject() { return annot; - } - - /** - * This will get the page number or null if it does not exist. - * - * @return The page number. - */ - public Integer getPage() - { - Integer retval = null; - COSNumber page = (COSNumber)annot.getDictionaryObject( COSName.PAGE ); - if( page != null ) - { - retval = page.intValue(); - } - return retval; - } - - /** - * This will set the page. - * - * @param page The page number. - */ - public void setPage( int page ) - { + } + + /** + * This will get the page number or null if it does not exist. + * + * @return The page number. + */ + public Integer getPage() + { + Integer retval = null; + COSNumber page = (COSNumber)annot.getDictionaryObject(COSName.PAGE); + if (page != null) + { + retval = page.intValue(); + } + return retval; + } + + /** + * This will set the page. + * + * @param page The page number. + */ + public final void setPage(int page) + { annot.setInt(COSName.PAGE, page); } - /** - * Get the annotation color. - * - * @return The annotation color, or null if there is none. - */ + /** + * Get the annotation color. + * + * @return The annotation color, or null if there is none. + */ public AWTColor getColor() { AWTColor retval = null; @@ -455,12 +461,12 @@ public AWTColor getColor() return retval; } - /** - * Set the annotation color. - * - * @param c The annotation color. - */ - public void setColor(AWTColor c) + /** + * Set the annotation color. + * + * @param c The annotation color. + */ + public final void setColor(AWTColor c) { COSArray color = null; if (c != null) @@ -472,259 +478,259 @@ public void setColor(AWTColor c) annot.setItem(COSName.C, color); } - /** - * Modification date. - * - * @return The date as a string. - */ - public String getDate() - { + /** + * Modification date. + * + * @return The date as a string. + */ + public String getDate() + { return annot.getString(COSName.M); } - /** - * The annotation modification date. - * - * @param date The date to store in the FDF annotation. - */ - public void setDate( String date ) - { + /** + * The annotation modification date. + * + * @param date The date to store in the FDF annotation. + */ + public final void setDate(String date) + { annot.setString(COSName.M, date); } - /** - * Get the invisible flag. - * - * @return The invisible flag. - */ - public boolean isInvisible() - { + /** + * Get the invisible flag. + * + * @return The invisible flag. + */ + public boolean isInvisible() + { return annot.getFlag(COSName.F, FLAG_INVISIBLE); } - /** - * Set the invisible flag. - * - * @param invisible The new invisible flag. - */ - public void setInvisible( boolean invisible ) - { + /** + * Set the invisible flag. + * + * @param invisible The new invisible flag. + */ + public final void setInvisible(boolean invisible) + { annot.setFlag(COSName.F, FLAG_INVISIBLE, invisible); } - /** - * Get the hidden flag. - * - * @return The hidden flag. - */ - public boolean isHidden() - { + /** + * Get the hidden flag. + * + * @return The hidden flag. + */ + public boolean isHidden() + { return annot.getFlag(COSName.F, FLAG_HIDDEN); } - /** - * Set the hidden flag. - * - * @param hidden The new hidden flag. - */ - public void setHidden( boolean hidden ) - { + /** + * Set the hidden flag. + * + * @param hidden The new hidden flag. + */ + public final void setHidden(boolean hidden) + { annot.setFlag(COSName.F, FLAG_HIDDEN, hidden); } - /** - * Get the printed flag. - * - * @return The printed flag. - */ - public boolean isPrinted() - { + /** + * Get the printed flag. + * + * @return The printed flag. + */ + public boolean isPrinted() + { return annot.getFlag(COSName.F, FLAG_PRINTED); } - /** - * Set the printed flag. - * - * @param printed The new printed flag. - */ - public void setPrinted( boolean printed ) - { + /** + * Set the printed flag. + * + * @param printed The new printed flag. + */ + public final void setPrinted(boolean printed) + { annot.setFlag(COSName.F, FLAG_PRINTED, printed); } - /** - * Get the noZoom flag. - * - * @return The noZoom flag. - */ - public boolean isNoZoom() - { + /** + * Get the noZoom flag. + * + * @return The noZoom flag. + */ + public boolean isNoZoom() + { return annot.getFlag(COSName.F, FLAG_NO_ZOOM); } - /** - * Set the noZoom flag. - * - * @param noZoom The new noZoom flag. - */ - public void setNoZoom( boolean noZoom ) - { + /** + * Set the noZoom flag. + * + * @param noZoom The new noZoom flag. + */ + public final void setNoZoom(boolean noZoom) + { annot.setFlag(COSName.F, FLAG_NO_ZOOM, noZoom); } - /** - * Get the noRotate flag. - * - * @return The noRotate flag. - */ - public boolean isNoRotate() - { + /** + * Get the noRotate flag. + * + * @return The noRotate flag. + */ + public boolean isNoRotate() + { return annot.getFlag(COSName.F, FLAG_NO_ROTATE); } - /** - * Set the noRotate flag. - * - * @param noRotate The new noRotate flag. - */ - public void setNoRotate( boolean noRotate ) - { + /** + * Set the noRotate flag. + * + * @param noRotate The new noRotate flag. + */ + public final void setNoRotate(boolean noRotate) + { annot.setFlag(COSName.F, FLAG_NO_ROTATE, noRotate); } - /** - * Get the noView flag. - * - * @return The noView flag. - */ - public boolean isNoView() - { + /** + * Get the noView flag. + * + * @return The noView flag. + */ + public boolean isNoView() + { return annot.getFlag(COSName.F, FLAG_NO_VIEW); } - /** - * Set the noView flag. - * - * @param noView The new noView flag. - */ - public void setNoView( boolean noView ) - { + /** + * Set the noView flag. + * + * @param noView The new noView flag. + */ + public final void setNoView(boolean noView) + { annot.setFlag(COSName.F, FLAG_NO_VIEW, noView); } - /** - * Get the readOnly flag. - * - * @return The readOnly flag. - */ - public boolean isReadOnly() - { + /** + * Get the readOnly flag. + * + * @return The readOnly flag. + */ + public boolean isReadOnly() + { return annot.getFlag(COSName.F, FLAG_READ_ONLY); } - /** - * Set the readOnly flag. - * - * @param readOnly The new readOnly flag. - */ - public void setReadOnly( boolean readOnly ) - { + /** + * Set the readOnly flag. + * + * @param readOnly The new readOnly flag. + */ + public final void setReadOnly(boolean readOnly) + { annot.setFlag(COSName.F, FLAG_READ_ONLY, readOnly); } - /** - * Get the locked flag. - * - * @return The locked flag. - */ - public boolean isLocked() - { + /** + * Get the locked flag. + * + * @return The locked flag. + */ + public boolean isLocked() + { return annot.getFlag(COSName.F, FLAG_LOCKED); } - /** - * Set the locked flag. - * - * @param locked The new locked flag. - */ - public void setLocked( boolean locked ) - { + /** + * Set the locked flag. + * + * @param locked The new locked flag. + */ + public final void setLocked(boolean locked) + { annot.setFlag(COSName.F, FLAG_LOCKED, locked); } - /** - * Get the toggleNoView flag. - * - * @return The toggleNoView flag. - */ - public boolean isToggleNoView() - { + /** + * Get the toggleNoView flag. + * + * @return The toggleNoView flag. + */ + public boolean isToggleNoView() + { return annot.getFlag(COSName.F, FLAG_TOGGLE_NO_VIEW); } - /** - * Set the toggleNoView flag. - * - * @param toggleNoView The new toggleNoView flag. - */ - public void setToggleNoView( boolean toggleNoView ) - { + /** + * Set the toggleNoView flag. + * + * @param toggleNoView The new toggleNoView flag. + */ + public final void setToggleNoView(boolean toggleNoView) + { annot.setFlag(COSName.F, FLAG_TOGGLE_NO_VIEW, toggleNoView); } - /** - * Set a unique name for an annotation. - * - * @param name The unique annotation name. - */ - public void setName( String name ) - { - annot.setString( COSName.NM, name ); - } - - /** - * Get the annotation name. - * - * @return The unique name of the annotation. - */ - public String getName() - { - return annot.getString( COSName.NM ); - } - - /** - * Set the rectangle associated with this annotation. - * - * @param rectangle The annotation rectangle. - */ - public void setRectangle( PDRectangle rectangle ) - { - annot.setItem( COSName.RECT, rectangle ); - } - - /** - * The rectangle associated with this annotation. - * - * @return The annotation rectangle. - */ - public PDRectangle getRectangle() - { - PDRectangle retval = null; - COSArray rectArray = (COSArray)annot.getDictionaryObject( COSName.RECT ); - if( rectArray != null ) - { - retval = new PDRectangle( rectArray ); - } - - return retval; - } + /** + * Set a unique name for an annotation. + * + * @param name The unique annotation name. + */ + public final void setName(String name) + { + annot.setString(COSName.NM, name); + } + + /** + * Get the annotation name. + * + * @return The unique name of the annotation. + */ + public String getName() + { + return annot.getString(COSName.NM); + } + + /** + * Set the rectangle associated with this annotation. + * + * @param rectangle The annotation rectangle. + */ + public final void setRectangle(PDRectangle rectangle) + { + annot.setItem(COSName.RECT, rectangle); + } + + /** + * The rectangle associated with this annotation. + * + * @return The annotation rectangle. + */ + public PDRectangle getRectangle() + { + PDRectangle retval = null; + COSArray rectArray = (COSArray)annot.getDictionaryObject(COSName.RECT); + if (rectArray != null) + { + retval = new PDRectangle(rectArray); + } + + return retval; + } /** * Set the contents, or a description, for an annotation. * * @param contents The annotation contents, or a description. */ - public void setContents(String contents) + public final void setContents(String contents) { annot.setString(COSName.CONTENTS, contents); } @@ -739,97 +745,96 @@ public String getContents() return annot.getString(COSName.CONTENTS); } - /** - * Set a unique title for an annotation. - * - * @param title The annotation title. - */ - public void setTitle( String title ) - { - annot.setString( COSName.T, title ); - } - - /** - * Get the annotation title. - * - * @return The title of the annotation. - */ - public String getTitle() - { - return annot.getString( COSName.T ); - } - - /** - * The annotation create date. - * - * @return The date of the creation of the annotation date - * - * @throws IOException If there is an error converting the string to a Calendar object. - */ - public Calendar getCreationDate() throws IOException - { - return annot.getDate( COSName.CREATION_DATE ); - } - - /** - * Set the creation date. - * - * @param date The date the annotation was created. - */ - public void setCreationDate( Calendar date ) - { - annot.setDate( COSName.CREATION_DATE, date ); - } - - /** - * Set the annotation opacity. - * - * @param opacity The new opacity value. - */ - public void setOpacity( float opacity ) - { - annot.setFloat( COSName.CA, opacity ); - } - - /** - * Get the opacity value. - * - * @return The opacity of the annotation. - */ - public float getOpacity() - { - return annot.getFloat( COSName.CA, 1f ); - - } - - /** - * A short description of the annotation. - * - * @param subject The annotation subject. - */ - public void setSubject( String subject ) - { - annot.setString( COSName.SUBJ, subject ); - } - - /** - * Get the description of the annotation. - * - * @return The subject of the annotation. - */ - public String getSubject() - { - return annot.getString( COSName.SUBJ ); - } + /** + * Set a unique title for an annotation. + * + * @param title The annotation title. + */ + public final void setTitle(String title) + { + annot.setString(COSName.T, title); + } + + /** + * Get the annotation title. + * + * @return The title of the annotation. + */ + public String getTitle() + { + return annot.getString(COSName.T); + } + + /** + * The annotation create date. + * + * @return The date of the creation of the annotation date + * @throws IOException If there is an error converting the string to a Calendar object. + */ + public Calendar getCreationDate() throws IOException + { + return annot.getDate(COSName.CREATION_DATE); + } + + /** + * Set the creation date. + * + * @param date The date the annotation was created. + */ + public final void setCreationDate(Calendar date) + { + annot.setDate(COSName.CREATION_DATE, date); + } + + /** + * Set the annotation opacity. + * + * @param opacity The new opacity value. + */ + public final void setOpacity(float opacity) + { + annot.setFloat(COSName.CA, opacity); + } + + /** + * Get the opacity value. + * + * @return The opacity of the annotation. + */ + public float getOpacity() + { + return annot.getFloat(COSName.CA, 1f); + + } + + /** + * A short description of the annotation. + * + * @param subject The annotation subject. + */ + public final void setSubject(String subject) + { + annot.setString(COSName.SUBJ, subject); + } + + /** + * Get the description of the annotation. + * + * @return The subject of the annotation. + */ + public String getSubject() + { + return annot.getString(COSName.SUBJ); + } /** * The intent of the annotation. * * @param intent The annotation's intent. */ - public void setIntent(String intent) + public final void setIntent(String intent) { - annot.setString(COSName.IT, intent); + annot.setName(COSName.IT, intent); } /** @@ -839,7 +844,7 @@ public void setIntent(String intent) */ public String getIntent() { - return annot.getString(COSName.IT); + return annot.getNameAsString(COSName.IT); } /** @@ -857,7 +862,7 @@ public String getRichContents() * * @param rc the rich text stream. */ - public void setRichContents(String rc) + public final void setRichContents(String rc) { annot.setItem(COSName.RC, new COSString(rc)); } @@ -867,7 +872,7 @@ public void setRichContents(String rc) * * @param bs the border style dictionary to set. */ - public void setBorderStyle(PDBorderStyleDictionary bs) + public final void setBorderStyle(PDBorderStyleDictionary bs) { annot.setItem(COSName.BS, bs); } @@ -897,7 +902,7 @@ public PDBorderStyleDictionary getBorderStyle() * * @param be the border effect dictionary to set. */ - public void setBorderEffect(PDBorderEffectDictionary be) + public final void setBorderEffect(PDBorderEffectDictionary be) { annot.setItem(COSName.BE, be); } @@ -949,7 +954,6 @@ else if (base instanceof COSStream) } } - private String richContentsToString(Node node, boolean root) { String retval = ""; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationCaret.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationCaret.java index 46c0fdcbc..a7b076ac2 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationCaret.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationCaret.java @@ -60,6 +60,7 @@ public FDFAnnotationCaret(COSDictionary a) * Constructor. * * @param element An XFDF element. + * * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationCaret(Element element) throws IOException @@ -67,6 +68,18 @@ public FDFAnnotationCaret(Element element) throws IOException super(element); annot.setName(COSName.SUBTYPE, SUBTYPE); + initFringe(element); + + String symbol = element.getAttribute("symbol"); + if (symbol != null && !symbol.isEmpty()) + { + setSymbol(element.getAttribute("symbol")); + } + } + + private void initFringe(Element element) throws IOException + { + String fringe = element.getAttribute("fringe"); if (fringe != null && !fringe.isEmpty()) { @@ -75,20 +88,12 @@ public FDFAnnotationCaret(Element element) throws IOException { throw new IOException("Error: wrong amount of numbers in attribute 'fringe'"); } - float[] values = new float[4]; - for (int i = 0; i < 4; i++) - { - values[i] = Float.parseFloat(fringeValues[i]); - } - COSArray array = new COSArray(); - array.setFloatArray(values); - setFringe(new PDRectangle(array)); - } - - String symbol = element.getAttribute("symbol"); - if (symbol != null && !symbol.isEmpty()) - { - setSymbol(element.getAttribute("symbol")); + PDRectangle rect = new PDRectangle(); + rect.setLowerLeftX(Float.parseFloat(fringeValues[0])); + rect.setLowerLeftY(Float.parseFloat(fringeValues[1])); + rect.setUpperRightX(Float.parseFloat(fringeValues[2])); + rect.setUpperRightY(Float.parseFloat(fringeValues[3])); + setFringe(rect); } } @@ -98,7 +103,7 @@ public FDFAnnotationCaret(Element element) throws IOException * * @param fringe the fringe */ - public void setFringe(PDRectangle fringe) + public final void setFringe(PDRectangle fringe) { annot.setItem(COSName.RD, fringe); } @@ -127,7 +132,7 @@ public PDRectangle getFringe() * * @param symbol the symbol */ - public void setSymbol(String symbol) + public final void setSymbol(String symbol) { String newSymbol = "None"; if ("paragraph".equals(symbol)) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationCircle.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationCircle.java index 45922c78f..67e092272 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationCircle.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationCircle.java @@ -60,9 +60,9 @@ public FDFAnnotationCircle( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationCircle( Element element ) throws IOException { @@ -76,6 +76,11 @@ public FDFAnnotationCircle( Element element ) throws IOException setInteriorColor(new AWTColor(colorValue)); } + initFringe(element); + } + + private void initFringe(Element element) throws IOException + { String fringe = element.getAttribute("fringe"); if (fringe != null && !fringe.isEmpty()) { @@ -84,14 +89,12 @@ public FDFAnnotationCircle( Element element ) throws IOException { throw new IOException("Error: wrong amount of numbers in attribute 'fringe'"); } - float[] values = new float[4]; - for (int i = 0; i < 4; i++) - { - values[i] = Float.parseFloat(fringeValues[i]); - } - COSArray array = new COSArray(); - array.setFloatArray(values); - setFringe(new PDRectangle(array)); + PDRectangle rect = new PDRectangle(); + rect.setLowerLeftX(Float.parseFloat(fringeValues[0])); + rect.setLowerLeftY(Float.parseFloat(fringeValues[1])); + rect.setUpperRightX(Float.parseFloat(fringeValues[2])); + rect.setUpperRightY(Float.parseFloat(fringeValues[3])); + setFringe(rect); } } @@ -100,7 +103,7 @@ public FDFAnnotationCircle( Element element ) throws IOException * * @param color The interior color of the circle. */ - public void setInteriorColor(AWTColor color) + public final void setInteriorColor(AWTColor color) { COSArray array = null; if (color != null) @@ -138,7 +141,7 @@ public AWTColor getInteriorColor() * * @param fringe the fringe */ - public void setFringe(PDRectangle fringe) + public final void setFringe(PDRectangle fringe) { annot.setItem(COSName.RD, fringe); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationFileAttachment.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationFileAttachment.java index eb9e22cdf..6f2634fcb 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationFileAttachment.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationFileAttachment.java @@ -20,6 +20,7 @@ import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; + import org.w3c.dom.Element; /** @@ -56,9 +57,9 @@ public FDFAnnotationFileAttachment( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationFileAttachment( Element element ) throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationFreeText.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationFreeText.java index fab652c4c..ca004fd71 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationFreeText.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationFreeText.java @@ -16,10 +16,18 @@ */ package com.tom_roush.pdfbox.pdmodel.fdf; +import android.util.Log; + import java.io.IOException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; import org.w3c.dom.Element; @@ -57,9 +65,9 @@ public FDFAnnotationFreeText( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationFreeText( Element element ) throws IOException { @@ -67,11 +75,99 @@ public FDFAnnotationFreeText( Element element ) throws IOException annot.setName(COSName.SUBTYPE, SUBTYPE); setJustification(element.getAttribute("justification")); + + XPath xpath = XPathFactory.newInstance().newXPath(); + try + { + setDefaultAppearance(xpath.evaluate("defaultappearance", element)); + setDefaultStyle(xpath.evaluate("defaultstyle", element)); + } + catch (XPathExpressionException ex) + { + Log.d("PdfBox-Android", "Error while evaluating XPath expression"); + } + initCallout(element); String rotation = element.getAttribute("rotation"); if (rotation != null && !rotation.isEmpty()) { setRotation(Integer.parseInt(rotation)); } + initFringe(element); + String lineEndingStyle = element.getAttribute("head"); + if (lineEndingStyle != null && !lineEndingStyle.isEmpty()) + { + setLineEndingStyle(lineEndingStyle); + } + } + + private void initFringe(Element element) throws IOException + { + String fringe = element.getAttribute("fringe"); + if (fringe != null && !fringe.isEmpty()) + { + String[] fringeValues = fringe.split(","); + if (fringeValues.length != 4) + { + throw new IOException("Error: wrong amount of numbers in attribute 'fringe'"); + } + PDRectangle rect = new PDRectangle(); + rect.setLowerLeftX(Float.parseFloat(fringeValues[0])); + rect.setLowerLeftY(Float.parseFloat(fringeValues[1])); + rect.setUpperRightX(Float.parseFloat(fringeValues[2])); + rect.setUpperRightY(Float.parseFloat(fringeValues[3])); + setFringe(rect); + } + } + + private void initCallout(Element element) throws IOException + { + String callout = element.getAttribute("callout"); + if (callout != null && !callout.isEmpty()) + { + String[] calloutValues = callout.split(","); + float[] values = new float[calloutValues.length]; + for (int i = 0; i < calloutValues.length; i++) + { + values[i] = Float.parseFloat(calloutValues[i]); + } + setCallout(values); + } + } + + /** + * This will set the coordinates of the callout line. + * + * @param callout An array of four or six numbers specifying a callout line attached to the free + * text annotation. Six numbers [ x1 y1 x2 y2 x3 y3 ] represent the starting, knee point, and + * ending coordinates of the line in default user space, Four numbers [ x1 y1 x2 y2 ] represent + * the starting and ending coordinates of the line. + */ + public void setCallout(float[] callout) + { + COSArray newCallout = new COSArray(); + newCallout.setFloatArray(callout); + annot.setItem(COSName.CL, newCallout); + } + + /** + * This will get the coordinates of the the callout line. + * + * @return An array of four or six numbers specifying a callout line attached to the free text + * annotation. Six numbers [ x1 y1 x2 y2 x3 y3 ] represent the starting, knee point, and ending + * coordinates of the line in default user space, Four numbers [ x1 y1 x2 y2 ] represent the + * starting and ending coordinates of the line. + */ + public float[] getCallout() + { + COSArray array = (COSArray)annot.getDictionaryObject(COSName.CL); + if (array != null) + { + return array.toFloatArray(); + } + else + { + return null; + } } /** @@ -79,7 +175,7 @@ public FDFAnnotationFreeText( Element element ) throws IOException * * @param justification The quadding of the text. */ - public void setJustification(String justification) + public final void setJustification(String justification) { int quadding = 0; if ("centered".equals(justification)) @@ -108,7 +204,7 @@ public String getJustification() * * @param rotation The number of degrees of clockwise rotation. */ - public void setRotation(int rotation) + public final void setRotation(int rotation) { annot.setInt(COSName.ROTATE, rotation); } @@ -122,4 +218,96 @@ public String getRotation() { return annot.getString(COSName.ROTATE); } + + /** + * Set the default appearance string. + * + * @param appearance The new default appearance string. + */ + public final void setDefaultAppearance(String appearance) + { + annot.setString(COSName.DA, appearance); + } + + /** + * Get the default appearance string. + * + * @return The default appearance of the annotation. + */ + public String getDefaultAppearance() + { + return annot.getString(COSName.DA); + + } + + /** + * Set the default style string. + * + * @param style The new default style string. + */ + public final void setDefaultStyle(String style) + { + annot.setString(COSName.DS, style); + } + + /** + * Get the default style string. + * + * @return The default style of the annotation. + */ + public String getDefaultStyle() + { + return annot.getString(COSName.DS); + } + + /** + * This will set the fringe rectangle. Giving the difference between the annotations rectangle + * and where the drawing occurs. (To take account of any effects applied through the BE entry + * for example) + * + * @param fringe the fringe + */ + public final void setFringe(PDRectangle fringe) + { + annot.setItem(COSName.RD, fringe); + } + + /** + * This will get the fringe. Giving the difference between the annotations rectangle and where + * the drawing occurs. (To take account of any effects applied through the BE entry for example) + * + * @return the rectangle difference + */ + public PDRectangle getFringe() + { + COSArray rd = (COSArray)annot.getDictionaryObject(COSName.RD); + if (rd != null) + { + return new PDRectangle(rd); + } + else + { + return null; + } + } + + /** + * This will set the line ending style. + * + * @param style The new style. + */ + public final void setLineEndingStyle(String style) + { + annot.setName(COSName.LE, style); + } + + /** + * This will retrieve the line ending style. + * + * @return The ending style for the start point. + */ + public String getLineEndingStyle() + { + return annot.getNameAsString(COSName.LE); + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationHighlight.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationHighlight.java index 8e13175f0..f1472e3c4 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationHighlight.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationHighlight.java @@ -57,9 +57,9 @@ public FDFAnnotationHighlight( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationHighlight( Element element ) throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationInk.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationInk.java index 09a6f621a..404882bee 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationInk.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationInk.java @@ -70,9 +70,9 @@ public FDFAnnotationInk( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationInk( Element element ) throws IOException { @@ -104,6 +104,7 @@ public FDFAnnotationInk( Element element ) throws IOException inklist.add(values); } } + setInkList(inklist); } catch (XPathExpressionException e) { @@ -120,7 +121,7 @@ public FDFAnnotationInk( Element element ) throws IOException * * @param inklist the List of arrays representing the paths. */ - public void setInkList(List inklist) + public final void setInkList(List inklist) { COSArray newInklist = new COSArray(); for (float[] array : inklist) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationLine.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationLine.java index 6740daa83..4411671e3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationLine.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationLine.java @@ -62,9 +62,9 @@ public FDFAnnotationLine( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationLine( Element element ) throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationLink.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationLink.java index 080e5bd4b..364c20b6d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationLink.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationLink.java @@ -16,13 +16,13 @@ */ package com.tom_roush.pdfbox.pdmodel.fdf; +import java.io.IOException; + import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import org.w3c.dom.Element; -import java.io.IOException; - /** * This represents a Polygon FDF annotation. */ @@ -56,6 +56,7 @@ public FDFAnnotationLink(COSDictionary a) * Constructor. * * @param element An XFDF element. + * * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationLink(Element element) throws IOException diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationPolygon.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationPolygon.java index 5af4990de..b10ec8c50 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationPolygon.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationPolygon.java @@ -35,6 +35,7 @@ * This represents a Polygon FDF annotation. * * @author Ben Litchfield + * @author Johanneke Lamberink */ public class FDFAnnotationPolygon extends FDFAnnotation { @@ -65,9 +66,9 @@ public FDFAnnotationPolygon( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationPolygon( Element element ) throws IOException { @@ -83,7 +84,7 @@ public FDFAnnotationPolygon( Element element ) throws IOException } } - private void initVertices(Element element) throws IOException, NumberFormatException + private void initVertices(Element element) throws IOException { XPath xpath = XPathFactory.newInstance().newXPath(); try @@ -93,7 +94,7 @@ private void initVertices(Element element) throws IOException, NumberFormatExcep { throw new IOException("Error: missing element 'vertices'"); } - String[] verticesValues = vertices.split(","); + String[] verticesValues = vertices.split(",|;"); float[] values = new float[verticesValues.length]; for (int i = 0; i < verticesValues.length; i++) { @@ -112,7 +113,7 @@ private void initVertices(Element element) throws IOException, NumberFormatExcep * * @param vertices array of floats [x1, y1, x2, y2, ...] vertex coordinates in default user space. */ - public void setVertices(float[] vertices) + public final void setVertices(float[] vertices) { COSArray newVertices = new COSArray(); newVertices.setFloatArray(vertices); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationPolyline.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationPolyline.java index bdd5143bb..862dd8547 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationPolyline.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationPolyline.java @@ -36,6 +36,7 @@ * This represents a Polyline FDF annotation. * * @author Ben Litchfield + * @author Johanneke Lamberink */ public class FDFAnnotationPolyline extends FDFAnnotation { @@ -66,9 +67,9 @@ public FDFAnnotationPolyline( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationPolyline( Element element ) throws IOException { @@ -79,7 +80,7 @@ public FDFAnnotationPolyline( Element element ) throws IOException initStyles(element); } - private void initVertices(Element element) throws IOException, NumberFormatException + private void initVertices(Element element) throws IOException { XPath xpath = XPathFactory.newInstance().newXPath(); try @@ -89,7 +90,7 @@ private void initVertices(Element element) throws IOException, NumberFormatExcep { throw new IOException("Error: missing element 'vertices'"); } - String[] verticesValues = vertices.split(","); + String[] verticesValues = vertices.split(",|;"); float[] values = new float[verticesValues.length]; for (int i = 0; i < verticesValues.length; i++) { @@ -104,7 +105,7 @@ private void initVertices(Element element) throws IOException, NumberFormatExcep } } - private void initStyles(Element element) throws NumberFormatException + private void initStyles(Element element) { String startStyle = element.getAttribute("head"); if (startStyle != null && !startStyle.isEmpty()) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSound.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSound.java index d43848e77..2c6a359b5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSound.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSound.java @@ -20,6 +20,7 @@ import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; + import org.w3c.dom.Element; /** @@ -56,9 +57,9 @@ public FDFAnnotationSound( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationSound( Element element ) throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSquare.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSquare.java index 44a28a0b3..278c899a5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSquare.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSquare.java @@ -61,9 +61,9 @@ public FDFAnnotationSquare( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationSquare( Element element ) throws IOException { @@ -77,6 +77,11 @@ public FDFAnnotationSquare( Element element ) throws IOException setInteriorColor(new AWTColor(colorValue)); } + initFringe(element); + } + + private void initFringe(Element element) throws IOException + { String fringe = element.getAttribute("fringe"); if (fringe != null && !fringe.isEmpty()) { @@ -85,14 +90,12 @@ public FDFAnnotationSquare( Element element ) throws IOException { throw new IOException("Error: wrong amount of numbers in attribute 'fringe'"); } - float[] values = new float[4]; - for (int i = 0; i < 4; i++) - { - values[i] = Float.parseFloat(fringeValues[i]); - } - COSArray array = new COSArray(); - array.setFloatArray(values); - setFringe(new PDRectangle(array)); + PDRectangle rect = new PDRectangle(); + rect.setLowerLeftX(Float.parseFloat(fringeValues[0])); + rect.setLowerLeftY(Float.parseFloat(fringeValues[1])); + rect.setUpperRightX(Float.parseFloat(fringeValues[2])); + rect.setUpperRightY(Float.parseFloat(fringeValues[3])); + setFringe(rect); } } @@ -101,7 +104,7 @@ public FDFAnnotationSquare( Element element ) throws IOException * * @param color The interior color of the circle. */ - public void setInteriorColor(AWTColor color) + public final void setInteriorColor(AWTColor color) { COSArray array = null; if (color != null) @@ -139,7 +142,7 @@ public AWTColor getInteriorColor() * * @param fringe the fringe */ - public void setFringe(PDRectangle fringe) + public final void setFringe(PDRectangle fringe) { annot.setItem(COSName.RD, fringe); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSquiggly.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSquiggly.java index ee6b3a870..2e64f8aff 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSquiggly.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationSquiggly.java @@ -57,9 +57,9 @@ public FDFAnnotationSquiggly( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationSquiggly( Element element ) throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java index 551c6d800..26d4a382a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationStamp.java @@ -20,6 +20,7 @@ import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; + import org.w3c.dom.Element; /** @@ -56,9 +57,9 @@ public FDFAnnotationStamp( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationStamp( Element element ) throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationStrikeOut.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationStrikeOut.java index 9d2891197..7acf10ef8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationStrikeOut.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationStrikeOut.java @@ -57,9 +57,9 @@ public FDFAnnotationStrikeOut( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationStrikeOut( Element element ) throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationText.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationText.java index 34c16dbe0..641a83f63 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationText.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationText.java @@ -59,9 +59,9 @@ public FDFAnnotationText( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationText( Element element ) throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationTextMarkup.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationTextMarkup.java index c24bed893..6ee8e82cd 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationTextMarkup.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationTextMarkup.java @@ -16,14 +16,14 @@ */ package com.tom_roush.pdfbox.pdmodel.fdf; +import java.io.IOException; + import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import org.w3c.dom.Element; -import java.io.IOException; - /** * This abstract class is used as a superclass for the different FDF annotations with text markup attributes. * @@ -53,6 +53,7 @@ public FDFAnnotationTextMarkup(COSDictionary a) * Constructor. * * @param element An XFDF element. + * * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationTextMarkup(Element element) throws IOException @@ -77,7 +78,6 @@ public FDFAnnotationTextMarkup(Element element) throws IOException setCoords(values); } - /** * Set the coordinates of individual words or group of words. * diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationUnderline.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationUnderline.java index 44b1dae7d..bc5160b79 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationUnderline.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFAnnotationUnderline.java @@ -57,9 +57,9 @@ public FDFAnnotationUnderline( COSDictionary a ) /** * Constructor. * - * @param element An XFDF element. + * @param element An XFDF element. * - * @throws IOException If there is an error extracting information from the element. + * @throws IOException If there is an error extracting information from the element. */ public FDFAnnotationUnderline( Element element ) throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFDictionary.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFDictionary.java index 4bd3c78e9..b5683cc22 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFDictionary.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFDictionary.java @@ -325,7 +325,7 @@ public void setID( COSArray id ) } /** - * This will get the list of FDF Fields. This will return a list of FDFField + * This will get the list of FDF Fields. This will return a list of FDFField * objects. * * @return A list of FDF fields. @@ -347,7 +347,7 @@ public List getFields() } /** - * This will set the list of fields. This should be a list of FDFField objects. + * This will set the list of fields. This should be a list of FDFField objects. * * @param fields The list of fields. */ @@ -378,7 +378,7 @@ public void setStatus( String status ) } /** - * This will get the list of FDF Pages. This will return a list of FDFPage objects. + * This will get the list of FDF Pages. This will return a list of FDFPage objects. * * @return A list of FDF pages. */ @@ -399,18 +399,18 @@ public List getPages() } /** - * This will set the list of pages. This should be a list of FDFPage objects. + * This will set the list of pages. This should be a list of FDFPage objects. * * * @param pages The list of pages. */ - public void setPages( List pages ) + public void setPages(List pages) { fdf.setItem( COSName.PAGES, COSArrayList.converterToCOSArray( pages ) ); } /** - * The encoding to be used for a FDF field. The default is PDFDocEncoding + * The encoding to be used for a FDF field. The default is PDFDocEncoding * and this method will never return null. * * @return The encoding value. @@ -437,7 +437,7 @@ public void setEncoding( String encoding ) } /** - * This will get the list of FDF Annotations. This will return a list of FDFAnnotation objects + * This will get the list of FDF Annotations. This will return a list of FDFAnnotation objects * or null if the entry is not set. * * @return A list of FDF annotations. @@ -461,7 +461,7 @@ public List getAnnotations() throws IOException } /** - * This will set the list of annotations. This should be a list of FDFAnnotation objects. + * This will set the list of annotations. This should be a list of FDFAnnotation objects. * * * @param annots The list of annotations. @@ -536,13 +536,13 @@ public List getEmbeddedFDFs() throws IOException } /** - * This will set the list of embedded FDFs. This should be a list of + * This will set the list of embedded FDFs. This should be a list of * PDFileSpecification objects. * * * @param embedded The list of embedded FDFs. */ - public void setEmbeddedFDFs( List embedded ) + public void setEmbeddedFDFs(List embedded) { fdf.setItem( COSName.EMBEDDED_FDFS, COSArrayList.converterToCOSArray( embedded ) ); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFDocument.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFDocument.java index d2b405a36..ceb8a81c1 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFDocument.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFDocument.java @@ -57,10 +57,10 @@ public FDFDocument() document = new COSDocument(); document.setVersion( 1.2f ); - //First we need a trailer + // First we need a trailer document.setTrailer( new COSDictionary() ); - //Next we need the root dictionary. + // Next we need the root dictionary. FDFCatalog catalog = new FDFCatalog(); setCatalog( catalog ); } @@ -112,8 +112,6 @@ public void writeXML( Writer output ) throws IOException output.write( "\n" ); } - - /** * This will get the low level document. * @@ -125,7 +123,7 @@ public COSDocument getDocument() } /** - * This will get the FDF Catalog. This is guaranteed to not return null. + * This will get the FDF Catalog. This is guaranteed to not return null. * * @return The documents /Root dictionary */ @@ -168,8 +166,8 @@ public void setCatalog( FDFCatalog cat ) */ public static FDFDocument load( String filename ) throws IOException { - FDFParser parser = new FDFParser(filename); - parser.parse(); + FDFParser parser = new FDFParser(filename); + parser.parse(); return new FDFDocument(parser.getDocument()); } @@ -184,8 +182,8 @@ public static FDFDocument load( String filename ) throws IOException */ public static FDFDocument load( File file ) throws IOException { - FDFParser parser = new FDFParser(file); - parser.parse(); + FDFParser parser = new FDFParser(file); + parser.parse(); return new FDFDocument(parser.getDocument()); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFField.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFField.java index 00e8e91b8..d4aebaccc 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFField.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFField.java @@ -156,7 +156,7 @@ public COSDictionary getCOSObject() } /** - * This will get the list of kids. This will return a list of FDFField objects. + * This will get the list of kids. This will return a list of FDFField objects. * This will return null if the underlying list is null. * * @return The list of kids. @@ -188,9 +188,9 @@ public void setKids( List kids ) } /** - * This will get the "T" entry in the field dictionary. A partial field - * name. Where the fully qualified field name is a concatenation of - * the parent's fully qualified field name and "." as a separator. For example
    + * This will get the "T" entry in the field dictionary. A partial field + * name. Where the fully qualified field name is a concatenation of + * the parent's fully qualified field name and "." as a separator. For example
    * Address.State
    * Address.City
    * @@ -212,7 +212,7 @@ public void setPartialFieldName( String partial ) } /** - * This will get the value for the field. This will return type will either be
    + * This will get the value for the field. This will return type will either be
    * String : Checkboxes, Radio Button
    * java.util.List of strings: Choice Field * PDTextStream: Textfields @@ -289,7 +289,7 @@ public void setValue( Object value ) throws IOException COSBase cos = null; if( value instanceof List ) { - cos = COSArrayList.convertStringListToCOSStringCOSArray( (List)value ); + cos = COSArrayList.convertStringListToCOSStringCOSArray((List)value); } else if( value instanceof String ) { @@ -317,7 +317,7 @@ public void setValue(COSBase value) } /** - * This will get the Ff entry of the cos dictionary. If it it not present then + * This will get the Ff entry of the cos dictionary. If it it not present then * this method will return null. * * @return The field flags. @@ -334,7 +334,7 @@ public Integer getFieldFlags() } /** - * This will get the field flags that are associated with this field. The Ff entry + * This will get the field flags that are associated with this field. The Ff entry * in the FDF field dictionary. * * @param ff The new value for the field flags. @@ -350,7 +350,7 @@ public void setFieldFlags( Integer ff ) } /** - * This will get the field flags that are associated with this field. The Ff entry + * This will get the field flags that are associated with this field. The Ff entry * in the FDF field dictionary. * * @param ff The new value for the field flags. @@ -361,7 +361,7 @@ public void setFieldFlags( int ff ) } /** - * This will get the SetFf entry of the cos dictionary. If it it not present then + * This will get the SetFf entry of the cos dictionary. If it it not present then * this method will return null. * * @return The field flags. @@ -378,7 +378,7 @@ public Integer getSetFieldFlags() } /** - * This will get the field flags that are associated with this field. The SetFf entry + * This will get the field flags that are associated with this field. The SetFf entry * in the FDF field dictionary. * * @param ff The new value for the "set field flags". @@ -394,7 +394,7 @@ public void setSetFieldFlags( Integer ff ) } /** - * This will get the field flags that are associated with this field. The SetFf entry + * This will get the field flags that are associated with this field. The SetFf entry * in the FDF field dictionary. * * @param ff The new value for the "set field flags". @@ -405,7 +405,7 @@ public void setSetFieldFlags( int ff ) } /** - * This will get the ClrFf entry of the cos dictionary. If it it not present then + * This will get the ClrFf entry of the cos dictionary. If it it not present then * this method will return null. * * @return The field flags. @@ -422,7 +422,7 @@ public Integer getClearFieldFlags() } /** - * This will get the field flags that are associated with this field. The ClrFf entry + * This will get the field flags that are associated with this field. The ClrFf entry * in the FDF field dictionary. * * @param ff The new value for the "clear field flags". @@ -438,7 +438,7 @@ public void setClearFieldFlags( Integer ff ) } /** - * This will get the field flags that are associated with this field. The ClrFf entry + * This will get the field flags that are associated with this field. The ClrFf entry * in the FDF field dictionary. * * @param ff The new value for the "clear field flags". @@ -449,7 +449,7 @@ public void setClearFieldFlags( int ff ) } /** - * This will get the F entry of the cos dictionary. If it it not present then + * This will get the F entry of the cos dictionary. If it it not present then * this method will return null. * * @return The widget field flags. @@ -466,7 +466,7 @@ public Integer getWidgetFieldFlags() } /** - * This will get the widget field flags that are associated with this field. The F entry + * This will get the widget field flags that are associated with this field. The F entry * in the FDF field dictionary. * * @param f The new value for the field flags. @@ -482,7 +482,7 @@ public void setWidgetFieldFlags( Integer f ) } /** - * This will get the field flags that are associated with this field. The F entry + * This will get the field flags that are associated with this field. The F entry * in the FDF field dictionary. * * @param f The new value for the field flags. @@ -493,7 +493,7 @@ public void setWidgetFieldFlags( int f ) } /** - * This will get the SetF entry of the cos dictionary. If it it not present then + * This will get the SetF entry of the cos dictionary. If it it not present then * this method will return null. * * @return The field flags. @@ -510,7 +510,7 @@ public Integer getSetWidgetFieldFlags() } /** - * This will get the widget field flags that are associated with this field. The SetF entry + * This will get the widget field flags that are associated with this field. The SetF entry * in the FDF field dictionary. * * @param ff The new value for the "set widget field flags". @@ -526,7 +526,7 @@ public void setSetWidgetFieldFlags( Integer ff ) } /** - * This will get the widget field flags that are associated with this field. The SetF entry + * This will get the widget field flags that are associated with this field. The SetF entry * in the FDF field dictionary. * * @param ff The new value for the "set widget field flags". @@ -537,7 +537,7 @@ public void setSetWidgetFieldFlags( int ff ) } /** - * This will get the ClrF entry of the cos dictionary. If it it not present then + * This will get the ClrF entry of the cos dictionary. If it it not present then * this method will return null. * * @return The widget field flags. @@ -554,7 +554,7 @@ public Integer getClearWidgetFieldFlags() } /** - * This will get the field flags that are associated with this field. The ClrF entry + * This will get the field flags that are associated with this field. The ClrF entry * in the FDF field dictionary. * * @param ff The new value for the "clear widget field flags". @@ -570,7 +570,7 @@ public void setClearWidgetFieldFlags( Integer ff ) } /** - * This will get the field flags that are associated with this field. The ClrF entry + * This will get the field flags that are associated with this field. The ClrF entry * in the FDF field dictionary. * * @param ff The new value for the "clear field flags". @@ -641,7 +641,7 @@ public void setAppearanceStreamReference( FDFNamedPageReference ref ) public FDFIconFit getIconFit() { FDFIconFit retval = null; - COSDictionary dic = (COSDictionary)field.getDictionaryObject( "IF" ); + COSDictionary dic = (COSDictionary)field.getDictionaryObject(COSName.IF); if( dic != null ) { retval = new FDFIconFit( dic ); @@ -660,8 +660,8 @@ public void setIconFit( FDFIconFit fit ) } /** - * This will return a list of options for a choice field. The value in the - * list will be 1 of 2 types. java.lang.String or FDFOptionElement. + * This will return a list of options for a choice field. The value in the + * list will be 1 of 2 types. java.lang.String or FDFOptionElement. * * @return A list of all options. */ @@ -691,12 +691,12 @@ public List getOptions() } /** - * This will set the options for the choice field. The objects in the list + * This will set the options for the choice field. The objects in the list * should either be java.lang.String or FDFOptionElement. * * @param options The options to set. */ - public void setOptions( List options ) + public void setOptions(List options) { COSArray value = COSArrayList.converterToCOSArray( options ); field.setItem( COSName.OPT, value ); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFIconFit.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFIconFit.java index eef03c13e..51e35d020 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFIconFit.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFIconFit.java @@ -57,8 +57,6 @@ public class FDFIconFit implements COSObjectable */ public static final String SCALE_TYPE_PROPORTIONAL = "P"; - - /** * Default constructor. */ @@ -89,7 +87,7 @@ public COSDictionary getCOSObject() } /** - * This will get the scale option. See the SCALE_OPTION_XXX constants. This + * This will get the scale option. See the SCALE_OPTION_XXX constants. This * is guaranteed to never return null. Default: Always * * @return The scale option. @@ -105,7 +103,7 @@ public String getScaleOption() } /** - * This will set the scale option for the icon. Set the SCALE_OPTION_XXX constants. + * This will set the scale option for the icon. Set the SCALE_OPTION_XXX constants. * * @param option The scale option. */ @@ -115,8 +113,8 @@ public void setScaleOption( String option ) } /** - * This will get the scale type. See the SCALE_TYPE_XXX constants. This is - * guaranteed to never return null. Default: Proportional + * This will get the scale type. See the SCALE_TYPE_XXX constants. This is + * guaranteed to never return null. Default: Proportional * * @return The scale type. */ @@ -131,7 +129,7 @@ public String getScaleType() } /** - * This will set the scale type. See the SCALE_TYPE_XXX constants. + * This will set the scale type. See the SCALE_TYPE_XXX constants. * * @param scale The scale type. */ @@ -181,7 +179,7 @@ public void setFractionalSpaceToAllocate( PDRange space ) } /** - * This will tell if the icon should scale to fit the annotation bounds. Default: false + * This will tell if the icon should scale to fit the annotation bounds. Default: false * * @return A flag telling if the icon should scale. */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFNamedPageReference.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFNamedPageReference.java index 686fd9dc6..8c212239e 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFNamedPageReference.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFNamedPageReference.java @@ -62,7 +62,7 @@ public COSDictionary getCOSObject() } /** - * This will get the name of the referenced page. A required parameter. + * This will get the name of the referenced page. A required parameter. * * @return The name of the referenced page. */ @@ -82,7 +82,7 @@ public void setName( String name ) } /** - * This will get the file specification of this reference. An optional parameter. + * This will get the file specification of this reference. An optional parameter. * * @return The F entry for this dictionary. * diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFOptionElement.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFOptionElement.java index 559d11119..31bdc253d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFOptionElement.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/FDFOptionElement.java @@ -29,7 +29,7 @@ */ public class FDFOptionElement implements COSObjectable { - private COSArray option; + private final COSArray option; /** * Default constructor. @@ -56,6 +56,7 @@ public FDFOptionElement( COSArray o ) * * @return The cos object that matches this Java object. */ + @Override public COSBase getCOSObject() { return option; @@ -72,7 +73,7 @@ public COSArray getCOSArray() } /** - * This will get the string of one of the available options. A required element. + * This will get the string of one of the available options. A required element. * * @return An available option. */ @@ -92,7 +93,7 @@ public void setOption( String opt ) } /** - * This will get the string of default appearance string. A required element. + * This will get the string of default appearance string. A required element. * * @return A default appearance string. */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/XMLUtil.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/XMLUtil.java index 84837ca6a..45136e0e9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/XMLUtil.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/fdf/XMLUtil.java @@ -63,15 +63,15 @@ public static Document parse( InputStream is ) throws IOException } catch (FactoryConfigurationError e ) { - throw new IOException( e.getMessage(), e ); + throw new IOException(e.getMessage(), e); } catch (ParserConfigurationException e) { - throw new IOException( e.getMessage(), e ); + throw new IOException(e.getMessage(), e); } catch (SAXException e) { - throw new IOException( e.getMessage(), e ); + throw new IOException(e.getMessage(), e); } } @@ -83,17 +83,17 @@ public static Document parse( InputStream is ) throws IOException */ public static String getNodeValue( Element node ) { - StringBuilder sb = new StringBuilder(); - NodeList children = node.getChildNodes(); - int numNodes = children.getLength(); - for( int i=0; i { private final FontBoxFont ttf; - CIDFontMapping(OpenTypeFont font, FontBoxFont fontBoxFont, boolean isFallback) + public CIDFontMapping(OpenTypeFont font, FontBoxFont fontBoxFont, boolean isFallback) { super(font, isFallback); this.ttf = fontBoxFont; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/CIDSystemInfo.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/CIDSystemInfo.java index 7199083d0..e86435255 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/CIDSystemInfo.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/CIDSystemInfo.java @@ -17,14 +17,12 @@ package com.tom_roush.pdfbox.pdmodel.font; -import java.io.Serializable; - /** * Represents a CIDSystemInfo for the FontMapper API. * * @author John Hewson */ -public final class CIDSystemInfo implements Serializable +public final class CIDSystemInfo { private final String registry; private final String ordering; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/CmapManager.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/CmapManager.java index 7fec4c664..13a847e41 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/CmapManager.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/CmapManager.java @@ -32,7 +32,7 @@ final class CMapManager { static Map cMapCache = Collections.synchronizedMap(new HashMap()); - + private CMapManager() { } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FileSystemFontProvider.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FileSystemFontProvider.java index 94a32d2db..20ef8b2b5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FileSystemFontProvider.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FileSystemFontProvider.java @@ -18,19 +18,20 @@ import android.util.Log; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; import java.net.URI; +import java.security.AccessControlException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; -import java.util.prefs.Preferences; +import java.util.Set; import com.tom_roush.fontbox.FontBoxFont; import com.tom_roush.fontbox.cff.CFFCIDFont; @@ -40,6 +41,7 @@ import com.tom_roush.fontbox.ttf.OpenTypeFont; import com.tom_roush.fontbox.ttf.TTFParser; import com.tom_roush.fontbox.ttf.TrueTypeCollection; +import com.tom_roush.fontbox.ttf.TrueTypeCollection.TrueTypeFontProcessor; import com.tom_roush.fontbox.ttf.TrueTypeFont; import com.tom_roush.fontbox.type1.Type1Font; import com.tom_roush.fontbox.util.autodetect.FontFileFinder; @@ -54,12 +56,10 @@ */ final class FileSystemFontProvider extends FontProvider { - private static final long serialVersionUID = 1; - private final List fontInfoList = new ArrayList(); private final FontCache cache; - private static class FSFontInfo extends FontInfo implements Serializable + private static class FSFontInfo extends FontInfo { private final String postScriptName; private final FontFormat format; @@ -185,7 +185,7 @@ public String toString() /** * Represents ignored fonts (i.e. bitmap fonts). */ - private static final class FSIgnored extends FSFontInfo implements Serializable + private static final class FSIgnored extends FSFontInfo { private FSIgnored(File file, FontFormat format, String postScriptName) { @@ -223,29 +223,39 @@ private FSIgnored(File file, FontFormat format, String postScriptName) } } - Log.v("PdfBox-Android", "Will search the local system for fonts"); - - // scan the local system for font files - List files = new ArrayList(); - FontFileFinder fontFileFinder = new FontFileFinder(); - List fonts = fontFileFinder.find(); - for (URI font : fonts) + try { - files.add(new File(font)); - } - Log.v("PdfBox-Android", "Found " + files.size() + " fonts on the local system"); + Log.v("PdfBox-Android", "Will search the local system for fonts"); - // load cached FontInfo objects - List cachedInfos = loadCache(files); - if (cachedInfos != null && cachedInfos.size() > 0) - { - fontInfoList.addAll(cachedInfos); + // scan the local system for font files + List files = new ArrayList(); + FontFileFinder fontFileFinder = new FontFileFinder(); + List fonts = fontFileFinder.find(); + for (URI font : fonts) + { + files.add(new File(font)); + } + Log.v("PdfBox-Android", "Found " + files.size() + " fonts on the local system"); + + // load cached FontInfo objects + List cachedInfos = loadDiskCache(files); + if (cachedInfos != null && cachedInfos.size() > 0) + { + fontInfoList.addAll(cachedInfos); + } + else + { + Log.w("PdfBox-Android", "Building font cache, this may take a while"); + scanFonts(files); + saveDiskCache(); + Log.w("PdfBox-Android", + "Finished building on-disk font cache, found " + fontInfoList.size() + + " fonts"); + } } - else + catch (AccessControlException e) { - Log.w("PdfBox-Android", "Building font cache, this may take a while"); - scanFonts(files); - saveCache(); + Log.e("PdfBox-Android", "Error accessing the file system", e); } } @@ -277,88 +287,210 @@ else if (file.getPath().toLowerCase().endsWith(".pfb")) } } - private void saveCache() + private File getDiskCacheFile() { - // Get the preferences database for this package. - Preferences prefs = Preferences.userNodeForPackage(FileSystemFontProvider.class); + String path = System.getProperty("pdfbox.fontcache"); + if (path == null) + { + path = System.getProperty("user.home"); + if (path == null) + { + path = System.getProperty("java.io.tmpdir"); + } + } + return new File(path, ".pdfbox.cache"); + } - // To save, write the object to a byte array. + /** + * Saves the font metadata cache to disk. + */ + private void saveDiskCache() + { + BufferedWriter writer = null; try { + File file = getDiskCacheFile(); + writer = new BufferedWriter(new FileWriter(file)); + for (FSFontInfo fontInfo : fontInfoList) { - ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); - ObjectOutputStream objectOut = new ObjectOutputStream(byteOut); - // write it to the stream - objectOut.writeObject(fontInfo); - prefs.putByteArray(fontInfo.file.getAbsolutePath(), byteOut.toByteArray()); + writer.write(fontInfo.postScriptName.trim()); + writer.write("|"); + writer.write(fontInfo.format.toString()); + writer.write("|"); + if (fontInfo.cidSystemInfo != null) + { + writer.write(fontInfo.cidSystemInfo.getRegistry() + '-' + + fontInfo.cidSystemInfo.getOrdering() + '-' + + fontInfo.cidSystemInfo.getSupplement()); + } + writer.write("|"); + if (fontInfo.usWeightClass > -1) + { + writer.write(Integer.toHexString(fontInfo.usWeightClass)); + } + writer.write("|"); + if (fontInfo.sFamilyClass > -1) + { + writer.write(Integer.toHexString(fontInfo.sFamilyClass)); + } + writer.write("|"); + writer.write(Integer.toHexString(fontInfo.ulCodePageRange1)); + writer.write("|"); + writer.write(Integer.toHexString(fontInfo.ulCodePageRange2)); + writer.write("|"); + if (fontInfo.macStyle > -1) + { + writer.write(Integer.toHexString(fontInfo.macStyle)); + } + writer.write("|"); + if (fontInfo.panose != null) + { + byte[] bytes = fontInfo.panose.getBytes(); + for (int i = 0; i < 10; i++) + { + String str = Integer.toHexString(bytes[i]); + if (str.length() == 1) + { + writer.write('0'); + } + writer.write(str); + } + } + writer.write("|"); + writer.write(fontInfo.file.getAbsolutePath()); + writer.newLine(); } } catch (IOException e) { Log.e("PdfBox-Android", "Could not write to font cache", e); } - Log.w("PdfBox-Android", - "Finished building font cache, found " + fontInfoList.size() + " fonts"); + finally + { + IOUtils.closeQuietly(writer); + } } - private List loadCache(List files) + /** + * Loads the font metadata cache from disk. + */ + private List loadDiskCache(List files) { - // Get the preferences database for this package. - Preferences prefs = Preferences.userNodeForPackage(FileSystemFontProvider.class); - List results = new ArrayList(); + Set pending = new HashSet(); for (File file : files) { - // The second argument is the default if the key isn't found. - byte[] stored = prefs.getByteArray(file.getAbsolutePath(), null); - if (stored != null) + pending.add(file.getAbsolutePath()); + } + + List results = new ArrayList(); + File file = getDiskCacheFile(); + if (file.exists()) + { + BufferedReader reader = null; + try { - try + reader = new BufferedReader(new FileReader(file)); + String line; + while ((line = reader.readLine()) != null) { - ByteArrayInputStream byteIn = new ByteArrayInputStream(stored); - ObjectInputStream objectIn = new ObjectInputStream(byteIn); - Object object = objectIn.readObject(); - if (object instanceof FSFontInfo) + String[] parts = line.split("\\|", 10); + if (parts.length < 10) { - FSFontInfo info = (FSFontInfo) object; - info.parent = this; - results.add(info); + Log.e("PdfBox-Android", + "Incorrect line '" + line + "' in font disk cache is skipped"); + continue; } - } - catch (ClassNotFoundException e) - { - Log.e("PdfBox-Android", "Error loading font cache, will be re-built", e); - return null; - } - catch (IOException e) - { - Log.e("PdfBox-Android", "Error loading font cache, will be re-built", e); - return null; + + String postScriptName; + FontFormat format; + CIDSystemInfo cidSystemInfo = null; + int usWeightClass = -1; + int sFamilyClass = -1; + int ulCodePageRange1; + int ulCodePageRange2; + int macStyle = -1; + byte[] panose = null; + File fontFile; + + postScriptName = parts[0]; + format = FontFormat.valueOf(parts[1]); + if (parts[2].length() > 0) + { + String[] ros = parts[2].split("-"); + cidSystemInfo = new CIDSystemInfo(ros[0], ros[1], Integer.parseInt(ros[2])); + } + if (parts[3].length() > 0) + { + usWeightClass = (int)Long.parseLong(parts[3], 16); + } + if (parts[4].length() > 0) + { + sFamilyClass = (int)Long.parseLong(parts[4], 16); + } + ulCodePageRange1 = (int)Long.parseLong(parts[5], 16); + ulCodePageRange2 = (int)Long.parseLong(parts[6], 16); + if (parts[7].length() > 0) + { + macStyle = (int)Long.parseLong(parts[7], 16); + } + if (parts[8].length() > 0) + { + panose = new byte[10]; + for (int i = 0; i < 10; i++) + { + String str = parts[8].substring(i * 2, i * 2 + 2); + int b = Integer.parseInt(str, 16); + panose[i] = (byte)(b & 0xff); + } + } + fontFile = new File(parts[9]); + + FSFontInfo info = new FSFontInfo(fontFile, format, postScriptName, + cidSystemInfo, usWeightClass, sFamilyClass, ulCodePageRange1, + ulCodePageRange2, macStyle, panose, this); + results.add(info); + pending.remove(fontFile.getAbsolutePath()); } } - else + catch (IOException e) { - // re-build the entire cache if we encounter un-cached fonts (could be optimised) - Log.w("PdfBox-Android", "New fonts found, font cache will be re-built"); + Log.e("PdfBox-Android", "Error loading font cache, will be re-built", e); return null; } + finally + { + IOUtils.closeQuietly(reader); + } + } + + if (pending.size() > 0) + { + // re-build the entire cache if we encounter un-cached fonts (could be optimised) + Log.w("PdfBox-Android", "New fonts found, font cache will be re-built"); + return null; } + return results; } /** * Adds a TTC or OTC to the file cache. To reduce memory, the parsed font is not cached. */ - private void addTrueTypeCollection(File ttcFile) throws IOException + private void addTrueTypeCollection(final File ttcFile) throws IOException { TrueTypeCollection ttc = null; try { ttc = new TrueTypeCollection(ttcFile); - for (TrueTypeFont ttf : ttc.getFonts()) + ttc.processAllFonts(new TrueTypeFontProcessor() { - addTrueTypeFontImpl(ttf, ttcFile); - } + @Override + public void process(TrueTypeFont ttf) throws IOException + { + addTrueTypeFontImpl(ttf, ttcFile); + } + }); } catch (NullPointerException e) // TTF parser is buggy { @@ -408,7 +540,7 @@ private void addTrueTypeFont(File ttfFile) throws IOException } /** - * Adds an OTF or TTf font to the file cache. To reduce memory, the parsed font is not cached. + * Adds an OTF or TTF font to the file cache. To reduce memory, the parsed font is not cached. */ private void addTrueTypeFontImpl(TrueTypeFont ttf, File file) throws IOException { @@ -417,12 +549,19 @@ private void addTrueTypeFontImpl(TrueTypeFont ttf, File file) throws IOException // read PostScript name, if any if (ttf.getName() != null) { + // ignore bitmap fonts + if (ttf.getHeader() == null) + { + fontInfoList.add(new FSIgnored(file, FontFormat.TTF, ttf.getName())); + return; + } + int macStyle = ttf.getHeader().getMacStyle(); + int sFamilyClass = -1; int usWeightClass = -1; int ulCodePageRange1 = 0; int ulCodePageRange2 = 0; byte[] panose = null; - // Apple's AAT fonts don't have an OS/2 table if (ttf.getOS2Windows() != null) { @@ -433,14 +572,6 @@ private void addTrueTypeFontImpl(TrueTypeFont ttf, File file) throws IOException panose = ttf.getOS2Windows().getPanose(); } - // ignore bitmap fonts - if (ttf.getHeader() == null) - { - fontInfoList.add(new FSIgnored(file, FontFormat.TTF, ttf.getName())); - return; - } - int macStyle = ttf.getHeader().getMacStyle(); - String format; if (ttf instanceof OpenTypeFont && ((OpenTypeFont) ttf).isPostScript()) { @@ -466,9 +597,10 @@ private void addTrueTypeFontImpl(TrueTypeFont ttf, File file) throws IOException { // Apple's AAT fonts have a "gcid" table with CID info byte[] bytes = ttf.getTableBytes(ttf.getTableMap().get("gcid")); - String registryName = new String(bytes, 10, 64, Charsets.US_ASCII) - .trim(); - String orderName = new String(bytes, 76, 64, Charsets.US_ASCII).trim(); + String reg = new String(bytes, 10, 64, Charsets.US_ASCII); + String registryName = reg.substring(0, reg.indexOf('\0')); + String ord = new String(bytes, 76, 64, Charsets.US_ASCII); + String orderName = ord.substring(0, ord.indexOf('\0')); int supplementVersion = bytes[140] << 8 & bytes[141]; ros = new CIDSystemInfo(registryName, orderName, supplementVersion); } @@ -488,11 +620,13 @@ private void addTrueTypeFontImpl(TrueTypeFont ttf, File file) throws IOException } else { + fontInfoList.add(new FSIgnored(file, FontFormat.TTF, "*skipnoname*")); Log.w("PdfBox-Android", "Missing 'name' entry for PostScript name in font " + file); } } catch (IOException e) { + fontInfoList.add(new FSIgnored(file, FontFormat.TTF, "*skipexception*")); Log.e("PdfBox-Android", "Could not load font file: " + file, e); } finally @@ -540,11 +674,11 @@ private TrueTypeFont getTrueTypeFont(String postScriptName, File file) } catch (NullPointerException e) // TTF parser is buggy { - Log.d("PdfBox-Android", "Could not load font file: " + file, e); + Log.e("PdfBox-Android", "Could not load font file: " + file, e); } catch (IOException e) { - Log.d("PdfBox-Android", "Could not load font file: " + file, e); + Log.e("PdfBox-Android", "Could not load font file: " + file, e); } return null; } @@ -554,14 +688,13 @@ private TrueTypeFont readTrueTypeFont(String postScriptName, File file) throws I if (file.getName().toLowerCase().endsWith(".ttc")) { TrueTypeCollection ttc = new TrueTypeCollection(file); - for (TrueTypeFont ttf : ttc.getFonts()) + TrueTypeFont ttf = ttc.getFontByName(postScriptName); + if (ttf == null) { - if (ttf.getName().equals(postScriptName)) - { - return ttf; - } + ttc.close(); + throw new IOException("Font " + postScriptName + " not found in " + file); } - throw new IOException("Font " + postScriptName + " not found in " + file); + return ttf; } else { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontCache.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontCache.java index b99ac6c5a..2eceb0b82 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontCache.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontCache.java @@ -17,14 +17,14 @@ package com.tom_roush.pdfbox.pdmodel.font; -import com.tom_roush.fontbox.FontBoxFont; - import java.lang.ref.SoftReference; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.tom_roush.fontbox.FontBoxFont; + /** - * A cache for system fonts. This allows PDFBox to manage caching for a {@link FontProvider}. + * An in-memory cache for system fonts. This allows PDFBox to manage caching for a {@link FontProvider}. * PDFBox is free to purge this cache at will. * * @author John Hewson diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapper.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapper.java index 85dc0949a..74adf4504 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapper.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapper.java @@ -16,709 +16,39 @@ */ package com.tom_roush.pdfbox.pdmodel.font; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.PriorityQueue; -import java.util.Set; - import com.tom_roush.fontbox.FontBoxFont; -import com.tom_roush.fontbox.cff.CFFFont; -import com.tom_roush.fontbox.cff.CFFType1Font; -import com.tom_roush.fontbox.ttf.OpenTypeFont; -import com.tom_roush.fontbox.ttf.TTFParser; import com.tom_roush.fontbox.ttf.TrueTypeFont; -import com.tom_roush.fontbox.type1.Type1Font; -import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; /** - * Font mapper, locates non-embedded fonts via a pluggable FontProvider. + * Font mapper, locates non-embedded fonts. If you implement this then you're responsible for + * caching the fonts. SoftReference is recommended. * * @author John Hewson */ -final class FontMapper +public interface FontMapper { - private FontMapper() {} - - private static final FontCache fontCache = new FontCache(); // todo: static cache isn't ideal - private static FontProvider fontProvider; - private static Map fontInfoByName; - - private static final TrueTypeFont lastResortFont; - - static - { - try - { - String ttfName = "com/tom_roush/pdfbox/resources/ttf/LiberationSans-Regular.ttf"; - InputStream ttfStream; - if(PDFBoxResourceLoader.isReady()) { - ttfStream = PDFBoxResourceLoader.getStream(ttfName); - if (ttfStream == null) - { - throw new IOException("Error loading resource: " + ttfStream); - } - } else { - // Fallback - URL url = FontMapper.class.getClassLoader().getResource(ttfName); - if (url == null) - { - throw new IOException("Error loading resource: " + ttfName); - } - ttfStream = url.openStream(); - } - - TTFParser ttfParser = new TTFParser(); - lastResortFont = ttfParser.parse(ttfStream); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - } - - // lazy thread safe singleton - private static class DefaultFontProvider - { - private static final FontProvider INSTANCE = new FileSystemFontProvider(fontCache); - } - - /** - * Sets the font service provider. - */ - public synchronized static void setProvider(FontProvider fontProvider) - { - FontMapper.fontProvider = fontProvider; - fontInfoByName = createFontInfoByName(fontProvider.getFontInfo()); - } - - /** - * Returns the font service provider. Defaults to using FileSystemFontProvider. - */ - public synchronized static FontProvider getProvider() - { - if (fontProvider == null) - { - setProvider(DefaultFontProvider.INSTANCE); - } - return fontProvider; - } - - /** - * Returns the font cache associated with this FontMapper. This method is needed by - * FontProvider subclasses. - */ - public static FontCache getFontCache() - { - return fontCache; - } - - private static Map createFontInfoByName(List fontInfoList) - { - Map map = new LinkedHashMap(); - for (FontInfo info : fontInfoList) - { - for (String name : getPostScriptNames(info.getPostScriptName())) - { - map.put(name, info); - } - } - return map; - } - - /** - * Gets alternative names, as seen in some PDFs, e.g. PDFBOX-142. - */ - private static Set getPostScriptNames(String postScriptName) - { - Set names = new HashSet(); - - // built-in PostScript name - names.add(postScriptName); - - // remove hyphens (e.g. Arial-Black -> ArialBlack) - names.add(postScriptName.replaceAll("-", "")); - - return names; - } - /** - * Map of PostScript name substitutes, in priority order. - */ - private static final Map> substitutes = new HashMap>(); - - static - { - // substitutes for standard 14 fonts - substitutes.put("Courier", - Arrays.asList("CourierNew", "CourierNewPSMT", "LiberationMono", "NimbusMonL-Regu", - "DroidSansMono")); - substitutes.put("Courier-Bold", - Arrays.asList("CourierNewPS-BoldMT", "CourierNew-Bold", "LiberationMono-Bold", - "NimbusMonL-Bold","DroidSansMono")); - substitutes.put("Courier-Oblique", - Arrays.asList("CourierNewPS-ItalicMT","CourierNew-Italic", - "LiberationMono-Italic", "NimbusMonL-ReguObli","DroidSansMono")); - substitutes.put("Courier-BoldOblique", - Arrays.asList("CourierNewPS-BoldItalicMT","CourierNew-BoldItalic", - "LiberationMono-BoldItalic", "NimbusMonL-BoldObli","DroidSansMono")); - substitutes.put("Helvetica", - Arrays.asList("ArialMT", "Arial", "LiberationSans", "NimbusSanL-Regu","Roboto-Regular")); - substitutes.put("Helvetica-Bold", - Arrays.asList("Arial-BoldMT", "Arial-Bold", "LiberationSans-Bold", - "NimbusSanL-Bold","Roboto-Bold")); - substitutes.put("Helvetica-Oblique", - Arrays.asList("Arial-ItalicMT", "Arial-Italic", "Helvetica-Italic", - "LiberationSans-Italic", "NimbusSanL-ReguItal", "Roboto-Italic")); - substitutes.put("Helvetica-BoldOblique", - Arrays.asList("Arial-BoldItalicMT", "Helvetica-BoldItalic", - "LiberationSans-BoldItalic", "NimbusSanL-BoldItal","Roboto-BoldItalic")); - substitutes.put("Times-Roman", - Arrays.asList("TimesNewRomanPSMT", "TimesNewRoman", "TimesNewRomanPS", - "LiberationSerif", "NimbusRomNo9L-Regu","DroidSerif-Regular", "Roboto-Regular")); - substitutes.put("Times-Bold", - Arrays.asList("TimesNewRomanPS-BoldMT", "TimesNewRomanPS-Bold", - "TimesNewRoman-Bold", "LiberationSerif-Bold", - "NimbusRomNo9L-Medi", "DroidSerif-Bold", "Roboto-Bold")); - substitutes.put("Times-Italic", - Arrays.asList("TimesNewRomanPS-ItalicMT", "TimesNewRomanPS-Italic", - "TimesNewRoman-Italic", "LiberationSerif-Italic", - "NimbusRomNo9L-ReguItal","DroidSerif-Italic", "Roboto-Italic")); - substitutes.put("Times-BoldItalic", - Arrays.asList("TimesNewRomanPS-BoldItalicMT", "TimesNewRomanPS-BoldItalic", - "TimesNewRoman-BoldItalic", "LiberationSerif-BoldItalic", - "NimbusRomNo9L-MediItal","DroidSerif-BoldItalic", "Roboto-BoldItalic")); - substitutes.put("Symbol", Arrays.asList("Symbol", "SymbolMT", "StandardSymL")); - substitutes.put("ZapfDingbats", Arrays.asList("ZapfDingbatsITC", "Dingbats", "MS-Gothic")); - // TODO: PdfBox-Android load extra fonts? (DroidSerif for times and a symbol font) - - // Acrobat also uses alternative names for Standard 14 fonts, which we map to those above - // these include names such as "Arial" and "TimesNewRoman" - for (String baseName : Standard14Fonts.getNames()) - { - if (!substitutes.containsKey(baseName)) - { - String mappedName = Standard14Fonts.getMappedFontName(baseName); - substitutes.put(baseName, copySubstitutes(mappedName)); - } - } - } - - /** - * Copies a list of font substitutes, adding the original font at the start of the list. - */ - private static List copySubstitutes(String postScriptName) - { - return new ArrayList(substitutes.get(postScriptName)); - } - - /** - * Adds a top-priority substitute for the given font. - * - * @param match PostScript name of the font to match - * @param replace PostScript name of the font to use as a replacement - */ - public static void addSubstitute(String match, String replace) - { - if (!substitutes.containsKey(match)) - { - substitutes.put(match, new ArrayList()); - } - substitutes.get(match).add(replace); - } - - /** - * Returns the substitutes for a given font. - */ - private static List getSubstitutes(String postScriptName) - { - List subs = substitutes.get(postScriptName.replaceAll(" ", "")); - if (subs != null) - { - return subs; - } - else - { - return Collections.emptyList(); - } - } - - /** - * Attempts to find a good fallback based on the font descriptor. - */ - private static String getFallbackFontName(PDFontDescriptor fontDescriptor) - { - String fontName; - if (fontDescriptor != null) - { - // heuristic detection of bold - boolean isBold = false; - String name = fontDescriptor.getFontName(); - if (name != null) - { - String lower = fontDescriptor.getFontName().toLowerCase(); - isBold = lower.contains("bold") || - lower.contains("black") || - lower.contains("heavy"); - } - - // font descriptor flags should describe the style - if (fontDescriptor.isFixedPitch()) - { - fontName = "Courier"; - if (isBold && fontDescriptor.isItalic()) - { - fontName += "-BoldOblique"; - } - else if (isBold) - { - fontName += "-Bold"; - } - else if (fontDescriptor.isItalic()) - { - fontName += "-Oblique"; - } - } - else if (fontDescriptor.isSerif()) - { - fontName = "Times"; - if (isBold && fontDescriptor.isItalic()) - { - fontName += "-BoldItalic"; - } - else if (isBold) - { - fontName += "-Bold"; - } - else if (fontDescriptor.isItalic()) - { - fontName += "-Italic"; - } - else - { - fontName += "-Roman"; - } - } - else - { - fontName = "Helvetica"; - if (isBold && fontDescriptor.isItalic()) - { - fontName += "-BoldOblique"; - } - else if (isBold) - { - fontName += "-Bold"; - } - else if (fontDescriptor.isItalic()) - { - fontName += "-Oblique"; - } - } - } - else - { - // if there is no FontDescriptor then we just fall back to Times Roman - fontName = "Times-Roman"; - } - return fontName; - } - - /** - * Finds a TrueType font with the given PostScript name, or a suitable substitute, or null. - * + * Finds a TrueType font with the given PostScript name, or a suitable substitute, or null. + * * @param fontDescriptor FontDescriptor */ - public static FontMapping getTrueTypeFont(String baseFont, - PDFontDescriptor fontDescriptor) - { - TrueTypeFont ttf = (TrueTypeFont) findFont(FontFormat.TTF, baseFont); - if (ttf != null) - { - return new FontMapping(ttf, false); - } - else - { - // fallback - todo: i.e. fuzzy match - String fontName = getFallbackFontName(fontDescriptor); - ttf = (TrueTypeFont) findFont(FontFormat.TTF, fontName); - if (ttf == null) - { - // we have to return something here as TTFs aren't strictly required on the system - ttf = lastResortFont; - } - return new FontMapping(ttf, true); - } - } + FontMapping getTrueTypeFont(String baseFont, PDFontDescriptor fontDescriptor); - /** + /** * Finds a font with the given PostScript name, or a suitable substitute, or null. This allows * any font to be substituted with a PFB, TTF or OTF. * * @param fontDescriptor the FontDescriptor of the font to find */ - public static FontMapping getFontBoxFont(String baseFont, - PDFontDescriptor fontDescriptor) - { - FontBoxFont font = findFontBoxFont(baseFont); - if (font != null) - { - return new FontMapping(font, false); - } - else - { - // fallback - todo: i.e. fuzzy match - String fallbackName = getFallbackFontName(fontDescriptor); - font = findFontBoxFont(fallbackName); - if (font == null) - { - // we have to return something here as TTFs aren't strictly required on the system - font = lastResortFont; - } - return new FontMapping(font, true); - } - } - - /** - * Finds a font with the given PostScript name, or a suitable substitute, or null. - * - * @param postScriptName PostScript font name - */ - private static FontBoxFont findFontBoxFont(String postScriptName) - { - Type1Font t1 = (Type1Font) findFont(FontFormat.PFB, postScriptName); - if (t1 != null) - { - return t1; - } - CFFFont cff = (CFFFont) findFont(FontFormat.OTF, postScriptName); - if (cff instanceof CFFType1Font) - { - return cff; - } - - TrueTypeFont ttf = (TrueTypeFont) findFont(FontFormat.TTF, postScriptName); - if (ttf != null) - { - return ttf; - } - - return null; - } - - /** - * Finds a font with the given PostScript name, or a suitable substitute, or null. - * - * @param postScriptName PostScript font name - */ - private static FontBoxFont findFont(FontFormat format, String postScriptName) - { - // handle damaged PDFs, see PDFBOX-2884 - if (postScriptName == null) - { - return null; - } - - // make sure the font provider is initialized - if (fontProvider == null) - { - getProvider(); - } + FontMapping getFontBoxFont(String baseFont, PDFontDescriptor fontDescriptor); - // first try to match the PostScript name - FontInfo info = getFont(format, postScriptName); - if (info != null) - { - return info.getFont(); - } - - // remove hyphens (e.g. Arial-Black -> ArialBlack) - info = getFont(format, postScriptName.replaceAll("-", "")); - if (info != null) - { - return info.getFont(); - } - - // then try named substitutes - for (String substituteName : getSubstitutes(postScriptName)) - { - info = getFont(format, substituteName); - if (info != null) - { - return info.getFont(); - } - } - - // then try converting Windows names e.g. (ArialNarrow,Bold) -> (ArialNarrow-Bold) - info = getFont(format, postScriptName.replaceAll(",", "-")); - if (info != null) - { - return info.getFont(); - } - - // no matches - return null; - } - - /** - * Finds the named font with the given format. - */ - private static FontInfo getFont(FontFormat format, String postScriptName) - { - // strip subset tag (happens when we substitute a corrupt embedded font, see PDFBOX-2642) - if (postScriptName.contains("+")) - { - postScriptName = postScriptName.substring(postScriptName.indexOf('+') + 1); - } - - // look up the PostScript name - FontInfo info = fontInfoByName.get(postScriptName); - if (info != null && info.getFormat() == format) - { - return info; - } - return null; - } - - /** + /** * Finds a CFF CID-Keyed font with the given PostScript name, or a suitable substitute, or null. * This method can also map CJK fonts via their CIDSystemInfo (ROS). * * @param fontDescriptor FontDescriptor * @param cidSystemInfo the CID system info, e.g. "Adobe-Japan1", if any. */ - public static CIDFontMapping getCIDFont(String baseFont, PDFontDescriptor fontDescriptor, - PDCIDSystemInfo cidSystemInfo) - { - // try name match or substitute with OTF - OpenTypeFont otf1 = (OpenTypeFont) findFont(FontFormat.OTF, baseFont); - if (otf1 != null) - { - return new CIDFontMapping(otf1, null, false); - } - - // try name match or substitute with TTF - TrueTypeFont ttf = (TrueTypeFont) findFont(FontFormat.TTF, baseFont); - if (ttf != null) - { - return new CIDFontMapping(null, ttf, false); - } - - if (cidSystemInfo != null) - { - // "In Acrobat 3.0.1 and later, Type 0 fonts that use a CMap whose CIDSystemInfo - // dictionary defines the Adobe-GB1, Adobe-CNS1 Adobe-Japan1, or Adobe-Korea1 character - // collection can also be substituted." - Adobe Supplement to the ISO 32000 - - String collection = cidSystemInfo.getRegistry() + "-" + cidSystemInfo.getOrdering(); - - if (collection.equals("Adobe-GB1") || collection.equals("Adobe-CNS1") || - collection.equals("Adobe-Japan1") || collection.equals("Adobe-Korea1")) - { - // try automatic substitutes via character collection - PriorityQueue queue = getFontMatches(fontDescriptor, cidSystemInfo); - FontMatch bestMatch = queue.poll(); - if (bestMatch != null) - { - FontBoxFont font = bestMatch.info.getFont(); - if (font instanceof OpenTypeFont) - { - return new CIDFontMapping((OpenTypeFont) font, null, true); - } - else - { - return new CIDFontMapping(null, font, true); - } - } - } - } - - // last-resort fallback - return new CIDFontMapping(null, lastResortFont, true); - } - - /** - * Returns a list of matching fonts, scored by suitability. Positive scores indicate matches - * for certain attributes, while negative scores indicate mismatches. Zero scores are neutral. - * - * @param fontDescriptor FontDescriptor, always present. - * @param cidSystemInfo Font's CIDSystemInfo, may be null. - */ - private static PriorityQueue getFontMatches(PDFontDescriptor fontDescriptor, - PDCIDSystemInfo cidSystemInfo) - { - PriorityQueue queue = new PriorityQueue(20); - - for (FontInfo info : fontInfoByName.values()) - { - // filter by CIDSystemInfo, if given - if (cidSystemInfo != null && !isCharSetMatch(cidSystemInfo, info)) - { - continue; - } - - FontMatch match = new FontMatch(info); - - // Panose is the most reliable - if (fontDescriptor.getPanose() != null && info.getPanose() != null) - { - PDPanoseClassification panose = fontDescriptor.getPanose().getPanose(); - if (panose.getFamilyKind() == info.getPanose().getFamilyKind()) - { - // serifs - if (panose.getSerifStyle() == info.getPanose().getSerifStyle()) - { - // exact match - match.score += 2; - } - else if (panose.getSerifStyle() >= 2 && panose.getSerifStyle() <= 5 && - info.getPanose().getSerifStyle() >= 2 && - info.getPanose().getSerifStyle() <= 5) - { - // cove (serif) - match.score += 1; - } - else if (panose.getSerifStyle() >= 11 && panose.getSerifStyle() <= 13 && - info.getPanose().getSerifStyle() >= 11 && - info.getPanose().getSerifStyle() <= 13) - { - // sans-serif - match.score += 1; - } - else if (panose.getSerifStyle() != 0 && info.getPanose().getSerifStyle() != 0) - { - // mismatch - match.score -= 1; - } - - // weight - int weight = info.getPanose().getWeight(); - int weightClass = info.getWeightClassAsPanose(); - if (Math.abs(weight - weightClass) > 2) - { - // inconsistent data in system font, usWeightClass wins - weight = weightClass; - } - - if (panose.getWeight() == weight) - { - // exact match - match.score += 2; - } - else if (panose.getWeight() > 1 && weight > 1) - { - float dist = Math.abs(panose.getWeight() - weight); - match.score += 1 - dist * 0.5; - } - - // todo: italic - // ... - } - } - else if (fontDescriptor.getFontWeight() > 0 && info.getWeightClass() > 0) - { - // usWeightClass is pretty reliable - float dist = Math.abs(fontDescriptor.getFontWeight() - info.getWeightClass()); - match.score += 1 - (dist / 100) * 0.5; - } - // todo: italic - // ... - - queue.add(match); - } - return queue; - } - - /** - * Returns true if the character set described by CIDSystemInfo is present in the given font. - * Only applies to Adobe-GB1, Adobe-CNS1, Adobe-Japan1, Adobe-Korea1, as per the PDF spec. - */ - private static boolean isCharSetMatch(PDCIDSystemInfo cidSystemInfo, FontInfo info) - { - if (info.getCIDSystemInfo() != null) - { - return info.getCIDSystemInfo().getRegistry().equals(cidSystemInfo.getRegistry()) && - info.getCIDSystemInfo().getOrdering().equals(cidSystemInfo.getOrdering()); - } - else - { - long codePageRange = info.getCodePageRange(); - - long JIS_JAPAN = 1 << 17; - long CHINESE_SIMPLIFIED = 1 << 18; - long KOREAN_WANSUNG = 1 << 19; - long CHINESE_TRADITIONAL = 1 << 20; - long KOREAN_JOHAB = 1 << 21; - - if (cidSystemInfo.getOrdering().equals("GB1") && - (codePageRange & CHINESE_SIMPLIFIED) == CHINESE_SIMPLIFIED) - { - return true; - } - else if (cidSystemInfo.getOrdering().equals("CNS1") && - (codePageRange & CHINESE_TRADITIONAL) == CHINESE_TRADITIONAL) - { - return true; - } - else if (cidSystemInfo.getOrdering().equals("Japan1") && - (codePageRange & JIS_JAPAN) == JIS_JAPAN) - { - return true; - } - else - { - return cidSystemInfo.getOrdering().equals("Korea1") && - (codePageRange & KOREAN_WANSUNG) == KOREAN_WANSUNG || - (codePageRange & KOREAN_JOHAB) == KOREAN_JOHAB; - } - } - } - - /** - * A potential match for a font substitution. - */ - private static class FontMatch implements Comparable - { - double score; - final FontInfo info; - - FontMatch(FontInfo info) - { - this.info = info; - } - - @Override - public int compareTo(FontMatch match) - { - return Double.compare(match.score, this.score); - } - } - - /** - * For debugging. Prints all matches and returns the best match. - */ - private static FontMatch printMatches(PriorityQueue queue) - { - FontMatch bestMatch = queue.peek(); - System.out.println("-------"); - while (!queue.isEmpty()) - { - FontMatch match = queue.poll(); - FontInfo info = match.info; - System.out.println(match.score + " | " + info.getMacStyle() + " " + - info.getFamilyClass() + " " + info.getPanose() + " " + - info.getCIDSystemInfo() + " " + info.getPostScriptName() + " " + - info.getFormat()); - } - System.out.println("-------"); - return bestMatch; - } + CIDFontMapping getCIDFont(String baseFont, PDFontDescriptor fontDescriptor, + PDCIDSystemInfo cidSystemInfo); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapperImpl.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapperImpl.java new file mode 100644 index 000000000..3054466de --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapperImpl.java @@ -0,0 +1,721 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.font; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Set; + +import com.tom_roush.fontbox.FontBoxFont; +import com.tom_roush.fontbox.cff.CFFFont; +import com.tom_roush.fontbox.cff.CFFType1Font; +import com.tom_roush.fontbox.ttf.OpenTypeFont; +import com.tom_roush.fontbox.ttf.TTFParser; +import com.tom_roush.fontbox.ttf.TrueTypeFont; +import com.tom_roush.fontbox.type1.Type1Font; +import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; + +/** + * Font mapper, locates non-embedded fonts via a pluggable FontProvider. + * + * @author John Hewson + */ +final class FontMapperImpl implements FontMapper +{ + private static final FontCache fontCache = new FontCache(); // todo: static cache isn't ideal + private FontProvider fontProvider; + private Map fontInfoByName; + private final TrueTypeFont lastResortFont; + + /** + * Map of PostScript name substitutes, in priority order. + */ + private final Map> substitutes = new HashMap>(); + + FontMapperImpl() + { + // substitutes for standard 14 fonts + substitutes.put("Courier", + Arrays.asList("CourierNew", "CourierNewPSMT", "LiberationMono", "NimbusMonL-Regu", + "DroidSansMono")); + substitutes.put("Courier-Bold", + Arrays.asList("CourierNewPS-BoldMT", "CourierNew-Bold", "LiberationMono-Bold", + "NimbusMonL-Bold","DroidSansMono")); + substitutes.put("Courier-Oblique", + Arrays.asList("CourierNewPS-ItalicMT","CourierNew-Italic", + "LiberationMono-Italic", "NimbusMonL-ReguObli","DroidSansMono")); + substitutes.put("Courier-BoldOblique", + Arrays.asList("CourierNewPS-BoldItalicMT","CourierNew-BoldItalic", + "LiberationMono-BoldItalic", "NimbusMonL-BoldObli","DroidSansMono")); + substitutes.put("Helvetica", + Arrays.asList("ArialMT", "Arial", "LiberationSans", "NimbusSanL-Regu","Roboto-Regular")); + substitutes.put("Helvetica-Bold", + Arrays.asList("Arial-BoldMT", "Arial-Bold", "LiberationSans-Bold", + "NimbusSanL-Bold","Roboto-Bold")); + substitutes.put("Helvetica-Oblique", + Arrays.asList("Arial-ItalicMT", "Arial-Italic", "Helvetica-Italic", + "LiberationSans-Italic", "NimbusSanL-ReguItal", "Roboto-Italic")); + substitutes.put("Helvetica-BoldOblique", + Arrays.asList("Arial-BoldItalicMT", "Helvetica-BoldItalic", + "LiberationSans-BoldItalic", "NimbusSanL-BoldItal","Roboto-BoldItalic")); + substitutes.put("Times-Roman", + Arrays.asList("TimesNewRomanPSMT", "TimesNewRoman", "TimesNewRomanPS", + "LiberationSerif", "NimbusRomNo9L-Regu","DroidSerif-Regular", "Roboto-Regular")); + substitutes.put("Times-Bold", + Arrays.asList("TimesNewRomanPS-BoldMT", "TimesNewRomanPS-Bold", + "TimesNewRoman-Bold", "LiberationSerif-Bold", + "NimbusRomNo9L-Medi", "DroidSerif-Bold", "Roboto-Bold")); + substitutes.put("Times-Italic", + Arrays.asList("TimesNewRomanPS-ItalicMT", "TimesNewRomanPS-Italic", + "TimesNewRoman-Italic", "LiberationSerif-Italic", + "NimbusRomNo9L-ReguItal","DroidSerif-Italic", "Roboto-Italic")); + substitutes.put("Times-BoldItalic", + Arrays.asList("TimesNewRomanPS-BoldItalicMT", "TimesNewRomanPS-BoldItalic", + "TimesNewRoman-BoldItalic", "LiberationSerif-BoldItalic", + "NimbusRomNo9L-MediItal","DroidSerif-BoldItalic", "Roboto-BoldItalic")); + substitutes.put("Symbol", Arrays.asList("Symbol", "SymbolMT", "StandardSymL")); + substitutes.put("ZapfDingbats", Arrays.asList("ZapfDingbatsITC", "Dingbats", "MS-Gothic")); + // TODO: PdfBox-Android load extra fonts? (DroidSerif for times and a symbol font) + + // Acrobat also uses alternative names for Standard 14 fonts, which we map to those above + // these include names such as "Arial" and "TimesNewRoman" + for (String baseName : Standard14Fonts.getNames()) + { + if (!substitutes.containsKey(baseName)) + { + String mappedName = Standard14Fonts.getMappedFontName(baseName); + substitutes.put(baseName, copySubstitutes(mappedName)); + } + } + + // ------------------------- + + try + { + String ttfName = "com/tom_roush/pdfbox/resources/ttf/LiberationSans-Regular.ttf"; + InputStream ttfStream = null; + if (PDFBoxResourceLoader.isReady()) + { + ttfStream = PDFBoxResourceLoader.getStream(ttfName); + } + + if (ttfStream == null) + { + // Fallback + URL url = FontMapper.class.getClassLoader().getResource(ttfName); + if (url == null) + { + throw new IOException("Error loading resource: " + ttfName); + } + ttfStream = url.openStream(); + } + + TTFParser ttfParser = new TTFParser(); + lastResortFont = ttfParser.parse(ttfStream); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + // lazy thread safe singleton + private static class DefaultFontProvider + { + private static final FontProvider INSTANCE = new FileSystemFontProvider(fontCache); + } + + /** + * Sets the font service provider. + */ + public synchronized void setProvider(FontProvider fontProvider) + { + this.fontProvider = fontProvider; + fontInfoByName = createFontInfoByName(fontProvider.getFontInfo()); + } + + /** + * Returns the font service provider. Defaults to using FileSystemFontProvider. + */ + public synchronized FontProvider getProvider() + { + if (fontProvider == null) + { + setProvider(DefaultFontProvider.INSTANCE); + } + return fontProvider; + } + + /** + * Returns the font cache associated with this FontMapper. This method is needed by + * FontProvider subclasses. + */ + public FontCache getFontCache() + { + return fontCache; + } + + private Map createFontInfoByName(List fontInfoList) + { + Map map = new LinkedHashMap(); + for (FontInfo info : fontInfoList) + { + for (String name : getPostScriptNames(info.getPostScriptName())) + { + map.put(name, info); + } + } + return map; + } + + /** + * Gets alternative names, as seen in some PDFs, e.g. PDFBOX-142. + */ + private Set getPostScriptNames(String postScriptName) + { + Set names = new HashSet(); + + // built-in PostScript name + names.add(postScriptName); + + // remove hyphens (e.g. Arial-Black -> ArialBlack) + names.add(postScriptName.replaceAll("-", "")); + + return names; + } + + /** + * Copies a list of font substitutes, adding the original font at the start of the list. + */ + private List copySubstitutes(String postScriptName) + { + return new ArrayList(substitutes.get(postScriptName)); + } + + /** + * Adds a top-priority substitute for the given font. + * + * @param match PostScript name of the font to match + * @param replace PostScript name of the font to use as a replacement + */ + public void addSubstitute(String match, String replace) + { + if (!substitutes.containsKey(match)) + { + substitutes.put(match, new ArrayList()); + } + substitutes.get(match).add(replace); + } + + /** + * Returns the substitutes for a given font. + */ + private List getSubstitutes(String postScriptName) + { + List subs = substitutes.get(postScriptName.replaceAll(" ", "")); + if (subs != null) + { + return subs; + } + else + { + return Collections.emptyList(); + } + } + + /** + * Attempts to find a good fallback based on the font descriptor. + */ + private String getFallbackFontName(PDFontDescriptor fontDescriptor) + { + String fontName; + if (fontDescriptor != null) + { + // heuristic detection of bold + boolean isBold = false; + String name = fontDescriptor.getFontName(); + if (name != null) + { + String lower = fontDescriptor.getFontName().toLowerCase(); + isBold = lower.contains("bold") || lower.contains("black") || lower.contains( + "heavy"); + } + + // font descriptor flags should describe the style + if (fontDescriptor.isFixedPitch()) + { + fontName = "Courier"; + if (isBold && fontDescriptor.isItalic()) + { + fontName += "-BoldOblique"; + } + else if (isBold) + { + fontName += "-Bold"; + } + else if (fontDescriptor.isItalic()) + { + fontName += "-Oblique"; + } + } + else if (fontDescriptor.isSerif()) + { + fontName = "Times"; + if (isBold && fontDescriptor.isItalic()) + { + fontName += "-BoldItalic"; + } + else if (isBold) + { + fontName += "-Bold"; + } + else if (fontDescriptor.isItalic()) + { + fontName += "-Italic"; + } + else + { + fontName += "-Roman"; + } + } + else + { + fontName = "Helvetica"; + if (isBold && fontDescriptor.isItalic()) + { + fontName += "-BoldOblique"; + } + else if (isBold) + { + fontName += "-Bold"; + } + else if (fontDescriptor.isItalic()) + { + fontName += "-Oblique"; + } + } + } + else + { + // if there is no FontDescriptor then we just fall back to Times Roman + fontName = "Times-Roman"; + } + return fontName; + } + + /** + * Finds a TrueType font with the given PostScript name, or a suitable substitute, or null. + * + * @param fontDescriptor FontDescriptor + */ + @Override + public FontMapping getTrueTypeFont(String baseFont, + PDFontDescriptor fontDescriptor) + { + TrueTypeFont ttf = (TrueTypeFont)findFont(FontFormat.TTF, baseFont); + if (ttf != null) + { + return new FontMapping(ttf, false); + } + else + { + // fallback - todo: i.e. fuzzy match + String fontName = getFallbackFontName(fontDescriptor); + ttf = (TrueTypeFont)findFont(FontFormat.TTF, fontName); + if (ttf == null) + { + // we have to return something here as TTFs aren't strictly required on the system + ttf = lastResortFont; + } + return new FontMapping(ttf, true); + } + } + + /** + * Finds a font with the given PostScript name, or a suitable substitute, or null. This allows + * any font to be substituted with a PFB, TTF or OTF. + * + * @param fontDescriptor the FontDescriptor of the font to find + */ + @Override + public FontMapping getFontBoxFont(String baseFont, PDFontDescriptor fontDescriptor) + { + FontBoxFont font = findFontBoxFont(baseFont); + if (font != null) + { + return new FontMapping(font, false); + } + else + { + // fallback - todo: i.e. fuzzy match + String fallbackName = getFallbackFontName(fontDescriptor); + font = findFontBoxFont(fallbackName); + if (font == null) + { + // we have to return something here as TTFs aren't strictly required on the system + font = lastResortFont; + } + return new FontMapping(font, true); + } + } + + /** + * Finds a font with the given PostScript name, or a suitable substitute, or null. + * + * @param postScriptName PostScript font name + */ + private FontBoxFont findFontBoxFont(String postScriptName) + { + Type1Font t1 = (Type1Font)findFont(FontFormat.PFB, postScriptName); + if (t1 != null) + { + return t1; + } + CFFFont cff = (CFFFont)findFont(FontFormat.OTF, postScriptName); + if (cff instanceof CFFType1Font) + { + return cff; + } + + TrueTypeFont ttf = (TrueTypeFont)findFont(FontFormat.TTF, postScriptName); + if (ttf != null) + { + return ttf; + } + + return null; + } + + /** + * Finds a font with the given PostScript name, or a suitable substitute, or null. + * + * @param postScriptName PostScript font name + */ + private FontBoxFont findFont(FontFormat format, String postScriptName) + { + // handle damaged PDFs, see PDFBOX-2884 + if (postScriptName == null) + { + return null; + } + + // make sure the font provider is initialized + if (fontProvider == null) + { + getProvider(); + } + + // first try to match the PostScript name + FontInfo info = getFont(format, postScriptName); + if (info != null) + { + return info.getFont(); + } + + // remove hyphens (e.g. Arial-Black -> ArialBlack) + info = getFont(format, postScriptName.replaceAll("-", "")); + if (info != null) + { + return info.getFont(); + } + + // then try named substitutes + for (String substituteName : getSubstitutes(postScriptName)) + { + info = getFont(format, substituteName); + if (info != null) + { + return info.getFont(); + } + } + + // then try converting Windows names e.g. (ArialNarrow,Bold) -> (ArialNarrow-Bold) + info = getFont(format, postScriptName.replaceAll(",", "-")); + if (info != null) + { + return info.getFont(); + } + + // no matches + return null; + } + + /** + * Finds the named font with the given format. + */ + private FontInfo getFont(FontFormat format, String postScriptName) + { + // strip subset tag (happens when we substitute a corrupt embedded font, see PDFBOX-2642) + if (postScriptName.contains("+")) + { + postScriptName = postScriptName.substring(postScriptName.indexOf('+') + 1); + } + + // look up the PostScript name + FontInfo info = fontInfoByName.get(postScriptName); + if (info != null && info.getFormat() == format) + { + return info; + } + return null; + } + + /** + * Finds a CFF CID-Keyed font with the given PostScript name, or a suitable substitute, or null. + * This method can also map CJK fonts via their CIDSystemInfo (ROS). + * + * @param fontDescriptor FontDescriptor + * @param cidSystemInfo the CID system info, e.g. "Adobe-Japan1", if any. + */ + @Override + public CIDFontMapping getCIDFont(String baseFont, PDFontDescriptor fontDescriptor, + PDCIDSystemInfo cidSystemInfo) + { + // try name match or substitute with OTF + OpenTypeFont otf1 = (OpenTypeFont)findFont(FontFormat.OTF, baseFont); + if (otf1 != null) + { + return new CIDFontMapping(otf1, null, false); + } + + // try name match or substitute with TTF + TrueTypeFont ttf = (TrueTypeFont)findFont(FontFormat.TTF, baseFont); + if (ttf != null) + { + return new CIDFontMapping(null, ttf, false); + } + + if (cidSystemInfo != null) + { + // "In Acrobat 3.0.1 and later, Type 0 fonts that use a CMap whose CIDSystemInfo + // dictionary defines the Adobe-GB1, Adobe-CNS1 Adobe-Japan1, or Adobe-Korea1 character + // collection can also be substituted." - Adobe Supplement to the ISO 32000 + + String collection = cidSystemInfo.getRegistry() + "-" + cidSystemInfo.getOrdering(); + + if (collection.equals("Adobe-GB1") || collection.equals("Adobe-CNS1") || + collection.equals("Adobe-Japan1") || collection.equals("Adobe-Korea1")) + { + // try automatic substitutes via character collection + PriorityQueue queue = getFontMatches(fontDescriptor, cidSystemInfo); + FontMatch bestMatch = queue.poll(); + if (bestMatch != null) + { + FontBoxFont font = bestMatch.info.getFont(); + if (font instanceof OpenTypeFont) + { + return new CIDFontMapping((OpenTypeFont)font, null, true); + } + else + { + return new CIDFontMapping(null, font, true); + } + } + } + } + + // last-resort fallback + return new CIDFontMapping(null, lastResortFont, true); + } + + /** + * Returns a list of matching fonts, scored by suitability. Positive scores indicate matches + * for certain attributes, while negative scores indicate mismatches. Zero scores are neutral. + * + * @param fontDescriptor FontDescriptor, always present. + * @param cidSystemInfo Font's CIDSystemInfo, may be null. + */ + private PriorityQueue getFontMatches(PDFontDescriptor fontDescriptor, + PDCIDSystemInfo cidSystemInfo) + { + PriorityQueue queue = new PriorityQueue(20); + + for (FontInfo info : fontInfoByName.values()) + { + // filter by CIDSystemInfo, if given + if (cidSystemInfo != null && !isCharSetMatch(cidSystemInfo, info)) + { + continue; + } + + FontMatch match = new FontMatch(info); + + // Panose is the most reliable + if (fontDescriptor.getPanose() != null && info.getPanose() != null) + { + PDPanoseClassification panose = fontDescriptor.getPanose().getPanose(); + if (panose.getFamilyKind() == info.getPanose().getFamilyKind()) + { + // serifs + if (panose.getSerifStyle() == info.getPanose().getSerifStyle()) + { + // exact match + match.score += 2; + } + else if (panose.getSerifStyle() >= 2 && panose.getSerifStyle() <= 5 && + info.getPanose().getSerifStyle() >= 2 && + info.getPanose().getSerifStyle() <= 5) + { + // cove (serif) + match.score += 1; + } + else if (panose.getSerifStyle() >= 11 && panose.getSerifStyle() <= 13 && + info.getPanose().getSerifStyle() >= 11 && + info.getPanose().getSerifStyle() <= 13) + { + // sans-serif + match.score += 1; + } + else if (panose.getSerifStyle() != 0 && info.getPanose().getSerifStyle() != 0) + { + // mismatch + match.score -= 1; + } + + // weight + int weight = info.getPanose().getWeight(); + int weightClass = info.getWeightClassAsPanose(); + if (Math.abs(weight - weightClass) > 2) + { + // inconsistent data in system font, usWeightClass wins + weight = weightClass; + } + + if (panose.getWeight() == weight) + { + // exact match + match.score += 2; + } + else if (panose.getWeight() > 1 && weight > 1) + { + float dist = Math.abs(panose.getWeight() - weight); + match.score += 1 - dist * 0.5; + } + + // todo: italic + // ... + } + } + else if (fontDescriptor.getFontWeight() > 0 && info.getWeightClass() > 0) + { + // usWeightClass is pretty reliable + float dist = Math.abs(fontDescriptor.getFontWeight() - info.getWeightClass()); + match.score += 1 - (dist / 100) * 0.5; + } + // todo: italic + // ... + + queue.add(match); + } + return queue; + } + + /** + * Returns true if the character set described by CIDSystemInfo is present in the given font. + * Only applies to Adobe-GB1, Adobe-CNS1, Adobe-Japan1, Adobe-Korea1, as per the PDF spec. + */ + private boolean isCharSetMatch(PDCIDSystemInfo cidSystemInfo, FontInfo info) + { + if (info.getCIDSystemInfo() != null) + { + return info.getCIDSystemInfo().getRegistry().equals(cidSystemInfo.getRegistry()) && + info.getCIDSystemInfo().getOrdering().equals(cidSystemInfo.getOrdering()); + } + else + { + long codePageRange = info.getCodePageRange(); + + long JIS_JAPAN = 1 << 17; + long CHINESE_SIMPLIFIED = 1 << 18; + long KOREAN_WANSUNG = 1 << 19; + long CHINESE_TRADITIONAL = 1 << 20; + long KOREAN_JOHAB = 1 << 21; + + if (cidSystemInfo.getOrdering().equals("GB1") && + (codePageRange & CHINESE_SIMPLIFIED) == CHINESE_SIMPLIFIED) + { + return true; + } + else if (cidSystemInfo.getOrdering().equals("CNS1") && + (codePageRange & CHINESE_TRADITIONAL) == CHINESE_TRADITIONAL) + { + return true; + } + else if (cidSystemInfo.getOrdering().equals("Japan1") && + (codePageRange & JIS_JAPAN) == JIS_JAPAN) + { + return true; + } + else + { + return cidSystemInfo.getOrdering().equals("Korea1") && + (codePageRange & KOREAN_WANSUNG) == KOREAN_WANSUNG || + (codePageRange & KOREAN_JOHAB) == KOREAN_JOHAB; + } + } + } + + /** + * A potential match for a font substitution. + */ + private class FontMatch implements Comparable + { + double score; + final FontInfo info; + + FontMatch(FontInfo info) + { + this.info = info; + } + + @Override + public int compareTo(FontMatch match) + { + return Double.compare(match.score, this.score); + } + } + + /** + * For debugging. Prints all matches and returns the best match. + */ + private FontMatch printMatches(PriorityQueue queue) + { + FontMatch bestMatch = queue.peek(); + System.out.println("-------"); + while (!queue.isEmpty()) + { + FontMatch match = queue.poll(); + FontInfo info = match.info; + System.out.println( + match.score + " | " + info.getMacStyle() + " " + info.getFamilyClass() + " " + + info.getPanose() + " " + info.getCIDSystemInfo() + " " + + info.getPostScriptName() + " " + info.getFormat()); + } + System.out.println("-------"); + return bestMatch; + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/BadSecurityHandlerException.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMappers.java similarity index 55% rename from library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/BadSecurityHandlerException.java rename to library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMappers.java index 06f97a891..db479ba12 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/encryption/BadSecurityHandlerException.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMappers.java @@ -14,44 +14,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.tom_roush.pdfbox.pdmodel.encryption; + +package com.tom_roush.pdfbox.pdmodel.font; /** - * This exception can be thrown by the SecurityHandlersManager class when - * a document required an unimplemented security handler to be opened. + * FontMapper factory class. * - * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr) - * @version $Revision: 1.2 $ + * @author John Hewson */ - -public class BadSecurityHandlerException extends Exception +public final class FontMappers { - /** - * Default Constructor. - */ - public BadSecurityHandlerException() + private static FontMapper instance; + + private FontMappers() { - super(); + } + + // lazy thread safe singleton + private static class DefaultFontMapper + { + private static final FontMapper INSTANCE = new FontMapperImpl(); } /** - * Constructor. - * - * @param e A sub exception. + * Returns the singleton FontMapper instance. */ - public BadSecurityHandlerException(Exception e) + public static FontMapper instance() { - super(e); + if (instance == null) + { + instance = DefaultFontMapper.INSTANCE; + } + return instance; } /** - * Constructor. - * - * @param msg Message describing exception. + * Sets the singleton FontMapper instance. */ - public BadSecurityHandlerException(String msg) + public static synchronized void set(FontMapper fontMapper) { - super(msg); + instance = fontMapper; } - } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapping.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapping.java index c98266b4e..ac77fde7d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapping.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/FontMapping.java @@ -29,7 +29,7 @@ public class FontMapping private final T font; private final boolean isFallback; - FontMapping(T font, boolean isFallback) + public FontMapping(T font, boolean isFallback) { this.font = font; this.isFallback = isFallback; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFont.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFont.java index 7f2f4399c..346a1243e 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFont.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFont.java @@ -17,6 +17,7 @@ package com.tom_roush.pdfbox.pdmodel.font; import java.io.IOException; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -44,6 +45,7 @@ public abstract class PDCIDFont implements COSObjectable, PDFontLike, PDVectorFo private Map widths; private float defaultWidth; + private float averageWidth; private final Map verticalDisplacementY = new HashMap(); // w1y private final Map positionVectors = new HashMap(); // v @@ -239,25 +241,17 @@ private float getDefaultWidth() */ private Vector getDefaultPositionVector(int cid) { - float w0; - if (widths.containsKey(cid)) - { - Float w = widths.get(cid); - if (w != null) - { - w0 = w; - } - else - { - w0 = getDefaultWidth(); - } - } - else + return new Vector(getWidthForCID(cid) / 2, dw2[0]); + } + + private float getWidthForCID(int cid) + { + Float width = widths.get(cid); + if (width == null) { - w0 = getDefaultWidth(); + width = getDefaultWidth(); } - - return new Vector(w0 / 2, dw2[0]); + return width; } @Override @@ -265,14 +259,11 @@ public Vector getPositionVector(int code) { int cid = codeToCID(code); Vector v = positionVectors.get(cid); - if (v != null) - { - return v; - } - else + if (v == null) { return getDefaultPositionVector(cid); } + return v; } /** @@ -285,14 +276,11 @@ public float getVerticalDisplacementVectorY(int code) { int cid = codeToCID(code); Float w1y = verticalDisplacementY.get(cid); - if (w1y != null) - { - return w1y; - } - else + if (w1y == null) { return dw2[1]; } + return w1y; } @Override @@ -305,23 +293,7 @@ public float getWidth(int code) throws IOException // program, but PDFBOX-563 shows that when they are not, Acrobat overrides the embedded // font widths with the widths given in the font dictionary - int cid = codeToCID(code); - if (widths.containsKey(cid)) - { - Float w = widths.get(cid); - if (w != null) - { - return w; - } - else - { - return getDefaultWidth(); - } - } - else - { - return getWidthFromFont(code); - } + return getWidthForCID(codeToCID(code)); } @Override @@ -334,50 +306,33 @@ public float getWidth(int code) throws IOException // todo: this method is highly suspicious, the average glyph width is not usually a good metric public float getAverageFontWidth() { - float totalWidths = 0.0f; - float characterCount = 0.0f; - COSArray widths = (COSArray) dict.getDictionaryObject(COSName.W); - - if (widths != null) + if (averageWidth == 0) { - for (int i = 0; i < widths.size(); i++) + float totalWidths = 0.0f; + float characterCount = 0.0f; + if (widths != null) { - COSNumber firstCode = (COSNumber) widths.getObject(i++); - COSBase next = widths.getObject(i); - if (next instanceof COSArray) + characterCount = widths.size(); + Collection widthsValues = widths.values(); + for (Float width : widthsValues) { - COSArray array = (COSArray) next; - for (int j = 0; j < array.size(); j++) - { - COSNumber width = (COSNumber) array.get(j); - totalWidths += width.floatValue(); - characterCount += 1; - } - } - else - { - i++; - COSNumber rangeWidth = (COSNumber) widths.getObject(i); - if (rangeWidth.floatValue() > 0) - { - totalWidths += rangeWidth.floatValue(); - characterCount += 1; - } + totalWidths += width; } } + float averageWidth = totalWidths / characterCount; + if (averageWidth <= 0 || Float.isNaN(averageWidth)) + { + averageWidth = getDefaultWidth(); + } } - float average = totalWidths / characterCount; - if (average <= 0) - { - average = getDefaultWidth(); - } - return average; + return averageWidth; } /** * Returns the CIDSystemInfo, or null if it is missing (which isn't allowed but could happen). */ - public PDCIDSystemInfo getCIDSystemInfo(){ + public PDCIDSystemInfo getCIDSystemInfo() + { COSDictionary cidSystemInfoDict = (COSDictionary) dict.getDictionaryObject(COSName.CIDSYSTEMINFO); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType0.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType0.java index 7c40d366f..c0738b7b1 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType0.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType0.java @@ -35,9 +35,12 @@ import com.tom_roush.harmony.awt.geom.AffineTransform; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.io.IOUtils; +import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; import com.tom_roush.pdfbox.pdmodel.common.PDStream; import com.tom_roush.pdfbox.util.Matrix; +import static com.tom_roush.pdfbox.pdmodel.font.UniUtil.getUniNameOfCodePoint; + /** * Type 0 CIDFont (CFF). * @@ -56,6 +59,7 @@ public class PDCIDFontType0 extends PDCIDFont private Float avgWidth = null; private Matrix fontMatrix; private final AffineTransform fontMatrixTransform; + private BoundingBox fontBBox; /** * Constructor. @@ -91,7 +95,7 @@ else if (bytes != null) CFFParser cffParser = new CFFParser(); try { - cffFont = cffParser.parse(bytes).get(0); + cffFont = cffParser.parse(bytes, new ByteSource()).get(0); } catch (IOException e) { @@ -119,8 +123,8 @@ else if (bytes != null) else { // find font or substitute - CIDFontMapping mapping = FontMapper.getCIDFont(getBaseFont(), getFontDescriptor(), - getCIDSystemInfo()); + CIDFontMapping mapping = FontMappers.instance().getCIDFont(getBaseFont(), + getFontDescriptor(), getCIDSystemInfo()); FontBoxFont font; if (mapping.isCIDFont()) @@ -184,9 +188,38 @@ public final Matrix getFontMatrix() return fontMatrix; } + private class ByteSource implements CFFParser.ByteSource + { + @Override + public byte[] getBytes() throws IOException + { + PDStream ff3Stream = getFontDescriptor().getFontFile3(); + return IOUtils.toByteArray(ff3Stream.createInputStream()); + } + } + @Override public BoundingBox getBoundingBox() { + if (fontBBox == null) + { + fontBBox = generateBoundingBox(); + } + return fontBBox; + } + + private BoundingBox generateBoundingBox() + { + if (getFontDescriptor() != null) + { + PDRectangle bbox = getFontDescriptor().getFontBoundingBox(); + if (bbox.getLowerLeftX() != 0 || bbox.getLowerLeftY() != 0 || + bbox.getUpperRightX() != 0 || bbox.getUpperRightY() != 0) + { + return new BoundingBox(bbox.getLowerLeftX(), bbox.getLowerLeftY(), + bbox.getUpperRightX(), bbox.getUpperRightY()); + } + } if (cidFont != null) { return cidFont.getFontBBox(); @@ -272,7 +305,7 @@ private String getGlyphName(int code) throws IOException { return ".notdef"; } - return String.format("uni%04X", unicodes.codePointAt(0)); + return getUniNameOfCodePoint(unicodes.codePointAt(0)); } @Override diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2.java index 8c45b1a8b..5bbc9a0bc 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2.java @@ -38,6 +38,7 @@ import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSStream; import com.tom_roush.pdfbox.io.IOUtils; +import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; import com.tom_roush.pdfbox.pdmodel.common.PDStream; import com.tom_roush.pdfbox.util.Matrix; @@ -50,116 +51,143 @@ public class PDCIDFontType2 extends PDCIDFont { private final TrueTypeFont ttf; private final int[] cid2gid; - private final Map gid2cid; - private final boolean hasIdentityCid2Gid; private final boolean isEmbedded; private final boolean isDamaged; private final CmapSubtable cmap; // may be null private Matrix fontMatrix; + private BoundingBox fontBBox; /** * Constructor. * * @param fontDictionary The font dictionary according to the PDF specification. + * @param parent The parent font. + * @throws IOException */ public PDCIDFontType2(COSDictionary fontDictionary, PDType0Font parent) throws IOException + { + this(fontDictionary, parent, null); + } + + /** + * Constructor. + * + * @param fontDictionary The font dictionary according to the PDF specification. + * @param parent The parent font. + * @param trueTypeFont The true type font used to create the parent font + * + * @throws IOException + */ + public PDCIDFontType2(COSDictionary fontDictionary, PDType0Font parent, + TrueTypeFont trueTypeFont) throws IOException { super(fontDictionary, parent); PDFontDescriptor fd = getFontDescriptor(); - PDStream ff2Stream = fd.getFontFile2(); - PDStream ff3Stream = fd.getFontFile3(); - - // Acrobat looks in FontFile too, even though it is not in the spec, see PDFBOX-2599 - if (ff2Stream == null && ff3Stream == null) { - ff2Stream = fd.getFontFile(); - } - - TrueTypeFont ttfFont = null; - boolean fontIsDamaged = false; - if (ff2Stream != null) + if (trueTypeFont != null) { - try - { - // embedded - TTFParser ttfParser = new TTFParser(true); - ttfFont = ttfParser.parse(ff2Stream.createInputStream()); - } - catch (NullPointerException e) // TTF parser is buggy - { - Log.w("PdfBox-Android", "Could not read embedded TTF for font " + getBaseFont(), e); - fontIsDamaged = true; - } - catch (IOException e) - { - Log.w("PdfBox-Android", "Could not read embedded TTF for font " + getBaseFont(), e); - fontIsDamaged = true; - } + ttf = trueTypeFont; + isEmbedded = true; + isDamaged = false; } - else if (ff3Stream != null) + else { - try + boolean fontIsDamaged = false; + TrueTypeFont ttfFont = null; + PDStream ff2Stream = fd.getFontFile2(); + PDStream ff3Stream = fd.getFontFile3(); + + // Acrobat looks in FontFile too, even though it is not in the spec, see PDFBOX-2599 + if (ff2Stream == null && ff3Stream == null) { - // embedded - OTFParser otfParser = new OTFParser(true); - OpenTypeFont otf = otfParser.parse(ff3Stream.createInputStream()); - ttfFont = otf; + ff2Stream = fd.getFontFile(); + } - if (otf.isPostScript()) + if (ff2Stream != null) + { + try { - // todo: we need more abstraction to support CFF fonts here - throw new IOException("Not implemented: OpenType font with CFF table " + - getBaseFont()); + // embedded + TTFParser ttfParser = new TTFParser(true); + ttfFont = ttfParser.parse(ff2Stream.createInputStream()); } - - if (otf.hasLayoutTables()) + catch (NullPointerException e) // TTF parser is buggy { - Log.e("PdfBox-Android", "OpenType Layout tables used in font " + getBaseFont() + - " are not implemented in PDFBox and will be ignored"); + Log.w("PdfBox-Android", "Could not read embedded TTF for font " + getBaseFont(), + e); + fontIsDamaged = true; + } + catch (IOException e) + { + Log.w("PdfBox-Android", "Could not read embedded TTF for font " + getBaseFont(), + e); + fontIsDamaged = true; } } - catch (NullPointerException e) // TTF parser is buggy - { - fontIsDamaged = true; - Log.w("PdfBox-Android", "Could not read embedded OTF for font " + getBaseFont(), e); - } - catch (IOException e) + else if (ff3Stream != null) { - fontIsDamaged = true; - Log.w("PdfBox-Android", "Could not read embedded OTF for font " + getBaseFont(), e); + try + { + // embedded + OTFParser otfParser = new OTFParser(true); + OpenTypeFont otf = otfParser.parse(ff3Stream.createInputStream()); + ttfFont = otf; + + if (otf.isPostScript()) + { + // todo: we need more abstraction to support CFF fonts here + throw new IOException( + "Not implemented: OpenType font with CFF table " + getBaseFont()); + } + + if (otf.hasLayoutTables()) + { + Log.e("PdfBox-Android", + "OpenType Layout tables used in font " + getBaseFont() + + " are not implemented in PDFBox and will be ignored"); + } + } + catch (NullPointerException e) // TTF parser is buggy + { + fontIsDamaged = true; + Log.w("PdfBox-Android", "Could not read embedded OTF for font " + getBaseFont(), + e); + } + catch (IOException e) + { + fontIsDamaged = true; + Log.w("PdfBox-Android", "Could not read embedded OTF for font " + getBaseFont(), + e); + } } - } + isEmbedded = ttfFont != null; + isDamaged = fontIsDamaged; - isEmbedded = ttfFont != null; - isDamaged = fontIsDamaged; - if (ttfFont == null) - { - // find font or substitute - CIDFontMapping mapping = FontMapper.getCIDFont(getBaseFont(), getFontDescriptor(), - getCIDSystemInfo()); - - if (mapping.isCIDFont()) - { - ttfFont = mapping.getFont(); - } - else + if (ttfFont == null) { - ttfFont = (TrueTypeFont) mapping.getTrueTypeFont(); - } + // find font or substitute + CIDFontMapping mapping = FontMappers.instance().getCIDFont(getBaseFont(), + getFontDescriptor(), getCIDSystemInfo()); - if (mapping.isFallback()) - { - Log.w("PdfBox-Android", - "Using fallback font " + ttfFont.getName() + " for CID-keyed TrueType font " + getBaseFont()); + if (mapping.isCIDFont()) + { + ttfFont = mapping.getFont(); + } + else + { + ttfFont = (TrueTypeFont)mapping.getTrueTypeFont(); + } + + if (mapping.isFallback()) + { + Log.w("PdfBox-Android", "Using fallback font " + ttfFont.getName() + + " for CID-keyed TrueType font " + getBaseFont()); + } } + ttf = ttfFont; } - ttf = ttfFont; cmap = ttf.getUnicodeCmap(false); - cid2gid = readCIDToGIDMap(); - gid2cid = invert(cid2gid); - COSBase map = dict.getDictionaryObject(COSName.CID_TO_GID_MAP); - hasIdentityCid2Gid = map instanceof COSName && ((COSName) map).getName().equals("Identity"); } @Override @@ -176,6 +204,25 @@ public Matrix getFontMatrix() @Override public BoundingBox getBoundingBox() throws IOException { + if (fontBBox == null) + { + fontBBox = generateBoundingBox(); + } + return fontBBox; + } + + private BoundingBox generateBoundingBox() throws IOException + { + if (getFontDescriptor() != null) + { + PDRectangle bbox = getFontDescriptor().getFontBoundingBox(); + if (bbox.getLowerLeftX() != 0 || bbox.getLowerLeftY() != 0 || + bbox.getUpperRightX() != 0 || bbox.getUpperRightY() != 0) + { + return new BoundingBox(bbox.getLowerLeftX(), bbox.getLowerLeftY(), + bbox.getUpperRightX(), bbox.getUpperRightY()); + } + } return ttf.getFontBBox(); } @@ -186,6 +233,7 @@ private int[] readCIDToGIDMap() throws IOException if (map instanceof COSStream) { COSStream stream = (COSStream) map; + InputStream is = stream.createInputStream(); byte[] mapAsBytes = IOUtils.toByteArray(is); IOUtils.closeQuietly(is); @@ -208,7 +256,7 @@ private Map invert(int[] cid2gid) { return null; } - Map inverse = new HashMap(); + Map inverse = new HashMap(cid2gid.length); for (int i = 0; i < cid2gid.length; i++) { inverse.put(cid2gid[i], i); @@ -235,7 +283,9 @@ public int codeToCID(int code) * * @param code character code * @return GID + * @throws IOException */ + @Override public int codeToGID(int code) throws IOException { if (!isEmbedded) @@ -245,21 +295,14 @@ public int codeToGID(int code) throws IOException // font's 'cmap' table. The means by which this is accomplished are implementation- // dependent. - boolean hasUnicodeMap = parent.getCMapUCS2() != null; - - if (cid2gid != null) + // omit the CID2GID mapping if the embedded font is replaced by an external font + if (cid2gid != null && !isDamaged) { // Acrobat allows non-embedded GIDs - todo: can we find a test PDF for this? + Log.w("PdfBox-Android", "Using non-embedded GIDs in font " + getName()); int cid = codeToCID(code); return cid2gid[cid]; } - else if (hasIdentityCid2Gid || !hasUnicodeMap) - { - // same as above, but for the default Identity CID2GIDMap or when there is no - // ToUnicode CMap to fallback to, see PDFBOX-2599 and PDFBOX-2560 - // todo: can we find a test PDF for the Identity case? - return codeToCID(code); - } else { // fallback to the ToUnicode CMap, test with PDFBOX-1422 and PDFBOX-2560 @@ -267,7 +310,9 @@ else if (hasIdentityCid2Gid || !hasUnicodeMap) if (unicode == null) { Log.w("PdfBox-Android", "Failed to find a character mapping for " + code + " in " + getName()); - return 0; + // Acrobat is willing to use the CID as a GID, even when the font isn't embedded + // see PDFBOX-2599 + return codeToCID(code); } else if (unicode.length() > 1) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java index b474f16b5..c13c0a462 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDCIDFontType2Embedder.java @@ -23,7 +23,10 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import com.tom_roush.fontbox.ttf.TrueTypeFont; import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSInteger; @@ -51,14 +54,14 @@ final class PDCIDFontType2Embedder extends TrueTypeEmbedder * * @param document parent document * @param dict font dictionary - * @param ttfStream TTF stream + * @param ttf True Type Font * @param parent parent Type 0 font * @throws IOException if the TTF could not be read */ - PDCIDFontType2Embedder(PDDocument document, COSDictionary dict, InputStream ttfStream, + PDCIDFontType2Embedder(PDDocument document, COSDictionary dict, TrueTypeFont ttf, boolean embedSubset, PDType0Font parent) throws IOException { - super(document, dict, ttfStream, embedSubset); + super(document, dict, ttf, embedSubset); this.document = document; this.dict = dict; this.parent = parent; @@ -75,7 +78,7 @@ final class PDCIDFontType2Embedder extends TrueTypeEmbedder dict.setItem(COSName.DESCENDANT_FONTS, descendantFonts); // build GID -> Unicode map - gidToUni = new HashMap(); + gidToUni = new HashMap(ttf.getMaximumProfile().getNumGlyphs()); for (int gid = 1, max = ttf.getMaximumProfile().getNumGlyphs(); gid <= max; gid++) { // skip composite glyph components that have no code point @@ -85,7 +88,6 @@ final class PDCIDFontType2Embedder extends TrueTypeEmbedder gidToUni.put(gid, codePoint); // CID = GID } } - // ToUnicode CMap buildToUnicodeCMap(null); } @@ -98,7 +100,7 @@ protected void buildSubset(InputStream ttfSubset, String tag, Map cidToGid = new HashMap(); + Map cidToGid = new HashMap(gidToCid.size()); for (Map.Entry entry : gidToCid.entrySet()) { int newGID = entry.getKey(); @@ -236,7 +238,7 @@ private void buildCIDToGIDMap(Map cidToGid) throws IOException InputStream input = new ByteArrayInputStream(out.toByteArray()); PDStream stream = new PDStream(document, input, COSName.FLATE_DECODE); - stream.getStream().setInt(COSName.LENGTH1, stream.toByteArray().length); + stream.getCOSObject().setInt(COSName.LENGTH1, stream.toByteArray().length); cidFont.setItem(COSName.CID_TO_GID_MAP, stream); } @@ -246,21 +248,21 @@ private void buildCIDToGIDMap(Map cidToGid) throws IOException */ private void buildCIDSet(Map cidToGid) throws IOException { - byte[] bytes = new byte[Collections.max(cidToGid.keySet()) / 8 + 1]; - for (int cid : cidToGid.keySet()) - { - int mask = 1 << 7 - cid % 8; - bytes[cid / 8] |= mask; - } - - InputStream input = new ByteArrayInputStream(bytes); + byte[] bytes = new byte[Collections.max(cidToGid.keySet()) / 8 + 1]; + for (int cid : cidToGid.keySet()) + { + int mask = 1 << 7 - cid % 8; + bytes[cid / 8] |= mask; + } + + InputStream input = new ByteArrayInputStream(bytes); PDStream stream = new PDStream(document, input, COSName.FLATE_DECODE); - fontDescriptor.setCIDSet(stream); + fontDescriptor.setCIDSet(stream); } - /** - * Builds wieths with a custom CIDToGIDMap (for embedding font subset). + /** + * Builds wieths with a custom CIDToGIDMap (for embedding font subset). */ private void buildWidths(Map cidToGid) throws IOException { @@ -268,18 +270,13 @@ private void buildWidths(Map cidToGid) throws IOException COSArray widths = new COSArray(); COSArray ws = new COSArray(); - int prev = -1; - - for (int cid : cidToGid.keySet()) + int prev = Integer.MIN_VALUE; + // Use a sorted list to get an optimal width array + Set keys = new TreeSet(cidToGid.keySet()); + for (int cid : keys) { - if (!cidToGid.containsKey(cid)) - { - continue; - } - int gid = cidToGid.get(cid); float width = ttf.getHorizontalMetrics().getAdvanceWidth(gid) * scaling; - // c [w1 w2 ... wn] if (prev != cid - 1) { @@ -290,7 +287,6 @@ private void buildWidths(Map cidToGid) throws IOException ws.add(COSInteger.get(Math.round(width))); // wi prev = cid; } - cidFont.setItem(COSName.W, widths); } @@ -312,7 +308,7 @@ private void buildWidths(COSDictionary cidFont) throws IOException enum State { - FIRST, BRACKET, SERIAL + FIRST, BRACKET, SERIAL } private COSArray getWidths(int[] widths) throws IOException @@ -416,6 +412,6 @@ else if (cid == lastCid + 1) */ public PDCIDFont getCIDFont() throws IOException { - return PDFontFactory.createDescendantFont(cidFont, parent); + return new PDCIDFontType2(cidFont, parent, ttf); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFont.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFont.java index 321359c1d..491063b67 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFont.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFont.java @@ -54,8 +54,7 @@ public abstract class PDFont implements COSObjectable, PDFontLike private final CMap toUnicodeCMap; private final FontMetrics afmStandard14; // AFM for standard 14 fonts private PDFontDescriptor fontDescriptor; - - private List widths; + private List widths; private float avgFontWidth; private float fontWidthOfSpace = -1f; @@ -70,12 +69,14 @@ public abstract class PDFont implements COSObjectable, PDFontLike fontDescriptor = null; afmStandard14 = null; } + /** * Constructor for Standard 14. */ PDFont(String baseFont) { dict = new COSDictionary(); + dict.setItem(COSName.TYPE, COSName.FONT); toUnicodeCMap = null; afmStandard14 = Standard14Fonts.getAFM(baseFont); if (afmStandard14 == null) @@ -222,16 +223,18 @@ public float getWidth(int code) throws IOException { int firstChar = dict.getInt(COSName.FIRST_CHAR, -1); int lastChar = dict.getInt(COSName.LAST_CHAR, -1); - if (getWidths().size() > 0 && code >= firstChar && code <= lastChar) + int siz = getWidths().size(); + int idx = code - firstChar; + if (siz > 0 && code >= firstChar && code <= lastChar && idx < siz) { - return getWidths().get(code - firstChar).floatValue(); + return getWidths().get(idx); } PDFontDescriptor fd = getFontDescriptor(); - if (fd != null) + if (fd != null && fd.hasMissingWidth()) { - // if there's nothing to override with, then obviously we fall back to the font - return fd.getMissingWidth(); // default is 0 + // get entry from /MissingWidth entry + return fd.getMissingWidth(); } } @@ -396,11 +399,13 @@ public String toUnicode(int code) throws IOException // if the font dictionary containsName a ToUnicode CMap, use that CMap if (toUnicodeCMap != null) { - if (toUnicodeCMap.getName() != null && toUnicodeCMap.getName().startsWith("Identity-")) + if (toUnicodeCMap.getName() != null && toUnicodeCMap.getName().startsWith( + "Identity-") && dict.getDictionaryObject(COSName.TO_UNICODE) instanceof COSName) { // handle the undocumented case of using Identity-H/V as a ToUnicode CMap, this - // isn't actually valid as the Identity-x CMaps are code->CID maps, not + // isn't actually valid as the Identity-x CMaps are code->CID maps, not // code->Unicode maps. See sample_fonts_solidconvertor.pdf for an example. + // PDFBOX-3123: do this only if the /ToUnicode entry is a name return new String(new char[] { (char) code }); } else @@ -444,14 +449,14 @@ public String getSubType() * * @return The widths of the characters. */ - protected final List getWidths() + protected final List getWidths() { if (widths == null) { COSArray array = (COSArray) dict.getDictionaryObject(COSName.WIDTHS); if (array != null) { - widths = COSArrayList.convertIntegerCOSArrayToList(array); + widths = COSArrayList.convertFloatCOSArrayToList(array); } else { @@ -523,6 +528,7 @@ public boolean isStandard14() { return false; } + // if the name matches, this is a Standard 14 font return Standard14Fonts.containsName(getName()); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontDescriptor.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontDescriptor.java index f038c0d37..106ccd8cd 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontDescriptor.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontDescriptor.java @@ -272,6 +272,7 @@ private void setFlagBit( int bit, boolean value ) * * @return The cos object that matches this Java object. */ + @Override public COSDictionary getCOSObject() { return dic; @@ -285,10 +286,10 @@ public COSDictionary getCOSObject() public String getFontName() { String retval = null; - COSName name = (COSName)dic.getDictionaryObject( COSName.FONT_NAME ); - if( name != null ) + COSBase base = dic.getDictionaryObject(COSName.FONT_NAME); + if (base instanceof COSName) { - retval = name.getName(); + retval = ((COSName)base).getName(); } return retval; } @@ -680,9 +681,17 @@ public boolean hasWidths() } /** - * This will get the missing width for the font. + * Returns true if the missing widths entry is present in the font descriptor. + */ + public boolean hasMissingWidth() + { + return dic.containsKey(COSName.MISSING_WIDTH); + } + + /** + * This will get the missing width for the font from the /MissingWidth dictionary entry. * - * @return The missing width value. + * @return The missing width value, or 0 if there is no such dictionary entry. */ public float getMissingWidth() { @@ -807,6 +816,21 @@ public void setFontFile3( PDStream stream ) { dic.setItem( COSName.FONT_FILE3, stream ); } + + /** + * Get the CIDSet stream. + * + * @return A stream containing a CIDSet. + */ + public PDStream getCIDSet() + { + COSObjectable cidSet = dic.getDictionaryObject(COSName.CID_SET); + if (cidSet instanceof COSStream) + { + return new PDStream((COSStream)cidSet); + } + return null; + } /** * Set a stream containing a CIDSet. @@ -815,7 +839,7 @@ public void setFontFile3( PDStream stream ) */ public void setCIDSet( PDStream stream ) { - dic.setItem( COSName.CID_SET, stream ); + dic.setItem(COSName.CID_SET, stream); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontFactory.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontFactory.java index ab09be15e..077e70052 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontFactory.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontFactory.java @@ -142,4 +142,4 @@ public static PDFont createDefaultFont() throws IOException dict.setString(COSName.BASE_FONT, "Arial"); return createFont(dict); } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontLike.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontLike.java index 24b4325e5..20712476f 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontLike.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDFontLike.java @@ -17,6 +17,7 @@ package com.tom_roush.pdfbox.pdmodel.font; import java.io.IOException; + import com.tom_roush.fontbox.util.BoundingBox; import com.tom_roush.pdfbox.util.Matrix; import com.tom_roush.pdfbox.util.Vector; @@ -60,14 +61,26 @@ public interface PDFontLike /** * Returns the height of the given character, in glyph space. This can be expensive to - * calculate. Results are only approximate. + * calculate. Results are only approximate.

    + * + * Warning: This method is deprecated in PDFBox 2.0 because there is no meaningful value + * which it can return. The {@link #getWidth} method returns the advance width of a glyph, + * but there is no corresponding advance height. The logical height of a character is the same + * for every character in a font, so if you want that, retrieve the font bbox's height. + * Otherwise if you want the visual bounds of the glyph then call getPath(..) on the appropriate + * PDFont subclass to retrieve the glyph outline as a Path. * * @param code character code + * @deprecated Use {@link #getBoundingBox().#getHeight(int)} instead. */ + @Deprecated float getHeight(int code) throws IOException; /** - * Returns the advance width of the given character, in glyph space. + * Returns the advance width of the given character, in glyph space.

    + * + * If you want the visual bounds of the glyph then call getPath(..) on the appropriate + * PDFont subclass to retrieve the glyph outline as a GeneralPath instead. * * @param code character code */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDPanoseClassification.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDPanoseClassification.java index 12f751564..ca2c9bee2 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDPanoseClassification.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDPanoseClassification.java @@ -17,15 +17,13 @@ package com.tom_roush.pdfbox.pdmodel.font; -import java.io.Serializable; - /** * Represents a 10-byte PANOSE classification. * - * @author John Hewson * @link http://www.monotype.com/services/pan2 + * @author John Hewson */ -public class PDPanoseClassification implements Serializable +public class PDPanoseClassification { private final byte[] bytes; @@ -84,6 +82,11 @@ public int getXHeight() return bytes[9]; } + public byte[] getBytes() + { + return bytes; + } + @Override public String toString() { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDSimpleFont.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDSimpleFont.java index ab613d885..ec0fa7fb3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDSimpleFont.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDSimpleFont.java @@ -20,7 +20,6 @@ import android.util.Log; import java.io.IOException; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -105,7 +104,7 @@ protected void readEncoding() throws IOException if (this.encoding == null) { Log.w("PdfBox-Android", "Unknown encoding: " + encodingName.getName()); - this.encoding = readEncodingFromFont(); + this.encoding = readEncodingFromFont(); // fallback } } else if (encoding instanceof COSDictionary) @@ -118,6 +117,7 @@ else if (encoding instanceof COSDictionary) { builtIn = readEncodingFromFont(); } + if (symbolic == null) { symbolic = false; @@ -145,11 +145,6 @@ else if (encoding instanceof COSDictionary) } } - private void readEncodingFromDictionary(COSDictionary encodingDict) throws IOException - { - - } - private void readEncodingFromName(COSName encodingName) throws IOException { this.encoding = Encoding.getInstance(encodingName); @@ -184,28 +179,6 @@ public GlyphList getGlyphList() return glyphList; } - /** - * Inverts the font's Encoding. Any duplicate (Name -> Code) mappings will be lost. - */ - protected Map getInvertedEncoding() - { - if (invertedEncoding != null) - { - return invertedEncoding; - } - - invertedEncoding = new HashMap(); - Map codeToName = encoding.getCodeToNameMap(); - for (Map.Entry entry : codeToName.entrySet()) - { - if (!invertedEncoding.containsKey(entry.getValue())) - { - invertedEncoding.put(entry.getValue(), entry.getKey()); - } - } - return invertedEncoding; - } - /** * Returns true the font is a symbolic (that is, it does not use the Adobe Standard Roman * character set). diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDTrueTypeFont.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDTrueTypeFont.java index 5103a0a06..99414aca3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDTrueTypeFont.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDTrueTypeFont.java @@ -37,6 +37,7 @@ import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.PDDocument; +import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; import com.tom_roush.pdfbox.pdmodel.common.PDStream; import com.tom_roush.pdfbox.pdmodel.font.encoding.BuiltInEncoding; import com.tom_roush.pdfbox.pdmodel.font.encoding.Encoding; @@ -46,6 +47,8 @@ import com.tom_roush.pdfbox.pdmodel.font.encoding.Type1Encoding; import com.tom_roush.pdfbox.pdmodel.font.encoding.WinAnsiEncoding; +import static com.tom_roush.pdfbox.pdmodel.font.UniUtil.getUniNameOfCodePoint; + /** * TrueType font. * @@ -57,7 +60,8 @@ public class PDTrueTypeFont extends PDSimpleFont implements PDVectorFont private static final int START_RANGE_F100 = 0xF100; private static final int START_RANGE_F200 = 0xF200; - private static final Map INVERTED_MACOS_ROMAN = new HashMap(); + private static final Map INVERTED_MACOS_ROMAN = new HashMap( + 250); static { Map codeToName = MacOSRomanEncoding.INSTANCE.getCodeToNameMap(); @@ -113,6 +117,7 @@ public static PDTrueTypeFont load(PDDocument doc, InputStream input, Encoding en * @param file A TTF file. * @return a PDTrueTypeFont instance. * @throws IOException If there is an error loading the data. + * * @deprecated Use {@link PDType0Font#load(PDDocument, File)} instead. */ @Deprecated @@ -146,6 +151,7 @@ public static PDTrueTypeFont loadTTF(PDDocument doc, InputStream input) throws I private final TrueTypeFont ttf; private final boolean isEmbedded; private final boolean isDamaged; + private BoundingBox fontBBox; /** * Creates a new TrueType font from a Font dictionary. @@ -188,8 +194,8 @@ public PDTrueTypeFont(COSDictionary fontDictionary) throws IOException // substitute if (ttfFont == null) { - FontMapping mapping = FontMapper.getTrueTypeFont(getBaseFont(), - getFontDescriptor()); + FontMapping mapping = FontMappers.instance().getTrueTypeFont( + getBaseFont(), getFontDescriptor()); ttfFont = mapping.getFont(); if (mapping.isFallback()) @@ -293,6 +299,21 @@ public String getName() @Override public BoundingBox getBoundingBox() throws IOException { + if (fontBBox == null) + { + fontBBox = generateBoundingBox(); + } + return fontBBox; + } + + private BoundingBox generateBoundingBox() throws IOException + { + if (getFontDescriptor() != null) + { + PDRectangle bbox = getFontDescriptor().getFontBoundingBox(); + return new BoundingBox(bbox.getLowerLeftX(), bbox.getLowerLeftY(), + bbox.getUpperRightX(), bbox.getUpperRightY()); + } return ttf.getFontBBox(); } @@ -338,21 +359,22 @@ public float getHeight(int code) throws IOException @Override protected byte[] encode(int unicode) throws IOException { - if (getEncoding() != null) + if (encoding != null) { - if (!getEncoding().contains(getGlyphList().codePointToName(unicode))) + if (!encoding.contains(getGlyphList().codePointToName(unicode))) { - throw new IllegalArgumentException( - String.format("U+%04X is not available in this font's Encoding", unicode)); + throw new IllegalArgumentException(String + .format("U+%04X is not available in this font's encoding: %s", unicode, + encoding.getEncodingName())); } String name = getGlyphList().codePointToName(unicode); - Map inverted = getInvertedEncoding(); + Map inverted = encoding.getNameToCodeMap(); if (!ttf.hasGlyph(name)) { // try unicode name - String uniName = String.format("uni%04X", unicode); + String uniName = getUniNameOfCodePoint(unicode); if (!ttf.hasGlyph(uniName)) { throw new IllegalArgumentException( diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDTrueTypeFontEmbedder.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDTrueTypeFontEmbedder.java index 62b81e91a..c31669451 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDTrueTypeFontEmbedder.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDTrueTypeFontEmbedder.java @@ -47,6 +47,7 @@ final class PDTrueTypeFontEmbedder extends TrueTypeEmbedder * @param document The parent document * @param dict Font dictionary * @param ttfStream TTF stream + * @param encoding The PostScript encoding vector to be used for embedding. * @throws IOException if the TTF could not be read */ PDTrueTypeFontEmbedder(PDDocument document, COSDictionary dict, InputStream ttfStream, @@ -58,7 +59,6 @@ final class PDTrueTypeFontEmbedder extends TrueTypeEmbedder GlyphList glyphList = GlyphList.getAdobeGlyphList(); this.fontEncoding = encoding; dict.setItem(COSName.ENCODING, encoding.getCOSObject()); - fontDescriptor.setSymbolic(false); fontDescriptor.setNonSymbolic(true); @@ -92,17 +92,16 @@ private void setWidths(COSDictionary font, GlyphList glyphList) throws IOExcepti // afterwards, the glyph name is translated to a glyph ID. for (Map.Entry entry : codeToName.entrySet()) { - int code = entry.getKey(); - String name = entry.getValue(); - - if (code >= firstChar && code <= lastChar) + int code = entry.getKey(); + String name = entry.getValue(); + + if (code >= firstChar && code <= lastChar) { - // todo: we're supposed to use the 'provided font encoding' - String uni = glyphList.toUnicode(name); + String uni = glyphList.toUnicode(name); int charCode = uni.codePointAt(0); int gid = cmap.getGlyphId(charCode); widths.set(entry.getKey() - firstChar, - Math.round(hmtx.getAdvanceWidth(gid) * scaling)); + Math.round(hmtx.getAdvanceWidth(gid) * scaling)); } } @@ -116,14 +115,14 @@ private void setWidths(COSDictionary font, GlyphList glyphList) throws IOExcepti */ public Encoding getFontEncoding() { - return fontEncoding; + return fontEncoding; } @Override - protected void buildSubset(InputStream ttfSubset, String tag, - Map gidToCid) throws IOException + protected void buildSubset(InputStream ttfSubset, String tag, Map gidToCid) + throws IOException { - // use PDType0Font instead - throw new UnsupportedOperationException(); + // use PDType0Font instead + throw new UnsupportedOperationException(); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType0Font.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType0Font.java index f59916300..878b9deb5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType0Font.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType0Font.java @@ -20,11 +20,14 @@ import android.util.Log; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.HashSet; +import java.util.Set; import com.tom_roush.fontbox.cmap.CMap; +import com.tom_roush.fontbox.ttf.TTFParser; +import com.tom_roush.fontbox.ttf.TrueTypeFont; import com.tom_roush.fontbox.util.BoundingBox; import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; @@ -46,6 +49,7 @@ public class PDType0Font extends PDFont implements PDVectorFont private boolean isCMapPredefined; private boolean isDescendantCJK; private PDCIDFontType2Embedder embedder; + private final Set noUnicode = new HashSet(); /** * Loads a TTF to be embedded into a document as a Type 0 font. @@ -57,7 +61,7 @@ public class PDType0Font extends PDFont implements PDVectorFont */ public static PDType0Font load(PDDocument doc, File file) throws IOException { - return new PDType0Font(doc, new FileInputStream(file), true); + return new PDType0Font(doc, new TTFParser().parse(file), true); } /** @@ -65,12 +69,13 @@ public static PDType0Font load(PDDocument doc, File file) throws IOException * * @param doc The PDF document that will hold the embedded font. * @param input A TrueType font. + * * @return A Type0 font with a CIDFontType2 descendant. * @throws IOException If there is an error reading the font stream. */ public static PDType0Font load(PDDocument doc, InputStream input) throws IOException { - return new PDType0Font(doc, input, true); + return new PDType0Font(doc, new TTFParser().parse(input), true); } /** @@ -85,13 +90,30 @@ public static PDType0Font load(PDDocument doc, InputStream input) throws IOExcep public static PDType0Font load(PDDocument doc, InputStream input, boolean embedSubset) throws IOException { - return new PDType0Font(doc, input, embedSubset); + return new PDType0Font(doc, new TTFParser().parse(input), embedSubset); + } + + /** + * Loads a TTF to be embedded into a document as a Type 0 font. + * + * @param doc The PDF document that will hold the embedded font. + * @param ttf A TrueType font. + * @param embedSubset True if the font will be subset before embedding + * + * @return A Type0 font with a CIDFontType2 descendant. + * @throws IOException If there is an error reading the font stream. + */ + public static PDType0Font load(PDDocument doc, TrueTypeFont ttf, boolean embedSubset) + throws IOException + { + return new PDType0Font(doc, ttf, embedSubset); } /** * Constructor for reading a Type0 font from a PDF file. * * @param fontDictionary The font dictionary according to the PDF specification. + * @throws IOException if the descendant font is missing. */ public PDType0Font(COSDictionary fontDictionary) throws IOException { @@ -112,10 +134,10 @@ public PDType0Font(COSDictionary fontDictionary) throws IOException /** * Private. Creates a new TrueType font for embedding. */ - private PDType0Font(PDDocument document, InputStream ttfStream, boolean embedSubset) + private PDType0Font(PDDocument document, TrueTypeFont ttf, boolean embedSubset) throws IOException { - embedder = new PDCIDFontType2Embedder(document, dict, ttfStream, embedSubset, this); + embedder = new PDCIDFontType2Embedder(document, dict, ttf, embedSubset, this); descendantFont = embedder.getCIDFont(); readEncoding(); fetchCMapUCS2(); @@ -197,9 +219,12 @@ else if (!cMap.hasCIDMappings()) */ private void fetchCMapUCS2() throws IOException { - // if the font is composite and uses a predefined cmap (excluding Identity-H/V) then - // or if its decendant font uses Adobe-GB1/CNS1/Japan1/Korea1 - if (isCMapPredefined) + // if the font is composite and uses a predefined cmap (excluding Identity-H/V) + // or whose descendant CIDFont uses the Adobe-GB1, Adobe-CNS1, Adobe-Japan1, or + // Adobe-Korea1 character collection: + COSName name = dict.getCOSName(COSName.ENCODING); + if (isCMapPredefined && !(name == COSName.IDENTITY_H || name == COSName.IDENTITY_V) || + isDescendantCJK) { // a) Map the character code to a CID using the font's CMap // b) Obtain the ROS from the font's CIDSystemInfo @@ -207,32 +232,23 @@ private void fetchCMapUCS2() throws IOException // d) Obtain the CMap with the constructed name // e) Map the CID according to the CMap from step d), producing a Unicode value - String cMapName = null; - - // get the encoding CMap - COSBase encoding = dict.getDictionaryObject(COSName.ENCODING); - if (encoding instanceof COSName) + // todo: not sure how to interpret the PDF spec here, do we always override? or only when Identity-H/V? + String strName = null; + if (isDescendantCJK) { - cMapName = ((COSName)encoding).getName(); + strName = descendantFont.getCIDSystemInfo().getRegistry() + "-" + + descendantFont.getCIDSystemInfo().getOrdering() + "-" + + descendantFont.getCIDSystemInfo().getSupplement(); } - - if ("Identity-H".equals(cMapName) || "Identity-V".equals(cMapName)) + else if (name != null) { - if (isDescendantCJK) - { - cMapName = getCJKCMap(descendantFont.getCIDSystemInfo()); - } - else - { - // we can't map Identity-H or Identity-V to Unicode - return; - } + strName = name.getName(); } // try to find the corresponding Unicode (UC2) CMap - if (cMapName != null) + if (strName != null) { - CMap cMap = CMapManager.getPredefinedCMap(cMapName); + CMap cMap = CMapManager.getPredefinedCMap(strName); if (cMap != null) { String ucs2Name = cMap.getRegistry() + "-" + cMap.getOrdering() + "-UCS2"; @@ -396,7 +412,7 @@ public String toUnicode(int code) throws IOException return unicode; } - if (isCMapPredefined && cMapUCS2 != null) + if ((isCMapPredefined || isDescendantCJK) && cMapUCS2 != null) { // if the font is composite and uses a predefined cmap (excluding Identity-H/V) then // or if its decendant font uses Adobe-GB1/CNS1/Japan1/Korea1 @@ -409,9 +425,15 @@ public String toUnicode(int code) throws IOException } else { - // if no value has been produced, there is no way to obtain Unicode for the character. - String cid = "CID+" + codeToCID(code); - Log.w("PdfBox-Android", "No Unicode mapping for " + cid + " (" + code + ") in font " + getName()); + if (!noUnicode.contains(code)) + { + // if no value has been produced, there is no way to obtain Unicode for the character. + String cid = "CID+" + codeToCID(code); + Log.w("PdfBox-Android", + "No Unicode mapping for " + cid + " (" + code + ") in font " + getName()); + // we keep track of which warnings have been issued, so we don't log multiple times + noUnicode.add(code); + } return null; } } @@ -425,6 +447,7 @@ public String getName() @Override public BoundingBox getBoundingBox() throws IOException { + // Will be cached by underlying font return descendantFont.getBoundingBox(); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1CFont.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1CFont.java index 33fdc825b..2a977ab5a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1CFont.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1CFont.java @@ -35,12 +35,15 @@ import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.io.IOUtils; +import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; import com.tom_roush.pdfbox.pdmodel.common.PDStream; import com.tom_roush.pdfbox.pdmodel.font.encoding.Encoding; import com.tom_roush.pdfbox.pdmodel.font.encoding.StandardEncoding; import com.tom_roush.pdfbox.pdmodel.font.encoding.Type1Encoding; import com.tom_roush.pdfbox.util.Matrix; +import static com.tom_roush.pdfbox.pdmodel.font.UniUtil.getUniNameOfCodePoint; + /** * Type 1-equivalent CFF font. * @@ -58,6 +61,7 @@ public class PDType1CFont extends PDSimpleFont private final FontBoxFont genericFont; // embedded or system font for rendering private final boolean isEmbedded; private final boolean isDamaged; + private BoundingBox fontBBox; /** * Constructor. @@ -93,7 +97,7 @@ public PDType1CFont(COSDictionary fontDictionary) throws IOException { // note: this could be an OpenType file, fortunately CFFParser can handle that CFFParser cffParser = new CFFParser(); - cffEmbedded = (CFFType1Font)cffParser.parse(bytes).get(0); + cffEmbedded = (CFFType1Font)cffParser.parse(bytes, new ByteSource()).get(0); } } catch (IOException e) @@ -111,7 +115,8 @@ public PDType1CFont(COSDictionary fontDictionary) throws IOException } else { - FontMapping mapping = FontMapper.getFontBoxFont(getBaseFont(), fd); + FontMapping mapping = FontMappers.instance().getFontBoxFont(getBaseFont(), + fd); genericFont = mapping.getFont(); if (mapping.isFallback()) @@ -126,6 +131,16 @@ public PDType1CFont(COSDictionary fontDictionary) throws IOException fontMatrixTransform.scale(1000, 1000); } + private class ByteSource implements CFFParser.ByteSource + { + @Override + public byte[] getBytes() throws IOException + { + PDStream ff3Stream = getFontDescriptor().getFontFile3(); + return IOUtils.toByteArray(ff3Stream.createInputStream()); + } + } + @Override public FontBoxFont getFontBoxFont() { @@ -144,7 +159,7 @@ public final String getBaseFont() public Path getPath(String name) throws IOException { // Acrobat only draws .notdef for embedded or "Standard 14" fonts, see PDFBOX-2372 - if (isEmbedded() && name.equals(".notdef") && !isEmbedded() && !isStandard14()) + if (name.equals(".notdef") && !isEmbedded() && !isStandard14()) { return new Path(); } @@ -169,6 +184,25 @@ public final String getName() @Override public BoundingBox getBoundingBox() throws IOException { + if (fontBBox == null) + { + fontBBox = generateBoundingBox(); + } + return fontBBox; + } + + private BoundingBox generateBoundingBox() throws IOException + { + if (getFontDescriptor() != null) + { + PDRectangle bbox = getFontDescriptor().getFontBoundingBox(); + if (bbox.getLowerLeftX() != 0 || bbox.getLowerLeftY() != 0 || + bbox.getUpperRightX() != 0 || bbox.getUpperRightY() != 0) + { + return new BoundingBox(bbox.getLowerLeftX(), bbox.getLowerLeftY(), + bbox.getUpperRightX(), bbox.getUpperRightY()); + } + } return genericFont.getFontBBox(); } @@ -191,7 +225,6 @@ protected Encoding readEncodingFromFont() throws IOException // extract from Type1 font/substitute if (genericFont instanceof EncodedFont) { - //FIXME dead instanceof return Type1Encoding.fromFontBox(((EncodedFont) genericFont).getEncoding()); } else @@ -277,7 +310,26 @@ public float getHeight(int code) throws IOException @Override protected byte[] encode(int unicode) throws IOException { - throw new UnsupportedOperationException("Not implemented: Type1C"); + String name = getGlyphList().codePointToName(unicode); + if (!encoding.contains(name)) + { + throw new IllegalArgumentException(String + .format("U+%04X ('%s') is not available in this font's encoding: %s", unicode, name, + encoding.getEncodingName())); + } + + String nameInFont = getNameInFont(name); + + Map inverted = encoding.getNameToCodeMap(); + + if (nameInFont.equals(".notdef") || !genericFont.hasGlyph(nameInFont)) + { + throw new IllegalArgumentException( + String.format("No glyph for U+%04X in font %s", unicode, getName())); + } + + int code = inverted.get(name); + return new byte[] { (byte)code }; } @Override @@ -319,4 +371,30 @@ private float getAverageCharacterWidth() // todo: not implemented, highly suspect return 500; } + + /** + * Maps a PostScript glyph name to the name in the underlying font, for example when + * using a TTF font we might map "W" to "uni0057". + */ + private String getNameInFont(String name) throws IOException + { + if (isEmbedded() || genericFont.hasGlyph(name)) + { + return name; + } + else + { + // try unicode name + String unicodes = getGlyphList().toUnicode(name); + if (unicodes != null && unicodes.length() == 1) + { + String uniName = getUniNameOfCodePoint(unicodes.codePointAt(0)); + if (genericFont.hasGlyph(uniName)) + { + return uniName; + } + } + } + return ".notdef"; + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1Font.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1Font.java index c161d11bf..155d94a92 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1Font.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1Font.java @@ -38,6 +38,7 @@ import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSStream; import com.tom_roush.pdfbox.pdmodel.PDDocument; +import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; import com.tom_roush.pdfbox.pdmodel.common.PDStream; import com.tom_roush.pdfbox.pdmodel.font.encoding.Encoding; import com.tom_roush.pdfbox.pdmodel.font.encoding.StandardEncoding; @@ -45,6 +46,8 @@ import com.tom_roush.pdfbox.pdmodel.font.encoding.WinAnsiEncoding; import com.tom_roush.pdfbox.util.Matrix; +import static com.tom_roush.pdfbox.pdmodel.font.UniUtil.getUniNameOfCodePoint; + /** * A PostScript Type 1 Font. * @@ -90,6 +93,7 @@ public class PDType1Font extends PDSimpleFont private final boolean isDamaged; private Matrix fontMatrix; private final AffineTransform fontMatrixTransform; + private BoundingBox fontBBox; /** * Creates a Type 1 standard 14 font for embedding. @@ -107,7 +111,7 @@ private PDType1Font(String baseFont) // todo: could load the PFB font here if we wanted to support Standard 14 embedding type1font = null; - FontMapping mapping = FontMapper.getFontBoxFont(getBaseFont(), + FontMapping mapping = FontMappers.instance().getFontBoxFont(getBaseFont(), getFontDescriptor()); genericFont = mapping.getFont(); @@ -154,10 +158,10 @@ public PDType1Font(PDDocument doc, InputStream pfbIn) throws IOException * * @param doc PDF document to write to * @param pfbIn PFB file stream + * @param encoding * @throws IOException */ public PDType1Font(PDDocument doc, InputStream pfbIn, Encoding encoding) throws IOException - { PDType1FontEmbedder embedder = new PDType1FontEmbedder(doc, dict, pfbIn, encoding); this.encoding = encoding; @@ -173,6 +177,8 @@ public PDType1Font(PDDocument doc, InputStream pfbIn, Encoding encoding) throws * Creates a Type 1 font from a Font dictionary in a PDF. * * @param fontDictionary font dictionary + * @throws IOException if there was an error initializing the font. + * @throws IllegalArgumentException if /FontFile3 was used. */ public PDType1Font(COSDictionary fontDictionary) throws IOException { @@ -196,7 +202,7 @@ public PDType1Font(COSDictionary fontDictionary) throws IOException { try { - COSStream stream = fontFile.getStream(); + COSStream stream = fontFile.getCOSObject(); int length1 = stream.getInt(COSName.LENGTH1); int length2 = stream.getInt(COSName.LENGTH2); @@ -236,7 +242,6 @@ public PDType1Font(COSDictionary fontDictionary) throws IOException } isEmbedded = t1 != null; isDamaged = fontIsDamaged; - type1font = t1; // find a generic font to use for rendering, could be a .pfb, but might be a .ttf @@ -246,7 +251,8 @@ public PDType1Font(COSDictionary fontDictionary) throws IOException } else { - FontMapping mapping = FontMapper.getFontBoxFont(getBaseFont(), fd); + FontMapping mapping = FontMappers.instance().getFontBoxFont(getBaseFont(), + fd); genericFont = mapping.getFont(); if (mapping.isFallback()) @@ -272,6 +278,10 @@ private int repairLength1(byte[] bytes, int length1) { // scan backwards from the end of the first segment to find 'exec' int offset = Math.max(0, length1 - 4); + if (offset <= 0 || offset > bytes.length - 4) + { + offset = bytes.length - 4; + } while (offset > 0) { if (bytes[offset + 0] == 'e' && @@ -292,7 +302,8 @@ private int repairLength1(byte[] bytes, int length1) if (length1 - offset != 0 && offset > 0) { - Log.w("PdfBox-Android", "Ignored invalid Length1 for Type 1 font " + getName()); + Log.w("PdfBox-Android", + "Ignored invalid Length1 " + length1 + " for Type 1 font " + getName()); return offset; } @@ -328,14 +339,16 @@ public float getHeight(int code) throws IOException @Override protected byte[] encode(int unicode) throws IOException { - if (unicode > 0xff) + String name = getGlyphList().codePointToName(unicode); + if (!encoding.contains(name)) { - throw new IllegalArgumentException("This font type only supports 8-bit code points"); + throw new IllegalArgumentException(String + .format("U+%04X ('%s') is not available in this font's encoding: %s", unicode, name, + encoding.getEncodingName())); } - String name = getGlyphList().codePointToName(unicode); String nameInFont = getNameInFont(name); - Map inverted = getInvertedEncoding(); + Map inverted = encoding.getNameToCodeMap(); if (nameInFont.equals(".notdef") || !genericFont.hasGlyph(nameInFont)) { @@ -351,6 +364,7 @@ protected byte[] encode(int unicode) throws IOException public float getWidthFromFont(int code) throws IOException { String name = codeToName(code); + // width of .notdef is ignored for substitutes, see PDFBOX-1900 if (!isEmbedded && name.equals(".notdef")) { @@ -419,6 +433,7 @@ public Type1Font getType1Font() return type1font; } + @Override public FontBoxFont getFontBoxFont() { return genericFont; @@ -433,6 +448,25 @@ public String getName() @Override public BoundingBox getBoundingBox() throws IOException { + if (fontBBox == null) + { + fontBBox = generateBoundingBox(); + } + return fontBBox; + } + + private BoundingBox generateBoundingBox() throws IOException + { + if (getFontDescriptor() != null) + { + PDRectangle bbox = getFontDescriptor().getFontBoundingBox(); + if (bbox.getLowerLeftX() != 0 || bbox.getLowerLeftY() != 0 || + bbox.getUpperRightX() != 0 || bbox.getUpperRightY() != 0) + { + return new BoundingBox(bbox.getLowerLeftX(), bbox.getLowerLeftY(), + bbox.getUpperRightX(), bbox.getUpperRightY()); + } + } return genericFont.getFontBBox(); } @@ -467,7 +501,7 @@ private String getNameInFont(String name) throws IOException String unicodes = getGlyphList().toUnicode(name); if (unicodes != null && unicodes.length() == 1) { - String uniName = String.format("uni%04X", unicodes.codePointAt(0)); + String uniName = getUniNameOfCodePoint(unicodes.codePointAt(0)); if (genericFont.hasGlyph(uniName)) { return uniName; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1FontEmbedder.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1FontEmbedder.java index e2df1aa17..7e68bdd48 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1FontEmbedder.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType1FontEmbedder.java @@ -16,7 +16,6 @@ */ package com.tom_roush.pdfbox.pdmodel.font; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -61,8 +60,8 @@ class PDType1FontEmbedder // read the pfb byte[] pfbBytes = IOUtils.toByteArray(pfbStream); - PfbParser pfbParser = new PfbParser(new ByteArrayInputStream(pfbBytes)); - type1 = Type1Font.createWithPFB(new ByteArrayInputStream(pfbBytes)); + PfbParser pfbParser = new PfbParser(pfbBytes); + type1 = Type1Font.createWithPFB(pfbBytes); if (encoding == null) { @@ -77,10 +76,10 @@ class PDType1FontEmbedder PDFontDescriptor fd = buildFontDescriptor(type1); PDStream fontStream = new PDStream(doc, pfbParser.getInputStream(), COSName.FLATE_DECODE); - fontStream.getStream().setInt("Length", pfbParser.size()); + fontStream.getCOSObject().setInt("Length", pfbParser.size()); for (int i = 0; i < pfbParser.getLengths().length; i++) { - fontStream.getStream().setInt("Length" + (i + 1), pfbParser.getLengths()[i]); + fontStream.getCOSObject().setInt("Length" + (i + 1), pfbParser.getLengths()[i]); } fd.setFontFile(fontStream); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3CharProc.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3CharProc.java index f263de1f0..80df6d4bb 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3CharProc.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3CharProc.java @@ -18,9 +18,16 @@ import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; import com.tom_roush.pdfbox.contentstream.PDContentStream; +import com.tom_roush.pdfbox.contentstream.operator.Operator; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSNumber; +import com.tom_roush.pdfbox.cos.COSObject; import com.tom_roush.pdfbox.cos.COSStream; +import com.tom_roush.pdfbox.pdfparser.PDFStreamParser; import com.tom_roush.pdfbox.pdmodel.PDResources; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; @@ -77,10 +84,112 @@ public PDRectangle getBBox() return font.getFontBBox(); } + /** + * Calculate the bounding box of this glyph. This will work only if the first operator in the + * stream is d1. + * + * @return the bounding box of this glyph, or null if the first operator is not d1. + * @throws IOException If an io error occurs while parsing the stream. + */ + public PDRectangle getGlyphBBox() throws IOException + { + List arguments = new ArrayList(); + PDFStreamParser parser = new PDFStreamParser(this); + Object token = parser.parseNextToken(); + while (token != null) + { + if (token instanceof COSObject) + { + arguments.add(((COSObject)token).getObject()); + } + else if (token instanceof Operator) + { + if (((Operator)token).getName().equals("d1") && arguments.size() == 6) + { + for (int i = 0; i < 6; ++i) + { + if (!(arguments.get(i) instanceof COSNumber)) + { + return null; + } + } + return new PDRectangle(((COSNumber)arguments.get(2)).floatValue(), + ((COSNumber)arguments.get(3)).floatValue(), ((COSNumber)arguments.get(4)) + .floatValue() - ((COSNumber)arguments.get(2)).floatValue(), + ((COSNumber)arguments.get(5)).floatValue() - ((COSNumber)arguments.get(3)) + .floatValue()); + } + else + { + return null; + } + } + else + { + arguments.add((COSBase)token); + } + token = parser.parseNextToken(); + } + return null; + } + @Override - public Matrix getMatrix() { + public Matrix getMatrix() + { return font.getFontMatrix(); } - // todo: add methods for getting the character's width from the stream + /** + * todo. + * + * @return + * @throws IOException + */ + public float getWidth() throws IOException + { + List arguments = new ArrayList(); + PDFStreamParser parser = new PDFStreamParser(this); + Object token = parser.parseNextToken(); + while (token != null) + { + if (token instanceof COSObject) + { + arguments.add(((COSObject)token).getObject()); + } + else if (token instanceof Operator) + { + return parseWidth((Operator)token, arguments); + } + else + { + arguments.add((COSBase)token); + } + token = parser.parseNextToken(); + } + throw new IOException("Unexpected end of stream"); + } + + private float parseWidth(Operator operator, List arguments) throws IOException + { + if (operator.getName().equals("d0") || operator.getName().equals("d1")) + { + Object obj = arguments.get(0); + if (obj instanceof Number) + { + return ((Number)obj).floatValue(); + } + else if (obj instanceof COSNumber) + { + return ((COSNumber)obj).floatValue(); + } + else + { + throw new IOException("Unexpected argument type: " + obj.getClass().getName()); + } + } + else + { + throw new IOException("First operator must be d0 or d1"); + } + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3Font.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3Font.java index 315549214..d16454f90 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3Font.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/PDType3Font.java @@ -17,7 +17,6 @@ package com.tom_roush.pdfbox.pdmodel.font; import android.graphics.Path; -import android.util.Log; import java.io.IOException; import java.io.InputStream; @@ -25,6 +24,7 @@ import com.tom_roush.fontbox.FontBoxFont; import com.tom_roush.fontbox.util.BoundingBox; import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSStream; @@ -46,6 +46,7 @@ public class PDType3Font extends PDSimpleFont private PDResources resources; private COSDictionary charProcs; private Matrix fontMatrix; + private BoundingBox fontBBox; /** * Constructor. @@ -69,7 +70,7 @@ protected final void readEncoding() throws IOException { COSDictionary encodingDict = (COSDictionary)dict.getDictionaryObject(COSName.ENCODING); encoding = new DictionaryEncoding(encodingDict); - glyphList = GlyphList.getZapfDingbats(); + glyphList = GlyphList.getAdobeGlyphList(); } @Override @@ -120,7 +121,7 @@ public float getWidth(int code) throws IOException int lastChar = dict.getInt(COSName.LAST_CHAR, -1); if (getWidths().size() > 0 && code >= firstChar && code <= lastChar) { - return getWidths().get(code - firstChar).floatValue(); + return getWidths().get(code - firstChar); } else { @@ -131,18 +132,20 @@ public float getWidth(int code) throws IOException } else { - // todo: call getWidthFromFont? - Log.e("PdfBox-Android", "No width for glyph " + code + " in font " + getName()); - return 0; + return getWidthFromFont(code); } } } @Override - public float getWidthFromFont(int code) + public float getWidthFromFont(int code) throws IOException { - // todo: could these be extracted from the font's stream? - throw new UnsupportedOperationException("not suppported"); + PDType3CharProc charProc = getCharProc(code); + if (charProc == null) + { + return 0; + } + return charProc.getWidth(); } @Override @@ -243,7 +246,7 @@ public PDResources getResources() } /** - * This will get the fonts bounding box. + * This will get the fonts bounding box from its dictionary. * * @return The fonts bounding box. */ @@ -260,10 +263,53 @@ public PDRectangle getFontBBox() @Override public BoundingBox getBoundingBox() + { + if (fontBBox == null) + { + fontBBox = generateBoundingBox(); + } + return fontBBox; + } + + private BoundingBox generateBoundingBox() { PDRectangle rect = getFontBBox(); - return new BoundingBox(rect.getLowerLeftX(), rect.getLowerLeftY(), - rect.getWidth(), rect.getHeight()); + if (rect.getLowerLeftX() == 0 && rect.getLowerLeftY() == 0 && rect.getUpperRightX() == 0 && + rect.getUpperRightY() == 0) + { + // Plan B: get the max bounding box of the glyphs + COSDictionary cp = getCharProcs(); + for (COSName name : cp.keySet()) + { + COSBase base = cp.getDictionaryObject(name); + if (base instanceof COSStream) + { + PDType3CharProc charProc = new PDType3CharProc(this, (COSStream)base); + try + { + PDRectangle glyphBBox = charProc.getGlyphBBox(); + if (glyphBBox == null) + { + continue; + } + rect.setLowerLeftX( + Math.min(rect.getLowerLeftX(), glyphBBox.getLowerLeftX())); + rect.setLowerLeftY( + Math.min(rect.getLowerLeftY(), glyphBBox.getLowerLeftY())); + rect.setUpperRightX( + Math.max(rect.getUpperRightX(), glyphBBox.getUpperRightX())); + rect.setUpperRightY( + Math.max(rect.getUpperRightY(), glyphBBox.getUpperRightY())); + } + catch (IOException ex) + { + // ignore + } + } + } + } + return new BoundingBox(rect.getLowerLeftX(), rect.getLowerLeftY(), rect.getWidth(), + rect.getHeight()); } /** @@ -289,10 +335,14 @@ public COSDictionary getCharProcs() public PDType3CharProc getCharProc(int code) { String name = getEncoding().getName(code); - if (name != null) + if (!name.equals(".notdef")) { COSStream stream; stream = (COSStream)getCharProcs().getDictionaryObject(COSName.getPDFName(name)); + if (stream == null) + { + return null; + } return new PDType3CharProc(this, stream); } return null; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/Standard14Fonts.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/Standard14Fonts.java index 300c8aac3..f4375600d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/Standard14Fonts.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/Standard14Fonts.java @@ -41,14 +41,14 @@ private Standard14Fonts() { } - private static final Set STANDARD_14_NAMES = new HashSet(); - private static final Map STANDARD_14_MAPPING = new HashMap(); - private static final Map STANDARD14_AFM_MAP; + private static final Set STANDARD_14_NAMES = new HashSet(34); + private static final Map STANDARD_14_MAPPING = new HashMap(34); + private static final Map STANDARD14_AFM_MAP = + new HashMap(34); static { try { - STANDARD14_AFM_MAP = new HashMap(); addAFM("Courier-Bold"); addAFM("Courier-BoldOblique"); addAFM("Courier"); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/ToUnicodeWriter.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/ToUnicodeWriter.java index f7f640130..91fda72f8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/ToUnicodeWriter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/ToUnicodeWriter.java @@ -91,9 +91,9 @@ public void writeTo(OutputStream out) throws IOException writeLine(writer, "begincmap"); writeLine(writer, "/CIDSystemInfo"); - writeLine(writer, "<< /Registry ()"); - writeLine(writer, "/Ordering ()"); - writeLine(writer, "/Supplement "); + writeLine(writer, "<< /Registry (Adobe)"); + writeLine(writer, "/Ordering (UCS)"); + writeLine(writer, "/Supplement 0"); writeLine(writer, ">> def\n"); writeLine(writer, "/CMapName /Adobe-Identity-UCS" + " def"); @@ -116,28 +116,28 @@ public void writeTo(OutputStream out) throws IOException int srcPrev = -1; String dstPrev = null; - + int srcCode1 = -1; for (Map.Entry entry : cidToUnicode.entrySet()) { - int cid = entry.getKey(); - String text = entry.getValue(); - - if (cid == srcPrev + 1 && // CID must be last CID + 1 - dstPrev.codePointCount(0, dstPrev.length()) == 1 && // no UTF-16 surrogates - text.codePointAt(0) == dstPrev.codePointAt(0) + 1 && // dstString must be prev + 1 - dstPrev.codePointAt(0) + 1 <= 255 - (cid - srcCode1)) // increment last byte only - { - // extend range - srcTo.set(srcTo.size() - 1, cid); - } - else - { - // begin range - srcCode1 = cid; - srcFrom.add(cid); - srcTo.add(cid); + int cid = entry.getKey(); + String text = entry.getValue(); + + if (cid == srcPrev + 1 && // CID must be last CID + 1 + dstPrev.codePointCount(0, dstPrev.length()) == 1 && // no UTF-16 surrogates + text.codePointAt(0) == dstPrev.codePointAt(0) + 1 && // dstString must be prev + 1 + dstPrev.codePointAt(0) + 1 <= 255 - (cid - srcCode1)) // increment last byte only + { + // extend range + srcTo.set(srcTo.size() - 1, cid); + } + else + { + // begin range + srcCode1 = cid; + srcFrom.add(cid); + srcTo.add(cid); dstString.add(text); } srcPrev = cid; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/TrueTypeEmbedder.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/TrueTypeEmbedder.java index 89c19a7bd..a4f22896c 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/TrueTypeEmbedder.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/TrueTypeEmbedder.java @@ -76,10 +76,27 @@ abstract class TrueTypeEmbedder implements Subsetter cmap = ttf.getUnicodeCmap(); } + /** + * Creates a new TrueType font for embedding. + */ + TrueTypeEmbedder(PDDocument document, COSDictionary dict, TrueTypeFont ttf, boolean embedSubset) + throws IOException + { + this.document = document; + this.embedSubset = embedSubset; + this.ttf = ttf; + fontDescriptor = createFontDescriptor(ttf); + + dict.setName(COSName.BASE_FONT, ttf.getName()); + + // choose a Unicode "cmap" + cmap = ttf.getUnicodeCmap(); + } + public void buildFontFile2(InputStream ttfStream) throws IOException { PDStream stream = new PDStream(document, ttfStream, COSName.FLATE_DECODE); - stream.getStream().setInt(COSName.LENGTH1, stream.toByteArray().length); + stream.getCOSObject().setInt(COSName.LENGTH1, stream.toByteArray().length); // as the stream was closed within the PDStream constructor, we have to recreate it InputStream input = null; @@ -288,6 +305,7 @@ public void subset() throws IOException // re-build the embedded font buildSubset(new ByteArrayInputStream(out.toByteArray()), tag, gidToCid); + ttf.close(); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/UniUtil.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/UniUtil.java new file mode 100644 index 000000000..0146e70ab --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/UniUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.font; + +import java.util.Locale; + +/** + * Utility class for Unicode fallback. + * + * @author Philip Helger + */ +final class UniUtil +{ + private UniUtil() + { + } + + // faster than String.format("uni%04X", codePoint) + static String getUniNameOfCodePoint(int codePoint) + { + String hex = Integer.toString(codePoint, 16).toUpperCase(Locale.US); + switch (hex.length()) + { + case 1: + return "uni000" + hex; + case 2: + return "uni00" + hex; + case 3: + return "uni0" + hex; + default: + return "uni" + hex; + } + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/BuiltInEncoding.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/BuiltInEncoding.java index b7454a336..8bc066ba8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/BuiltInEncoding.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/BuiltInEncoding.java @@ -17,10 +17,10 @@ package com.tom_roush.pdfbox.pdmodel.font.encoding; -import com.tom_roush.pdfbox.cos.COSBase; - import java.util.Map; +import com.tom_roush.pdfbox.cos.COSBase; + /** * A font's built-in encoding. * @@ -46,4 +46,10 @@ public COSBase getCOSObject() { throw new UnsupportedOperationException("Built-in encodings cannot be serialized"); } + + @Override + public String getEncodingName() + { + return "built-in (TTF)"; + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/DictionaryEncoding.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/DictionaryEncoding.java index 74eabf00a..118d08207 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/DictionaryEncoding.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/DictionaryEncoding.java @@ -60,7 +60,7 @@ public DictionaryEncoding(COSName baseEncoding, COSArray differences) } codeToName.putAll(this.baseEncoding.codeToName); - names.addAll(this.baseEncoding.names); + inverted.putAll(this.baseEncoding.inverted); applyDifferences(); } @@ -118,7 +118,7 @@ public DictionaryEncoding(COSDictionary fontEncoding, boolean isNonSymbolic, Enc baseEncoding = base; codeToName.putAll(baseEncoding.codeToName); - names.addAll(baseEncoding.names); + inverted.putAll(baseEncoding.inverted); applyDifferences(); } @@ -169,4 +169,10 @@ public COSBase getCOSObject() { return encoding; } + + @Override + public String getEncodingName() + { + return baseEncoding.getEncodingName() + " with differences"; + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/Encoding.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/Encoding.java index c07e51db3..5113d262d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/Encoding.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/Encoding.java @@ -58,19 +58,31 @@ else if (COSName.MAC_ROMAN_ENCODING.equals(name)) } } - protected final Map codeToName = new HashMap(); - protected final Set names = new HashSet(); + protected final Map codeToName = new HashMap(250); + protected final Map inverted = new HashMap(250); + private Set names; /** - * Returns an unmodifiable view of the Code2Name mapping. - * - * @return the Code2Name map + * Returns an unmodifiable view of the code -> name mapping. + * + * @return the code -> name map */ public Map getCodeToNameMap() { return Collections.unmodifiableMap(codeToName); } + /** + * Returns an unmodifiable view of the name -> code mapping. More than one name may map to + * the same code. + * + * @return the name -> code map + */ + public Map getNameToCodeMap() + { + return Collections.unmodifiableMap(inverted); + } + /** * This will add a character encoding. * @@ -80,7 +92,7 @@ public Map getCodeToNameMap() protected void add(int code, String name) { codeToName.put(code, name); - names.add(name); + inverted.put(name, code); } /** @@ -90,6 +102,13 @@ protected void add(int code, String name) */ public boolean contains(String name) { + // we have to wait until all add() calls are done before building the name cache + // otherwise /Differences won't be accounted for + if (names == null) + { + names = new HashSet(codeToName.size()); + names.addAll(codeToName.values()); + } return names.contains(name); } @@ -118,4 +137,9 @@ public String getName(int code) } return ".notdef"; } + + /** + * Returns the name of this encoding. + */ + public abstract String getEncodingName(); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/GlyphList.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/GlyphList.java index 55187d4fa..49ff17e48 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/GlyphList.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/GlyphList.java @@ -34,28 +34,29 @@ public final class GlyphList { // Adobe Glyph List (AGL) - private static final GlyphList DEFAULT = load("glyphlist.txt"); + private static final GlyphList DEFAULT = load("glyphlist.txt", 4281); // Zapf Dingbats has its own glyph list - private static final GlyphList ZAPF_DINGBATS = load("zapfdingbats.txt"); + private static final GlyphList ZAPF_DINGBATS = load("zapfdingbats.txt", 201); /** * Loads a glyph list from disk. */ - private static GlyphList load(String filename) + private static GlyphList load(String filename, int numberOfEntries) { try { String path = "com/tom_roush/pdfbox/resources/glyphlist/"; if (PDFBoxResourceLoader.isReady()) { - return new GlyphList(PDFBoxResourceLoader.getStream(path + filename)); + return new GlyphList(PDFBoxResourceLoader.getStream(path + filename), + numberOfEntries); } else { // Fallback ClassLoader loader = GlyphList.class.getClassLoader(); - return new GlyphList(loader.getResourceAsStream(path + filename)); + return new GlyphList(loader.getResourceAsStream(path + filename), numberOfEntries); } } catch (IOException e) @@ -109,14 +110,16 @@ public static GlyphList getZapfDingbats() * Creates a new GlyphList from a glyph list file. * * @param input glyph list in Adobe format + * @param numberOfEntries number of expected values used to preallocate the correct amount of memory * @throws IOException if the glyph list could not be read */ - public GlyphList(InputStream input) throws IOException + public GlyphList(InputStream input, int numberOfEntries) throws IOException { - nameToUnicode = new HashMap(); - unicodeToName = new HashMap(); + nameToUnicode = new HashMap(numberOfEntries); + unicodeToName = new HashMap(numberOfEntries); loadList(input); } + /** * Creates a new GlyphList from multiple glyph list files. * @@ -136,23 +139,26 @@ private void loadList(InputStream input) throws IOException BufferedReader in = new BufferedReader(new InputStreamReader(input, "ISO-8859-1")); try { - String line = null; - while ((line = in.readLine()) != null) + while (in.ready()) { - if (!line.startsWith("#")) + String line = in.readLine(); + if (line != null && !line.startsWith("#")) { String[] parts = line.split(";"); if (parts.length < 2) { throw new IOException("Invalid glyph list entry: " + line); } + String name = parts[0]; String[] unicodeList = parts[1].split(" "); + if (nameToUnicode.containsKey(name)) { Log.w("PdfBox-Android", "duplicate value for " + name + " -> " + parts[1] + " " + nameToUnicode.get(name)); } + int[] codePoints = new int[unicodeList.length]; int index = 0; for (String hex : unicodeList) @@ -160,8 +166,10 @@ private void loadList(InputStream input) throws IOException codePoints[index++] = Integer.parseInt(hex, 16); } String string = new String(codePoints, 0 , codePoints.length); + // forward mapping nameToUnicode.put(name, string); + // reverse mapping if (!unicodeToName.containsKey(string)) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/MacOSRomanEncoding.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/MacOSRomanEncoding.java index 0fe9278fa..ba26d350a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/MacOSRomanEncoding.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/MacOSRomanEncoding.java @@ -24,6 +24,31 @@ */ public class MacOSRomanEncoding extends MacRomanEncoding { + private static final int CHAR_CODE = 0; + private static final int CHAR_NAME = 1; + + /** + * Table of octal character codes and their corresponding names + * on top of {@link MacRomanEncoding}. + */ + private static final Object[][] MAC_OS_ROMAN_ENCODING_TABLE = { + {255, "notequal"}, + {260, "infinity"}, + {262, "lessequal"}, + {263, "greaterequal"}, + {266, "partialdiff"}, + {267, "summation"}, + {270, "product"}, + {271, "pi"}, + {272, "integral"}, + {275, "Omega"}, + {303, "radical"}, + {305, "approxequal"}, + {306, "Delta"}, + {327, "lozenge"}, + {333, "Euro"}, + {360, "apple"} + }; /** * Singleton instance of this class. @@ -38,22 +63,12 @@ public class MacOSRomanEncoding extends MacRomanEncoding public MacOSRomanEncoding() { super(); - add(255, "notequal"); - add(260, "infinity"); - add(262, "lessequal"); - add(263, "greaterequal"); - add(266, "partialdiff"); - add(267, "summation"); - add(270, "product"); - add(271, "pi"); - add(272, "integral"); - add(275, "Omega"); - add(303, "radical"); - add(305, "approxequal"); - add(306, "Delta"); - add(327, "lozenge"); - add(333, "Euro"); - add(360, "apple"); + + // differences and additions to MacRomanEncoding + for (Object[] encodingEntry : MAC_OS_ROMAN_ENCODING_TABLE) + { + add((Integer)encodingEntry[CHAR_CODE], encodingEntry[CHAR_NAME].toString()); + } } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/MacRomanEncoding.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/MacRomanEncoding.java index f253e185d..4a3807324 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/MacRomanEncoding.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/MacRomanEncoding.java @@ -26,6 +26,223 @@ */ public class MacRomanEncoding extends Encoding { + private static final int CHAR_CODE = 0; + private static final int CHAR_NAME = 1; + + /** + * Table of octal character codes and their corresponding names. + */ + private static final Object[][] MAC_ROMAN_ENCODING_TABLE = { + {0101, "A"}, + {0256, "AE"}, + {0347, "Aacute"}, + {0345, "Acircumflex"}, + {0200, "Adieresis"}, + {0313, "Agrave"}, + {0201, "Aring"}, + {0314, "Atilde"}, + {0102, "B"}, + {0103, "C"}, + {0202, "Ccedilla"}, + {0104, "D"}, + {0105, "E"}, + {0203, "Eacute"}, + {0346, "Ecircumflex"}, + {0350, "Edieresis"}, + {0351, "Egrave"}, + {0106, "F"}, + {0107, "G"}, + {0110, "H"}, + {0111, "I"}, + {0352, "Iacute"}, + {0353, "Icircumflex"}, + {0354, "Idieresis"}, + {0355, "Igrave"}, + {0112, "J"}, + {0113, "K"}, + {0114, "L"}, + {0115, "M"}, + {0116, "N"}, + {0204, "Ntilde"}, + {0117, "O"}, + {0316, "OE"}, + {0356, "Oacute"}, + {0357, "Ocircumflex"}, + {0205, "Odieresis"}, + {0361, "Ograve"}, + {0257, "Oslash"}, + {0315, "Otilde"}, + {0120, "P"}, + {0121, "Q"}, + {0122, "R"}, + {0123, "S"}, + {0124, "T"}, + {0125, "U"}, + {0362, "Uacute"}, + {0363, "Ucircumflex"}, + {0206, "Udieresis"}, + {0364, "Ugrave"}, + {0126, "V"}, + {0127, "W"}, + {0130, "X"}, + {0131, "Y"}, + {0331, "Ydieresis"}, + {0132, "Z"}, + {0141, "a"}, + {0207, "aacute"}, + {0211, "acircumflex"}, + {0253, "acute"}, + {0212, "adieresis"}, + {0276, "ae"}, + {0210, "agrave"}, + {046, "ampersand"}, + {0214, "aring"}, + {0136, "asciicircum"}, + {0176, "asciitilde"}, + {052, "asterisk"}, + {0100, "at"}, + {0213, "atilde"}, + {0142, "b"}, + {0134, "backslash"}, + {0174, "bar"}, + {0173, "braceleft"}, + {0175, "braceright"}, + {0133, "bracketleft"}, + {0135, "bracketright"}, + {0371, "breve"}, + {0245, "bullet"}, + {0143, "c"}, + {0377, "caron"}, + {0215, "ccedilla"}, + {0374, "cedilla"}, + {0242, "cent"}, + {0366, "circumflex"}, + {072, "colon"}, + {054, "comma"}, + {0251, "copyright"}, + {0333, "currency"}, + {0144, "d"}, + {0240, "dagger"}, + {0340, "daggerdbl"}, + {0241, "degree"}, + {0254, "dieresis"}, + {0326, "divide"}, + {044, "dollar"}, + {0372, "dotaccent"}, + {0365, "dotlessi"}, + {0145, "e"}, + {0216, "eacute"}, + {0220, "ecircumflex"}, + {0221, "edieresis"}, + {0217, "egrave"}, + {070, "eight"}, + {0311, "ellipsis"}, + {0321, "emdash"}, + {0320, "endash"}, + {075, "equal"}, + {041, "exclam"}, + {0301, "exclamdown"}, + {0146, "f"}, + {0336, "fi"}, + {065, "five"}, + {0337, "fl"}, + {0304, "florin"}, + {064, "four"}, + {0332, "fraction"}, + {0147, "g"}, + {0247, "germandbls"}, + {0140, "grave"}, + {076, "greater"}, + {0307, "guillemotleft"}, + {0310, "guillemotright"}, + {0334, "guilsinglleft"}, + {0335, "guilsinglright"}, + {0150, "h"}, + {0375, "hungarumlaut"}, + {055, "hyphen"}, + {0151, "i"}, + {0222, "iacute"}, + {0224, "icircumflex"}, + {0225, "idieresis"}, + {0223, "igrave"}, + {0152, "j"}, + {0153, "k"}, + {0154, "l"}, + {074, "less"}, + {0302, "logicalnot"}, + {0155, "m"}, + {0370, "macron"}, + {0265, "mu"}, + {0156, "n"}, + {071, "nine"}, + {0226, "ntilde"}, + {043, "numbersign"}, + {0157, "o"}, + {0227, "oacute"}, + {0231, "ocircumflex"}, + {0232, "odieresis"}, + {0317, "oe"}, + {0376, "ogonek"}, + {0230, "ograve"}, + {061, "one"}, + {0273, "ordfeminine"}, + {0274, "ordmasculine"}, + {0277, "oslash"}, + {0233, "otilde"}, + {0160, "p"}, + {0246, "paragraph"}, + {050, "parenleft"}, + {051, "parenright"}, + {045, "percent"}, + {056, "period"}, + {0341, "periodcentered"}, + {0344, "perthousand"}, + {053, "plus"}, + {0261, "plusminus"}, + {0161, "q"}, + {077, "question"}, + {0300, "questiondown"}, + {042, "quotedbl"}, + {0343, "quotedblbase"}, + {0322, "quotedblleft"}, + {0323, "quotedblright"}, + {0324, "quoteleft"}, + {0325, "quoteright"}, + {0342, "quotesinglbase"}, + {047, "quotesingle"}, + {0162, "r"}, + {0250, "registered"}, + {0373, "ring"}, + {0163, "s"}, + {0244, "section"}, + {073, "semicolon"}, + {067, "seven"}, + {066, "six"}, + {057, "slash"}, + {040, "space"}, + {0243, "sterling"}, + {0164, "t"}, + {063, "three"}, + {0367, "tilde"}, + {0252, "trademark"}, + {062, "two"}, + {0165, "u"}, + {0234, "uacute"}, + {0236, "ucircumflex"}, + {0237, "udieresis"}, + {0235, "ugrave"}, + {0137, "underscore"}, + {0166, "v"}, + {0167, "w"}, + {0170, "x"}, + {0171, "y"}, + {0330, "ydieresis"}, + {0264, "yen"}, + {0172, "z"}, + {060, "zero"}, + // adding an additional mapping as defined in Appendix D of the pdf spec + {0312, "space"} + }; /** * Singleton instance of this class. @@ -39,216 +256,10 @@ public class MacRomanEncoding extends Encoding */ public MacRomanEncoding() { - add(0101, "A"); - add(0256, "AE"); - add(0347, "Aacute"); - add(0345, "Acircumflex"); - add(0200, "Adieresis"); - add(0313, "Agrave"); - add(0201, "Aring"); - add(0314, "Atilde"); - add(0102, "B"); - add(0103, "C"); - add(0202, "Ccedilla"); - add(0104, "D"); - add(0105, "E"); - add(0203, "Eacute"); - add(0346, "Ecircumflex"); - add(0350, "Edieresis"); - add(0351, "Egrave"); - add(0106, "F"); - add(0107, "G"); - add(0110, "H"); - add(0111, "I"); - add(0352, "Iacute"); - add(0353, "Icircumflex"); - add(0354, "Idieresis"); - add(0355, "Igrave"); - add(0112, "J"); - add(0113, "K"); - add(0114, "L"); - add(0115, "M"); - add(0116, "N"); - add(0204, "Ntilde"); - add(0117, "O"); - add(0316, "OE"); - add(0356, "Oacute"); - add(0357, "Ocircumflex"); - add(0205, "Odieresis"); - add(0361, "Ograve"); - add(0257, "Oslash"); - add(0315, "Otilde"); - add(0120, "P"); - add(0121, "Q"); - add(0122, "R"); - add(0123, "S"); - add(0124, "T"); - add(0125, "U"); - add(0362, "Uacute"); - add(0363, "Ucircumflex"); - add(0206, "Udieresis"); - add(0364, "Ugrave"); - add(0126, "V"); - add(0127, "W"); - add(0130, "X"); - add(0131, "Y"); - add(0331, "Ydieresis"); - add(0132, "Z"); - add(0141, "a"); - add(0207, "aacute"); - add(0211, "acircumflex"); - add(0253, "acute"); - add(0212, "adieresis"); - add(0276, "ae"); - add(0210, "agrave"); - add(046, "ampersand"); - add(0214, "aring"); - add(0136, "asciicircum"); - add(0176, "asciitilde"); - add(052, "asterisk"); - add(0100, "at"); - add(0213, "atilde"); - add(0142, "b"); - add(0134, "backslash"); - add(0174, "bar"); - add(0173, "braceleft"); - add(0175, "braceright"); - add(0133, "bracketleft"); - add(0135, "bracketright"); - add(0371, "breve"); - add(0245, "bullet"); - add(0143, "c"); - add(0377, "caron"); - add(0215, "ccedilla"); - add(0374, "cedilla"); - add(0242, "cent"); - add(0366, "circumflex"); - add(072, "colon"); - add(054, "comma"); - add(0251, "copyright"); - add(0333, "currency"); - add(0144, "d"); - add(0240, "dagger"); - add(0340, "daggerdbl"); - add(0241, "degree"); - add(0254, "dieresis"); - add(0326, "divide"); - add(044, "dollar"); - add(0372, "dotaccent"); - add(0365, "dotlessi"); - add(0145, "e"); - add(0216, "eacute"); - add(0220, "ecircumflex"); - add(0221, "edieresis"); - add(0217, "egrave"); - add(070, "eight"); - add(0311, "ellipsis"); - add(0321, "emdash"); - add(0320, "endash"); - add(075, "equal"); - add(041, "exclam"); - add(0301, "exclamdown"); - add(0146, "f"); - add(0336, "fi"); - add(065, "five"); - add(0337, "fl"); - add(0304, "florin"); - add(064, "four"); - add(0332, "fraction"); - add(0147, "g"); - add(0247, "germandbls"); - add(0140, "grave"); - add(076, "greater"); - add(0307, "guillemotleft"); - add(0310, "guillemotright"); - add(0334, "guilsinglleft"); - add(0335, "guilsinglright"); - add(0150, "h"); - add(0375, "hungarumlaut"); - add(055, "hyphen"); - add(0151, "i"); - add(0222, "iacute"); - add(0224, "icircumflex"); - add(0225, "idieresis"); - add(0223, "igrave"); - add(0152, "j"); - add(0153, "k"); - add(0154, "l"); - add(074, "less"); - add(0302, "logicalnot"); - add(0155, "m"); - add(0370, "macron"); - add(0265, "mu"); - add(0156, "n"); - add(071, "nine"); - add(0226, "ntilde"); - add(043, "numbersign"); - add(0157, "o"); - add(0227, "oacute"); - add(0231, "ocircumflex"); - add(0232, "odieresis"); - add(0317, "oe"); - add(0376, "ogonek"); - add(0230, "ograve"); - add(061, "one"); - add(0273, "ordfeminine"); - add(0274, "ordmasculine"); - add(0277, "oslash"); - add(0233, "otilde"); - add(0160, "p"); - add(0246, "paragraph"); - add(050, "parenleft"); - add(051, "parenright"); - add(045, "percent"); - add(056, "period"); - add(0341, "periodcentered"); - add(0344, "perthousand"); - add(053, "plus"); - add(0261, "plusminus"); - add(0161, "q"); - add(077, "question"); - add(0300, "questiondown"); - add(042, "quotedbl"); - add(0343, "quotedblbase"); - add(0322, "quotedblleft"); - add(0323, "quotedblright"); - add(0324, "quoteleft"); - add(0325, "quoteright"); - add(0342, "quotesinglbase"); - add(047, "quotesingle"); - add(0162, "r"); - add(0250, "registered"); - add(0373, "ring"); - add(0163, "s"); - add(0244, "section"); - add(073, "semicolon"); - add(067, "seven"); - add(066, "six"); - add(057, "slash"); - add(040, "space"); - add(0243, "sterling"); - add(0164, "t"); - add(063, "three"); - add(0367, "tilde"); - add(0252, "trademark"); - add(062, "two"); - add(0165, "u"); - add(0234, "uacute"); - add(0236, "ucircumflex"); - add(0237, "udieresis"); - add(0235, "ugrave"); - add(0137, "underscore"); - add(0166, "v"); - add(0167, "w"); - add(0170, "x"); - add(0171, "y"); - add(0330, "ydieresis"); - add(0264, "yen"); - add(0172, "z"); - add(060, "zero"); - // adding an additional mapping as defined in Appendix D of the pdf spec -// Currently not functioning properly, hopefully not needed -// add(0312, "space"); + for (Object[] encodingEntry : MAC_ROMAN_ENCODING_TABLE) + { + add((Integer)encodingEntry[CHAR_CODE], encodingEntry[CHAR_NAME].toString()); + } } /** @@ -260,4 +271,10 @@ public COSBase getCOSObject() { return COSName.MAC_ROMAN_ENCODING; } + + @Override + public String getEncodingName() + { + return "MacRomanEncoding"; + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/StandardEncoding.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/StandardEncoding.java index e5b0a244c..40b577820 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/StandardEncoding.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/StandardEncoding.java @@ -26,6 +26,163 @@ */ public class StandardEncoding extends Encoding { + private static final int CHAR_CODE = 0; + private static final int CHAR_NAME = 1; + + /** + * Table of octal character codes and their corresponding names. + */ + private static final Object[][] STANDARD_ENCODING_TABLE = { + {0101, "A"}, + {0341, "AE"}, + {0102, "B"}, + {0103, "C"}, + {0104, "D"}, + {0105, "E"}, + {0106, "F"}, + {0107, "G"}, + {0110, "H"}, + {0111, "I"}, + {0112, "J"}, + {0113, "K"}, + {0114, "L"}, + {0350, "Lslash"}, + {0115, "M"}, + {0116, "N"}, + {0117, "O"}, + {0352, "OE"}, + {0351, "Oslash"}, + {0120, "P"}, + {0121, "Q"}, + {0122, "R"}, + {0123, "S"}, + {0124, "T"}, + {0125, "U"}, + {0126, "V"}, + {0127, "W"}, + {0130, "X"}, + {0131, "Y"}, + {0132, "Z"}, + {0141, "a"}, + {0302, "acute"}, + {0361, "ae"}, + {0046, "ampersand"}, + {0136, "asciicircum"}, + {0176, "asciitilde"}, + {0052, "asterisk"}, + {0100, "at"}, + {0142, "b"}, + {0134, "backslash"}, + {0174, "bar"}, + {0173, "braceleft"}, + {0175, "braceright"}, + {0133, "bracketleft"}, + {0135, "bracketright"}, + {0306, "breve"}, + {0267, "bullet"}, + {0143, "c"}, + {0317, "caron"}, + {0313, "cedilla"}, + {0242, "cent"}, + {0303, "circumflex"}, + {0072, "colon"}, + {0054, "comma"}, + {0250, "currency"}, + {0144, "d"}, + {0262, "dagger"}, + {0263, "daggerdbl"}, + {0310, "dieresis"}, + {0044, "dollar"}, + {0307, "dotaccent"}, + {0365, "dotlessi"}, + {0145, "e"}, + {0070, "eight"}, + {0274, "ellipsis"}, + {0320, "emdash"}, + {0261, "endash"}, + {0075, "equal"}, + {0041, "exclam"}, + {0241, "exclamdown"}, + {0146, "f"}, + {0256, "fi"}, + {0065, "five"}, + {0257, "fl"}, + {0246, "florin"}, + {0064, "four"}, + {0244, "fraction"}, + {0147, "g"}, + {0373, "germandbls"}, + {0301, "grave"}, + {0076, "greater"}, + {0253, "guillemotleft"}, + {0273, "guillemotright"}, + {0254, "guilsinglleft"}, + {0255, "guilsinglright"}, + {0150, "h"}, + {0315, "hungarumlaut"}, + {0055, "hyphen"}, + {0151, "i"}, + {0152, "j"}, + {0153, "k"}, + {0154, "l"}, + {0074, "less"}, + {0370, "lslash"}, + {0155, "m"}, + {0305, "macron"}, + {0156, "n"}, + {0071, "nine"}, + {0043, "numbersign"}, + {0157, "o"}, + {0372, "oe"}, + {0316, "ogonek"}, + {0061, "one"}, + {0343, "ordfeminine"}, + {0353, "ordmasculine"}, + {0371, "oslash"}, + {0160, "p"}, + {0266, "paragraph"}, + {0050, "parenleft"}, + {0051, "parenright"}, + {0045, "percent"}, + {0056, "period"}, + {0264, "periodcentered"}, + {0275, "perthousand"}, + {0053, "plus"}, + {0161, "q"}, + {0077, "question"}, + {0277, "questiondown"}, + {0042, "quotedbl"}, + {0271, "quotedblbase"}, + {0252, "quotedblleft"}, + {0272, "quotedblright"}, + {0140, "quoteleft"}, + {0047, "quoteright"}, + {0270, "quotesinglbase"}, + {0251, "quotesingle"}, + {0162, "r"}, + {0312, "ring"}, + {0163, "s"}, + {0247, "section"}, + {0073, "semicolon"}, + {0067, "seven"}, + {0066, "six"}, + {0057, "slash"}, + {0040, "space"}, + {0243, "sterling"}, + {0164, "t"}, + {0063, "three"}, + {0304, "tilde"}, + {0062, "two"}, + {0165, "u"}, + {0137, "underscore"}, + {0166, "v"}, + {0167, "w"}, + {0170, "x"}, + {0171, "y"}, + {0245, "yen"}, + {0172, "z"}, + {0060, "zero"} + }; /** * Singleton instance of this class. @@ -39,155 +196,10 @@ public class StandardEncoding extends Encoding */ public StandardEncoding() { - add(0101, "A"); - add(0341, "AE"); - add(0102, "B"); - add(0103, "C"); - add(0104, "D"); - add(0105, "E"); - add(0106, "F"); - add(0107, "G"); - add(0110, "H"); - add(0111, "I"); - add(0112, "J"); - add(0113, "K"); - add(0114, "L"); - add(0350, "Lslash"); - add(0115, "M"); - add(0116, "N"); - add(0117, "O"); - add(0352, "OE"); - add(0351, "Oslash"); - add(0120, "P"); - add(0121, "Q"); - add(0122, "R"); - add(0123, "S"); - add(0124, "T"); - add(0125, "U"); - add(0126, "V"); - add(0127, "W"); - add(0130, "X"); - add(0131, "Y"); - add(0132, "Z"); - add(0141, "a"); - add(0302, "acute"); - add(0361, "ae"); - add(0046, "ampersand"); - add(0136, "asciicircum"); - add(0176, "asciitilde"); - add(0052, "asterisk"); - add(0100, "at"); - add(0142, "b"); - add(0134, "backslash"); - add(0174, "bar"); - add(0173, "braceleft"); - add(0175, "braceright"); - add(0133, "bracketleft"); - add(0135, "bracketright"); - add(0306, "breve"); - add(0267, "bullet"); - add(0143, "c"); - add(0317, "caron"); - add(0313, "cedilla"); - add(0242, "cent"); - add(0303, "circumflex"); - add(0072, "colon"); - add(0054, "comma"); - add(0250, "currency"); - add(0144, "d"); - add(0262, "dagger"); - add(0263, "daggerdbl"); - add(0310, "dieresis"); - add(0044, "dollar"); - add(0307, "dotaccent"); - add(0365, "dotlessi"); - add(0145, "e"); - add(0070, "eight"); - add(0274, "ellipsis"); - add(0320, "emdash"); - add(0261, "endash"); - add(0075, "equal"); - add(0041, "exclam"); - add(0241, "exclamdown"); - add(0146, "f"); - add(0256, "fi"); - add(0065, "five"); - add(0257, "fl"); - add(0246, "florin"); - add(0064, "four"); - add(0244, "fraction"); - add(0147, "g"); - add(0373, "germandbls"); - add(0301, "grave"); - add(0076, "greater"); - add(0253, "guillemotleft"); - add(0273, "guillemotright"); - add(0254, "guilsinglleft"); - add(0255, "guilsinglright"); - add(0150, "h"); - add(0315, "hungarumlaut"); - add(0055, "hyphen"); - add(0151, "i"); - add(0152, "j"); - add(0153, "k"); - add(0154, "l"); - add(0074, "less"); - add(0370, "lslash"); - add(0155, "m"); - add(0305, "macron"); - add(0156, "n"); - add(0071, "nine"); - add(0043, "numbersign"); - add(0157, "o"); - add(0372, "oe"); - add(0316, "ogonek"); - add(0061, "one"); - add(0343, "ordfeminine"); - add(0353, "ordmasculine"); - add(0371, "oslash"); - add(0160, "p"); - add(0266, "paragraph"); - add(0050, "parenleft"); - add(0051, "parenright"); - add(0045, "percent"); - add(0056, "period"); - add(0264, "periodcentered"); - add(0275, "perthousand"); - add(0053, "plus"); - add(0161, "q"); - add(0077, "question"); - add(0277, "questiondown"); - add(0042, "quotedbl"); - add(0271, "quotedblbase"); - add(0252, "quotedblleft"); - add(0272, "quotedblright"); - add(0140, "quoteleft"); - add(0047, "quoteright"); - add(0270, "quotesinglbase"); - add(0251, "quotesingle"); - add(0162, "r"); - add(0312, "ring"); - add(0163, "s"); - add(0247, "section"); - add(0073, "semicolon"); - add(0067, "seven"); - add(0066, "six"); - add(0057, "slash"); - add(0040, "space"); - add(0243, "sterling"); - add(0164, "t"); - add(0063, "three"); - add(0304, "tilde"); - add(0062, "two"); - add(0165, "u"); - add(0137, "underscore"); - add(0166, "v"); - add(0167, "w"); - add(0170, "x"); - add(0171, "y"); - add(0245, "yen"); - add(0172, "z"); - add(0060, "zero"); + for (Object[] encodingEntry : STANDARD_ENCODING_TABLE) + { + add((Integer)encodingEntry[CHAR_CODE], encodingEntry[CHAR_NAME].toString()); + } } /** @@ -199,4 +211,10 @@ public COSBase getCOSObject() { return COSName.STANDARD_ENCODING; } + + @Override + public String getEncodingName() + { + return "StandardEncoding"; + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/Type1Encoding.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/Type1Encoding.java index f814ee869..06f5dd9fa 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/Type1Encoding.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/Type1Encoding.java @@ -40,7 +40,7 @@ public static Type1Encoding fromFontBox(com.tom_roush.fontbox.encoding.Encoding for (Map.Entry entry : codeToName.entrySet()) { - enc.add(entry.getKey(), entry.getValue()); + enc.add(entry.getKey(), entry.getValue()); } return enc; @@ -71,4 +71,10 @@ public COSBase getCOSObject() { return null; } + + @Override + public String getEncodingName() + { + return "built-in (Type 1)"; + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/WinAnsiEncoding.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/WinAnsiEncoding.java index 2b1cd8b09..a24a806ad 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/WinAnsiEncoding.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/font/encoding/WinAnsiEncoding.java @@ -26,6 +26,233 @@ */ public class WinAnsiEncoding extends Encoding { + private static final int CHAR_CODE = 0; + private static final int CHAR_NAME = 1; + + /** + * Table of octal character codes and their corresponding names. + */ + private static final Object[][] WIN_ANSI_ENCODING_TABLE = { + {0101, "A"}, + {0306, "AE"}, + {0301, "Aacute"}, + {0302, "Acircumflex"}, + {0304, "Adieresis"}, + {0300, "Agrave"}, + {0305, "Aring"}, + {0303, "Atilde"}, + {0102, "B"}, + {0103, "C"}, + {0307, "Ccedilla"}, + {0104, "D"}, + {0105, "E"}, + {0311, "Eacute"}, + {0312, "Ecircumflex"}, + {0313, "Edieresis"}, + {0310, "Egrave"}, + {0320, "Eth"}, + {0200, "Euro"}, + {0106, "F"}, + {0107, "G"}, + {0110, "H"}, + {0111, "I"}, + {0315, "Iacute"}, + {0316, "Icircumflex"}, + {0317, "Idieresis"}, + {0314, "Igrave"}, + {0112, "J"}, + {0113, "K"}, + {0114, "L"}, + {0115, "M"}, + {0116, "N"}, + {0321, "Ntilde"}, + {0117, "O"}, + {0214, "OE"}, + {0323, "Oacute"}, + {0324, "Ocircumflex"}, + {0326, "Odieresis"}, + {0322, "Ograve"}, + {0330, "Oslash"}, + {0325, "Otilde"}, + {0120, "P"}, + {0121, "Q"}, + {0122, "R"}, + {0123, "S"}, + {0212, "Scaron"}, + {0124, "T"}, + {0336, "Thorn"}, + {0125, "U"}, + {0332, "Uacute"}, + {0333, "Ucircumflex"}, + {0334, "Udieresis"}, + {0331, "Ugrave"}, + {0126, "V"}, + {0127, "W"}, + {0130, "X"}, + {0131, "Y"}, + {0335, "Yacute"}, + {0237, "Ydieresis"}, + {0132, "Z"}, + {0216, "Zcaron"}, + {0141, "a"}, + {0341, "aacute"}, + {0342, "acircumflex"}, + {0264, "acute"}, + {0344, "adieresis"}, + {0346, "ae"}, + {0340, "agrave"}, + {046, "ampersand"}, + {0345, "aring"}, + {0136, "asciicircum"}, + {0176, "asciitilde"}, + {052, "asterisk"}, + {0100, "at"}, + {0343, "atilde"}, + {0142, "b"}, + {0134, "backslash"}, + {0174, "bar"}, + {0173, "braceleft"}, + {0175, "braceright"}, + {0133, "bracketleft"}, + {0135, "bracketright"}, + {0246, "brokenbar"}, + {0225, "bullet"}, + {0143, "c"}, + {0347, "ccedilla"}, + {0270, "cedilla"}, + {0242, "cent"}, + {0210, "circumflex"}, + {072, "colon"}, + {054, "comma"}, + {0251, "copyright"}, + {0244, "currency"}, + {0144, "d"}, + {0206, "dagger"}, + {0207, "daggerdbl"}, + {0260, "degree"}, + {0250, "dieresis"}, + {0367, "divide"}, + {044, "dollar"}, + {0145, "e"}, + {0351, "eacute"}, + {0352, "ecircumflex"}, + {0353, "edieresis"}, + {0350, "egrave"}, + {070, "eight"}, + {0205, "ellipsis"}, + {0227, "emdash"}, + {0226, "endash"}, + {075, "equal"}, + {0360, "eth"}, + {041, "exclam"}, + {0241, "exclamdown"}, + {0146, "f"}, + {065, "five"}, + {0203, "florin"}, + {064, "four"}, + {0147, "g"}, + {0337, "germandbls"}, + {0140, "grave"}, + {076, "greater"}, + {0253, "guillemotleft"}, + {0273, "guillemotright"}, + {0213, "guilsinglleft"}, + {0233, "guilsinglright"}, + {0150, "h"}, + {055, "hyphen"}, + {0151, "i"}, + {0355, "iacute"}, + {0356, "icircumflex"}, + {0357, "idieresis"}, + {0354, "igrave"}, + {0152, "j"}, + {0153, "k"}, + {0154, "l"}, + {074, "less"}, + {0254, "logicalnot"}, + {0155, "m"}, + {0257, "macron"}, + {0265, "mu"}, + {0327, "multiply"}, + {0156, "n"}, + {071, "nine"}, + {0361, "ntilde"}, + {043, "numbersign"}, + {0157, "o"}, + {0363, "oacute"}, + {0364, "ocircumflex"}, + {0366, "odieresis"}, + {0234, "oe"}, + {0362, "ograve"}, + {061, "one"}, + {0275, "onehalf"}, + {0274, "onequarter"}, + {0271, "onesuperior"}, + {0252, "ordfeminine"}, + {0272, "ordmasculine"}, + {0370, "oslash"}, + {0365, "otilde"}, + {0160, "p"}, + {0266, "paragraph"}, + {050, "parenleft"}, + {051, "parenright"}, + {045, "percent"}, + {056, "period"}, + {0267, "periodcentered"}, + {0211, "perthousand"}, + {053, "plus"}, + {0261, "plusminus"}, + {0161, "q"}, + {077, "question"}, + {0277, "questiondown"}, + {042, "quotedbl"}, + {0204, "quotedblbase"}, + {0223, "quotedblleft"}, + {0224, "quotedblright"}, + {0221, "quoteleft"}, + {0222, "quoteright"}, + {0202, "quotesinglbase"}, + {047, "quotesingle"}, + {0162, "r"}, + {0256, "registered"}, + {0163, "s"}, + {0232, "scaron"}, + {0247, "section"}, + {073, "semicolon"}, + {067, "seven"}, + {066, "six"}, + {057, "slash"}, + {040, "space"}, + {0243, "sterling"}, + {0164, "t"}, + {0376, "thorn"}, + {063, "three"}, + {0276, "threequarters"}, + {0263, "threesuperior"}, + {0230, "tilde"}, + {0231, "trademark"}, + {062, "two"}, + {0262, "twosuperior"}, + {0165, "u"}, + {0372, "uacute"}, + {0373, "ucircumflex"}, + {0374, "udieresis"}, + {0371, "ugrave"}, + {0137, "underscore"}, + {0166, "v"}, + {0167, "w"}, + {0170, "x"}, + {0171, "y"}, + {0375, "yacute"}, + {0377, "ydieresis"}, + {0245, "yen"}, + {0172, "z"}, + {0236, "zcaron"}, + {060, "zero"}, + // adding some additional mappings as defined in Appendix D of the pdf spec + {0240, "space"}, + {0255, "hyphen"} + }; /** * Singleton instance of this class. @@ -39,225 +266,13 @@ public class WinAnsiEncoding extends Encoding */ public WinAnsiEncoding() { - add(0101, "A"); - add(0306, "AE"); - add(0301, "Aacute"); - add(0302, "Acircumflex"); - add(0304, "Adieresis"); - add(0300, "Agrave"); - add(0305, "Aring"); - add(0303, "Atilde"); - add(0102, "B"); - add(0103, "C"); - add(0307, "Ccedilla"); - add(0104, "D"); - add(0105, "E"); - add(0311, "Eacute"); - add(0312, "Ecircumflex"); - add(0313, "Edieresis"); - add(0310, "Egrave"); - add(0320, "Eth"); - add(0200, "Euro"); - add(0106, "F"); - add(0107, "G"); - add(0110, "H"); - add(0111, "I"); - add(0315, "Iacute"); - add(0316, "Icircumflex"); - add(0317, "Idieresis"); - add(0314, "Igrave"); - add(0112, "J"); - add(0113, "K"); - add(0114, "L"); - add(0115, "M"); - add(0116, "N"); - add(0321, "Ntilde"); - add(0117, "O"); - add(0214, "OE"); - add(0323, "Oacute"); - add(0324, "Ocircumflex"); - add(0326, "Odieresis"); - add(0322, "Ograve"); - add(0330, "Oslash"); - add(0325, "Otilde"); - add(0120, "P"); - add(0121, "Q"); - add(0122, "R"); - add(0123, "S"); - add(0212, "Scaron"); - add(0124, "T"); - add(0336, "Thorn"); - add(0125, "U"); - add(0332, "Uacute"); - add(0333, "Ucircumflex"); - add(0334, "Udieresis"); - add(0331, "Ugrave"); - add(0126, "V"); - add(0127, "W"); - add(0130, "X"); - add(0131, "Y"); - add(0335, "Yacute"); - add(0237, "Ydieresis"); - add(0132, "Z"); - add(0216, "Zcaron"); - add(0141, "a"); - add(0341, "aacute"); - add(0342, "acircumflex"); - add(0264, "acute"); - add(0344, "adieresis"); - add(0346, "ae"); - add(0340, "agrave"); - add(046, "ampersand"); - add(0345, "aring"); - add(0136, "asciicircum"); - add(0176, "asciitilde"); - add(052, "asterisk"); - add(0100, "at"); - add(0343, "atilde"); - add(0142, "b"); - add(0134, "backslash"); - add(0174, "bar"); - add(0173, "braceleft"); - add(0175, "braceright"); - add(0133, "bracketleft"); - add(0135, "bracketright"); - add(0246, "brokenbar"); - add(0225, "bullet"); - add(0143, "c"); - add(0347, "ccedilla"); - add(0270, "cedilla"); - add(0242, "cent"); - add(0210, "circumflex"); - add(072, "colon"); - add(054, "comma"); - add(0251, "copyright"); - add(0244, "currency"); - add(0144, "d"); - add(0206, "dagger"); - add(0207, "daggerdbl"); - add(0260, "degree"); - add(0250, "dieresis"); - add(0367, "divide"); - add(044, "dollar"); - add(0145, "e"); - add(0351, "eacute"); - add(0352, "ecircumflex"); - add(0353, "edieresis"); - add(0350, "egrave"); - add(070, "eight"); - add(0205, "ellipsis"); - add(0227, "emdash"); - add(0226, "endash"); - add(075, "equal"); - add(0360, "eth"); - add(041, "exclam"); - add(0241, "exclamdown"); - add(0146, "f"); - add(065, "five"); - add(0203, "florin"); - add(064, "four"); - add(0147, "g"); - add(0337, "germandbls"); - add(0140, "grave"); - add(076, "greater"); - add(0253, "guillemotleft"); - add(0273, "guillemotright"); - add(0213, "guilsinglleft"); - add(0233, "guilsinglright"); - add(0150, "h"); - add(055, "hyphen"); - add(0151, "i"); - add(0355, "iacute"); - add(0356, "icircumflex"); - add(0357, "idieresis"); - add(0354, "igrave"); - add(0152, "j"); - add(0153, "k"); - add(0154, "l"); - add(074, "less"); - add(0254, "logicalnot"); - add(0155, "m"); - add(0257, "macron"); - add(0265, "mu"); - add(0327, "multiply"); - add(0156, "n"); - add(071, "nine"); - add(0361, "ntilde"); - add(043, "numbersign"); - add(0157, "o"); - add(0363, "oacute"); - add(0364, "ocircumflex"); - add(0366, "odieresis"); - add(0234, "oe"); - add(0362, "ograve"); - add(061, "one"); - add(0275, "onehalf"); - add(0274, "onequarter"); - add(0271, "onesuperior"); - add(0252, "ordfeminine"); - add(0272, "ordmasculine"); - add(0370, "oslash"); - add(0365, "otilde"); - add(0160, "p"); - add(0266, "paragraph"); - add(050, "parenleft"); - add(051, "parenright"); - add(045, "percent"); - add(056, "period"); - add(0267, "periodcentered"); - add(0211, "perthousand"); - add(053, "plus"); - add(0261, "plusminus"); - add(0161, "q"); - add(077, "question"); - add(0277, "questiondown"); - add(042, "quotedbl"); - add(0204, "quotedblbase"); - add(0223, "quotedblleft"); - add(0224, "quotedblright"); - add(0221, "quoteleft"); - add(0222, "quoteright"); - add(0202, "quotesinglbase"); - add(047, "quotesingle"); - add(0162, "r"); - add(0256, "registered"); - add(0163, "s"); - add(0232, "scaron"); - add(0247, "section"); - add(073, "semicolon"); - add(067, "seven"); - add(066, "six"); - add(057, "slash"); - add(040, "space"); - add(0243, "sterling"); - add(0164, "t"); - add(0376, "thorn"); - add(063, "three"); - add(0276, "threequarters"); - add(0263, "threesuperior"); - add(0230, "tilde"); - add(0231, "trademark"); - add(062, "two"); - add(0262, "twosuperior"); - add(0165, "u"); - add(0372, "uacute"); - add(0373, "ucircumflex"); - add(0374, "udieresis"); - add(0371, "ugrave"); - add(0137, "underscore"); - add(0166, "v"); - add(0167, "w"); - add(0170, "x"); - add(0171, "y"); - add(0375, "yacute"); - add(0377, "ydieresis"); - add(0245, "yen"); - add(0172, "z"); - add(0236, "zcaron"); - add(060, "zero"); - // adding some additional mappings as defined in Appendix D of the pdf spec - add(0240, "space"); - add(0255, "hyphen"); + for (Object[] encodingEntry : WIN_ANSI_ENCODING_TABLE) + { + add((Integer)encodingEntry[CHAR_CODE], encodingEntry[CHAR_NAME].toString()); + } + + // From the PDF specification: + // In WinAnsiEncoding, all unused codes greater than 40 map to the bullet character. for (int i = 041; i <= 255; i++) { if (!codeToName.containsKey(i)) @@ -276,4 +291,10 @@ public COSBase getCOSObject() { return COSName.WIN_ANSI_ENCODING; } + + @Override + public String getEncodingName() + { + return "WinAnsiEncoding"; + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/PDXObject.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/PDXObject.java index 8c73c1666..4766e2d16 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/PDXObject.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/PDXObject.java @@ -19,6 +19,7 @@ import java.io.IOException; import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSStream; import com.tom_roush.pdfbox.pdmodel.PDDocument; @@ -27,6 +28,7 @@ import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; import com.tom_roush.pdfbox.pdmodel.common.PDStream; import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; +import com.tom_roush.pdfbox.pdmodel.graphics.form.PDTransparencyGroup; import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImageXObject; /** @@ -43,6 +45,7 @@ public class PDXObject implements COSObjectable * Creates a new XObject instance of the appropriate type for the COS stream. * * @param base The stream which is wrapped by this XObject. + * @param resources * @return A new XObject instance. * @throws java.io.IOException if there is an error creating the XObject. */ @@ -66,9 +69,14 @@ public static PDXObject createXObject(COSBase base, PDResources resources) throw { return new PDImageXObject(new PDStream(stream), resources); } - if (COSName.FORM.getName().equals(subtype)) + else if (COSName.FORM.getName().equals(subtype)) { ResourceCache cache = resources != null ? resources.getResourceCache() : null; + COSDictionary group = (COSDictionary)stream.getDictionaryObject(COSName.GROUP); + if (group != null && COSName.TRANSPARENCY.equals(group.getCOSName(COSName.S))) + { + return new PDTransparencyGroup(stream, cache); + } return new PDFormXObject(stream, cache); } else if (COSName.PS.getName().equals(subtype)) @@ -85,6 +93,7 @@ else if (COSName.PS.getName().equals(subtype)) * Creates a new XObject from the given stream and subtype. * * @param stream The stream to read. + * @param subtype */ protected PDXObject(COSStream stream, COSName subtype) { @@ -98,13 +107,14 @@ protected PDXObject(COSStream stream, COSName subtype) * Creates a new XObject from the given stream and subtype. * * @param stream The stream to read. + * @param subtype */ protected PDXObject(PDStream stream, COSName subtype) { this.stream = stream; // could be used for writing: - stream.getStream().setName(COSName.TYPE, COSName.XOBJECT.getName()); - stream.getStream().setName(COSName.SUBTYPE, subtype.getName()); + stream.getCOSObject().setName(COSName.TYPE, COSName.XOBJECT.getName()); + stream.getCOSObject().setName(COSName.SUBTYPE, subtype.getName()); } /** @@ -115,8 +125,8 @@ protected PDXObject(PDStream stream, COSName subtype) protected PDXObject(PDDocument document, COSName subtype) { stream = new PDStream(document); - stream.getStream().setName(COSName.TYPE, COSName.XOBJECT.getName()); - stream.getStream().setName(COSName.SUBTYPE, subtype.getName()); + stream.getCOSObject().setName(COSName.TYPE, COSName.XOBJECT.getName()); + stream.getCOSObject().setName(COSName.SUBTYPE, subtype.getName()); } /** @@ -124,7 +134,7 @@ protected PDXObject(PDDocument document, COSName subtype) * {@inheritDoc} */ @Override - public final COSBase getCOSObject() + public final COSStream getCOSObject() { return stream.getCOSObject(); } @@ -132,10 +142,12 @@ public final COSBase getCOSObject() /** * Returns the stream. * @return The stream for this object. + * @deprecated use {@link #getCOSObject() } */ + @Deprecated public final COSStream getCOSStream() { - return stream.getStream(); + return stream.getCOSObject(); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/blend/BlendMode.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/blend/BlendMode.java index 251e38720..c3881750a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/blend/BlendMode.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/blend/BlendMode.java @@ -48,14 +48,14 @@ else if (cosBlendMode instanceof COSArray) COSArray cosBlendModeArray = (COSArray) cosBlendMode; for (int i = 0; i < cosBlendModeArray.size(); i++) { - result = BLEND_MODES.get(cosBlendModeArray.get(i)); + result = BLEND_MODES.get((COSName)cosBlendModeArray.get(i)); if (result != null) { break; } } } - + if (result != null) { return result; @@ -65,7 +65,7 @@ else if (cosBlendMode instanceof COSArray) public static final SeparableBlendMode NORMAL = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return srcValue; @@ -76,7 +76,7 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode MULTIPLY = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return srcValue * dstValue; @@ -85,7 +85,7 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode SCREEN = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return srcValue + dstValue - srcValue * dstValue; @@ -94,7 +94,7 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode OVERLAY = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return (dstValue <= 0.5) ? 2 * dstValue * srcValue : 2 * (srcValue + dstValue - srcValue @@ -104,7 +104,7 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode DARKEN = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return Math.min(srcValue, dstValue); @@ -113,7 +113,7 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode LIGHTEN = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return Math.max(srcValue, dstValue); @@ -122,7 +122,7 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode COLOR_DODGE = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return (srcValue < 1) ? Math.min(1, dstValue / (1 - srcValue)) : 1; @@ -131,7 +131,7 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode COLOR_BURN = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return (srcValue > 0) ? 1 - Math.min(1, (1 - dstValue) / srcValue) : 0; @@ -140,7 +140,7 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode HARD_LIGHT = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return (srcValue <= 0.5) ? 2 * dstValue * srcValue : @@ -150,7 +150,7 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode SOFT_LIGHT = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { if (srcValue <= 0.5) @@ -168,7 +168,7 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode DIFFERENCE = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return Math.abs(dstValue - srcValue); @@ -177,18 +177,19 @@ public float blendChannel(float srcValue, float dstValue) public static final SeparableBlendMode EXCLUSION = new SeparableBlendMode() { - @Override + @Override public float blendChannel(float srcValue, float dstValue) { return dstValue + srcValue - 2 * dstValue * srcValue; } }; - + + // this map *must* come after the declarations above, otherwise its values will be null private static final Map BLEND_MODES = createBlendModeMap(); private static Map createBlendModeMap() { - Map map = new HashMap(); + Map map = new HashMap(13); map.put(COSName.NORMAL, BlendMode.NORMAL); map.put(COSName.COMPATIBLE, BlendMode.COMPATIBLE); map.put(COSName.MULTIPLY, BlendMode.MULTIPLY); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java index 1a2c3f7bf..533abba45 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDColorSpace.java @@ -35,7 +35,8 @@ * @author John Hewson * @author Ben Litchfield */ -public abstract class PDColorSpace implements COSObjectable { +public abstract class PDColorSpace implements COSObjectable +{ /** * Creates a color space space given a name or array. * @@ -43,102 +44,171 @@ public abstract class PDColorSpace implements COSObjectable { * @return a new color space * @throws IOException if the color space is unknown or cannot be created */ - public static PDColorSpace create(COSBase colorSpace) throws IOException { + public static PDColorSpace create(COSBase colorSpace) throws IOException + { return create(colorSpace, null); } /** - * Creates a color space given a name or array. + * Creates a color space given a name or array. Abbreviated device color names are not supported + * here, please replace them first. * * @param colorSpace the color space COS object - * @param resources the current resources. + * @param resources the current resources. * @return a new color space * @throws MissingResourceException if the color space is missing in the resources dictionary - * @throws IOException if the color space is unknown or cannot be created + * @throws IOException if the color space is unknown or cannot be created + */ + public static PDColorSpace create(COSBase colorSpace, PDResources resources) throws IOException + { + return create(colorSpace, resources, false); + } + + /** + * Creates a color space given a name or array. Abbreviated device color names are not supported + * here, please replace them first. This method is for PDFBox internal use only, others should + * use {@link #create(COSBase, PDResources)}. + * + * @param colorSpace the color space COS object + * @param resources the current resources. + * @param wasDefault if current color space was used by a default color space. + * + * @return a new color space. + * @throws MissingResourceException if the color space is missing in the resources dictionary + * @throws IOException if the color space is unknown or cannot be created. */ - public static PDColorSpace create(COSBase colorSpace, - PDResources resources) - throws IOException { - if (colorSpace instanceof COSObject) { + public static PDColorSpace create(COSBase colorSpace, PDResources resources, boolean wasDefault) + throws IOException + { + if (colorSpace instanceof COSObject) + { return create(((COSObject) colorSpace).getObject(), resources); - } else if (colorSpace instanceof COSName) { + } + else if (colorSpace instanceof COSName) + { COSName name = (COSName) colorSpace; // default color spaces - if (resources != null) { + if (resources != null) + { COSName defaultName = null; - if (name.equals(COSName.DEVICECMYK) && - resources.hasColorSpace(COSName.DEFAULT_CMYK)) { + if (name.equals(COSName.DEVICECMYK) && resources.hasColorSpace( + COSName.DEFAULT_CMYK)) + { defaultName = COSName.DEFAULT_CMYK; - } else if (name.equals(COSName.DEVICERGB) && - resources.hasColorSpace(COSName.DEFAULT_RGB)) { + } + else if (name.equals(COSName.DEVICERGB) && resources.hasColorSpace( + COSName.DEFAULT_RGB)) + { defaultName = COSName.DEFAULT_RGB; - } else if (name.equals(COSName.DEVICEGRAY) && - resources.hasColorSpace(COSName.DEFAULT_GRAY)) { + } + else if (name.equals(COSName.DEVICEGRAY) && resources.hasColorSpace( + COSName.DEFAULT_GRAY)) + { defaultName = COSName.DEFAULT_GRAY; } - if (resources.hasColorSpace(defaultName)) { - return resources.getColorSpace(defaultName); + if (resources.hasColorSpace(defaultName) && !wasDefault) + { + return resources.getColorSpace(defaultName, true); } } // built-in color spaces - /*if (name == COSName.DEVICECMYK || name == COSName.CMYK) { + /*if (name == COSName.DEVICECMYK) + { return PDDeviceCMYK.INSTANCE; - } else*/ if (name == COSName.DEVICERGB || name == COSName.RGB) { + } TODO: PdfBox-Android + else*/ + if (name == COSName.DEVICERGB) + { return PDDeviceRGB.INSTANCE; - } else if (name == COSName.DEVICEGRAY || name == COSName.G) { + } + else if (name == COSName.DEVICEGRAY) + { return PDDeviceGray.INSTANCE; - } /*else if (name == COSName.PATTERN) { + } + /*else if (name == COSName.PATTERN) + { return new PDPattern(resources); - } */else if (resources != null) { - if (!resources.hasColorSpace(name)) { + } TODO: PdfBox-Android + */ + else if (resources != null) + { + if (!resources.hasColorSpace(name)) + { throw new MissingResourceException("Missing color space: " + name.getName()); } return resources.getColorSpace(name); - } else { + } + else + { throw new MissingResourceException("Unknown color space: " + name.getName()); } - } else if (colorSpace instanceof COSArray) { + } + else if (colorSpace instanceof COSArray) + { COSArray array = (COSArray) colorSpace; COSName name = (COSName) array.get(0); -// -// // TODO cache these returned color spaces? -// -// if (name == COSName.CALGRAY) { + + // TODO cache these returned color spaces? + +// if (name == COSName.CALGRAY) +// { // return new PDCalGray(array); -// } else if (name == COSName.CALRGB) { +// } +// else if (name == COSName.CALRGB) +// { // return new PDCalRGB(array); -// } else if (name == COSName.DEVICEN) { +// } +// else if (name == COSName.DEVICEN) +// { // return new PDDeviceN(array); -// } else if (name == COSName.INDEXED || name == COSName.I) { +// } +// else if (name == COSName.INDEXED || name == COSName.I) +// { // return new PDIndexed(array); -// } else if (name == COSName.SEPARATION) { +// } +// else if (name == COSName.SEPARATION) +// { // return new PDSeparation(array); -// } else if (name == COSName.ICCBASED) { +// } +// else if (name == COSName.ICCBASED) +// { // return new PDICCBased(array); -// } else if (name == COSName.LAB) { +// } +// else if (name == COSName.LAB) +// { // return new PDLab(array); -// } else if (name == COSName.PATTERN) { -// if (array.size() == 1) { +// } +// else if (name == COSName.PATTERN) +// { +// if (array.size() == 1) +// { // return new PDPattern(resources); -// } else { +// } +// else +// { // return new PDPattern(resources, PDColorSpace.create(array.get(1))); // } -// } else if (name == COSName.DEVICECMYK || name == COSName.CMYK || -// name == COSName.DEVICERGB || name == COSName.RGB || -// name == COSName.DEVICEGRAY || name == COSName.PATTERN) { -// // not allowed in an array, but we sometimes encounter these regardless -// return create(name, resources); -// } else { +// } TODO: PdfBox-Android + /*else*/ if (name == COSName.DEVICECMYK || name == COSName.DEVICERGB || + name == COSName.DEVICEGRAY) + { + // not allowed in an array, but we sometimes encounter these regardless + return create(name, resources); + } +// else +// { // throw new IOException("Invalid color space kind: " + name); // } -// throw new IOException("Invalid color space kind: " + name); + // throw new IOException("Invalid color space kind: " + name); Log.e("PdfBox-Android", "Invalid color space kind: " + name + ". Will try DeviceRGB instead"); return PDDeviceRGB.INSTANCE; - } else { + } + else + { throw new IOException("Expected a name or array but got: " + colorSpace); } } @@ -162,6 +232,7 @@ public static PDColorSpace create(COSBase colorSpace, /** * Returns the default decode array for this color space. + * @param bitsPerComponent the number of bits per component. * * @return the default decode array */ @@ -183,14 +254,14 @@ public static PDColorSpace create(COSBase colorSpace, */ public abstract float[] toRGB(float[] value) throws IOException; -// /** -// * Returns the (A)RGB equivalent of the given raster. -// * @param raster the source raster -// * @return an (A)RGB Bitmap -// * @throws IOException if the color conversion fails -// */ -// public abstract Bitmap toRGBImage(WritableRaster raster) throws IOException; TODO: PdfBox-Android - + /** + * Returns the (A)RGB equivalent of the given raster. + * + * @param raster the source raster + * + * @return an (A)RGB Bitmap + * @throws IOException if the color conversion fails + */ public abstract Bitmap toRGBImage(Bitmap raster) throws IOException; // /** @@ -220,7 +291,8 @@ public static PDColorSpace create(COSBase colorSpace, // } TODO: PdfBox-Android @Override - public COSBase getCOSObject() { + public COSBase getCOSObject() + { return array; } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceGray.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceGray.java index 10a0788d1..fdce18702 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceGray.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceGray.java @@ -35,7 +35,7 @@ public final class PDDeviceGray extends PDDeviceColorSpace { /** The single instance of this class. */ public static final PDDeviceGray INSTANCE = new PDDeviceGray(); - + private final PDColor initialColor = new PDColor(new float[] { 0 }, this); private PDDeviceGray() diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java index 1ea24e72a..812ded09f 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDDeviceRGB.java @@ -30,7 +30,8 @@ * @author Ben Litchfield * @author John Hewson */ -public final class PDDeviceRGB extends PDDeviceColorSpace { +public final class PDDeviceRGB extends PDDeviceColorSpace +{ /** * This is the single instance of this class. */ @@ -71,19 +72,23 @@ private void init() } @Override - public String getName() { + public String getName() + { return COSName.DEVICERGB.getName(); } /** * @inheritDoc */ - public int getNumberOfComponents() { + @Override + public int getNumberOfComponents() + { return 3; } @Override - public float[] getDefaultDecode(int bitsPerComponent) { + public float[] getDefaultDecode(int bitsPerComponent) + { return new float[]{0, 1, 0, 1, 0, 1}; } @@ -93,13 +98,14 @@ public PDColor getInitialColor() { } @Override - public float[] toRGB(float[] value) { + public float[] toRGB(float[] value) + { // This is just assuming that the values being sent to it are already in RGB color space. if (value.length == 3) { return value; } else { // init(); -// return awtColorSpace.toRGB(value); +// return awtColorSpace.toRGB(value); TODO: PdfBox-Android return initialColor.getComponents(); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDOutputIntent.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDOutputIntent.java index 40d7eccaf..0ae0599e1 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDOutputIntent.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/color/PDOutputIntent.java @@ -37,7 +37,7 @@ */ public final class PDOutputIntent implements COSObjectable { - private COSDictionary dictionary; + private final COSDictionary dictionary; public PDOutputIntent(PDDocument doc, InputStream colorProfile) throws IOException { @@ -53,6 +53,7 @@ public PDOutputIntent(COSDictionary dictionary) this.dictionary = dictionary; } + @Override public COSBase getCOSObject() { return dictionary; @@ -106,6 +107,9 @@ public void setRegistryName(String value) private PDStream configureOutputProfile(PDDocument doc, InputStream colorProfile) throws IOException { +// ICC_Profile icc = ICC_Profile.getInstance(colorProfile); +// PDStream stream = new PDStream(doc, new ByteArrayInputStream(icc.getData()), COSName.FLATE_DECODE); +// stream.getCOSObject().setInt(COSName.N, icc.getNumComponents()); TODO: PdFBox-Android PDStream stream = new PDStream(doc, colorProfile, COSName.FLATE_DECODE); stream.getStream().setInt(COSName.N, 3); return stream; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDFormXObject.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDFormXObject.java index 6707f1691..0ca792843 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDFormXObject.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDFormXObject.java @@ -55,7 +55,7 @@ */ public class PDFormXObject extends PDXObject implements PDContentStream { - private PDGroup group; + private PDTransparencyGroupAttributes group; private final ResourceCache cache; /** @@ -105,7 +105,7 @@ public PDFormXObject(PDDocument document) */ public int getFormType() { - return getCOSStream().getInt(COSName.FORMTYPE, 1); + return getCOSObject().getInt(COSName.FORMTYPE, 1); } /** @@ -114,22 +114,22 @@ public int getFormType() */ public void setFormType(int formType) { - getCOSStream().setInt(COSName.FORMTYPE, formType); + getCOSObject().setInt(COSName.FORMTYPE, formType); } /** - * Returns the group attributes dictionary (Group XObject). + * Returns the group attributes dictionary. * * @return the group attributes dictionary */ - public PDGroup getGroup() + public PDTransparencyGroupAttributes getGroup() { if( group == null ) { - COSDictionary dic = (COSDictionary) getCOSStream().getDictionaryObject(COSName.GROUP); + COSDictionary dic = (COSDictionary) getCOSObject().getDictionaryObject(COSName.GROUP); if( dic != null ) { - group = new PDGroup(dic); + group = new PDTransparencyGroupAttributes(dic); } } return group; @@ -137,13 +137,13 @@ public PDGroup getGroup() public PDStream getContentStream() { - return new PDStream(getCOSStream()); + return new PDStream(getCOSObject()); } @Override public InputStream getContents() throws IOException { - return getCOSStream().createInputStream(); + return getCOSObject().createInputStream(); } /** @@ -155,7 +155,7 @@ public InputStream getContents() throws IOException @Override public PDResources getResources() { - COSDictionary resources = (COSDictionary) getCOSStream().getDictionaryObject(COSName.RESOURCES); + COSDictionary resources = (COSDictionary) getCOSObject().getDictionaryObject(COSName.RESOURCES); if (resources != null) { return new PDResources(resources, cache); @@ -169,7 +169,7 @@ public PDResources getResources() */ public void setResources(PDResources resources) { - getCOSStream().setItem(COSName.RESOURCES, resources); + getCOSObject().setItem(COSName.RESOURCES, resources); } /** @@ -183,7 +183,7 @@ public void setResources(PDResources resources) public PDRectangle getBBox() { PDRectangle retval = null; - COSArray array = (COSArray) getCOSStream().getDictionaryObject(COSName.BBOX); + COSArray array = (COSArray) getCOSObject().getDictionaryObject(COSName.BBOX); if (array != null) { retval = new PDRectangle(array); @@ -199,11 +199,11 @@ public void setBBox(PDRectangle bbox) { if (bbox == null) { - getCOSStream().removeItem(COSName.BBOX); + getCOSObject().removeItem(COSName.BBOX); } else { - getCOSStream().setItem(COSName.BBOX, bbox.getCOSArray()); + getCOSObject().setItem(COSName.BBOX, bbox.getCOSArray()); } } @@ -214,11 +214,13 @@ public void setBBox(PDRectangle bbox) @Override public Matrix getMatrix() { - COSArray array = (COSArray) getCOSStream().getDictionaryObject(COSName.MATRIX); + COSArray array = (COSArray) getCOSObject().getDictionaryObject(COSName.MATRIX); if (array != null) { return new Matrix(array); - } else { + } + else + { // default value is the identity matrix return new Matrix(); } @@ -237,7 +239,7 @@ public void setMatrix(AffineTransform transform) { matrix.add(new COSFloat((float) v)); } - getCOSStream().setItem(COSName.MATRIX, matrix); + getCOSObject().setItem(COSName.MATRIX, matrix); } /** @@ -248,7 +250,7 @@ public void setMatrix(AffineTransform transform) */ public int getStructParents() { - return getCOSStream().getInt(COSName.STRUCT_PARENTS, 0); + return getCOSObject().getInt(COSName.STRUCT_PARENTS, 0); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDTransparencyGroup.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDTransparencyGroup.java new file mode 100644 index 000000000..36ab1eaf9 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDTransparencyGroup.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.graphics.form; + +import com.tom_roush.pdfbox.cos.COSStream; +import com.tom_roush.pdfbox.pdmodel.PDDocument; +import com.tom_roush.pdfbox.pdmodel.ResourceCache; +import com.tom_roush.pdfbox.pdmodel.common.PDStream; + +/** + * A transparency group. + * + * @author John Hewson + */ +public class PDTransparencyGroup extends PDFormXObject +{ + /** + * Creates a Transparency Group for reading. + * + * @param stream The XObject stream + */ + public PDTransparencyGroup(PDStream stream) + { + super(stream); + } + + /** + * Creates a Transparency Group for reading. + * + * @param stream The XObject stream + */ + public PDTransparencyGroup(COSStream stream, ResourceCache cache) + { + super(stream, cache); + } + + /** + * Creates a Transparency Group for writing, in the given document. + * + * @param document The current document + */ + public PDTransparencyGroup(PDDocument document) + { + super(document); + // todo: set mandatory fields + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDGroup.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDTransparencyGroupAttributes.java similarity index 83% rename from library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDGroup.java rename to library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDTransparencyGroupAttributes.java index 7b259c97c..ebdd80f03 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDGroup.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/form/PDTransparencyGroupAttributes.java @@ -24,21 +24,21 @@ import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; /** - * Transparency group. - * - * @author K�hn & Weyh Software, GmbH + * Transparency group attributes. + * + * @author Kühn & Weyh Software, GmbH */ -public final class PDGroup implements COSObjectable +public final class PDTransparencyGroupAttributes implements COSObjectable { private final COSDictionary dictionary; - private COSName subType; private PDColorSpace colorSpace; /** * Creates a group object from a given dictionary + * * @param dic {@link COSDictionary} object */ - public PDGroup(COSDictionary dic) + public PDTransparencyGroupAttributes(COSDictionary dic) { dictionary = dic; } @@ -49,18 +49,6 @@ public COSDictionary getCOSObject() return dictionary; } - /** - * Returns the groups's subtype, should be "Transparency". - */ - public COSName getSubType() - { - if (subType == null) - { - subType = (COSName) getCOSObject().getDictionaryObject(COSName.S); - } - return subType; - } - /** * Returns the blending color space * diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactory.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactory.java index 129958a74..b22865591 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactory.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/CCITTFactory.java @@ -42,7 +42,7 @@ private CCITTFactory() } /** - * Creates a new CCITT Fax compressed Image XObject from the first page of + * Creates a new CCITT Fax compressed image XObject from the first page of * a TIFF file. * * @param document the document to create the image as part of. @@ -55,13 +55,13 @@ private CCITTFactory() */ @Deprecated public static PDImageXObject createFromRandomAccess(PDDocument document, RandomAccess reader) - throws IOException + throws IOException { - return createFromRandomAccessImpl(document, reader, 0); + return createFromRandomAccessImpl(document, reader, 0); } /** - * Creates a new CCITT Fax compressed Image XObject from a TIFF file. + * Creates a new CCITT Fax compressed image XObject from a specific image of a TIFF file. * * @param document the document to create the image as part of. * @param reader the random access TIFF file which contains a suitable CCITT @@ -74,14 +74,17 @@ public static PDImageXObject createFromRandomAccess(PDDocument document, RandomA */ @Deprecated public static PDImageXObject createFromRandomAccess(PDDocument document, RandomAccess reader, - int number) throws IOException + int number) throws IOException { - return createFromRandomAccessImpl(document, reader, number); + return createFromRandomAccessImpl(document, reader, number); } /** - * Creates a new CCITT Fax compressed Image XObject from the first page of - * a TIFF file. + * Creates a new CCITT Fax compressed image XObject from the first image of a TIFF file. Only + * single-strip CCITT T4 or T6 compressed TIFF files are supported. If you're not sure what TIFF + * files you have, use + * {@link LosslessFactory#createFromImage(PDDocument, android.graphics.Bitmap)} + * instead. * * @param document the document to create the image as part of. * @param file the TIFF file which contains a suitable CCITT compressed image @@ -89,30 +92,32 @@ public static PDImageXObject createFromRandomAccess(PDDocument document, RandomA * @throws IOException if there is an error reading the TIFF data. */ public static PDImageXObject createFromFile(PDDocument document, File file) - throws IOException + throws IOException { - return createFromRandomAccessImpl(document, new RandomAccessFile(file, "r"), 0); + return createFromRandomAccessImpl(document, new RandomAccessFile(file, "r"), 0); } /** - * Creates a new CCITT Fax compressed Image XObject from the first page of - * a TIFF file. + * Creates a new CCITT Fax compressed image XObject from a specific image of a TIFF file. Only + * single-strip CCITT T4 or T6 compressed TIFF files are supported. If you're not sure what TIFF + * files you have, use + * {@link LosslessFactory#createFromImage(PDDocument, android.graphics.Bitmap)} + * instead. * * @param document the document to create the image as part of. * @param file the TIFF file which contains a suitable CCITT compressed image * @param number TIFF image number, starting from 0 - * compressed image * @return a new Image XObject * @throws IOException if there is an error reading the TIFF data. */ public static PDImageXObject createFromFile(PDDocument document, File file, int number) throws IOException { - return createFromRandomAccessImpl(document, new RandomAccessFile(file, "r"), number); + return createFromRandomAccessImpl(document, new RandomAccessFile(file, "r"), number); } /** - * Creates a new CCITT Fax compressed Image XObject from a TIFF file. + * Creates a new CCITT Fax compressed image XObject from a TIFF file. * * @param document the document to create the image as part of. * @param reader the random access TIFF file which contains a suitable CCITT @@ -122,8 +127,8 @@ public static PDImageXObject createFromFile(PDDocument document, File file, int * @throws IOException if there is an error reading the TIFF data. */ private static PDImageXObject createFromRandomAccessImpl(PDDocument document, - RandomAccess reader, - int number) throws IOException + RandomAccess reader, + int number) throws IOException { COSDictionary decodeParms = new COSDictionary(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -141,7 +146,7 @@ private static PDImageXObject createFromRandomAccessImpl(PDDocument document, 1, PDDeviceGray.INSTANCE); - COSDictionary dict = pdImage.getCOSStream(); + COSDictionary dict = pdImage.getCOSObject(); dict.setItem(COSName.DECODE_PARMS, decodeParms); return pdImage; } @@ -208,6 +213,7 @@ private static void extractFromTiff(RandomAccess reader, OutputStream os, // Default value to detect error int k = -1000; + int dataoffset = 0; int datalength = 0; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactory.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactory.java index 505ffb33b..80e42f84f 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactory.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/JPEGFactory.java @@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; @@ -136,10 +137,10 @@ private static Bitmap getAlphaImage(Bitmap image) throws IOException private static PDImageXObject createJPEG(PDDocument document, Bitmap image, float quality, int dpi) throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - image.compress(Bitmap.CompressFormat.JPEG, (int)(quality * 100), bos); - byte[] bitmapData = bos.toByteArray(); - ByteArrayInputStream byteStream = new ByteArrayInputStream(bitmapData); + // create XObject + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + encodeImageToJPEGStream(image, quality, dpi, baos); + ByteArrayInputStream byteStream = new ByteArrayInputStream(baos.toByteArray()); PDImageXObject pdImage = new PDImageXObject(document, byteStream, COSName.DCT_DECODE, image.getWidth(), image.getHeight(), @@ -150,10 +151,10 @@ private static PDImageXObject createJPEG(PDDocument document, Bitmap image, // alpha -> soft mask if (image.hasAlpha()) { - PDImageXObject xAlpha = createAlphaFromARGBImage(document, image); - - pdImage.getCOSStream().setItem(COSName.SMASK, xAlpha); + PDImage xAlpha = createAlphaFromARGBImage(document, image); + pdImage.getCOSObject().setItem(COSName.SMASK, xAlpha); } + return pdImage; } @@ -248,9 +249,10 @@ private static PDImageXObject prepareImageXObject(PDDocument document, width, height, bitsPerComponent, initColorSpace); } - // private static void encodeImageToJPEGStream(BufferedImage image, float quality, int dpi, - // OutputStream out) throws IOException - // { + private static void encodeImageToJPEGStream(Bitmap image, float quality, int dpi, + OutputStream out) throws IOException + { + image.compress(Bitmap.CompressFormat.JPEG, (int)(quality * 100), out); // // encode to JPEG // ImageOutputStream ios = null; // ImageWriter imageWriter = null; @@ -287,8 +289,8 @@ private static PDImageXObject prepareImageXObject(PDDocument document, // { // imageWriter.dispose(); // } - // } - // } TODO: PdfBox-Android + // } TODO: PdfBox-Android + } // returns a PDColorSpace for a given BufferedImage // private static PDColorSpace getColorSpaceFromAWT(Bitmap awtImage) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactory.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactory.java index c37be6e2e..895777ca3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactory.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/LosslessFactory.java @@ -43,7 +43,7 @@ public final class LosslessFactory private LosslessFactory() { } - + /** * Creates a new lossless encoded Image XObject from a Buffered Image. * @@ -120,7 +120,7 @@ public static PDImageXObject createFromImage(PDDocument document, Bitmap image) PDImage xAlpha = createAlphaFromARGBImage(document, image); // TODO: PdfBox-Android - simplify with extract alpha? if (xAlpha != null) { - pdImage.getCOSStream().setItem(COSName.SMASK, xAlpha); + pdImage.getCOSObject().setItem(COSName.SMASK, xAlpha); } return pdImage; @@ -140,10 +140,10 @@ public static PDImageXObject createFromImage(PDDocument document, Bitmap image) private static PDImageXObject createAlphaFromARGBImage(PDDocument document, Bitmap image) throws IOException { - // this implementation makes the assumption that the raster uses - // SinglePixelPackedSampleModel, i.e. the values can be used 1:1 for - // the stream. - // Sadly the type of the databuffer is TYPE_INT and not TYPE_BYTE. + // this implementation makes the assumption that the raster values can be used 1:1 for + // the stream. + // Sadly the type of the databuffer is usually TYPE_INT and not TYPE_BYTE so we can't just + // save it directly if (!image.hasAlpha()) { return null; @@ -198,6 +198,7 @@ private static PDImageXObject createAlphaFromARGBImage(PDDocument document, Bitm PDImageXObject pdImage = prepareImageXObject(document, bos.toByteArray(), image.getWidth(), image.getHeight(), bpc, PDDeviceGray.INSTANCE); + return pdImage; } @@ -246,9 +247,9 @@ private static PDImageXObject createAlphaFromARGBImage(PDDocument document, Bitm // } /** - * Create a PDImageXObject while making a decision whether not to + * Create a PDImageXObject while making a decision whether not to * compress, use Flate filter only, or Flate and LZW filters. - * + * * @param document The document. * @param byteArray array with data. * @param width the image width @@ -256,11 +257,10 @@ private static PDImageXObject createAlphaFromARGBImage(PDDocument document, Bitm * @param bitsPerComponent the bits per component * @param initColorSpace the color space * @return the newly created PDImageXObject with the data compressed. - * @throws IOException + * @throws IOException */ - private static PDImageXObject prepareImageXObject(PDDocument document, - byte [] byteArray, int width, int height, int bitsPerComponent, - PDColorSpace initColorSpace) throws IOException + private static PDImageXObject prepareImageXObject(PDDocument document, byte[] byteArray, + int width, int height, int bitsPerComponent, PDColorSpace initColorSpace) throws IOException { // Pre-size the output stream to half of the input ByteArrayOutputStream baos = new ByteArrayOutputStream(byteArray.length / 2); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImage.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImage.java index 721fbd7db..492f910e3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImage.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImage.java @@ -25,7 +25,6 @@ import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; -import com.tom_roush.pdfbox.pdmodel.common.PDStream; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; /** @@ -52,17 +51,11 @@ public interface PDImage extends COSObjectable */ Bitmap getStencilImage(Paint paint) throws IOException; - /** - * Returns a stream containing this image's data. Null for inline images. - * @throws IOException if the stream could not be read. - */ - PDStream getStream() throws IOException; - /** * Returns an InputStream containing the image data, irrespective of whether this is an * inline image or an image XObject. * - * @return Decoded stream\ + * @return Decoded stream * @throws IOException if the data could not be read. */ InputStream createInputStream() throws IOException; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImageXObject.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImageXObject.java index c103d1565..5e54c14a5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImageXObject.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDImageXObject.java @@ -22,6 +22,7 @@ import android.graphics.Paint; import android.util.Log; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -43,6 +44,8 @@ import com.tom_roush.pdfbox.pdmodel.graphics.PDXObject; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceGray; +import com.tom_roush.pdfbox.util.filetypedetector.FileType; +import com.tom_roush.pdfbox.util.filetypedetector.FileTypeDetector; /** * An Image XObject. @@ -94,7 +97,7 @@ public PDImageXObject(PDDocument document, InputStream encodedStream, COSBase co int width, int height, int bitsPerComponent, PDColorSpace initColorSpace) throws IOException { super(createRawStream(document, encodedStream), COSName.IMAGE); - getCOSStream().setItem(COSName.FILTER, cosFilter); + getCOSObject().setItem(COSName.FILTER, cosFilter); resources = null; colorSpace = null; setBitsPerComponent(bitsPerComponent); @@ -138,7 +141,7 @@ public PDImageXObject(PDStream stream, PDResources resources) throws IOException } /** - * Create a PDImageXObject from an image file, see {@link #createFromFile(File, PDDocument)} for + * Create a PDImageXObject from an image file, see {@link #createFromFileByExtension(File, PDDocument)} for * more details. * * @param imagePath the image file path. @@ -149,7 +152,7 @@ public PDImageXObject(PDStream stream, PDResources resources) throws IOException */ public static PDImageXObject createFromFile(String imagePath, PDDocument doc) throws IOException { - return createFromFile(new File(imagePath), doc); + return createFromFileByExtension(new File(imagePath), doc); } /** @@ -167,30 +170,21 @@ public static PDImageXObject createFromFile(String imagePath, PDDocument doc) th * PDImageXObject. * @throws IllegalArgumentException if the image type is not supported. */ - public static PDImageXObject createFromFile(File file, PDDocument doc) throws IOException + public static PDImageXObject createFromFileByExtension(File file, PDDocument doc) throws IOException { String name = file.getName(); int dot = file.getName().lastIndexOf('.'); if (dot == -1) { - throw new IOException("Image type not supported: " + name); + throw new IllegalArgumentException("Image type not supported: " + name); } String ext = name.substring(dot + 1).toLowerCase(); if ("jpg".equals(ext) || "jpeg".equals(ext)) { - InputStream stream = null; - try { - stream = new FileInputStream(file); - return JPEGFactory.createFromStream(doc, stream); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - // do nothing here - } - } - } + FileInputStream fis = new FileInputStream(file); + PDImageXObject imageXObject = JPEGFactory.createFromStream(doc, fis); + fis.close(); + return imageXObject; } if ("tif".equals(ext) || "tiff".equals(ext)) { @@ -204,6 +198,65 @@ public static PDImageXObject createFromFile(File file, PDDocument doc) throws IO throw new IOException("Image type not supported: " + name); } + /** + * Create a PDImageXObject from an image file. The file format is determined by the file + * content. The following file types are supported: jpg, jpeg, tif, tiff, gif, bmp and png. This + * is a convenience method that calls {@link JPEGFactory#createFromStream}, + * {@link CCITTFactory#createFromFile} or {@link ImageIO#read} combined with + * {@link LosslessFactory#createFromImage}. (The later can also be used to create a + * PDImageXObject from a BufferedImage). + * + * @param file the image file. + * @param doc the document that shall use this PDImageXObject. + * @return a PDImageXObject. + * @throws IOException if there is an error when reading the file or creating the + * PDImageXObject. + * @throws IllegalArgumentException if the image type is not supported. + */ + public static PDImageXObject createFromFileByContent(File file, PDDocument doc) throws IOException + { + FileInputStream fileInputStream = null; + BufferedInputStream bufferedInputStream = null; + FileType fileType = null; + try + { + fileInputStream = new FileInputStream(file); + bufferedInputStream = new BufferedInputStream(fileInputStream); + fileType = FileTypeDetector.detectFileType(bufferedInputStream); + } + catch (IOException e) + { + throw new IOException("Could not determine file type: " + file.getName(), e); + } + finally + { + IOUtils.closeQuietly(fileInputStream); + IOUtils.closeQuietly(bufferedInputStream); + } + if (fileType == null) + { + throw new IllegalArgumentException("Image type not supported: " + file.getName()); + } + + if (fileType.equals(FileType.JPEG)) + { + FileInputStream fis = new FileInputStream(file); + PDImageXObject imageXObject = JPEGFactory.createFromStream(doc, fis); + fis.close(); + return imageXObject; + } + if (fileType.equals(FileType.TIFF)) + { + return CCITTFactory.createFromFile(doc, file); + } + if (fileType.equals(FileType.BMP) || fileType.equals(FileType.GIF) || fileType.equals(FileType.PNG)) + { + Bitmap bim = BitmapFactory.decodeFile(file.getPath()); + return LosslessFactory.createFromImage(doc, bim); + } + throw new IllegalArgumentException("Image type not supported: " + file.getName()); + } + // repairs parameters using decode result private PDImageXObject(PDStream stream, PDResources resources, COSInputStream input) { @@ -215,7 +268,7 @@ private PDImageXObject(PDStream stream, PDResources resources, COSInputStream in // repairs parameters using decode result private static PDStream repair(PDStream stream, COSInputStream input) { - stream.getStream().addAll(input.getDecodeResult().getParameters()); + stream.getCOSObject().addAll(input.getDecodeResult().getParameters()); return stream; } @@ -225,7 +278,7 @@ private static PDStream repair(PDStream stream, COSInputStream input) */ public PDMetadata getMetadata() { - COSStream cosStream = (COSStream) getCOSStream().getDictionaryObject(COSName.METADATA); + COSStream cosStream = (COSStream) getCOSObject().getDictionaryObject(COSName.METADATA); if (cosStream != null) { return new PDMetadata(cosStream); @@ -239,7 +292,7 @@ public PDMetadata getMetadata() */ public void setMetadata(PDMetadata meta) { - getCOSStream().setItem(COSName.METADATA, meta); + getCOSObject().setItem(COSName.METADATA, meta); } /** @@ -248,7 +301,7 @@ public void setMetadata(PDMetadata meta) */ public int getStructParent() { - return getCOSStream().getInt(COSName.STRUCT_PARENT, 0); + return getCOSObject().getInt(COSName.STRUCT_PARENT, 0); } /** @@ -257,7 +310,7 @@ public int getStructParent() */ public void setStructParent(int key) { - getCOSStream().setInt(COSName.STRUCT_PARENT, key); + getCOSObject().setInt(COSName.STRUCT_PARENT, key); } /** @@ -287,9 +340,9 @@ public Bitmap getImage() throws IOException } else { - // explicit mask + // explicit mask - to be applied only if /ImageMask true PDImageXObject mask = getMask(); - if (mask != null) + if (mask != null && mask.isStencil()) { image = applyMask(image, mask.getOpaqueImage(), false); } @@ -337,59 +390,66 @@ private Bitmap applyMask(Bitmap image, Bitmap mask, boolean isSoft) int width = image.getWidth(); int height = image.getHeight(); + // scale mask to fit image, or image to fit mask, whichever is larger if (mask.getWidth() < width || mask.getHeight() < height) { - mask = Bitmap.createScaledBitmap(mask, width, height, true); + mask = scaleImage(mask, width, height); } else if (mask.getWidth() > width || mask.getHeight() > height) { width = mask.getWidth(); height = mask.getHeight(); - image = Bitmap.createScaledBitmap(image, width, height, true); + image = scaleImage(image, width, height); } // compose to ARGB Bitmap masked = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - // scale mask to fit image - if (mask.getWidth() != width || mask.getHeight() != height) - { - mask = Bitmap.createScaledBitmap(mask, width, height, true); - } - - int[] imagePixels = new int[width * height]; - image.getPixels(imagePixels, 0, width, 0, 0, width, height); + int[] src = new int[width * height]; + image.getPixels(src, 0, width, 0, 0, width, height); - int[] maskPixels = new int[width * height]; - mask.getPixels(maskPixels, 0, width, 0, 0, width, height); + int[] dest = new int[width * height]; + mask.getPixels(dest, 0, width, 0, 0, width, height); int alphaPixel; for (int pixelIdx = 0; pixelIdx < width * height; pixelIdx++) { - int color = imagePixels[pixelIdx]; + int rgb = src[pixelIdx]; // Greyscale, any rgb component should do - alphaPixel = Color.red(maskPixels[pixelIdx]); - if (!isSoft) + if (isSoft) + { + alphaPixel = Color.red(dest[pixelIdx]); + } + else { - alphaPixel = 255 - alphaPixel; + alphaPixel = 255 - Color.red(dest[pixelIdx]); } - maskPixels[pixelIdx] = Color.argb(alphaPixel, Color.red(color), Color.green(color), - Color.blue(color)); + dest[pixelIdx] = Color.argb(alphaPixel, Color.red(rgb), Color.green(rgb), + Color.blue(rgb)); } - masked.setPixels(maskPixels, 0, width, 0, 0, width, height); + masked.setPixels(dest, 0, width, 0, 0, width, height); return masked; } + /** + * High-quality image scaling. + */ + private Bitmap scaleImage(Bitmap image, int width, int height) + { + return Bitmap.createScaledBitmap(image, width, height, true); + } + /** * Returns the Mask Image XObject associated with this image, or null if there is none. * @return Mask Image XObject + * @#throws java.io.IOException */ public PDImageXObject getMask() throws IOException { - COSBase mask = getCOSStream().getDictionaryObject(COSName.MASK); + COSBase mask = getCOSObject().getDictionaryObject(COSName.MASK); if (mask instanceof COSArray) { // color key mask, no explicit mask to return @@ -397,7 +457,7 @@ public PDImageXObject getMask() throws IOException } else { - COSStream cosStream = (COSStream)getCOSStream().getDictionaryObject(COSName.MASK); + COSStream cosStream = (COSStream)getCOSObject().getDictionaryObject(COSName.MASK); if (cosStream != null) { // always DeviceGray @@ -413,7 +473,7 @@ public PDImageXObject getMask() throws IOException */ public COSArray getColorKeyMask() { - COSBase mask = getCOSStream().getDictionaryObject(COSName.MASK); + COSBase mask = getCOSObject().getDictionaryObject(COSName.MASK); if (mask instanceof COSArray) { return (COSArray)mask; @@ -424,10 +484,11 @@ public COSArray getColorKeyMask() /** * Returns the Soft Mask Image XObject associated with this image, or null if there is none. * @return the SMask Image XObject, or null. + * @throws java.io.IOException */ public PDImageXObject getSoftMask() throws IOException { - COSStream cosStream = (COSStream)getCOSStream().getDictionaryObject(COSName.SMASK); + COSStream cosStream = (COSStream)getCOSObject().getDictionaryObject(COSName.SMASK); if (cosStream != null) { // always DeviceGray @@ -445,14 +506,14 @@ public int getBitsPerComponent() } else { - return getCOSStream().getInt(COSName.BITS_PER_COMPONENT, COSName.BPC); + return getCOSObject().getInt(COSName.BITS_PER_COMPONENT, COSName.BPC); } } @Override public void setBitsPerComponent(int bpc) { - getCOSStream().setInt(COSName.BITS_PER_COMPONENT, bpc); + getCOSObject().setInt(COSName.BITS_PER_COMPONENT, bpc); } @Override @@ -460,7 +521,7 @@ public PDColorSpace getColorSpace() throws IOException { if (colorSpace == null) { - COSBase cosBase = getCOSStream().getDictionaryObject(COSName.COLORSPACE, COSName.CS); + COSBase cosBase = getCOSObject().getDictionaryObject(COSName.COLORSPACE, COSName.CS); if (cosBase != null) { colorSpace = PDColorSpace.create(cosBase, resources); @@ -488,67 +549,67 @@ public InputStream createInputStream() throws IOException @Override public InputStream createInputStream(List stopFilters) throws IOException { - return createInputStream(); + return getStream().createInputStream(stopFilters); } @Override public boolean isEmpty() { - return getStream().getStream().getLength() == 0; + return getStream().getCOSObject().getLength() == 0; } @Override public void setColorSpace(PDColorSpace cs) { - getCOSStream().setItem(COSName.COLORSPACE, cs != null ? cs.getCOSObject() : null); + getCOSObject().setItem(COSName.COLORSPACE, cs != null ? cs.getCOSObject() : null); } @Override public int getHeight() { - return getCOSStream().getInt(COSName.HEIGHT); + return getCOSObject().getInt(COSName.HEIGHT); } @Override public void setHeight(int h) { - getCOSStream().setInt(COSName.HEIGHT, h); + getCOSObject().setInt(COSName.HEIGHT, h); } @Override public int getWidth() { - return getCOSStream().getInt(COSName.WIDTH); + return getCOSObject().getInt(COSName.WIDTH); } @Override public void setWidth(int w) { - getCOSStream().setInt(COSName.WIDTH, w); + getCOSObject().setInt(COSName.WIDTH, w); } @Override public boolean getInterpolate() { - return getCOSStream().getBoolean(COSName.INTERPOLATE, false); + return getCOSObject().getBoolean(COSName.INTERPOLATE, false); } @Override public void setInterpolate(boolean value) { - getCOSStream().setBoolean(COSName.INTERPOLATE, value); + getCOSObject().setBoolean(COSName.INTERPOLATE, value); } @Override public void setDecode(COSArray decode) { - getCOSStream().setItem(COSName.DECODE, decode); + getCOSObject().setItem(COSName.DECODE, decode); } @Override public COSArray getDecode() { - COSBase decode = getCOSStream().getDictionaryObject(COSName.DECODE); + COSBase decode = getCOSObject().getDictionaryObject(COSName.DECODE); if (decode instanceof COSArray) { return (COSArray) decode; @@ -559,13 +620,13 @@ public COSArray getDecode() @Override public boolean isStencil() { - return getCOSStream().getBoolean(COSName.IMAGE_MASK, false); + return getCOSObject().getBoolean(COSName.IMAGE_MASK, false); } @Override public void setStencil(boolean isStencil) { - getCOSStream().setBoolean(COSName.IMAGE_MASK, isStencil); + getCOSObject().setBoolean(COSName.IMAGE_MASK, isStencil); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImage.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImage.java index 8e10d5a8d..f8f908d7e 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImage.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/PDInlineImage.java @@ -19,6 +19,12 @@ import android.graphics.Bitmap; import android.graphics.Paint; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; @@ -28,16 +34,9 @@ import com.tom_roush.pdfbox.filter.FilterFactory; import com.tom_roush.pdfbox.pdmodel.PDResources; import com.tom_roush.pdfbox.pdmodel.common.COSArrayList; -import com.tom_roush.pdfbox.pdmodel.common.PDStream; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceGray; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - /** * An inline image object which uses a special syntax to express the data for a * small image directly within the content stream. @@ -61,8 +60,9 @@ public final class PDInlineImage implements PDImage * Creates an inline image from the given parameters and data. * * @param parameters the image parameters - * @param data the image data - * @param resources the current resources + * @param data the image data + * @param resources the current resources + * @throws IOException if the stream cannot be decoded */ public PDInlineImage(COSDictionary parameters, byte[] data, PDResources resources) throws IOException @@ -127,16 +127,11 @@ public void setBitsPerComponent(int bitsPerComponent) @Override public PDColorSpace getColorSpace() throws IOException { - COSBase cs = parameters.getDictionaryObject(COSName.CS); - if (cs == null) - { - cs = parameters.getDictionaryObject(COSName.COLORSPACE); - } + COSBase cs = parameters.getDictionaryObject(COSName.CS, COSName.COLORSPACE); if (cs != null) { - // TODO: handling of abbreviated color space names belongs here, not in the factory - return PDColorSpace.create(cs, resources); + return createColorSpace(cs); } else if (isStencil()) { @@ -146,8 +141,52 @@ else if (isStencil()) else { // an image without a color space is always broken - throw new IOException("could not determine color space"); + throw new IOException("could not determine inline image color space"); + } + } + + // deliver the long name of a device colorspace, or the parameter + private COSBase toLongName(COSBase cs) + { + if (COSName.RGB.equals(cs)) + { + return COSName.DEVICERGB; + } + if (COSName.CMYK.equals(cs)) + { + return COSName.DEVICECMYK; + } + if (COSName.G.equals(cs)) + { + return COSName.DEVICEGRAY; + } + return cs; + } + + private PDColorSpace createColorSpace(COSBase cs) throws IOException + { + if (cs instanceof COSName) + { + return PDColorSpace.create(toLongName(cs), resources); + } + + if (cs instanceof COSArray && ((COSArray)cs).size() > 1) + { + COSArray srcArray = (COSArray)cs; + COSBase csType = srcArray.get(0); + if (COSName.I.equals(csType) || COSName.INDEXED.equals(csType)) + { + COSArray dstArray = new COSArray(); + dstArray.addAll(srcArray); + dstArray.set(0, COSName.INDEXED); + dstArray.set(1, toLongName(srcArray.get(1))); + return PDColorSpace.create(dstArray, resources); + } + + throw new IOException("Illegal type of inline image color space: " + csType); } + + throw new IOException("Illegal type of object for inline image color space: " + cs); } @Override @@ -254,15 +293,6 @@ public void setStencil(boolean isStencil) parameters.setBoolean(COSName.IM, isStencil); } - /** - * Always null, use {@link #createInputStream()} instead. - */ - @Override - public PDStream getStream() throws IOException - { - return null; - } - @Override public InputStream createInputStream() throws IOException { @@ -313,7 +343,7 @@ public Bitmap getImage() throws IOException return SampledImageReader.getRGBImage(this, getColorKeyMask()); } - // @Override TODO: PdfBox-Android + @Override public Bitmap getStencilImage(Paint paint) throws IOException { if (!isStencil()) @@ -344,6 +374,7 @@ public COSArray getColorKeyMask() * * @return The image suffix. */ + @Override public String getSuffix() { // TODO implement me diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/SampledImageReader.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/SampledImageReader.java index 6c4c85ac1..5259272e6 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/SampledImageReader.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/image/SampledImageReader.java @@ -118,7 +118,7 @@ public static Bitmap getRGBImage(PDImage pdImage, COSArray colorKey) throws IOEx final float[] defaultDecode = pdImage.getColorSpace().getDefaultDecode(8); if (pdImage.getSuffix() != null && pdImage.getSuffix().equals("jpg")) { - return BitmapFactory.decodeStream(pdImage.getStream().createInputStream()); + return BitmapFactory.decodeStream(pdImage.createInputStream()); } else if (bitsPerComponent == 8 && Arrays.equals(decode, defaultDecode) && colorKey == null) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentGroup.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentGroup.java index a07cf8bdd..527168ae2 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentGroup.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentGroup.java @@ -42,7 +42,7 @@ public PDOptionalContentGroup(String name) */ public PDOptionalContentGroup(COSDictionary dict) { - super(dict); + super(dict); if (!dict.getItem(COSName.TYPE).equals(COSName.OCG)) { throw new IllegalArgumentException( diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentProperties.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentProperties.java index af705091f..709ed9abb 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentProperties.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/optionalcontent/PDOptionalContentProperties.java @@ -36,7 +36,7 @@ public class PDOptionalContentProperties implements COSObjectable /** * Enumeration for the BaseState dictionary entry on the "D" dictionary. */ - public enum BaseState + public static enum BaseState { /** The "ON" value. */ @@ -48,7 +48,7 @@ public enum BaseState private final COSName name; - BaseState(COSName value) + private BaseState(COSName value) { this.name = value; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDAbstractPattern.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDAbstractPattern.java index a2a93fc25..75f50e27c 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDAbstractPattern.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDAbstractPattern.java @@ -82,8 +82,8 @@ public PDAbstractPattern(COSDictionary resourceDictionary) } /** - * Convert this standard java object to a COS object. - * @return The cos object that matches this Java object. + * This will get the underlying dictionary. + * @return The dictionary for these pattern resources. */ @Override public COSDictionary getCOSObject() @@ -123,7 +123,7 @@ public void setPatternType(int patternType) * @return The pattern type */ public abstract int getPatternType(); - + /** * Returns the pattern matrix, or the identity matrix is none is available. */ @@ -131,14 +131,14 @@ public Matrix getMatrix() { COSArray array = (COSArray) getCOSObject().getDictionaryObject(COSName.MATRIX); if (array != null) - { - return new Matrix(array); - } - else - { - // default value is the identity matrix - return new Matrix(); - } + { + return new Matrix(array); + } + else + { + // default value is the identity matrix + return new Matrix(); + } } /** @@ -147,13 +147,13 @@ public Matrix getMatrix() */ public void setMatrix(AffineTransform transform) { - COSArray matrix = new COSArray(); - double[] values = new double[6]; - transform.getMatrix(values); - for (double v : values) - { - matrix.add(new COSFloat((float)v)); - } + COSArray matrix = new COSArray(); + double[] values = new double[6]; + transform.getMatrix(values); + for (double v : values) + { + matrix.add(new COSFloat((float)v)); + } getCOSObject().setItem(COSName.MATRIX, matrix); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDShadingPattern.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDShadingPattern.java index 3daad6e87..86137f340 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDShadingPattern.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDShadingPattern.java @@ -75,9 +75,9 @@ public PDExtendedGraphicsState getExtendedGraphicsState() /** * This will set the external graphics state for this pattern. - * @param extendedGraphicsState The new external graphics state for this pattern. + * @param extendedGraphicsState The new extended graphics state for this pattern. */ - public void setExternalGraphicsState( PDExtendedGraphicsState extendedGraphicsState ) + public void setExtendedGraphicsState( PDExtendedGraphicsState extendedGraphicsState ) { this.extendedGraphicsState = extendedGraphicsState; getCOSObject().setItem(COSName.EXT_G_STATE, extendedGraphicsState); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDTilingPattern.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDTilingPattern.java index 98f9e1cbc..27a40eb41 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDTilingPattern.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/pattern/PDTilingPattern.java @@ -157,7 +157,12 @@ public PDStream getContentStream() @Override public InputStream getContents() throws IOException { - return ((COSStream) getCOSObject()).createInputStream(); + COSDictionary dict = getCOSObject(); + if (dict instanceof COSStream) + { + return ((COSStream)getCOSObject()).createInputStream(); + } + return null; } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Average.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Average.java deleted file mode 100644 index 4b50170b1..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Average.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.graphics.predictor; - -/** - * We can use raw on the right hand side of - * the decoding formula because it is already decoded. - * - * average(i,j) = raw(i,j) + (raw(i-1,j)+raw(i,j-1)/2 - * - * decoding - * - * raw(i,j) = avarage(i,j) - (raw(i-1,j)+raw(i,j-1)/2 - * - * @author xylifyx@yahoo.co.uk - * @version $Revision: 1.3 $ - */ -public class Average extends PredictorAlgorithm -{ - /** - * Not an optimal version, but close to the def. - * - * {@inheritDoc} - */ - public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - int bpl = getWidth() * getBpp(); - for (int x = 0; x < bpl; x++) - { - dest[x + destOffset] = (byte) (src[x + srcOffset] - ((leftPixel( - src, srcOffset, srcDy, x) + abovePixel(src, srcOffset, - srcDy, x)) >>> 2)); - } - } - - /** - * {@inheritDoc} - */ - public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - int bpl = getWidth() * getBpp(); - for (int x = 0; x < bpl; x++) - { - dest[x + destOffset] = (byte) (src[x + srcOffset] + ((leftPixel( - dest, destOffset, destDy, x) + abovePixel(dest, - destOffset, destDy, x)) >>> 2)); - } - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/None.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/None.java deleted file mode 100644 index 2dc2709af..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/None.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.graphics.predictor; - -/** - * The none algorithm. - * - * None(i,j) = Raw(i,j) - * - * Raw(i,j) = None(i,j) - * - * @author xylifyx@yahoo.co.uk - * @version $Revision: 1.3 $ - */ -public class None extends PredictorAlgorithm -{ - /** - * encode a byte array full of image data using the filter that this object - * implements. - * - * @param src - * buffer - * @param dest - * buffer - */ - public void encode(byte[] src, byte[] dest) - { - checkBufsiz(dest, src); - System.arraycopy(src,0,dest,0,src.length); - } - - /** - * decode a byte array full of image data using the filter that this object - * implements. - * - * @param src - * buffer - * @param dest - * buffer - */ - public void decode(byte[] src, byte[] dest) - { - System.arraycopy(src,0,dest,0,src.length); - } - - - - /** - * {@inheritDoc} - */ - public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - int bpl = getWidth() * getBpp(); - for (int x = 0; x < bpl; x++) - { - dest[destOffset + x] = src[srcOffset + x]; - } - } - - /** - * {@inheritDoc} - */ - public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - int bpl = getWidth() * getBpp(); - for (int x = 0; x < bpl; x++) - { - dest[destOffset + x] = src[srcOffset + x]; - } - } - -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Optimum.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Optimum.java deleted file mode 100644 index 55f778d8b..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Optimum.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.graphics.predictor; - -/** -* -* -* In an Uptimum encoded image, each line takes up width*bpp+1 bytes. The first -* byte holds a number that signifies which algorithm encoded the line. -* -* @author xylifyx@yahoo.co.uk -* @version $Revision: 1.1 $ -*/ -public class Optimum extends PredictorAlgorithm -{ - /** - * {@inheritDoc} - */ - public void checkBufsiz(byte[] filtered, byte[] raw) - { - if (filtered.length != (getWidth() * getBpp() + 1) * getHeight()) - { - - throw new IllegalArgumentException( - "filtered.length != (width*bpp + 1) * height, " - + filtered.length + " " - + (getWidth() * getBpp() + 1) * getHeight() - + "w,h,bpp=" + getWidth() + "," + getHeight() + "," - + getBpp()); - } - if (raw.length != getWidth() * getHeight() * getBpp()) - { - throw new IllegalArgumentException( - "raw.length != width * height * bpp, raw.length=" - + raw.length + " w,h,bpp=" + getWidth() + "," - + getHeight() + "," + getBpp()); - } - } - - /** - * {@inheritDoc} - */ - public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - throw new UnsupportedOperationException("encodeLine"); - } - - /** - * {@inheritDoc} - */ - public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - throw new UnsupportedOperationException("decodeLine"); - } - - /** - * {@inheritDoc} - */ - public void encode(byte[] src, byte[] dest) - { - checkBufsiz(dest, src); - throw new UnsupportedOperationException("encode"); - } - - /** - * Filter indexed by byte code. - */ - PredictorAlgorithm[] filter = { new None(), new Sub(), new Up(), new Average(), - new Paeth() }; - - /** - * {@inheritDoc} - */ - public void setBpp(int bpp) - { - super.setBpp(bpp); - for (int i = 0; i < filter.length; i++) - { - filter[i].setBpp(bpp); - } - } - /** - * {@inheritDoc} - */ - public void setHeight(int height) - { - super.setHeight(height); - for (int i = 0; i < filter.length; i++) - { - filter[i].setHeight(height); - } - } - - /** - * {@inheritDoc} - */ - public void setWidth(int width) - { - super.setWidth(width); - for (int i = 0; i < filter.length; i++) - { - filter[i].setWidth(width); - } - } - - /** - * {@inheritDoc} - */ - public void decode(byte[] src, byte[] dest) - { - checkBufsiz(src, dest); - int bpl = getWidth() * getBpp(); - int srcDy = bpl + 1; - for (int y = 0; y < getHeight(); y++) - { - PredictorAlgorithm f = filter[src[y * srcDy]]; - int srcOffset = y * srcDy + 1; - f.decodeLine(src, dest, srcDy, srcOffset, bpl, y * bpl); - } - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Paeth.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Paeth.java deleted file mode 100644 index 90801265a..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Paeth.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.graphics.predictor; - -/** - * From http://www.w3.org/TR/PNG-Filters.html: The Paeth filter computes a - * simple linear function of the three neighboring pixels (left, above, upper - * left), then chooses as predictor the neighboring pixel closest to the - * computed value. This technique is due to Alan W. Paeth [PAETH]. - * - * To compute the Paeth filter, apply the following formula to each byte of the - * scanline: - * - * Paeth(i,j) = Raw(i,j) - PaethPredictor(Raw(i-1,j), Raw(i,j-1), Raw(i-1,j-1)) - * - * To decode the Paeth filter - * - * Raw(i,j) = Paeth(i,j) - PaethPredictor(Raw(i-1,j), Raw(i,j-1), Raw(i-1,j-1)) - * - * @author xylifyx@yahoo.co.uk - * @version $Revision: 1.3 $ - */ -public class Paeth extends PredictorAlgorithm -{ - /** - * The paeth predictor function. - * - * This function is taken almost directly from the PNG definition on - * http://www.w3.org/TR/PNG-Filters.html - * - * @param a - * left - * @param b - * above - * @param c - * upper left - * @return The result of the paeth predictor. - */ - public int paethPredictor(int a, int b, int c) - { - int p = a + b - c; // initial estimate - int pa = Math.abs(p - a); // distances to a, b, c - int pb = Math.abs(p - b); - int pc = Math.abs(p - c); - // return nearest of a,b,c, - // breaking ties in order a,b,c. - if (pa <= pb && pa <= pc) - { - return a; - } - else if (pb <= pc) - { - return b; - } - else - { - return c; - } - } - - /** - * {@inheritDoc} - */ - public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - int bpl = getWidth() * getBpp(); - for (int x = 0; x < bpl; x++) - { - dest[x + destOffset] = (byte) (src[x + srcOffset] - paethPredictor( - leftPixel(src, srcOffset, srcDy, x), abovePixel(src, - srcOffset, srcDy, x), aboveLeftPixel(src, - srcOffset, srcDy, x))); - } - } - - /** - * {@inheritDoc} - */ - public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - int bpl = getWidth() * getBpp(); - for (int x = 0; x < bpl; x++) - { - dest[x + destOffset] = (byte) (src[x + srcOffset] + paethPredictor( - leftPixel(dest, destOffset, destDy, x), abovePixel(dest, - destOffset, destDy, x), aboveLeftPixel(dest, - destOffset, destDy, x))); - } - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/PredictorAlgorithm.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/PredictorAlgorithm.java deleted file mode 100644 index e98b594ed..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/PredictorAlgorithm.java +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.graphics.predictor; - -import java.util.Random; - -/** - * Implements different PNG predictor algorithms that is used in PDF files. - * - * @author xylifyx@yahoo.co.uk - * @version $Revision: 1.4 $ - * @see PNG Filters - */ -public abstract class PredictorAlgorithm -{ - private int width; - - private int height; - - private int bpp; - - /** - * check that buffer sizes matches width,height,bpp. This implementation is - * used by most of the filters, but not Uptimum. - * - * @param src The source buffer. - * @param dest The destination buffer. - */ - public void checkBufsiz(byte[] src, byte[] dest) - { - if (src.length != dest.length) - { - throw new IllegalArgumentException("src.length != dest.length"); - } - if (src.length != getWidth() * getHeight() * getBpp()) - { - throw new IllegalArgumentException( - "src.length != width * height * bpp"); - } - } - - /** - * encode line of pixel data in src from srcOffset and width*bpp bytes - * forward, put the decoded bytes into dest. - * - * @param src - * raw image data - * @param dest - * encoded data - * @param srcDy - * byte offset between lines - * @param srcOffset - * beginning of line data - * @param destDy - * byte offset between lines - * @param destOffset - * beginning of line data - */ - public abstract void encodeLine(byte[] src, byte[] dest, int srcDy, - int srcOffset, int destDy, int destOffset); - - /** - * decode line of pixel data in src from src_offset and width*bpp bytes - * forward, put the decoded bytes into dest. - * - * @param src - * encoded image data - * @param dest - * raw data - * @param srcDy - * byte offset between lines - * @param srcOffset - * beginning of line data - * @param destDy - * byte offset between lines - * @param destOffset - * beginning of line data - */ - public abstract void decodeLine(byte[] src, byte[] dest, int srcDy, - int srcOffset, int destDy, int destOffset); - - /** - * Simple command line program to test the algorithm. - * - * @param args The command line arguments. - */ - public static void main(String[] args) - { - Random rnd = new Random(); - int width = 5; - int height = 5; - int bpp = 3; - byte[] raw = new byte[width * height * bpp]; - rnd.nextBytes(raw); - System.out.println("raw: "); - dump(raw); - for (int i = 10; i < 15; i++) - { - byte[] decoded = new byte[width * height * bpp]; - byte[] encoded = new byte[width * height * bpp]; - - PredictorAlgorithm filter = PredictorAlgorithm.getFilter(i); - filter.setWidth(width); - filter.setHeight(height); - filter.setBpp(bpp); - filter.encode(raw, encoded); - filter.decode(encoded, decoded); - System.out.println(filter.getClass().getName()); - dump(decoded); - } - } - - /** - * Get the left pixel from the buffer. - * - * @param buf The buffer. - * @param offset The offset into the buffer. - * @param dy The dy value. - * @param x The x value. - * - * @return The left pixel. - */ - public int leftPixel(byte[] buf, int offset, int dy, int x) - { - return x >= getBpp() ? buf[offset + x - getBpp()] : 0; - } - - /** - * Get the above pixel from the buffer. - * - * @param buf The buffer. - * @param offset The offset into the buffer. - * @param dy The dy value. - * @param x The x value. - * - * @return The above pixel. - */ - public int abovePixel(byte[] buf, int offset, int dy, int x) - { - return offset >= dy ? buf[offset + x - dy] : 0; - } - - /** - * Get the above-left pixel from the buffer. - * - * @param buf The buffer. - * @param offset The offset into the buffer. - * @param dy The dy value. - * @param x The x value. - * - * @return The above-left pixel. - */ - public int aboveLeftPixel(byte[] buf, int offset, int dy, int x) - { - return offset >= dy && x >= getBpp() ? buf[offset + x - dy - getBpp()] - : 0; - } - - /** - * Simple helper to print out a buffer. - * - * @param raw The bytes to print out. - */ - private static void dump(byte[] raw) - { - for (int i = 0; i < raw.length; i++) - { - System.out.print(raw[i] + " "); - } - System.out.println(); - } - - /** - * @return Returns the bpp. - */ - public int getBpp() - { - return bpp; - } - - /** - * @param newBpp - * The bpp to set. - */ - public void setBpp(int newBpp) - { - bpp = newBpp; - } - - /** - * @return Returns the height. - */ - public int getHeight() - { - return height; - } - - /** - * @param newHeight - * The height to set. - */ - public void setHeight(int newHeight) - { - height = newHeight; - } - - /** - * @return Returns the width. - */ - public int getWidth() - { - return width; - } - - /** - * @param newWidth - * The width to set. - */ - public void setWidth(int newWidth) - { - this.width = newWidth; - } - - - /** - * encode a byte array full of image data using the filter that this object - * implements. - * - * @param src - * buffer - * @param dest - * buffer - */ - public void encode(byte[] src, byte[] dest) - { - checkBufsiz(dest, src); - int dy = getWidth()*getBpp(); - for (int y = 0; y < height; y++) - { - int yoffset = y * dy; - encodeLine(src, dest, dy, yoffset, dy, yoffset); - } - } - - /** - * decode a byte array full of image data using the filter that this object - * implements. - * - * @param src - * buffer - * @param dest - * buffer - */ - public void decode(byte[] src, byte[] dest) - { - checkBufsiz(src, dest); - int dy = width * bpp; - for (int y = 0; y < height; y++) - { - int yoffset = y * dy; - decodeLine(src, dest, dy, yoffset, dy, yoffset); - } - } - - /** - * @param predictor - *
      - *
    • 1 No prediction (the default value) - *
    • 2 TIFF Predictor 2 - *
    • 10 PNG prediction (on encoding, PNG None on all rows) - *
    • 11 PNG prediction (on encoding, PNG Sub on all rows) - *
    • 12 PNG prediction (on encoding, PNG Up on all rows) - *
    • 13 PNG prediction (on encoding, PNG Average on all rows) - *
    • 14 PNG prediction (on encoding, PNG Paeth on all rows) - *
    • 15 PNG prediction (on encoding, PNG optimum) - *
    - * - * @return The predictor class based on the predictor code. - */ - public static PredictorAlgorithm getFilter(int predictor) - { - PredictorAlgorithm filter; - switch (predictor) - { - case 10: - filter = new None(); - break; - case 11: - filter = new Sub(); - break; - case 12: - filter = new Up(); - break; - case 13: - filter = new Average(); - break; - case 14: - filter = new Paeth(); - break; - case 15: - filter = new Optimum(); - break; - default: - filter = new None(); - } - return filter; - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Sub.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Sub.java deleted file mode 100644 index cb87e5484..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Sub.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.graphics.predictor; - -/** - * The sub algorithm. - * - * Sub(i,j) = Raw(i,j) - Raw(i-1,j) - * - * Raw(i,j) = Sub(i,j) + Raw(i-1,j) - * - * @author xylifyx@yahoo.co.uk - * @version $Revision: 1.3 $ - */ -public class Sub extends PredictorAlgorithm -{ - /** - * {@inheritDoc} - */ - public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - int bpl = getWidth()*getBpp(); - int bpp = getBpp(); - // case: x < bpp - for (int x = 0; x < bpl && x < bpp; x++) - { - dest[x + destOffset] = src[x + srcOffset]; - } - // otherwise - for (int x = getBpp(); x < bpl; x++) - { - dest[x + destOffset] = (byte) (src[x + srcOffset] - src[x - + srcOffset - bpp]); - } - } - - /** - * {@inheritDoc} - */ - public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - int bpl = getWidth()*getBpp(); - int bpp = getBpp(); - // case: x < bpp - for (int x = 0; x < bpl && x < bpp; x++) - { - dest[x + destOffset] = src[x + srcOffset]; - } - // otherwise - for (int x = getBpp(); x < bpl; x++) - { - dest[x + destOffset] = (byte) (src[x + srcOffset] + dest[x - + destOffset - bpp]); - } - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Up.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Up.java deleted file mode 100644 index 85ffbd827..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/predictor/Up.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.graphics.predictor; - -/** - * The up algorithm. - * - * Up(i,j) = Raw(i,j) - Raw(i,j-1) - * - * Raw(i,j) = Up(i,j) + Raw(i,j-1) - * - * @author xylifyx@yahoo.co.uk - * @version $Revision: 1.3 $ - */ -public class Up extends PredictorAlgorithm -{ - /** - * {@inheritDoc} - */ - public void encodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - int bpl = getWidth()*getBpp(); - // case: y = 0; - if (srcOffset - srcDy < 0) - { - if (0 < getHeight()) - { - for (int x = 0; x < bpl; x++) - { - dest[destOffset + x] = src[srcOffset + x]; - } - } - } - else - { - for (int x = 0; x < bpl; x++) - { - dest[destOffset + x] = (byte) (src[srcOffset + x] - src[srcOffset - + x - srcDy]); - } - } - } - - /** - * {@inheritDoc} - */ - public void decodeLine(byte[] src, byte[] dest, int srcDy, int srcOffset, - int destDy, int destOffset) - { - // case: y = 0; - int bpl = getWidth()*getBpp(); - if (destOffset - destDy < 0) - { - if (0 < getHeight()) - { - for (int x = 0; x < bpl; x++) - { - dest[destOffset + x] = src[srcOffset + x]; - } - } - } - else - { - for (int x = 0; x < bpl; x++) - { - dest[destOffset + x] = (byte) (src[srcOffset + x] + dest[destOffset - + x - destDy]); - } - } - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShading.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShading.java index e193a2439..0adae1a1d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShading.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShading.java @@ -236,15 +236,15 @@ public PDColorSpace getColorSpace() throws IOException */ public void setColorSpace(PDColorSpace colorSpace) { - this.colorSpace = colorSpace; - if (colorSpace != null) - { - dictionary.setItem(COSName.COLORSPACE, colorSpace.getCOSObject()); - } - else - { - dictionary.removeItem(COSName.COLORSPACE); - } + this.colorSpace = colorSpace; + if (colorSpace != null) + { + dictionary.setItem(COSName.COLORSPACE, colorSpace.getCOSObject()); + } + else + { + dictionary.removeItem(COSName.COLORSPACE); + } } /** @@ -315,7 +315,7 @@ public void setFunction(COSArray newFunctions) * This will return the function used to convert the color values. * * @return the function - * @throws java.io.IOException if we were not unable to create the function + * @throws java.io.IOException if we were not able to create the function */ public PDFunction getFunction() throws IOException { @@ -374,7 +374,7 @@ else if (functionObject instanceof COSArray) */ public float[] evalFunction(float inputValue) throws IOException { - return evalFunction(new float[] { inputValue }); + return evalFunction(new float[] { inputValue }); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShadingType1.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShadingType1.java index e6c67a50e..d124ad71c 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShadingType1.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShadingType1.java @@ -54,14 +54,14 @@ public Matrix getMatrix() { COSArray array = (COSArray) getCOSObject().getDictionaryObject(COSName.MATRIX); if (array != null) - { - return new Matrix(array); - } - else - { - // identity matrix is the default - return new Matrix(); - } + { + return new Matrix(array); + } + else + { + // identity matrix is the default + return new Matrix(); + } } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShadingType2.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShadingType2.java index a32b81085..d041a0fe0 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShadingType2.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDShadingType2.java @@ -67,7 +67,7 @@ public COSArray getExtend() public void setExtend(COSArray newExtend) { extend = newExtend; - getCOSObject().setItem(COSName.EXTEND, newExtend); getCOSObject().setItem(COSName.EXTEND, newExtend); + getCOSObject().setItem(COSName.EXTEND, newExtend); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDTriangleBasedShadingType.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDTriangleBasedShadingType.java index ced14c62b..5c4a63ae3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDTriangleBasedShadingType.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/shading/PDTriangleBasedShadingType.java @@ -22,8 +22,7 @@ import com.tom_roush.pdfbox.cos.COSDictionary; /** - * Intermediate class extended by the shading types 4,5,6 and 7 that contains the common methods - * used by those classes + * Common resources for shading types 4,5,6 and 7 */ abstract class PDTriangleBasedShadingType extends PDShading { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDExtendedGraphicsState.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDExtendedGraphicsState.java index 3265db145..a7db69d00 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDExtendedGraphicsState.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDExtendedGraphicsState.java @@ -143,6 +143,20 @@ else if( key.equals( COSName.BM ) ) { gs.setBlendMode( getBlendMode() ); } + else if (key.equals(COSName.TR)) + { + if (dict.containsKey(COSName.TR2)) + { + // "If both TR and TR2 are present in the same graphics state parameter dictionary, + // TR2 shall take precedence." + continue; + } + gs.setTransfer(getTransfer()); + } + else if (key.equals(COSName.TR2)) + { + gs.setTransfer(getTransfer2()); + } } } @@ -600,4 +614,68 @@ private void setFloatItem( COSName key, Float value ) dict.setItem( key, new COSFloat( value) ); } } + + /** + * This will get the transfer function of the /TR dictionary. + * + * @return The transfer function. According to the PDF specification, this is either a single + * function (which applies to all process colorants) or an array of four functions (which apply + * to the process colorants individually). The name Identity may be used to represent the + * identity function. + */ + public COSBase getTransfer() + { + COSBase base = dict.getDictionaryObject(COSName.TR); + if (base instanceof COSArray && ((COSArray)base).size() != 4) + { + return null; + } + return base; + } + + /** + * This will set the transfer function of the /TR dictionary. + * + * @param transfer The transfer function. According to the PDF specification, this is either a + * single function (which applies to all process colorants) or an array of four functions (which + * apply to the process colorants individually). The name Identity may be used to represent the + * identity function. + */ + public void setTransfer(COSBase transfer) + { + dict.setItem(COSName.TR, transfer); + } + + /** + * This will get the transfer function of the /TR2 dictionary. + * + * @return The transfer function. According to the PDF specification, this is either a single + * function (which applies to all process colorants) or an array of four functions (which apply + * to the process colorants individually). The name Identity may be used to represent the + * identity function, and the name Default denotes the transfer function that was in effect at + * the start of the page. + */ + public COSBase getTransfer2() + { + COSBase base = dict.getDictionaryObject(COSName.TR2); + if (base instanceof COSArray && ((COSArray)base).size() != 4) + { + return null; + } + return base; + } + + /** + * This will set the transfer function of the /TR2 dictionary. + * + * @param transfer2 The transfer function. According to the PDF specification, this is either a + * single function (which applies to all process colorants) or an array of four functions (which + * apply to the process colorants individually). The name Identity may be used to represent the + * identity function, and the name Default denotes the transfer function that was in effect at + * the start of the page. + */ + public void setTransfer2(COSBase transfer2) + { + dict.setItem(COSName.TR2, transfer2); + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDGraphicsState.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDGraphicsState.java index 8b6ebfe99..f18e6bfb6 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDGraphicsState.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDGraphicsState.java @@ -22,6 +22,7 @@ import android.graphics.RectF; import android.graphics.Region; +import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; import com.tom_roush.pdfbox.pdmodel.graphics.PDLineDashPattern; import com.tom_roush.pdfbox.pdmodel.graphics.blend.BlendMode; @@ -63,7 +64,7 @@ public class PDGraphicsState implements Cloneable private double overprintMode = 0; //black generation //undercolor removal - //transfer + private COSBase transfer = null; //halftone private double flatness = 1.0; private double smoothness = 0; @@ -75,12 +76,12 @@ public class PDGraphicsState implements Cloneable public PDGraphicsState(PDRectangle page) { // clippingPath = new Area(new GeneralPath(page.toGeneralPath()));TODO: PdfBox-Android - RectF bounds = new RectF(); - page.toGeneralPath().computeBounds(bounds, true); - clippingPath = new Region(); - Rect boundsRounded = new Rect(); - bounds.round(boundsRounded); - clippingPath.setPath(page.toGeneralPath(), new Region(boundsRounded)); + RectF bounds = new RectF(); + page.toGeneralPath().computeBounds(bounds, true); + clippingPath = new Region(); + Rect boundsRounded = new Rect(); + bounds.round(boundsRounded); + clippingPath.setPath(page.toGeneralPath(), new Region(boundsRounded)); } /** @@ -429,7 +430,7 @@ public void setLineDashPattern(PDLineDashPattern value) /** * This will get the rendering intent. * - * @see com.tom_roush.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState + * @see PDExtendedGraphicsState * * @return The rendering intent */ @@ -456,7 +457,7 @@ public PDGraphicsState clone() PDGraphicsState clone = (PDGraphicsState)super.clone(); clone.textState = textState.clone(); clone.currentTransformationMatrix = currentTransformationMatrix.clone(); - clone.strokingColor = strokingColor; // immutable + clone.strokingColor = strokingColor; // immutable clone.nonStrokingColor = nonStrokingColor; // immutable clone.lineDashPattern = lineDashPattern; // immutable clone.clippingPath = clippingPath; // not cloned, see intersectClippingPath @@ -477,7 +478,7 @@ public PDGraphicsState clone() */ public PDColor getStrokingColor() { - return strokingColor; + return strokingColor; } /** @@ -487,7 +488,7 @@ public PDColor getStrokingColor() */ public void setStrokingColor(PDColor color) { - strokingColor = color; + strokingColor = color; } /** @@ -497,7 +498,7 @@ public void setStrokingColor(PDColor color) */ public PDColor getNonStrokingColor() { - return nonStrokingColor; + return nonStrokingColor; } /** @@ -507,7 +508,7 @@ public PDColor getNonStrokingColor() */ public void setNonStrokingColor(PDColor color) { - nonStrokingColor = color; + nonStrokingColor = color; } /** @@ -517,7 +518,7 @@ public void setNonStrokingColor(PDColor color) */ public PDColorSpace getStrokingColorSpace() { - return strokingColorSpace; + return strokingColorSpace; } /** @@ -527,7 +528,7 @@ public PDColorSpace getStrokingColorSpace() */ public void setStrokingColorSpace(PDColorSpace colorSpace) { - strokingColorSpace = colorSpace; + strokingColorSpace = colorSpace; } /** @@ -537,7 +538,7 @@ public void setStrokingColorSpace(PDColorSpace colorSpace) */ public PDColorSpace getNonStrokingColorSpace() { - return nonStrokingColorSpace; + return nonStrokingColorSpace; } /** @@ -547,7 +548,7 @@ public PDColorSpace getNonStrokingColorSpace() */ public void setNonStrokingColorSpace(PDColorSpace colorSpace) { - nonStrokingColorSpace = colorSpace; + nonStrokingColorSpace = colorSpace; } /** @@ -556,13 +557,14 @@ public void setNonStrokingColorSpace(PDColorSpace colorSpace) */ public void intersectClippingPath(Path path) { - RectF bounds = new RectF(); - path.computeBounds(bounds, true); - Region r = new Region(); - Rect boundsRounded = new Rect(); - bounds.round(boundsRounded); - r.setPath(path, new Region(boundsRounded)); + RectF bounds = new RectF(); + path.computeBounds(bounds, true); + Region r = new Region(); + Rect boundsRounded = new Rect(); + bounds.round(boundsRounded); + r.setPath(path, new Region(boundsRounded)); intersectClippingPath(r); + // TODO: PdfBox-Android Verify correct behavior } /** @@ -574,12 +576,12 @@ public void intersectClippingPath(Region area) // lazy cloning of clipping path for performance if (!isClippingPathDirty) { - // deep copy (can't use clone() as it performs only a shallow copy) - Region cloned = new Region(area); + // deep copy (can't use clone() as it performs only a shallow copy) + Region cloned = new Region(area); // cloned.add(clippingPath); - clippingPath = cloned; + clippingPath = cloned; - isClippingPathDirty = true; + isClippingPathDirty = true; } // intersection as usual @@ -605,4 +607,32 @@ public Region getCurrentClippingPath() // { // return BlendComposite.getInstance(blendMode, (float) nonStrokingAlphaConstants); // }TODO: PdfBox-Android + + /** + * This will get the transfer function. + * + * @return The transfer function. According to the PDF specification, this is either a single + * function (which applies to all process colorants) or an array of four functions (which apply + * to the process colorants individually). The name Identity may be used to represent the + * identity function, and the name Default denotes the transfer function that was in effect at + * the start of the page. + */ + public COSBase getTransfer() + { + return transfer; + } + + /** + * This will set the transfer function. + * + * @param transfer The transfer function. According to the PDF specification, this is either a + * single function (which applies to all process colorants) or an array of four functions (which + * apply to the process colorants individually). The name Identity may be used to represent the + * identity function, and the name Default denotes the transfer function that was in effect at + * the start of the page. + */ + public void setTransfer(COSBase transfer) + { + this.transfer = transfer; + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDSoftMask.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDSoftMask.java index cc5f9a727..52db09cca 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDSoftMask.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/PDSoftMask.java @@ -27,7 +27,7 @@ import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; import com.tom_roush.pdfbox.pdmodel.common.function.PDFunction; import com.tom_roush.pdfbox.pdmodel.graphics.PDXObject; -import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; +import com.tom_roush.pdfbox.pdmodel.graphics.form.PDTransparencyGroup; /** * Soft mask. @@ -51,7 +51,7 @@ public static PDSoftMask create(COSBase dictionary) } else { - Log.w("PdfBox-Android", "Invalid SMask " + dictionary); + Log.w("PdfBox-Android", "Invalid SMask " + dictionary); return null; } } @@ -61,14 +61,14 @@ else if (dictionary instanceof COSDictionary) } else { - Log.w("PdfBox-Android", "Invalid SMask " + dictionary); + Log.w("PdfBox-Android", "Invalid SMask " + dictionary); return null; } } - private COSDictionary dictionary; + private final COSDictionary dictionary; private COSName subType = null; - private PDFormXObject group = null; + private PDTransparencyGroup group = null; private COSArray backdropColor = null; private PDFunction transferFunction = null; @@ -105,14 +105,14 @@ public COSName getSubType() * @return form containing the transparency group * @throws IOException */ - public PDFormXObject getGroup() throws IOException + public PDTransparencyGroup getGroup() throws IOException { if (group == null) { COSBase cosGroup = getCOSObject().getDictionaryObject(COSName.G); if (cosGroup != null) { - group = (PDFormXObject) PDXObject.createXObject(cosGroup, null); + group = (PDTransparencyGroup)PDXObject.createXObject(cosGroup, null); } } return group; @@ -132,6 +132,7 @@ public COSArray getBackdropColor() /** * Returns the transfer function. + * @throws IOException If we are unable to create the PDFunction object. */ public PDFunction getTransferFunction() throws IOException { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/RenderingIntent.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/RenderingIntent.java index 5f58f2e3a..9be3eda41 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/RenderingIntent.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/graphics/state/RenderingIntent.java @@ -39,7 +39,7 @@ public enum RenderingIntent SATURATION("Saturation"), /** - * Perceptual + * Perceptual. */ PERCEPTUAL("Perceptual"); @@ -61,7 +61,9 @@ else if (value.equals("Perceptual")) { return PERCEPTUAL; } - throw new IllegalArgumentException(value); + // "If a conforming reader does not recognize the specified name, + // it shall use the RelativeColorimetric intent by default." + return RELATIVE_COLORIMETRIC; } private final String value; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionFactory.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionFactory.java index 90a2366fb..63a9ebfc6 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionFactory.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionFactory.java @@ -17,6 +17,7 @@ package com.tom_roush.pdfbox.pdmodel.interactive.action; import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; /** * This class will take a dictionary and determine which type of action to create. @@ -46,7 +47,7 @@ public static PDAction createAction( COSDictionary action ) PDAction retval = null; if( action != null ) { - String type = action.getNameAsString( "S" ); + String type = action.getNameAsString(COSName.S); if( PDActionJavaScript.SUB_TYPE.equals( type ) ) { retval = new PDActionJavaScript( action ); @@ -69,7 +70,35 @@ else if( PDActionURI.SUB_TYPE.equals( type ) ) } else if (PDActionNamed.SUB_TYPE.equals(type)) { - retval = new PDActionNamed(action); + retval = new PDActionNamed(action); + } + else if (PDActionSound.SUB_TYPE.equals(type)) + { + retval = new PDActionSound(action); + } + else if (PDActionMovie.SUB_TYPE.equals(type)) + { + retval = new PDActionMovie(action); + } + else if (PDActionImportData.SUB_TYPE.equals(type)) + { + retval = new PDActionImportData(action); + } + else if (PDActionResetForm.SUB_TYPE.equals(type)) + { + retval = new PDActionResetForm(action); + } + else if (PDActionHide.SUB_TYPE.equals(type)) + { + retval = new PDActionHide(action); + } + else if (PDActionSubmitForm.SUB_TYPE.equals(type)) + { + retval = new PDActionSubmitForm(action); + } + else if (PDActionThread.SUB_TYPE.equals(type)) + { + retval = new PDActionThread(action); } } return retval; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionHide.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionHide.java new file mode 100644 index 000000000..3e5a217c0 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionHide.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.interactive.action; + +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSBoolean; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; + +/** + * This represents a thread action that can be executed in a PDF document. + * + * @author Evgeniy Muravitskiy + */ +public class PDActionHide extends PDAction +{ + + /** + * This type of action this object represents. + */ + public static final String SUB_TYPE = "Hide"; + + /** + * Default Constructor + */ + public PDActionHide() + { + setSubType(SUB_TYPE); + } + + /** + * Constructor + * + * @param a the action dictionary + */ + public PDActionHide(COSDictionary a) + { + super(a); + } + + /** + * The annotation or annotations to be hidden or shown + * + * @return The T entry of the specific thread action dictionary. + */ + public COSBase getT() + { + // Dictionary, String or Array + return this.action.getDictionaryObject(COSName.T); + } + + /** + * @param t annotation or annotations + */ + public void setT(COSBase t) + { + this.action.setItem(COSName.T, t); + } + + /** + * A flag indicating whether to hide the annotation or show it + * + * @return true if annotation is hidden + */ + public boolean getH() + { + return this.action.getBoolean(COSName.H, true); + } + + /** + * @param h hide flag + */ + public void setH(boolean h) + { + this.action.setItem(COSName.H, COSBoolean.getBoolean(h)); + } + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionImportData.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionImportData.java new file mode 100644 index 000000000..0156fabf5 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionImportData.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tom_roush.pdfbox.pdmodel.interactive.action; + +import java.io.IOException; + +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.common.filespecification.PDFileSpecification; + +/** + * @author Timur Kamalov + */ +public class PDActionImportData extends PDAction +{ + + /** + * This type of action this object represents. + */ + public static final String SUB_TYPE = "ImportData"; + + /** + * Default constructor. + */ + public PDActionImportData() + { + action = new COSDictionary(); + setSubType(SUB_TYPE); + } + + /** + * Constructor. + * + * @param a The action dictionary. + */ + public PDActionImportData(COSDictionary a) + { + super(a); + } + + /** + * This will get the file in which the destination is located. + * + * @return The F entry of the specific Submit-From action dictionary. + * @throws IOException If there is an error creating the file spec. + */ + public PDFileSpecification getFile() throws IOException + { + return PDFileSpecification.createFS(action.getDictionaryObject(COSName.F)); + } + + /** + * This will set the file in which the destination is located. + * + * @param fs The file specification. + */ + public void setFile(PDFileSpecification fs) + { + action.setItem(COSName.F, fs); + } + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionMovie.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionMovie.java new file mode 100644 index 000000000..0e693c3d4 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionMovie.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tom_roush.pdfbox.pdmodel.interactive.action; + +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; + +/** + * @author Timur Kamalov + */ +public class PDActionMovie extends PDAction +{ + + /** + * This type of action this object represents. + */ + public static final String SUB_TYPE = "Movie"; + + /** + * Default constructor. + */ + public PDActionMovie() + { + action = new COSDictionary(); + setSubType(SUB_TYPE); + } + + /** + * Constructor. + * + * @param a The action dictionary. + */ + public PDActionMovie(COSDictionary a) + { + super(a); + } + + /** + * This will get the type of action that the actions dictionary describes. It must be Movie for + * a Movie action. + * + * @return The S entry of the specific Movie action dictionary. + */ + public String getS() + { + return action.getNameAsString(COSName.S); + } + + /** + * This will set the type of action that the actions dictionary describes. It must be Movie for + * a Movie action. + * + * @param s The Movie action. + */ + public void setS(String s) + { + action.setName(COSName.S, s); + } + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionRemoteGoTo.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionRemoteGoTo.java index 892861e30..cfb6918e3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionRemoteGoTo.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionRemoteGoTo.java @@ -20,7 +20,7 @@ import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; - +import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.common.filespecification.PDFileSpecification; /** @@ -63,7 +63,7 @@ public PDActionRemoteGoTo( COSDictionary a ) */ public String getS() { - return action.getNameAsString( "S" ); + return action.getNameAsString(COSName.S); } /** @@ -74,7 +74,7 @@ public String getS() */ public void setS( String s ) { - action.setName( "S", s ); + action.setName(COSName.S, s); } /** @@ -86,7 +86,7 @@ public void setS( String s ) */ public PDFileSpecification getFile() throws IOException { - return PDFileSpecification.createFS( action.getDictionaryObject( "F" ) ); + return PDFileSpecification.createFS(action.getDictionaryObject(COSName.F)); } /** @@ -96,7 +96,7 @@ public PDFileSpecification getFile() throws IOException */ public void setFile( PDFileSpecification fs ) { - action.setItem( "F", fs ); + action.setItem(COSName.F, fs); } /** @@ -112,7 +112,7 @@ public void setFile( PDFileSpecification fs ) // Array or String. public COSBase getD() { - return action.getDictionaryObject( "D" ); + return action.getDictionaryObject(COSName.D); } /** @@ -128,7 +128,7 @@ public COSBase getD() // In case the value is an array. public void setD( COSBase d ) { - action.setItem( "D", d ); + action.setItem(COSName.D, d); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionResetForm.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionResetForm.java new file mode 100644 index 000000000..81094bfe8 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionResetForm.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tom_roush.pdfbox.pdmodel.interactive.action; + +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; + +/** + * @author Timur Kamalov + */ +public class PDActionResetForm extends PDAction +{ + + /** + * This type of action this object represents. + */ + public static final String SUB_TYPE = "ResetForm"; + + /** + * Default constructor. + */ + public PDActionResetForm() + { + action = new COSDictionary(); + setSubType(SUB_TYPE); + } + + /** + * Constructor. + * + * @param a The action dictionary. + */ + public PDActionResetForm(COSDictionary a) + { + super(a); + } + + /** + * An array identifying which fields to include in the submission or which to exclude, depending + * on the setting of the Include/Exclude flag in the Flags entry + * + * @return the array of fields + */ + public COSArray getFields() + { + COSBase retval = this.action.getDictionaryObject(COSName.FIELDS); + return retval instanceof COSArray ? (COSArray)retval : null; + } + + /** + * @param array the array of fields + */ + public void setFields(COSArray array) + { + this.action.setItem(COSName.FIELDS, array); + } + + /** + * A set of flags specifying various characteristics of the action + * + * @return the flags + */ + public int getFlags() + { + return this.action.getInt(COSName.FLAGS, 0); + } + + /** + * @param flags the flags + */ + public void setFlags(int flags) + { + this.action.setInt(COSName.FLAGS, flags); + } + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionSound.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionSound.java new file mode 100644 index 000000000..04ffcae6c --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionSound.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tom_roush.pdfbox.pdmodel.interactive.action; + +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; + +/** + * This represents a Sound action that can be executed in a PDF document + * + * @author Timur Kamalov + */ +public class PDActionSound extends PDAction +{ + + /** + * This type of action this object represents. + */ + public static final String SUB_TYPE = "Sound"; + + /** + * Default constructor. + */ + public PDActionSound() + { + action = new COSDictionary(); + setSubType(SUB_TYPE); + } + + /** + * Constructor. + * + * @param a The action dictionary. + */ + public PDActionSound(COSDictionary a) + { + super(a); + } + + /** + * This will get the type of action that the actions dictionary describes. It must be Sound for + * a Sound action. + * + * @return The S entry of the specific Sound action dictionary. + */ + public String getS() + { + return action.getNameAsString(COSName.S); + } + + /** + * This will set the type of action that the actions dictionary describes. It must be Sound for + * a Sound action. + * + * @param s The Sound action. + */ + public void setS(String s) + { + action.setName(COSName.S, s); + } + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionSubmitForm.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionSubmitForm.java new file mode 100644 index 000000000..06f2ecdbf --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionSubmitForm.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tom_roush.pdfbox.pdmodel.interactive.action; + +import java.io.IOException; + +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.common.filespecification.PDFileSpecification; + +/** + * This represents a Submit-Form action that can be executed in a PDF document. + * + * @author Evgeniy Muravitskiy + */ +public class PDActionSubmitForm extends PDAction +{ + + /** + * This type of action this object represents. + */ + public static final String SUB_TYPE = "SubmitForm"; + + /** + * Default Constructor + */ + public PDActionSubmitForm() + { + setSubType(SUB_TYPE); + } + + /** + * Constructor + * + * @param a the action dictionary + */ + public PDActionSubmitForm(COSDictionary a) + { + super(a); + } + + /** + * This will get the file in which the destination is located. + * + * @return The F entry of the specific Submit-From action dictionary. + * @throws IOException If there is an error creating the file spec. + */ + public PDFileSpecification getFile() throws IOException + { + return PDFileSpecification.createFS(action.getDictionaryObject(COSName.F)); + } + + /** + * This will set the file in which the destination is located. + * + * @param fs The file specification. + */ + public void setFile(PDFileSpecification fs) + { + action.setItem(COSName.F, fs); + } + + /** + * An array identifying which fields to include in the submission or which to exclude, depending + * on the setting of the Include/Exclude flag in the Flags entry + * + * @return the array of fields + */ + public COSArray getFields() + { + COSBase retval = this.action.getDictionaryObject(COSName.FIELDS); + return retval instanceof COSArray ? (COSArray)retval : null; + } + + /** + * @param array the array of fields + */ + public void setFields(COSArray array) + { + this.action.setItem(COSName.FIELDS, array); + } + + /** + * A set of flags specifying various characteristics of the action + * + * @return the flags + */ + public int getFlags() + { + return this.action.getInt(COSName.FLAGS, 0); + } + + /** + * @param flags the flags + */ + public void setFlags(int flags) + { + this.action.setInt(COSName.FLAGS, flags); + } + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionThread.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionThread.java new file mode 100644 index 000000000..5fc4d8b3a --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionThread.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tom_roush.pdfbox.pdmodel.interactive.action; + +import java.io.IOException; + +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.common.filespecification.PDFileSpecification; + +/** + * This represents a thread action that can be executed in a PDF document. + * + * @author Evgeniy Muravitskiy + */ +public class PDActionThread extends PDAction +{ + + /** + * This type of action this object represents. + */ + public static final String SUB_TYPE = "Thread"; + + /** + * Default constructor. + */ + public PDActionThread() + { + setSubType(SUB_TYPE); + } + + /** + * Constructor. + * + * @param a The action dictionary. + */ + public PDActionThread(COSDictionary a) + { + super(a); + } + + /** + * @return The D entry of the specific thread action dictionary. + */ + // Dictionary, Integer or String. + public COSBase getD() + { + return action.getDictionaryObject(COSName.D); + } + + /** + * @param d The destination. + */ + public void setD(COSBase d) + { + action.setItem(COSName.D, d); + } + + /** + * This will get the file in which the destination is located. + * + * @return The F entry of the specific thread action dictionary. + * @throws IOException If there is an error creating the file spec. + */ + public PDFileSpecification getFile() throws IOException + { + return PDFileSpecification.createFS(action.getDictionaryObject(COSName.F)); + } + + /** + * This will set the file in which the destination is located. + * + * @param fs The file specification. + */ + public void setFile(PDFileSpecification fs) + { + action.setItem(COSName.F, fs); + } + + /** + * @return The B entry of the specific thread action dictionary. + */ + // Dictionary or Integer. + public COSBase getB() + { + return action.getDictionaryObject(COSName.B); + } + + /** + * @param b The destination. + */ + public void setB(COSBase b) + { + action.setItem(COSName.B, b); + } + +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionURI.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionURI.java index 5ba7a5ab6..e594e018d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionURI.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDActionURI.java @@ -17,6 +17,7 @@ package com.tom_roush.pdfbox.pdmodel.interactive.action; import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; /** * This represents a URI action that can be executed in a PDF document. @@ -58,7 +59,7 @@ public PDActionURI(COSDictionary a) */ public String getS() { - return action.getNameAsString("S"); + return action.getNameAsString(COSName.S); } /** @@ -69,7 +70,7 @@ public String getS() */ public void setS(String s) { - action.setName("S", s); + action.setName(COSName.S, s); } /** @@ -80,7 +81,7 @@ public void setS(String s) */ public String getURI() { - return action.getString("URI"); + return action.getString(COSName.URI); } /** @@ -91,7 +92,7 @@ public String getURI() */ public void setURI(String uri) { - action.setString("URI", uri); + action.setString(COSName.URI, uri); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDAdditionalActions.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDAdditionalActions.java index 7096ad416..c650c8be1 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDAdditionalActions.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDAdditionalActions.java @@ -17,6 +17,7 @@ package com.tom_roush.pdfbox.pdmodel.interactive.action; import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; /** @@ -64,7 +65,7 @@ public COSDictionary getCOSObject() */ public PDAction getF() { - return PDActionFactory.createAction( (COSDictionary)actions.getDictionaryObject("F" ) ); + return PDActionFactory.createAction((COSDictionary)actions.getDictionaryObject(COSName.F)); } /** @@ -74,6 +75,6 @@ public PDAction getF() */ public void setF( PDAction action ) { - actions.setItem( "F", action ); + actions.setItem(COSName.F, action); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDAnnotationAdditionalActions.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDAnnotationAdditionalActions.java index a8755c375..300bd19d6 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDAnnotationAdditionalActions.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDAnnotationAdditionalActions.java @@ -17,6 +17,7 @@ package com.tom_roush.pdfbox.pdmodel.interactive.action; import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; /** @@ -124,7 +125,7 @@ public void setX( PDAction x ) */ public PDAction getD() { - COSDictionary d = (COSDictionary)actions.getDictionaryObject( "D" ); + COSDictionary d = (COSDictionary)actions.getDictionaryObject(COSName.D); PDAction retval = null; if( d != null ) { @@ -142,7 +143,7 @@ public PDAction getD() */ public void setD( PDAction d ) { - actions.setItem( "D", d ); + actions.setItem(COSName.D, d); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDPageAdditionalActions.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDPageAdditionalActions.java index bfd3bad77..76f6efda9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDPageAdditionalActions.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDPageAdditionalActions.java @@ -17,6 +17,7 @@ package com.tom_roush.pdfbox.pdmodel.interactive.action; import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; /** @@ -69,7 +70,7 @@ public COSDictionary getCOSObject() */ public PDAction getO() { - COSDictionary o = (COSDictionary)actions.getDictionaryObject( "O" ); + COSDictionary o = (COSDictionary)actions.getDictionaryObject(COSName.O); PDAction retval = null; if( o != null ) { @@ -88,7 +89,7 @@ public PDAction getO() */ public void setO( PDAction o ) { - actions.setItem( "O", o ); + actions.setItem(COSName.O, o); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDWindowsLaunchParams.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDWindowsLaunchParams.java index 3fb9f3f2e..0f31eca88 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDWindowsLaunchParams.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/action/PDWindowsLaunchParams.java @@ -17,6 +17,7 @@ package com.tom_roush.pdfbox.pdmodel.interactive.action; import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; /** @@ -76,7 +77,7 @@ public COSDictionary getCOSObject() */ public String getFilename() { - return params.getString( "F" ); + return params.getString(COSName.F); } /** @@ -86,7 +87,7 @@ public String getFilename() */ public void setFilename( String file ) { - params.setString( "F", file ); + params.setString(COSName.F, file); } /** @@ -96,7 +97,7 @@ public void setFilename( String file ) */ public String getDirectory() { - return params.getString( "D" ); + return params.getString(COSName.D); } /** @@ -106,7 +107,7 @@ public String getDirectory() */ public void setDirectory( String dir ) { - params.setString( "D", dir ); + params.setString(COSName.D, dir); } /** @@ -119,7 +120,7 @@ public void setDirectory( String dir ) */ public String getOperation() { - return params.getString( "O", OPERATION_OPEN ); + return params.getString(COSName.O, OPERATION_OPEN); } /** @@ -129,7 +130,7 @@ public String getOperation() */ public void setOperation( String op ) { - params.setString( "D", op ); + params.setString(COSName.D, op); } /** @@ -139,7 +140,7 @@ public void setOperation( String op ) */ public String getExecuteParam() { - return params.getString( "P" ); + return params.getString(COSName.P); } /** @@ -149,6 +150,6 @@ public String getExecuteParam() */ public void setExecuteParam( String param ) { - params.setString( "P", param ); + params.setString(COSName.P, param); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java index 01cf4b518..bd7110186 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java @@ -19,10 +19,12 @@ import android.util.Log; import java.io.IOException; +import java.util.Calendar; import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSInteger; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSNumber; import com.tom_roush.pdfbox.pdmodel.PDPage; @@ -37,7 +39,6 @@ * A PDF annotation. * * @author Ben Litchfield - * */ public abstract class PDAnnotation implements COSObjectable { @@ -556,13 +557,27 @@ public String getModifiedDate() /** * This will set 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. Date values used in a PDF shall + * conform to a standard date format, which closely follows that of the international standard + * ASN.1 (Abstract Syntax Notation One), defined in ISO/IEC 8824. A date shall be a text string + * of the form (D:YYYYMMDDHHmmSSOHH'mm). Alternatively, use + * {@link #setModifiedDate(java.util.Calendar)} */ public void setModifiedDate(String m) { getCOSObject().setString(COSName.M, m); } + /** + * This will set the date and time the annotation was modified. + * + * @param c the date and time the annotation was created. + */ + public void setModifiedDate(Calendar c) + { + getCOSObject().setDate(COSName.M, c); + } + /** * 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. @@ -605,6 +620,40 @@ public void setStructParent(int structParent) getCOSObject().setInt(COSName.STRUCT_PARENT, structParent); } + /** + * This will retrieve the border array. If none is available, it will return the default, which + * is [0 0 1]. + * + * @return the border array. + */ + public COSArray getBorder() + { + COSBase base = getCOSObject().getDictionaryObject(COSName.BORDER); + COSArray border; + if (!(base instanceof COSArray)) + { + border = new COSArray(); + border.add(COSInteger.ZERO); + border.add(COSInteger.ZERO); + border.add(COSInteger.ONE); + } + else + { + border = (COSArray)base; + } + return border; + } + + /** + * This will set the border array. + * + * @param borderArray the border array to set. + */ + public void setBorder(COSArray borderArray) + { + getCOSObject().setItem(COSName.BORDER, borderArray); + } + /** * This will set the color 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 @@ -647,9 +696,9 @@ protected PDColor getColor(COSName itemName) case 3: colorSpace = PDDeviceRGB.INSTANCE; break; - // case 4: - // colorSpace = PDDeviceCMYK.INSTANCE; - // break; TODO: PdfBox-Android + case 4: +// colorSpace = PDDeviceCMYK.INSTANCE; TODO: PdfBox-Android + break; default: break; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationLine.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationLine.java index f6c01afeb..fb883a685 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationLine.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationLine.java @@ -17,6 +17,7 @@ package com.tom_roush.pdfbox.pdmodel.interactive.annotation; import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSFloat; import com.tom_roush.pdfbox.cos.COSName; @@ -295,6 +296,7 @@ public boolean getCaption() * @param bs the border style dictionary to set. * */ + @Override public void setBorderStyle( PDBorderStyleDictionary bs ) { this.getCOSObject().setItem(COSName.BS, bs); @@ -306,17 +308,15 @@ public void setBorderStyle( PDBorderStyleDictionary bs ) * * @return the border style dictionary. */ + @Override public PDBorderStyleDictionary getBorderStyle() { - COSDictionary bs = (COSDictionary) this.getCOSObject().getItem(COSName.BS); - if (bs != null) + COSBase bs = getCOSObject().getDictionaryObject(COSName.BS); + if (bs instanceof COSDictionary) { - return new PDBorderStyleDictionary( bs ); - } - else - { - return null; + return new PDBorderStyleDictionary((COSDictionary)bs); } + return null; } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationLink.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationLink.java index e5486cbcb..053cf398d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationLink.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationLink.java @@ -36,7 +36,6 @@ public class PDAnnotationLink extends PDAnnotation { - /** * Constant values of the Text as defined in the PDF 1.6 reference Table 8.19. */ @@ -54,7 +53,6 @@ public class PDAnnotationLink extends PDAnnotation */ public static final String HIGHLIGHT_MODE_PUSH = "P"; - /** * The type of annotation. */ @@ -81,11 +79,10 @@ public PDAnnotationLink(COSDictionary field) } /** - * Get the action to be performed when this annotation is to be activated. + * Get the action to be performed when this annotation is to be activated. Either this or the + * destination entry should be set, but not both. * * @return The action to be performed when this annotation is activated. - * - * TODO not all annotations have an A entry */ public PDAction getAction() { @@ -95,10 +92,9 @@ public PDAction getAction() } /** - * Set the annotation action. - * As of PDF 1.6 this is only used for Widget Annotations + * Set the annotation action. Either this or the destination entry should be set, but not both. + * * @param action The annotation action. - * TODO not all annotations have an A entry */ public void setAction(PDAction action) { @@ -106,12 +102,9 @@ public void setAction(PDAction action) } /** - * This will set the border style dictionary, specifying the width and dash - * pattern used in drawing the line. + * This will set the border style dictionary, specifying the width and dash pattern used in drawing the line. * * @param bs the border style dictionary to set. - * TODO not all annotations may have a BS entry - * */ public void setBorderStyle(PDBorderStyleDictionary bs) { @@ -126,20 +119,17 @@ public void setBorderStyle(PDBorderStyleDictionary bs) */ public PDBorderStyleDictionary getBorderStyle() { - COSBase bs = this.getCOSObject().getDictionaryObject(COSName.BS); - if (bs instanceof COSDictionary) - { - return new PDBorderStyleDictionary((COSDictionary) bs); - } - else + COSBase bs = getCOSObject().getDictionaryObject(COSName.BS); + if (bs instanceof COSDictionary) { - return null; + return new PDBorderStyleDictionary((COSDictionary)bs); } + return null; } /** - * Get the destination to be displayed when the annotation is activated. Either - * this or the A should be set but not both. + * Get the destination to be displayed when the annotation is activated. Either this or the + * action entry should be set, but not both. * * @return The destination for this annotation. * @@ -148,13 +138,11 @@ public PDBorderStyleDictionary getBorderStyle() public PDDestination getDestination() throws IOException { COSBase base = getCOSObject().getDictionaryObject(COSName.DEST); - PDDestination retval = PDDestination.create( base ); - - return retval; + return PDDestination.create(base); } /** - * The new destination value. + * The new destination value. Either this or the action entry should be set, but not both. * * @param dest The updated destination. */ @@ -175,7 +163,7 @@ public String getHighlightMode() } /** - * Set the highlight mode. See the HIGHLIGHT_MODE_XXX constants. + * Set the highlight mode. See the HIGHLIGHT_MODE_XXX constants. * * @param mode The new highlight mode. */ @@ -208,10 +196,7 @@ public PDActionURI getPreviousURI() { return new PDActionURI( pa ); } - else - { - return null; - } + return null; } /** @@ -241,9 +226,7 @@ public float[] getQuadPoints() { return quadPoints.toFloatArray(); } - else - { - return null; // Should never happen as this is a required item - } + // Should never happen as this is a required item + return null; } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java index 851fc7052..f57fd1f10 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java @@ -26,7 +26,7 @@ import com.tom_roush.pdfbox.cos.COSString; /** - * This class represents the additonal fields of a Markup type Annotation. See + * This class represents the additonal fields of a Markup type Annotation. See * section 12.5.6 of ISO32000-1:2008 (starting with page 390) for details on * annotation types. * @@ -217,7 +217,7 @@ public Calendar getCreationDate() throws IOException } /** - * This will set date and time the annotation was created. + * This will set the date and time the annotation was created. * * @param creationDate * the date and time the annotation was created. @@ -231,14 +231,17 @@ public void setCreationDate( Calendar creationDate ) * This will retrieve the annotation to which this one is "In Reply To" the * actual relationship is specified by the RT entry. * - * @return the other annotation. - * @throws IOException - * if there is an error with the annotation. + * @return the other annotation or null if there is none. + * @throws IOException if there is an error creating the other annotation. */ public PDAnnotation getInReplyTo() throws IOException { - COSBase irt = getCOSObject().getDictionaryObject("IRT"); - return PDAnnotation.createAnnotation( irt ); + COSBase base = getCOSObject().getDictionaryObject("IRT"); + if (base instanceof COSDictionary) + { + return PDAnnotation.createAnnotation(base); + } + return null; } /** @@ -343,4 +346,29 @@ public void setExternalData(PDExternalDataDictionary externalData) this.getCOSObject().setItem("ExData", externalData); } + /** + * This will set the border style dictionary, specifying the width and dash pattern used in drawing the line. + * + * @param bs the border style dictionary to set. + */ + public void setBorderStyle(PDBorderStyleDictionary bs) + { + this.getCOSObject().setItem(COSName.BS, bs); + } + + /** + * This will retrieve the border style dictionary, specifying the width and dash pattern used in drawing the line. + * + * @return the border style dictionary. + */ + public PDBorderStyleDictionary getBorderStyle() + { + COSBase bs = getCOSObject().getDictionaryObject(COSName.BS); + if (bs instanceof COSDictionary) + { + return new PDBorderStyleDictionary((COSDictionary)bs); + } + return null; + } + } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationRubberStamp.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationRubberStamp.java index 6b1b1eb75..9561330ed 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationRubberStamp.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationRubberStamp.java @@ -117,7 +117,7 @@ public PDAnnotationRubberStamp(COSDictionary field) /** * This will set the name (and hence appearance, AP taking precedence) - * For this annotation. See the NAME_XXX constants for valid values. + * For this annotation. See the NAME_XXX constants for valid values. * * @param name The name of the rubber stamp. */ @@ -128,7 +128,7 @@ public void setName( String name ) /** * This will retrieve the name (and hence appearance, AP taking precedence) - * For this annotation. The default is DRAFT. + * For this annotation. The default is DRAFT. * * @return The name of this rubber stamp, see the NAME_XXX constants. */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java index 7f600b18f..6a98bf57d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationSquareCircle.java @@ -17,6 +17,7 @@ package com.tom_roush.pdfbox.pdmodel.interactive.annotation; import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; @@ -63,7 +64,6 @@ public PDAnnotationSquareCircle( COSDictionary field ) super( field ); } - /** * This will set interior color of the drawn area * Color is in DeviceRGB colorspace. @@ -72,7 +72,7 @@ public PDAnnotationSquareCircle( COSDictionary field ) */ public void setInteriorColor( PDColor ic ) { - getCOSObject().setItem(COSName.IC, ic.toCOSArray()); + getCOSObject().setItem(COSName.IC, ic.toCOSArray()); } /** @@ -83,7 +83,7 @@ public void setInteriorColor( PDColor ic ) */ public PDColor getInteriorColor() { - return getColor(COSName.IC); + return getColor(COSName.IC); } @@ -121,7 +121,7 @@ public PDBorderEffectDictionary getBorderEffect() /** * This will set the rectangle difference rectangle. Giving the difference * between the annotations rectangle and where the drawing occurs. - * (To take account of any effects applied through the BE entry forexample) + * (To take account of any effects applied through the BE entry forexample) * * @param rd the rectangle difference * @@ -134,7 +134,7 @@ public void setRectDifference( PDRectangle rd ) /** * This will get the rectangle difference rectangle. Giving the difference * between the annotations rectangle and where the drawing occurs. - * (To take account of any effects applied through the BE entry forexample) + * (To take account of any effects applied through the BE entry forexample) * * @return the rectangle difference */ @@ -182,6 +182,7 @@ public String getSubtype() * TODO not all annotations may have a BS entry * */ + @Override public void setBorderStyle( PDBorderStyleDictionary bs ) { this.getCOSObject().setItem(COSName.BS, bs); @@ -194,17 +195,15 @@ public void setBorderStyle( PDBorderStyleDictionary bs ) * @return the border style dictionary. * TODO not all annotations may have a BS entry */ + @Override public PDBorderStyleDictionary getBorderStyle() { - COSDictionary bs = (COSDictionary) this.getCOSObject().getItem(COSName.BS); - if (bs != null) + COSBase bs = getCOSObject().getDictionaryObject(COSName.BS); + if (bs instanceof COSDictionary) { - return new PDBorderStyleDictionary( bs ); - } - else - { - return null; + return new PDBorderStyleDictionary((COSDictionary)bs); } + return null; } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java index 424e55883..4c61200ab 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationText.java @@ -150,8 +150,8 @@ public String getState() /** * This will set the annotation state. - * - * @param state the annotation state + * + * @param state the annotation state */ public void setState(String state) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java index ede76d16b..e76011fdf 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationTextMarkup.java @@ -46,7 +46,6 @@ public class PDAnnotationTextMarkup extends PDAnnotationMarkup */ public static final String SUB_TYPE_STRIKEOUT = "StrikeOut"; - private PDAnnotationTextMarkup() { // Must be constructed with a subType or dictionary parameter diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationWidget.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationWidget.java index e186c7ebf..55697e9a0 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationWidget.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAnnotationWidget.java @@ -35,7 +35,6 @@ public class PDAnnotationWidget extends PDAnnotation */ public static final String SUB_TYPE = "Widget"; - /** * Constructor. */ @@ -45,7 +44,6 @@ public PDAnnotationWidget() getCOSObject().setName(COSName.SUBTYPE, SUB_TYPE); } - /** * Creates a PDWidget from a COSDictionary, expected to be * a correct object definition for a field in PDF. @@ -168,7 +166,7 @@ public void setAction(PDAction action) } /** - * Get the additional actions for this field. This will return null + * Get the additional actions for this field. This will return null * if there are no additional actions for this field. * As of PDF 1.6 this is only used for Widget Annotations. * @@ -215,15 +213,12 @@ public void setBorderStyle( PDBorderStyleDictionary bs ) */ public PDBorderStyleDictionary getBorderStyle() { - COSDictionary bs = (COSDictionary) this.getCOSObject().getItem(COSName.BS); - if (bs != null) - { - return new PDBorderStyleDictionary( bs ); - } - else + COSBase bs = getCOSObject().getDictionaryObject(COSName.BS); + if (bs instanceof COSDictionary) { - return null; + return new PDBorderStyleDictionary((COSDictionary)bs); } + return null; } // TODO where to get acroForm from? diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceCharacteristicsDictionary.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceCharacteristicsDictionary.java index fae395967..4eec553b8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceCharacteristicsDictionary.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceCharacteristicsDictionary.java @@ -48,6 +48,7 @@ public PDAppearanceCharacteristicsDictionary(COSDictionary dict) /** * Returns the dictionary. + * * @return the dictionary */ @Override @@ -83,7 +84,7 @@ public void setRotation(int rotation) */ public PDColor getBorderColour() { - return getColor(COSName.BC); + return getColor(COSName.BC); } /** @@ -93,7 +94,7 @@ public PDColor getBorderColour() */ public void setBorderColour(PDColor c) { - this.getCOSObject().setItem(COSName.BC, c.toCOSArray()); + this.getCOSObject().setItem(COSName.BC, c.toCOSArray()); } /** @@ -103,7 +104,7 @@ public void setBorderColour(PDColor c) */ public PDColor getBackground() { - return getColor(COSName.BG); + return getColor(COSName.BG); } /** @@ -113,7 +114,7 @@ public PDColor getBackground() */ public void setBackground(PDColor c) { - this.getCOSObject().setItem(COSName.BG, c.toCOSArray()); + this.getCOSObject().setItem(COSName.BG, c.toCOSArray()); } /** @@ -223,26 +224,26 @@ public PDFormXObject getAlternateIcon() private PDColor getColor(COSName itemName) { - COSBase c = this.getCOSObject().getItem(itemName); - if (c instanceof COSArray) - { - PDColorSpace colorSpace = null; - switch (((COSArray) c).size()) - { - case 1: - colorSpace = PDDeviceGray.INSTANCE; - break; - case 3: - colorSpace = PDDeviceRGB.INSTANCE; - break; -// case 4: -// colorSpace = PDDeviceCMYK.INSTANCE; -// break; TODO: PdfBox-Android - default: - break; - } - return new PDColor((COSArray) c, colorSpace); - } - return null; + COSBase c = this.getCOSObject().getItem(itemName); + if (c instanceof COSArray) + { + PDColorSpace colorSpace = null; + switch (((COSArray)c).size()) + { + case 1: + colorSpace = PDDeviceGray.INSTANCE; + break; + case 3: + colorSpace = PDDeviceRGB.INSTANCE; + break; + case 4: +// colorSpace = PDDeviceCMYK.INSTANCE; TODO: PdfBox-Android + break; + default: + break; + } + return new PDColor((COSArray)c, colorSpace); + } + return null; } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java index 1b1a95308..09e9e350e 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceDictionary.java @@ -36,7 +36,7 @@ public class PDAppearanceDictionary implements COSObjectable public PDAppearanceDictionary() { dictionary = new COSDictionary(); - //the N entry is required. + // the N entry is required. dictionary.setItem( COSName.N, new COSDictionary() ); } @@ -57,7 +57,7 @@ public COSDictionary getCOSObject() } /** - * This will return a list of appearances. In the case where there is + * This will return a list of appearances. In the case where there is * only one appearance the map will contain one entry whose key is the string * "default". * @@ -72,12 +72,12 @@ public PDAppearanceEntry getNormalAppearance() } else { - return new PDAppearanceEntry(entry); + return new PDAppearanceEntry(entry); } } /** - * This will set a list of appearances. If you would like to set the single + * This will set a list of appearances. If you would like to set the single * appearance then you should use the key "default", and when the PDF is written * back to the filesystem then there will only be one stream. * @@ -100,9 +100,9 @@ public void setNormalAppearance( PDAppearanceStream ap ) } /** - * This will return a list of appearances. In the case where there is + * This will return a list of appearances. In the case where there is * only one appearance the map will contain one entry whose key is the string - * "default". If there is no rollover appearance then the normal appearance + * "default". If there is no rollover appearance then the normal appearance * will be returned. Which means that this method will never return null. * * @return A list of key(java.lang.String) value(PDAppearanceStream) pairs @@ -116,12 +116,12 @@ public PDAppearanceEntry getRolloverAppearance() } else { - return new PDAppearanceEntry(entry); + return new PDAppearanceEntry(entry); } } /** - * This will set a list of appearances. If you would like to set the single + * This will set a list of appearances. If you would like to set the single * appearance then you should use the key "default", and when the PDF is written * back to the filesystem then there will only be one stream. * @@ -144,9 +144,9 @@ public void setRolloverAppearance( PDAppearanceStream ap ) } /** - * This will return a list of appearances. In the case where there is + * This will return a list of appearances. In the case where there is * only one appearance the map will contain one entry whose key is the string - * "default". If there is no rollover appearance then the normal appearance + * "default". If there is no rollover appearance then the normal appearance * will be returned. Which means that this method will never return null. * * @return A list of key(java.lang.String) value(PDAppearanceStream) pairs @@ -160,12 +160,12 @@ public PDAppearanceEntry getDownAppearance() } else { - return new PDAppearanceEntry(entry); + return new PDAppearanceEntry(entry); } } /** - * This will set a list of appearances. If you would like to set the single + * This will set a list of appearances. If you would like to set the single * appearance then you should use the key "default", and when the PDF is written * back to the filesystem then there will only be one stream. * @@ -175,7 +175,7 @@ public void setDownAppearance( PDAppearanceEntry entry ) { dictionary.setItem( COSName.D, entry ); } - + /** * This will set the down appearance when there is down appearance * to be shown. diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceEntry.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceEntry.java index 9d6a06de4..b08dfb702 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceEntry.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceEntry.java @@ -42,6 +42,7 @@ private PDAppearanceEntry() /** * Constructor for reading. + * * @param entry */ public PDAppearanceEntry(COSBase entry) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java index 68f1045b3..20068269b 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDAppearanceStream.java @@ -31,6 +31,7 @@ public class PDAppearanceStream extends PDFormXObject { /** * Creates a Form XObject for reading. + * * @param stream The XObject stream */ public PDAppearanceStream(COSStream stream) @@ -40,6 +41,7 @@ public PDAppearanceStream(COSStream stream) /** * Creates a Form Image XObject for writing, in the given document. + * * @param document The current document */ public PDAppearanceStream(PDDocument document) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDBorderEffectDictionary.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDBorderEffectDictionary.java index 422d58ef8..922b5be0f 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDBorderEffectDictionary.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDBorderEffectDictionary.java @@ -68,6 +68,7 @@ public PDBorderEffectDictionary( COSDictionary dict ) * * @return the dictionary */ + @Override public COSDictionary getCOSObject() { return dictionary; diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDExternalDataDictionary.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDExternalDataDictionary.java index 0708919bb..53d350d08 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDExternalDataDictionary.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/annotation/PDExternalDataDictionary.java @@ -39,8 +39,8 @@ public PDExternalDataDictionary() /** * Constructor. - * - * @param dictionary Dictionary + * + * @param dictionary Dictionary */ public PDExternalDataDictionary(COSDictionary dictionary) { @@ -70,6 +70,7 @@ public String getType() /** * returns the subtype of the external data dictionary. + * * @return the subtype of the external data dictionary */ public String getSubtype() @@ -79,6 +80,7 @@ public String getSubtype() /** * This will set the subtype of the external data dictionary. + * * @param subtype the subtype of the external data dictionary */ public void setSubtype(String subtype) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/PDPropBuildDataDict.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/PDPropBuildDataDict.java index 4354803f0..1966a71af 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/PDPropBuildDataDict.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/PDPropBuildDataDict.java @@ -16,6 +16,8 @@ */ package com.tom_roush.pdfbox.pdmodel.interactive.digitalsignature; +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; @@ -28,7 +30,7 @@ */ public class PDPropBuildDataDict implements COSObjectable { - private COSDictionary dictionary; + private final COSDictionary dictionary; /** * Default constructor. @@ -36,7 +38,8 @@ public class PDPropBuildDataDict implements COSObjectable public PDPropBuildDataDict() { dictionary = new COSDictionary(); - dictionary.setDirect(true); // the specification claim to use direct objects + // the specification claim to use direct objects + dictionary.setDirect(true); } /** @@ -47,7 +50,8 @@ public PDPropBuildDataDict() public PDPropBuildDataDict(COSDictionary dict) { dictionary = dict; - dictionary.setDirect(true); // the specification claim to use direct objects + // the specification claim to use direct objects + dictionary.setDirect(true); } /** @@ -55,6 +59,7 @@ public PDPropBuildDataDict(COSDictionary dict) * * @return The COS dictionary that matches this Java object. */ + @Override public COSDictionary getCOSObject() { return dictionary; @@ -66,7 +71,7 @@ public COSDictionary getCOSObject() */ public String getName() { - return dictionary.getString(COSName.NAME); + return dictionary.getNameAsString(COSName.NAME); } /** @@ -80,7 +85,9 @@ public void setName(String name) } /** - * The build date of the software module. + * The build date of the software module. This string is normally produced by the compiler that + * is used to compile the software, for example using the Date and Time preprocessor flags. As + * such, this not likely to be in PDF Date format. * * @return the build date of the software module */ @@ -90,8 +97,7 @@ public String getDate() } /** - * The build date of the software module. This string is normally produced by the - * compiler under C++. + * The build date of the software module. This string is normally produced by the compiler. * * @param date is the build date of the software module */ @@ -100,6 +106,34 @@ public void setDate(String date) dictionary.setString(COSName.DATE, date); } + /** + * A text string indicating the version of the application implementation, as described by the + * Name attribute in this dictionary. When set by Adobe Acrobat, this entry is in + * the format: major.minor.micro (for example 7.0.7). + *

    + * NOTE: Version value is specific for build data dictionary when used as the App + * dictionary in a build properties dictionary. + *

    + * + * @param applicationVersion the application implementation version + */ + public void setVersion(String applicationVersion) + { + dictionary.setString("REx", applicationVersion); + } + + /** + * A text string indicating the version of the application implementation, as described by the + * /Name attribute in this dictionary. When set by Adobe Acrobat, this entry is in + * the format: major.minor.micro (for example 7.0.7). + * + * @return the application implementation version + */ + public String getVersion() + { + return dictionary.getString("REx"); + } + /** * The software module revision number, corresponding to the Date attribute. * @@ -121,8 +155,11 @@ public void setRevision(long revision) } /** - * The software module revision number, used to determinate the minimum version - * of software that is required in order to process this signature. + * The software module revision number, used to determinate the minimum version of software that + * is required in order to process this signature. + *

    + * NOTE: this entry is deprecated for PDF v1.7 + *

    * * @return the revision of the software module */ @@ -132,8 +169,11 @@ public long getMinimumRevision() } /** - * The software module revision number, used to determinate the minimum version - * of software that is required in order to process this signature. + * The software module revision number, used to determinate the minimum version of software that + * is required in order to process this signature. + *

    + * NOTE: this entry is deprecated for PDF v1.7 + *

    * * @param revision is the software module revision number */ @@ -166,23 +206,52 @@ public void setPreRelease(boolean preRelease) } /** - * Indicates the operation system. The format isn't specified yet. + * Indicates the operating system. The string format isn't specified yet. In its PDF Signature + * Build Dictionary Specifications Adobe differently specifies the value type to store operating + * system string:
      + *
    • Specification for PDF v1.5 specifies type as string;
    • + *
    • Specification for PDF v1.7 specifies type as array and provided example for + * /PropBuild dictionary indicate it as array of names.
    • + *
    + * This method supports both types to retrieve the value. * - * @return a the operation system id or name. + * @return the operation system id or name. */ public String getOS() { + final COSBase cosBase = dictionary.getItem(COSName.OS); + if (cosBase instanceof COSArray) + { + return ((COSArray)cosBase).getName(0); + } + // PDF v1.5 style return dictionary.getString(COSName.OS); } /** - * Indicates the operation system. The format isn't specified yet. + * Indicates the operating system. The string format isn't specified yet. Value will be stored + * as first item of the array, as specified in PDF Signature Build Dictionary Specification for + * PDF v1.7. * * @param os is a string with the system id or name. */ public void setOS(String os) { - dictionary.setString(COSName.OS, os); + if (os == null) + { + dictionary.removeItem(COSName.OS); + } + else + { + COSBase osArray = dictionary.getItem(COSName.OS); + if (!(osArray instanceof COSArray)) + { + osArray = new COSArray(); + osArray.setDirect(true); + dictionary.setItem(COSName.OS, osArray); + } + ((COSArray)osArray).add(0, COSName.getPDFName(os)); + } } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java index ce7782ba0..97a8270b2 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/SignatureOptions.java @@ -32,9 +32,11 @@ public class SignatureOptions implements Closeable { private COSDocument visualSignature; - private int preferedSignatureSize; + private int preferredSignatureSize; private int pageNo; + public static final int DEFAULT_SIGNATURE_SIZE = 0x2500; + /** * Creates the default signature options. */ @@ -44,7 +46,7 @@ public SignatureOptions() } /** - * Set the page number. + * Set the 0-based page number. * * @param pageNo the page number */ @@ -54,7 +56,7 @@ public void setPage(int pageNo) } /** - * Get the page number. + * Get the 0-based page number. * * @return the page number */ @@ -113,24 +115,24 @@ public COSDocument getVisualSignature() /** * Get the preferred size of the signature. - * - * @return the preferred size + * + * @return the preferred size of the signature in bytes. */ - public int getPreferedSignatureSize() + public int getPreferredSignatureSize() { - return preferedSignatureSize; + return preferredSignatureSize; } /** * Set the preferred size of the signature. - * - * @param size the size of the signature + * + * @param size the size of the signature in bytes. Only values above 0 will be considered. */ - public void setPreferedSignatureSize(int size) + public void setPreferredSignatureSize(int size) { if (size > 0) { - preferedSignatureSize = size; + preferredSignatureSize = size; } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateBuilder.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateBuilder.java index 555b6a0e5..d01eb0a0f 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateBuilder.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateBuilder.java @@ -40,39 +40,39 @@ public interface PDFTemplateBuilder { /** - * In order to create Affine Transform, using parameters + * In order to create Affine Transform, using parameters. * @param params */ void createAffineTransform(byte [] params); /** - * Creates specified size page + * Creates specified size page. * @param properties */ void createPage(PDVisibleSignDesigner properties); /** - * Creates template using page + * Creates template using page. * @param page * @throws IOException */ void createTemplate(PDPage page) throws IOException; /** - * Creates Acro forms in the template + * Creates Acro forms in the template. * @param template */ void createAcroForm(PDDocument template); /** - * Creates signature fields + * Creates signature fields. * @param acroForm * @throws IOException */ void createSignatureField(PDAcroForm acroForm) throws IOException; /** - * Creates PDSignatureField + * Creates PDSignatureField. * @param pdSignatureField * @param page * @param signatureName @@ -82,7 +82,7 @@ void createSignature(PDSignatureField pdSignatureField, PDPage page, String signatureName) throws IOException; /** - * Create AcroForm Dictionary + * Create AcroForm Dictionary. * @param acroForm * @param signatureField * @throws IOException @@ -91,7 +91,7 @@ void createAcroFormDictionary(PDAcroForm acroForm, PDSignatureField signatureField) throws IOException; /** - * Creates SingatureRectangle + * Creates SingatureRectangle. * @param signatureField * @param properties * @throws IOException @@ -100,12 +100,12 @@ void createSignatureRectangle(PDSignatureField signatureField, PDVisibleSignDesigner properties) throws IOException; /** - * Creates procSetArray of PDF,Text,ImageB,ImageC,ImageI + * Creates procSetArray of PDF,Text,ImageB,ImageC,ImageI. */ void createProcSetArray(); /** - * Creates signature image + * Creates signature image. * @param template * @param image * @throws IOException @@ -116,7 +116,7 @@ void createSignatureRectangle(PDSignatureField signatureField, * * @param params */ - void createFormaterRectangle(byte [] params); + void createFormatterRectangle(byte[] params); /** * @@ -131,6 +131,7 @@ void createSignatureRectangle(PDSignatureField signatureField, /** * Creates Form + * * @param holderFormResources * @param holderFormStream * @param formrect @@ -140,6 +141,7 @@ void createHolderForm(PDResources holderFormResources, PDStream holderFormStream /** * Creates appearance dictionary + * * @param holderForml * @param signatureField * @throws IOException @@ -153,7 +155,6 @@ void createAppearanceDictionary(PDFormXObject holderForml, */ void createInnerFormStream(PDDocument template); - /** * Creates InnerForm */ @@ -173,7 +174,7 @@ void createInnerForm(PDResources innerFormResources, PDStream innerFormStream, * @param innerForm * @param holderFormResources */ - void insertInnerFormToHolerResources(PDFormXObject innerForm, + void insertInnerFormToHolderResources(PDFormXObject innerForm, PDResources holderFormResources); /** @@ -189,6 +190,7 @@ void insertInnerFormToHolerResources(PDFormXObject innerForm, /** * Creates Image form + * * @param imageFormResources * @param innerFormResource * @param imageFormStream @@ -204,6 +206,7 @@ void createImageForm(PDResources imageFormResources, PDResources innerFormResour /** * Inject procSetArray + * * @param innerForm * @param page * @param innerFormResources @@ -217,6 +220,7 @@ void injectProcSetArray(PDFormXObject innerForm, PDPage page, /** * injects appearance streams + * * @param holderFormStream * @param innterFormStream * @param imageFormStream @@ -233,12 +237,14 @@ void injectAppearanceStreams(PDStream holderFormStream, PDStream innterFormStrea /** * just to create visible signature + * * @param template */ void createVisualSignature(PDDocument template); /** * adds Widget Dictionary + * * @param signatureField * @param holderFormResources * @throws IOException @@ -254,6 +260,7 @@ void createWidgetDictionary(PDSignatureField signatureField, /** * Closes template + * * @param template * @throws IOException */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateCreator.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateCreator.java index 5d61c48f4..5f42934ed 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateCreator.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateCreator.java @@ -86,7 +86,7 @@ public InputStream buildPDF(PDVisibleSignDesigner properties) throws IOException pdfBuilder.createAcroForm(template); PDAcroForm acroForm = pdfStructure.getAcroForm(); - // AcroForm contains singature fields + // AcroForm contains signature fields pdfBuilder.createSignatureField(acroForm); PDSignatureField pdSignatureField = pdfStructure.getSignatureField(); @@ -102,8 +102,8 @@ public InputStream buildPDF(PDVisibleSignDesigner properties) throws IOException // rectangle, formatter, image. /AcroForm/DR/XObject contains that form pdfBuilder.createSignatureRectangle(pdSignatureField, properties); - pdfBuilder.createFormaterRectangle(properties.getFormaterRectangleParams()); - PDRectangle formater = pdfStructure.getFormaterRectangle(); + pdfBuilder.createFormatterRectangle(properties.getFormatterRectangleParams()); + PDRectangle formatter = pdfStructure.getFormatterRectangle(); pdfBuilder.createSignatureImage(template, properties.getImage()); // create form stream, form and resource. @@ -111,28 +111,28 @@ public InputStream buildPDF(PDVisibleSignDesigner properties) throws IOException PDStream holderFormStream = pdfStructure.getHolderFormStream(); pdfBuilder.createHolderFormResources(); PDResources holderFormResources = pdfStructure.getHolderFormResources(); - pdfBuilder.createHolderForm(holderFormResources, holderFormStream, formater); - + pdfBuilder.createHolderForm(holderFormResources, holderFormStream, formatter); + // that is /AP entry the appearance dictionary. pdfBuilder.createAppearanceDictionary(pdfStructure.getHolderForm(), pdSignatureField); - + // inner form stream, form and resource (hlder form containts inner form) pdfBuilder.createInnerFormStream(template); pdfBuilder.createInnerFormResource(); PDResources innerFormResource = pdfStructure.getInnerFormResources(); - pdfBuilder.createInnerForm(innerFormResource, pdfStructure.getInnterFormStream(), formater); + pdfBuilder.createInnerForm(innerFormResource, pdfStructure.getInnerFormStream(), formatter); PDFormXObject innerForm = pdfStructure.getInnerForm(); // inner form must be in the holder form as we wrote - pdfBuilder.insertInnerFormToHolerResources(innerForm, holderFormResources); + pdfBuilder.insertInnerFormToHolderResources(innerForm, holderFormResources); // Image form is in this structure: /AcroForm/DR/FRM0/Resources/XObject/n0 pdfBuilder.createImageFormStream(template); PDStream imageFormStream = pdfStructure.getImageFormStream(); pdfBuilder.createImageFormResources(); PDResources imageFormResources = pdfStructure.getImageFormResources(); - pdfBuilder.createImageForm(imageFormResources, innerFormResource, imageFormStream, formater, - transform, pdfStructure.getImage()); + pdfBuilder.createImageForm(imageFormResources, innerFormResource, imageFormStream, + formatter, transform, pdfStructure.getImage()); // now inject procSetArray pdfBuilder.injectProcSetArray(innerForm, page, innerFormResource, imageFormResources, @@ -142,7 +142,7 @@ public InputStream buildPDF(PDVisibleSignDesigner properties) throws IOException COSName imgName = pdfStructure.getImageName(); COSName innerFormName = pdfStructure.getInnerFormName(); - // now create Streams of AP + // now create Streams of AP pdfBuilder.injectAppearanceStreams(holderFormStream, imageFormStream, imageFormStream, imgFormName, imgName, innerFormName, properties); pdfBuilder.createVisualSignature(template); diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateStructure.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateStructure.java index b4579154d..323b535f3 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateStructure.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDFTemplateStructure.java @@ -16,6 +16,12 @@ */ package com.tom_roush.pdfbox.pdmodel.interactive.digitalsignature.visible; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import com.tom_roush.harmony.awt.geom.AffineTransform; import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSDocument; @@ -33,12 +39,6 @@ import com.tom_roush.pdfbox.pdmodel.interactive.form.PDAcroForm; import com.tom_roush.pdfbox.pdmodel.interactive.form.PDField; import com.tom_roush.pdfbox.pdmodel.interactive.form.PDSignatureField; -import com.tom_roush.harmony.awt.geom.AffineTransform; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.List; /** * Structure of PDF document with visible signature. @@ -53,11 +53,11 @@ public class PDFTemplateStructure private PDSignatureField signatureField; private PDSignature pdSignature; private COSDictionary acroFormDictionary; - private PDRectangle singatureRectangle; + private PDRectangle signatureRectangle; private AffineTransform affineTransform; private COSArray procSet; private PDImageXObject image; - private PDRectangle formaterRectangle; + private PDRectangle formatterRectangle; private PDStream holderFormStream; private PDResources holderFormResources; private PDFormXObject holderForm; @@ -194,9 +194,9 @@ public void setAcroFormDictionary(COSDictionary acroFormDictionary) * Gets SignatureRectangle * @return the rectangle for the signature */ - public PDRectangle getSingatureRectangle() + public PDRectangle getSignatureRectangle() { - return singatureRectangle; + return signatureRectangle; } /** @@ -205,7 +205,7 @@ public PDRectangle getSingatureRectangle() */ public void setSignatureRectangle(PDRectangle singatureRectangle) { - this.singatureRectangle = singatureRectangle; + this.signatureRectangle = singatureRectangle; } /** @@ -266,18 +266,18 @@ public void setImage(PDImageXObject image) * Gets formatter rectangle * @return the formatter rectangle */ - public PDRectangle getFormaterRectangle() + public PDRectangle getFormatterRectangle() { - return formaterRectangle; + return formatterRectangle; } /** * Sets formatter rectangle - * @param formaterRectangle + * @param formatterRectangle */ - public void setFormaterRectangle(PDRectangle formaterRectangle) + public void setFormatterRectangle(PDRectangle formatterRectangle) { - this.formaterRectangle = formaterRectangle; + this.formatterRectangle = formatterRectangle; } /** @@ -361,7 +361,7 @@ public void setAppearanceDictionary(PDAppearanceDictionary appearanceDictionary) * Gets Inner form Stream. * @return the inner form stream */ - public PDStream getInnterFormStream() + public PDStream getInnerFormStream() { return innterFormStream; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigBuilder.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigBuilder.java index 8c2d15459..11f836391 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigBuilder.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigBuilder.java @@ -33,7 +33,6 @@ import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; import com.tom_roush.pdfbox.pdmodel.common.PDStream; import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; -import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory; import com.tom_roush.pdfbox.pdmodel.graphics.image.LosslessFactory; import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImageXObject; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; @@ -56,7 +55,8 @@ public class PDVisibleSigBuilder implements PDFTemplateBuilder @Override public void createPage(PDVisibleSignDesigner properties) { - PDPage page = new PDPage(new PDRectangle(properties.getPageWidth(), properties.getPageHeight())); + PDPage page = new PDPage( + new PDRectangle(properties.getPageWidth(), properties.getPageHeight())); pdfStructure.setPage(page); Log.i("PdfBox-Android", "PDF page has been created"); } @@ -72,7 +72,7 @@ public void createTemplate(PDPage page) throws IOException public PDVisibleSigBuilder() { pdfStructure = new PDFTemplateStructure(); - Log.i("PdfBox-Android", "PDF Strucure has been Created"); + Log.i("PdfBox-Android", "PDF Strucure has been created"); } @Override @@ -81,7 +81,7 @@ public void createAcroForm(PDDocument template) PDAcroForm theAcroForm = new PDAcroForm(template); template.getDocumentCatalog().setAcroForm(theAcroForm); pdfStructure.setAcroForm(theAcroForm); - Log.i("PdfBox-Android", "Acro form page has been created"); + Log.i("PdfBox-Android", "AcroForm page has been created"); } @Override @@ -118,6 +118,7 @@ public void createSignature(PDSignatureField pdSignatureField, PDPage page, public void createAcroFormDictionary(PDAcroForm acroForm, PDSignatureField signatureField) throws IOException { + @SuppressWarnings("unchecked") List acroFormFields = acroForm.getFields(); COSDictionary acroFormDict = acroForm.getCOSObject(); acroForm.setSignaturesExist(true); @@ -143,14 +144,14 @@ public void createSignatureRectangle(PDSignatureField signatureField, rect.setLowerLeftX(properties.getxAxis()); signatureField.getWidgets().get(0).setRectangle(rect); pdfStructure.setSignatureRectangle(rect); - Log.i("PdfBox-Android", "rectangle of signature has been created"); + Log.i("PdfBox-Android", "Signature rectangle has been created"); } @Override public void createAffineTransform(byte[] params) { - AffineTransform transform = new AffineTransform(params[0], params[1], params[2], - params[3], params[4], params[5]); + AffineTransform transform = new AffineTransform(params[0], params[1], params[2], params[3], + params[4], params[5]); pdfStructure.setAffineTransform(transform); Log.i("PdfBox-Android", "Matrix has been added"); } @@ -171,28 +172,21 @@ public void createProcSetArray() @Override public void createSignatureImage(PDDocument template, Bitmap image) throws IOException { - if (image.hasAlpha()) - { - pdfStructure.setImage(LosslessFactory.createFromImage(template, image)); - } - else - { - pdfStructure.setImage(JPEGFactory.createFromImage(template, image)); - } + pdfStructure.setImage(LosslessFactory.createFromImage(template, image)); Log.i("PdfBox-Android", "Visible Signature Image has been created"); } @Override - public void createFormaterRectangle(byte[] params) + public void createFormatterRectangle(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); - Log.i("PdfBox-Android", "Formater rectangle has been created"); + PDRectangle formatterRectangle = new PDRectangle(); + formatterRectangle.setUpperRightX(params[0]); + formatterRectangle.setUpperRightY(params[1]); + formatterRectangle.setLowerLeftX(params[2]); + formatterRectangle.setLowerLeftY(params[3]); + + pdfStructure.setFormatterRectangle(formatterRectangle); + Log.i("PdfBox-Android", "Formatter rectangle has been created"); } @Override @@ -200,7 +194,7 @@ public void createHolderFormStream(PDDocument template) { PDStream holderForm = new PDStream(template); pdfStructure.setHolderFormStream(holderForm); - Log.i("PdfBox-Android", "Holder form Stream has been created"); + Log.i("PdfBox-Android", "Holder form stream has been created"); } @Override @@ -232,13 +226,13 @@ public void createAppearanceDictionary(PDFormXObject holderForml, PDAppearanceDictionary appearance = new PDAppearanceDictionary(); appearance.getCOSObject().setDirect(true); - PDAppearanceStream appearanceStream = new PDAppearanceStream(holderForml.getCOSStream()); + PDAppearanceStream appearanceStream = new PDAppearanceStream(holderForml.getCOSObject()); appearance.setNormalAppearance(appearanceStream); signatureField.getWidgets().get(0).setAppearance(appearance); pdfStructure.setAppearanceDictionary(appearance); - Log.i("PdfBox-Android", "PDF appereance Dictionary has been created"); + Log.i("PdfBox-Android", "PDF appearance dictionary has been created"); } @Override @@ -246,8 +240,8 @@ public void createInnerFormStream(PDDocument template) { PDStream innterFormStream = new PDStream(template); pdfStructure.setInnterFormStream(innterFormStream); - Log.i("PdfBox-Android", "Stream of another form (inner form - it would be inside holder form) " + - "has been created"); + Log.i("PdfBox-Android", + "Stream of another form (inner form - it will be inside holder form) has been created"); } @Override @@ -255,8 +249,8 @@ public void createInnerFormResource() { PDResources innerFormResources = new PDResources(); pdfStructure.setInnerFormResources(innerFormResources); - Log.i("PdfBox-Android", "Resources of another form (inner form - it would be inside holder form)" + - "have been created"); + Log.i("PdfBox-Android", + "Resources of another form (inner form - it will be inside holder form) has been created"); } @Override @@ -268,16 +262,17 @@ public void createInnerForm(PDResources innerFormResources, PDStream innerFormSt innerForm.setBBox(formrect); innerForm.setFormType(1); pdfStructure.setInnerForm(innerForm); - Log.i("PdfBox-Android", "Another form (inner form - it would be inside holder form) have been created"); + Log.i("PdfBox-Android", + "Another form (inner form - it will be inside holder form) has been created"); } @Override - public void insertInnerFormToHolerResources(PDFormXObject innerForm, + public void insertInnerFormToHolderResources(PDFormXObject innerForm, PDResources holderFormResources) { - COSName name = holderFormResources.add(innerForm, "FRM"); - pdfStructure.setInnerFormName(name); - Log.i("PdfBox-Android", "Already inserted inner form inside holder form"); + COSName innerFormName = holderFormResources.add(innerForm, "FRM"); + pdfStructure.setInnerFormName(innerFormName); + Log.i("PdfBox-Android", "Now inserted inner form inside holder form"); } @Override @@ -285,7 +280,7 @@ public void createImageFormStream(PDDocument template) { PDStream imageFormStream = new PDStream(template); pdfStructure.setImageFormStream(imageFormStream); - Log.i("PdfBox-Android", "Created image form Stream"); + Log.i("PdfBox-Android", "Created image form stream"); } @Override @@ -308,6 +303,7 @@ public void createImageForm(PDResources imageFormResources, PDResources innerFor imageForm.setFormType(1); imageFormResources.getCOSObject().setDirect(true); + COSName imageFormName = innerFormResource.add(imageForm, "n"); COSName imageName = imageFormResources.add(img, "img"); pdfStructure.setImageForm(imageForm); @@ -326,7 +322,7 @@ public void injectProcSetArray(PDFormXObject innerForm, PDPage page, innerFormResources.getCOSObject().setItem(COSName.PROC_SET, procSet); imageFormResources.getCOSObject().setItem(COSName.PROC_SET, procSet); holderFormResources.getCOSObject().setItem(COSName.PROC_SET, procSet); - Log.i("PdfBox-Android", "inserted ProcSet to PDF"); + Log.i("PdfBox-Android", "Inserted ProcSet to PDF"); } @Override @@ -345,11 +341,11 @@ public void injectAppearanceStreams(PDStream holderFormStream, PDStream innterFo appendRawCommands(pdfStructure.getHolderFormStream().createOutputStream(), holderFormComment); - appendRawCommands(pdfStructure.getInnterFormStream().createOutputStream(), + appendRawCommands(pdfStructure.getInnerFormStream().createOutputStream(), innerFormComment); appendRawCommands(pdfStructure.getImageFormStream().createOutputStream(), imgFormComment); - Log.i("PdfBox-Android", "Injected apereance stream to pdf"); + Log.i("PdfBox-Android", "Injected appearance stream to pdf"); } public void appendRawCommands(OutputStream os, String commands) throws IOException @@ -374,7 +370,7 @@ public void createWidgetDictionary(PDSignatureField signatureField, widgetDict.setItem(COSName.DR, holderFormResources.getCOSObject()); pdfStructure.setWidgetDictionary(widgetDict); - Log.i("PdfBox-Android", "WidgetDictionary has been crated"); + Log.i("PdfBox-Android", "WidgetDictionary has been created"); } @Override diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigProperties.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigProperties.java index d317e0964..4349ca807 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigProperties.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSigProperties.java @@ -129,8 +129,9 @@ public PDVisibleSigProperties page(int page) } /** - * gets our preferred size - * @return the signature's preferred size. + * Gets the preferred signature size in bytes. + * + * @return the signature's preferred size. A return value of 0 means to use default. */ public int getPreferredSize() { @@ -138,8 +139,9 @@ public int getPreferredSize() } /** - * sets our preferred size - * @param preferredSize + * Sets the preferred signature size in bytes. + * + * @param preferredSize The preferred signature size in bytes, or 0 to use default. * @return the visible signature properties. */ public PDVisibleSigProperties preferredSize(int preferredSize) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSignDesigner.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSignDesigner.java index 134383ee5..e50d11361 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSignDesigner.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/digitalsignature/visible/PDVisibleSignDesigner.java @@ -42,38 +42,40 @@ public class PDVisibleSignDesigner private float pageHeight; private float pageWidth; private Bitmap image; - private String signatureFieldName = "sig"; // default - private byte[] formaterRectangleParams = { 0, 0, 100, 50 }; // default - private byte[] AffineTransformParams = { 1, 0, 0, 1, 0, 0 }; // default + private String signatureFieldName = "sig"; + private byte[] formatterRectangleParams = { 0, 0, 100, 50 }; + private byte[] AffineTransformParams = { 1, 0, 0, 1, 0, 0 }; private float imageSizeInPercents; /** * Constructor. * * @param filename Path of the PDF file - * @param jpegStream JPEG image as a stream + * @param imageStream image as a stream * @param page The 1-based page number for which the page size should be calculated. + * * @throws IOException */ - public PDVisibleSignDesigner(String filename, InputStream jpegStream, int page) - throws IOException + public PDVisibleSignDesigner(String filename, InputStream imageStream, int page) + throws IOException { - this(new FileInputStream(filename), jpegStream, page); + this(new FileInputStream(filename), imageStream, page); } /** * Constructor. * * @param documentStream Original PDF document as stream - * @param jpegStream JPEG image as a stream + * @param imageStream Image as a stream * @param page The 1-based page number for which the page size should be calculated. + * * @throws IOException */ - public PDVisibleSignDesigner(InputStream documentStream, InputStream jpegStream, int page) - throws IOException + public PDVisibleSignDesigner(InputStream documentStream, InputStream imageStream, int page) + throws IOException { - // set visible singature image Input stream - signatureImageStream(jpegStream); + // set visible signature image Input stream + readImageStream(imageStream); // create PD document PDDocument document = PDDocument.load(documentStream); @@ -87,15 +89,67 @@ public PDVisibleSignDesigner(InputStream documentStream, InputStream jpegStream, /** * Constructor. * - * @param doc - Already created PDDocument of your PDF document - * @param jpegStream + * @param document Already created PDDocument of your PDF document. + * @param imageStream Image as a stream. * @param page The 1-based page number for which the page size should be calculated. - * @throws IOException - If we can't read, flush, or can't close stream + * + * @throws IOException If we can't read, flush, or can't close stream. + */ + public PDVisibleSignDesigner(PDDocument document, InputStream imageStream, int page) + throws IOException + { + readImageStream(imageStream); + calculatePageSize(document, page); + } + + /** + * Constructor. + * + * @param filename Path of the PDF file + * @param image + * @param page The 1-based page number for which the page size should be calculated. + * + * @throws IOException */ - public PDVisibleSignDesigner(PDDocument doc, InputStream jpegStream, int page) throws IOException + public PDVisibleSignDesigner(String filename, Bitmap image, int page) throws IOException { - signatureImageStream(jpegStream); - calculatePageSize(doc, page); + this(new FileInputStream(filename), image, page); + } + + /** + * Constructor. + * + * @param documentStream Original PDF document as stream + * @param image + * @param page The 1-based page number for which the page size should be calculated. + * @throws IOException + */ + public PDVisibleSignDesigner(InputStream documentStream, Bitmap image, int page) + throws IOException + { + // set visible signature image + setImage(image); + + // create PD document + PDDocument document = PDDocument.load(documentStream); + + // calculate height and width of document page + calculatePageSize(document, page); + + document.close(); + } + + /** + * Constructor. + * + * @param document Already created PDDocument of your PDF document. + * @param image + * @param page The 1-based page number for which the page size should be calculated. + */ + public PDVisibleSignDesigner(PDDocument document, Bitmap image, int page) + { + setImage(image); + calculatePageSize(document, page); } /** @@ -104,6 +158,7 @@ public PDVisibleSignDesigner(PDDocument doc, InputStream jpegStream, int page) t * * @param document * @param page The 1-based page number for which the page size should be calculated. + * @throws IllegalArgumentException if the page argument is lower than 0. */ private void calculatePageSize(PDDocument document, int page) { @@ -117,35 +172,37 @@ private void calculatePageSize(PDDocument document, int page) pageHeight(mediaBox.getHeight()); pageWidth = mediaBox.getWidth(); - float x = this.pageWidth; + float x = pageWidth; float y = 0; - pageWidth = this.pageWidth + y; + pageWidth += y; float tPercent = (100 * y / (x + y)); imageSizeInPercents = 100 - tPercent; } /** + * Set the image for the signature. * - * @param path of image location + * @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); + readImageStream(fin); + return this; } /** - * zoom signature image with some percent. + * Zoom signature image with some percent. * * @param percent increase image with x percent. * @return Visible Signature Configuration Object */ public PDVisibleSignDesigner zoom(float percent) { - imageHeight = imageHeight + (imageHeight * percent) / 100; - imageWidth = imageWidth + (imageWidth * percent) / 100; + imageHeight += (imageHeight * percent) / 100; + imageWidth += (imageWidth * percent) / 100; return this; } @@ -232,8 +289,8 @@ public float getHeight() } /** - * - * @param height signature image Height + * + * @param height signature image height * @return Visible Signature Configuration Object */ public PDVisibleSignDesigner height(float height) @@ -292,17 +349,26 @@ public Bitmap getImage() } /** - * + * Read the image stream of the signature and set height and width. + * * @param stream 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 stream) throws IOException + private void readImageStream(InputStream stream) throws IOException + { + setImage(BitmapFactory.decodeStream(stream)); + } + + /** + * Set image and its height and width. + * + * @param image + */ + private void setImage(Bitmap image) { - image = BitmapFactory.decodeStream(stream); + this.image = image; imageHeight = (float)image.getHeight(); imageWidth = (float)image.getWidth(); - return this; } /** @@ -329,20 +395,20 @@ public PDVisibleSignDesigner affineTransformParams(byte[] affineTransformParams) * * @return formatter PDRectanle parameters */ - public byte[] getFormaterRectangleParams() + public byte[] getFormatterRectangleParams() { - return formaterRectangleParams; + return formatterRectangleParams; } /** - * sets formatter PDRectangle; - * - * @param formaterRectangleParams + * Sets formatter PDRectangle + * + * @param formatterRectangleParams * @return Visible Signature Configuration Object */ - public PDVisibleSignDesigner formaterRectangleParams(byte[] formaterRectangleParams) + public PDVisibleSignDesigner formatterRectangleParams(byte[] formatterRectangleParams) { - this.formaterRectangleParams = formaterRectangleParams; + this.formatterRectangleParams = formatterRectangleParams; return this; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDDestination.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDDestination.java index 5efd23ebf..95b1819eb 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDDestination.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDDestination.java @@ -50,8 +50,8 @@ public static PDDestination create( COSBase base ) throws IOException //this is ok, just return null. } else if (base instanceof COSArray - && ((COSArray) base).size() > 1 - && ((COSArray) base).getObject(1) instanceof COSName) + && ((COSArray)base).size() > 1 + && ((COSArray)base).getObject(1) instanceof COSName) { COSArray array = (COSArray)base; COSName type = (COSName) array.getObject(1); @@ -81,7 +81,7 @@ else if( typeString.equals( PDPageXYZDestination.TYPE ) ) } else { - throw new IOException( "Unknown destination type: " + type.getName() ); + throw new IOException("Unknown destination type: " + type.getName()); } } else if( base instanceof COSString ) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDNamedDestination.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDNamedDestination.java index e754d9d25..78d3e1de5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDNamedDestination.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDNamedDestination.java @@ -117,5 +117,4 @@ public void setNamedDestination( String dest ) throws IOException namedDestination = new COSString( dest ); } } - } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageDestination.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageDestination.java index 1b984132d..8a8e38218 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageDestination.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageDestination.java @@ -56,10 +56,10 @@ protected PDPageDestination( COSArray arr ) } /** - * This will get the page for this destination. A page destination can either reference a page + * This will get the page for this destination. A page destination can either reference a page * (for a local destination) or a page number (when doing a remote destination to another PDF). * If this object is referencing by page number then this method will return null and - * {@Link #getPageNumber()} should be used. + * {@link #getPageNumber()} should be used. * * @return The page for this destination. */ @@ -88,9 +88,9 @@ public void setPage( PDPage page ) } /** - * This will get the page number for this destination. A page destination can either reference a + * This will get the page number for this destination. A page destination can either reference a * page (for a local destination) or a page number (when doing a remote destination to another - * PDF). If this object is referencing by page number then this method will return that number, + * PDF). If this object is referencing by page number then this method will return that number, * otherwise -1 will be returned. * * @return The zero-based page number for this destination. @@ -133,7 +133,6 @@ public int findPageNumber() } else if (page instanceof COSDictionary) { - //TODO make this a static utility method of PDPageTree? COSBase parent = page; while (((COSDictionary) parent).getDictionaryObject(COSName.PARENT, COSName.P) != null) { @@ -141,7 +140,7 @@ else if (page instanceof COSDictionary) } // now parent is the pages node PDPageTree pages = new PDPageTree((COSDictionary) parent); - retval = pages.indexOf(new PDPage((COSDictionary) page)) + 1; + return pages.indexOf(new PDPage((COSDictionary)page)) + 1; } } return retval; @@ -166,6 +165,7 @@ public int retrievePageNumber() } else if (page instanceof COSDictionary) { + //TODO make this a static utility method of PDPageTree? COSBase parent = page; while (((COSDictionary) parent).getDictionaryObject(COSName.PARENT, COSName.P) != null) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageXYZDestination.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageXYZDestination.java index b2be7604e..7ab9e5243 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageXYZDestination.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/destination/PDPageXYZDestination.java @@ -68,7 +68,7 @@ public int getLeft() } /** - * Set the left x-coordinate, values of 0 or -1 imply that the current x-coordinate + * Set the left x-coordinate, values 0 or -1 imply that the current x-coordinate * will be used. * @param x The left x coordinate. */ @@ -86,7 +86,7 @@ public void setLeft( int x ) } /** - * Get the top y coordinate. Return values of 0 or -1 imply that the current y-coordinate + * Get the top y coordinate. Return values 0 or -1 imply that the current y-coordinate * will be used. * * @return The top y coordinate. @@ -97,7 +97,7 @@ public int getTop() } /** - * Set the top y-coordinate, values of 0 or -1 imply that the current y-coordinate + * Set the top y-coordinate, values 0 or -1 imply that the current y-coordinate * will be used. * @param y The top ycoordinate. */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/outline/PDDocumentOutline.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/outline/PDDocumentOutline.java index ff7ef6eb8..370753b7f 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/outline/PDDocumentOutline.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/outline/PDDocumentOutline.java @@ -32,7 +32,7 @@ public final class PDDocumentOutline extends PDOutlineNode */ public PDDocumentOutline() { - getCOSObject().setName(COSName.TYPE, COSName.OUTLINES.getName()); + getCOSObject().setName(COSName.TYPE, COSName.OUTLINES.getName()); } /** @@ -42,25 +42,25 @@ public PDDocumentOutline() */ public PDDocumentOutline( COSDictionary dic ) { - super( dic ); - getCOSObject().setName(COSName.TYPE, COSName.OUTLINES.getName()); + super(dic); + getCOSObject().setName(COSName.TYPE, COSName.OUTLINES.getName()); } @Override public boolean isNodeOpen() { - return true; + return true; } @Override public void openNode() { - // The root of the outline hierarchy is not an OutlineItem and cannot be opened or closed + // The root of the outline hierarchy is not an OutlineItem and cannot be opened or closed } @Override public void closeNode() { - // The root of the outline hierarchy is not an OutlineItem and cannot be opened or closed + // The root of the outline hierarchy is not an OutlineItem and cannot be opened or closed } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/outline/PDOutlineItem.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/outline/PDOutlineItem.java index 798f3aef8..ccae559c8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/outline/PDOutlineItem.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/documentnavigation/outline/PDOutlineItem.java @@ -23,10 +23,7 @@ import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSFloat; import com.tom_roush.pdfbox.cos.COSName; -import com.tom_roush.pdfbox.pdmodel.PDDestinationNameTreeNode; import com.tom_roush.pdfbox.pdmodel.PDDocument; -import com.tom_roush.pdfbox.pdmodel.PDDocumentNameDestinationDictionary; -import com.tom_roush.pdfbox.pdmodel.PDDocumentNameDictionary; import com.tom_roush.pdfbox.pdmodel.PDPage; import com.tom_roush.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColor; @@ -71,15 +68,15 @@ public PDOutlineItem( COSDictionary dic ) /** * Insert a single sibling after this node. * - * @param newSibling The item to insert + * @param newSibling The item to insert. * @throws IllegalArgumentException if the given sibling node is part of a list * (i.e. if it has a previous or a next sibling) */ public void insertSiblingAfter(PDOutlineItem newSibling) { - requireSingleNode(newSibling); - PDOutlineNode parent = getParent(); - newSibling.setParent(parent); + requireSingleNode(newSibling); + PDOutlineNode parent = getParent(); + newSibling.setParent(parent); PDOutlineItem next = getNextSibling(); setNextSibling(newSibling); newSibling.setPreviousSibling(this); @@ -90,7 +87,7 @@ public void insertSiblingAfter(PDOutlineItem newSibling) } else if (parent != null) { - getParent().setLastChild(newSibling); + getParent().setLastChild(newSibling); } updateParentOpenCountForAddedChild(newSibling); } @@ -104,22 +101,22 @@ else if (parent != null) */ public void insertSiblingBefore(PDOutlineItem newSibling) { - requireSingleNode(newSibling); - PDOutlineNode parent = getParent(); - newSibling.setParent(parent); - PDOutlineItem previous = getPreviousSibling(); - setPreviousSibling(newSibling); - newSibling.setNextSibling(this); - if (previous != null) - { - previous.setNextSibling(newSibling); - newSibling.setPreviousSibling(previous); - } - else if (parent != null) - { - getParent().setFirstChild(newSibling); - } - updateParentOpenCountForAddedChild(newSibling); + requireSingleNode(newSibling); + PDOutlineNode parent = getParent(); + newSibling.setParent(parent); + PDOutlineItem previous = getPreviousSibling(); + setPreviousSibling(newSibling); + newSibling.setNextSibling(this); + if (previous != null) + { + previous.setNextSibling(newSibling); + newSibling.setPreviousSibling(previous); + } + else if (parent != null) + { + getParent().setFirstChild(newSibling); + } + updateParentOpenCountForAddedChild(newSibling); } /** @@ -129,7 +126,7 @@ else if (parent != null) */ public PDOutlineItem getPreviousSibling() { - return getOutlineItem(COSName.PREV); + return getOutlineItem(COSName.PREV); } /** @@ -139,7 +136,7 @@ public PDOutlineItem getPreviousSibling() */ void setPreviousSibling(PDOutlineNode outlineNode) { - getCOSObject().setItem(COSName.PREV, outlineNode); + getCOSObject().setItem(COSName.PREV, outlineNode); } /** @@ -147,7 +144,7 @@ void setPreviousSibling(PDOutlineNode outlineNode) */ public PDOutlineItem getNextSibling() { - return getOutlineItem(COSName.NEXT); + return getOutlineItem(COSName.NEXT); } /** @@ -157,7 +154,7 @@ public PDOutlineItem getNextSibling() */ void setNextSibling(PDOutlineNode outlineNode) { - getCOSObject().setItem(COSName.NEXT, outlineNode); + getCOSObject().setItem(COSName.NEXT, outlineNode); } /** @@ -167,7 +164,7 @@ void setNextSibling(PDOutlineNode outlineNode) */ public String getTitle() { - return getCOSObject().getString(COSName.TITLE); + return getCOSObject().getString(COSName.TITLE); } /** @@ -177,7 +174,7 @@ public String getTitle() */ public void setTitle(String title) { - getCOSObject().setString(COSName.TITLE, title); + getCOSObject().setString(COSName.TITLE, title); } /** @@ -188,7 +185,7 @@ public void setTitle(String title) */ public PDDestination getDestination() throws IOException { - return PDDestination.create(getCOSObject().getDictionaryObject(COSName.DEST)); + return PDDestination.create(getCOSObject().getDictionaryObject(COSName.DEST)); } /** @@ -198,7 +195,7 @@ public PDDestination getDestination() throws IOException */ public void setDestination(PDDestination dest) { - getCOSObject().setItem(COSName.DEST, dest); + getCOSObject().setItem(COSName.DEST, dest); } /** @@ -219,7 +216,7 @@ public void setDestination(PDPage page) /** * This method will attempt to find the page in this PDF document that this outline points to. - * If the outline does not point to anything then this method will return null. If the outline + * If the outline does not point to anything then this method will return null. If the outline * is an action that is not a GoTo action then this method will also return null. * * @param doc The document to get the page from. @@ -238,7 +235,6 @@ public PDPage findDestinationPage(PDDocument doc) throws IOException dest = ((PDActionGoTo) outlineAction).getDestination(); } } - if (dest == null) { return null; @@ -247,7 +243,8 @@ public PDPage findDestinationPage(PDDocument doc) throws IOException PDPageDestination pageDestination = null; if (dest instanceof PDNamedDestination) { - pageDestination = findNamedDestinationPage((PDNamedDestination) dest, doc); + pageDestination = doc.getDocumentCatalog().findNamedDestinationPage( + (PDNamedDestination)dest); if (pageDestination == null) { return null; @@ -265,6 +262,8 @@ else if (dest instanceof PDPageDestination) PDPage page = pageDestination.getPage(); if (page == null) { + // Malformed PDF: local destinations must have a page object, + // not a page number, these are meant for remote destinations. int pageNumber = pageDestination.getPageNumber(); if (pageNumber != -1) { @@ -274,33 +273,6 @@ else if (dest instanceof PDPageDestination) return page; } - //if we have a named destination we need to lookup the PDPageDestination - private PDPageDestination findNamedDestinationPage(PDNamedDestination namedDest, PDDocument doc) - throws IOException - { - PDPageDestination pageDestination = null; - PDDocumentNameDictionary namesDict = doc.getDocumentCatalog().getNames(); - if (namesDict != null) - { - PDDestinationNameTreeNode destsTree = namesDict.getDests(); - if (destsTree != null) - { - pageDestination = destsTree.getValue(namedDest.getNamedDestination()); - } - } - if (pageDestination == null) - { - // Look up /Dests dictionary from catalog - PDDocumentNameDestinationDictionary nameDestDict = doc.getDocumentCatalog().getDests(); - if (nameDestDict != null) - { - String name = namedDest.getNamedDestination(); - pageDestination = (PDPageDestination) nameDestDict.getDestination(name); - } - } - return pageDestination; - } - /** * Get the action of this node. * @@ -308,7 +280,8 @@ private PDPageDestination findNamedDestinationPage(PDNamedDestination namedDest, */ public PDAction getAction() { - return PDActionFactory.createAction((COSDictionary) getCOSObject().getDictionaryObject(COSName.A)); + return PDActionFactory.createAction( + (COSDictionary)getCOSObject().getDictionaryObject(COSName.A)); } /** @@ -318,7 +291,7 @@ public PDAction getAction() */ public void setAction( PDAction action ) { - getCOSObject().setItem(COSName.A, action); + getCOSObject().setItem(COSName.A, action); } /** @@ -342,9 +315,9 @@ public PDStructureElement getStructureElement() * * @param structureElement The new structure element for this node. */ - public void setStructuredElement( PDStructureElement structureElement ) + public void setStructureElement(PDStructureElement structureElement) { - getCOSObject().setItem(COSName.SE, structureElement); + getCOSObject().setItem(COSName.SE, structureElement); } /** @@ -372,7 +345,7 @@ public PDColor getTextColor() */ public void setTextColor( PDColor textColor ) { - getCOSObject().setItem( COSName.C, textColor.toCOSArray() ); + getCOSObject().setItem(COSName.C, textColor.toCOSArray()); } /** @@ -396,7 +369,7 @@ public void setTextColor( AWTColor textColor ) */ public boolean isItalic() { - return getCOSObject().getFlag( COSName.F, ITALIC_FLAG ); + return getCOSObject().getFlag(COSName.F, ITALIC_FLAG); } /** @@ -406,7 +379,7 @@ public boolean isItalic() */ public void setItalic( boolean italic ) { - getCOSObject().setFlag( COSName.F, ITALIC_FLAG, italic ); + getCOSObject().setFlag(COSName.F, ITALIC_FLAG, italic); } /** @@ -416,7 +389,7 @@ public void setItalic( boolean italic ) */ public boolean isBold() { - return getCOSObject().getFlag( COSName.F, BOLD_FLAG ); + return getCOSObject().getFlag(COSName.F, BOLD_FLAG); } /** @@ -426,6 +399,6 @@ public boolean isBold() */ public void setBold( boolean bold ) { - getCOSObject().setFlag( COSName.F, BOLD_FLAG, bold ); + getCOSObject().setFlag(COSName.F, BOLD_FLAG, bold); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java index 11569882d..e54478ab9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java @@ -49,7 +49,7 @@ class AppearanceGeneratorHelper private static final Operator BMC = Operator.getOperator("BMC"); private static final Operator EMC = Operator.getOperator("EMC"); - private final PDVariableText field; + private final PDVariableText field; private final PDDefaultAppearanceString defaultAppearance; private String value; @@ -81,15 +81,15 @@ class AppearanceGeneratorHelper * Constructs a COSAppearance from the given field. * * @param field the field which you wish to control the appearance of - * @throws IOException If there is an error creating the appearance. + * @throws IOException */ AppearanceGeneratorHelper(PDVariableText field) throws IOException { - this.field = field; + this.field = field; this.defaultAppearance = field.getDefaultAppearanceString(); } - /** + /** * This is the public method for setting the appearance stream. * * @param apValue the String value which the appearance should represent @@ -98,9 +98,9 @@ class AppearanceGeneratorHelper public void setAppearanceValue(String apValue) throws IOException { value = apValue; - for (PDAnnotationWidget widget : field.getWidgets()) - { - PDFormFieldAdditionalActions actions = field.getActions(); + for (PDAnnotationWidget widget : field.getWidgets()) + { + PDFormFieldAdditionalActions actions = field.getActions(); // in case all tests fail the field will be formatted by acrobat // when it is opened. See FreedomExpressions.pdf for an example of this. @@ -116,6 +116,7 @@ public void setAppearanceValue(String apValue) throws IOException PDAppearanceEntry appearance = appearanceDict.getNormalAppearance(); // TODO support appearances other than "normal" + PDAppearanceStream appearanceStream; if (appearance.isStream()) { @@ -250,6 +251,7 @@ private void setAppearanceContent(PDAnnotationWidget widget, // append contents after EMC writer.writeTokens(tokens.subList(emcIndex, tokens.size())); } + output.close(); writeToStream(output.toByteArray(), appearanceStream); } @@ -381,10 +383,10 @@ else if (field instanceof PDListBox) contents.close(); } - private boolean isMultiLine() - { - return field instanceof PDTextField && ((PDTextField) field).isMultiline(); - } + private boolean isMultiLine() + { + return field instanceof PDTextField && ((PDTextField)field).isMultiline(); + } /** * Determine if the appearance shall provide a comb output. @@ -508,6 +510,7 @@ private void insertGeneratedListboxAppearance(PDPageContentStream contents, contents.setNonStrokingColor(0); int q = field.getQ(); + if (q == PDVariableText.QUADDING_CENTERED || q == PDVariableText.QUADDING_RIGHT) { float fieldWidth = appearanceStream.getBBox().getWidth(); @@ -556,25 +559,25 @@ else if (q != PDVariableText.QUADDING_LEFT) } } - /** - * Writes the stream to the actual stream in the COSStream. - * - * @throws IOException If there is an error writing to the stream - */ - private void writeToStream(byte[] data, PDAppearanceStream appearanceStream) throws IOException - { - OutputStream out = appearanceStream.getCOSStream().createOutputStream(); + /** + * Writes the stream to the actual stream in the COSStream. + * + * @throws IOException If there is an error writing to the stream + */ + private void writeToStream(byte[] data, PDAppearanceStream appearanceStream) throws IOException + { + OutputStream out = appearanceStream.getCOSObject().createOutputStream(); out.write(data); out.close(); } - /** - * My "not so great" method for calculating the fontsize. It does not work superb, but it - * handles ok. - * @return the calculated font-size - * - * @throws IOException If there is an error getting the font information. - */ + /** + * My "not so great" method for calculating the fontsize. It does not work superb, but it + * handles ok. + * + * @return the calculated font-size + * @throws IOException If there is an error getting the font information. + */ private float calculateFontSize(PDFont font, PDRectangle contentRect) throws IOException { float fontSize = defaultAppearance.getFontSize(); @@ -609,32 +612,33 @@ private float calculateFontSize(PDFont font, PDRectangle contentRect) throws IOE } } return fontSize; - } - - /** - * Resolve the bounding box. - * - * @param fieldWidget the annotation widget. - * @param appearanceStream the annotations appearance stream. - * @return the resolved boundingBox. - */ + } + + /** + * Resolve the bounding box. + * + * @param fieldWidget the annotation widget. + * @param appearanceStream the annotations appearance stream. + * + * @return the resolved boundingBox. + */ private PDRectangle resolveBoundingBox(PDAnnotationWidget fieldWidget, PDAppearanceStream appearanceStream) { - PDRectangle boundingBox = appearanceStream.getBBox(); - if (boundingBox == null) - { - boundingBox = fieldWidget.getRectangle().createRetranslatedRectangle(); - } - return boundingBox; - } - - /** - * Apply padding to a box. - * - * @param box box - * @return the padded box. - */ + PDRectangle boundingBox = appearanceStream.getBBox(); + if (boundingBox == null) + { + boundingBox = fieldWidget.getRectangle().createRetranslatedRectangle(); + } + return boundingBox; + } + + /** + * Apply padding to a box. + * + * @param box box + * @return the padded box. + */ private PDRectangle applyPadding(PDRectangle box, float padding) { return new PDRectangle( diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/FieldUtils.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/FieldUtils.java index 6e43b7cf5..2343a22c5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/FieldUtils.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/FieldUtils.java @@ -57,7 +57,6 @@ public String getKey() return this.key; } - public String getValue() { return this.value; @@ -75,8 +74,8 @@ public String toString() */ static class KeyValueKeyComparator implements Serializable, Comparator { - private static final long serialVersionUID = 6715364290007167694L; - + private static final long serialVersionUID = 6715364290007167694L; + @Override public int compare(KeyValue o1, KeyValue o2) { @@ -89,8 +88,8 @@ public int compare(KeyValue o1, KeyValue o2) */ static class KeyValueValueComparator implements Serializable, Comparator { - private static final long serialVersionUID = -3984095679894798265L; - + private static final long serialVersionUID = -3984095679894798265L; + @Override public int compare(KeyValue o1, KeyValue o2) { @@ -161,31 +160,32 @@ static void sortByKey(List pairs) */ static List getPairableItems(COSBase items, int pairIdx) { - if (pairIdx < 0 || pairIdx > 1) - { - throw new IllegalArgumentException("Only 0 and 1 are allowed as an index into two-element arrays"); - } - - if (items instanceof COSString) - { - List array = new ArrayList(); - array.add(((COSString) items).getString()); - return array; - } - else if (items instanceof COSArray) - { - // test if there is a single text or a two-element array - COSBase entry = ((COSArray) items).get(0); - if (entry instanceof COSString) - { - return COSArrayList.convertCOSStringCOSArrayToList((COSArray)items); - } - else - { - return getItemsFromPair(items, pairIdx); - } - } - return Collections.emptyList(); + if (pairIdx < 0 || pairIdx > 1) + { + throw new IllegalArgumentException( + "Only 0 and 1 are allowed as an index into two-element arrays"); + } + + if (items instanceof COSString) + { + List array = new ArrayList(); + array.add(((COSString)items).getString()); + return array; + } + else if (items instanceof COSArray) + { + // test if there is a single text or a two-element array + COSBase entry = ((COSArray)items).get(0); + if (entry instanceof COSString) + { + return COSArrayList.convertCOSStringCOSArrayToList((COSArray)items); + } + else + { + return getItemsFromPair(items, pairIdx); + } + } + return Collections.emptyList(); } /** @@ -197,14 +197,14 @@ else if (items instanceof COSArray) */ private static List getItemsFromPair(COSBase items, int pairIdx) { - List exportValues = new ArrayList(); - int numItems = ((COSArray) items).size(); - for (int i=0;i exportValues = new ArrayList(); + int numItems = ((COSArray)items).size(); + for (int i = 0; i < numItems; i++) + { + COSArray pair = (COSArray)((COSArray)items).get(i); + COSString displayValue = (COSString)pair.get(pairIdx); + exportValues.add(displayValue.getString()); + } + return exportValues; } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDAcroForm.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDAcroForm.java index 43159c258..0e1803511 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDAcroForm.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDAcroForm.java @@ -16,10 +16,13 @@ */ package com.tom_roush.pdfbox.pdmodel.interactive.form; +import android.util.Log; + import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -28,8 +31,10 @@ import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSNumber; -import com.tom_roush.pdfbox.cos.COSString; import com.tom_roush.pdfbox.pdmodel.PDDocument; +import com.tom_roush.pdfbox.pdmodel.PDPage; +import com.tom_roush.pdfbox.pdmodel.PDPageContentStream; +import com.tom_roush.pdfbox.pdmodel.PDPageContentStream.AppendMode; import com.tom_roush.pdfbox.pdmodel.PDResources; import com.tom_roush.pdfbox.pdmodel.common.COSArrayList; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; @@ -37,6 +42,10 @@ import com.tom_roush.pdfbox.pdmodel.fdf.FDFDictionary; import com.tom_roush.pdfbox.pdmodel.fdf.FDFDocument; import com.tom_roush.pdfbox.pdmodel.fdf.FDFField; +import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; +import com.tom_roush.pdfbox.util.Matrix; /** * An interactive form, also known as an AcroForm. @@ -147,6 +156,163 @@ public FDFDocument exportFDF() throws IOException return fdf; } + /** + * This will flatten all form fields. + * + *

    Flattening a form field will take the current appearance and make that part + * of the pages content stream. All form fields and annotations associated are removed.

    + * + *

    The appearances for the form fields widgets will not be generated

    + * + * @throws IOException + */ + public void flatten() throws IOException + { + // for dynamic XFA forms there is no flatten as this would mean to do a rendering + // from the XFA content into a static PDF. + if (xfaIsDynamic()) + { + Log.w("PdfBox-Android", "Flatten for a dynamix XFA form is not supported"); + return; + } + + List fields = new ArrayList(); + for (PDField field : getFieldTree()) + { + fields.add(field); + } + flatten(fields, false); + } + + /** + * This will flatten the specified form fields. + * + *

    Flattening a form field will take the current appearance and make that part + * of the pages content stream. All form fields and annotations associated are removed.

    + * + * @param fields + * @param refreshAppearances if set to true the appearances for the form field widgets will be updated + * + * @throws IOException + */ + public void flatten(List fields, boolean refreshAppearances) throws IOException + { + // for dynamic XFA forms there is no flatten as this would mean to do a rendering + // from the XFA content into a static PDF. + if (xfaIsDynamic()) + { + Log.w("PdfBox-Android", "Flatten for a dynamix XFA form is not supported"); + return; + } + + // refresh the appearances if set + if (refreshAppearances) + { + refreshAppearances(fields); + } + + // indicates if the original content stream + // has been wrapped in a q...Q pair. + boolean isContentStreamWrapped = false; + + // the content stream to write to + PDPageContentStream contentStream; + + // Iterate over all form fields and their widgets and create a + // FormXObject at the page content level from that + for (PDField field : fields) + { + for (PDAnnotationWidget widget : field.getWidgets()) + { + if (widget.getNormalAppearanceStream() != null) + { + PDPage page = widget.getPage(); + if (!isContentStreamWrapped) + { + contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, + true, true); + isContentStreamWrapped = true; + } + else + { + contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, + true); + } + + PDFormXObject fieldObject = new PDFormXObject( + widget.getNormalAppearanceStream().getCOSObject()); + + Matrix translationMatrix = Matrix.getTranslateInstance( + widget.getRectangle().getLowerLeftX(), + widget.getRectangle().getLowerLeftY()); + contentStream.saveGraphicsState(); + contentStream.transform(translationMatrix); + contentStream.drawForm(fieldObject); + contentStream.restoreGraphicsState(); + contentStream.close(); + } + } + } + + // preserve all non widget annotations + for (PDPage page : document.getPages()) + { + List annotations = new ArrayList(); + + for (PDAnnotation annotation : page.getAnnotations()) + { + if (!(annotation instanceof PDAnnotationWidget)) + { + annotations.add(annotation); + } + } + page.setAnnotations(annotations); + } + + // remove the fields + setFields(Collections.emptyList()); + + // remove XFA for hybrid forms + dictionary.removeItem(COSName.XFA); + + } + + /** + * Refreshes the appearance streams and appearance dictionaries for + * the widget annotations of all fields. + * + * @throws IOException + */ + public void refreshAppearances() throws IOException + { + for (PDField field : getFieldTree()) + { + if (field instanceof PDTerminalField) + { + ((PDTerminalField)field).constructAppearances(); + } + } + } + + /** + * Refreshes the appearance streams and appearance dictionaries for + * the widget annotations of the specified fields. + * + * @param fields + * + * @throws IOException + */ + public void refreshAppearances(List fields) throws IOException + { + for (PDField field : fields) + { + if (field instanceof PDTerminalField) + { + ((PDTerminalField)field).constructAppearances(); + } + } + } + /** * This will return all of the documents root fields. * @@ -163,7 +329,7 @@ public List getFields() { COSArray cosFields = (COSArray) dictionary.getDictionaryObject(COSName.FIELDS); if( cosFields == null ) - { + { return Collections.emptyList(); } List pdFields = new ArrayList(); @@ -192,6 +358,22 @@ public void setFields(List fields) dictionary.setItem(COSName.FIELDS, COSArrayList.converterToCOSArray(fields)); } + /** + * Returns an iterator which walks all fields in the field tree, in order. + */ + public Iterator getFieldIterator() + { + return new PDFieldTree(this).iterator(); + } + + /** + * Return the field tree representing all form fields + */ + public PDFieldTree getFieldTree() + { + return new PDFieldTree(this); + } + /** * This will tell this form to cache the fields into a Map structure * for fast access via the getField method. The default is false. You would @@ -199,16 +381,14 @@ public void setFields(List fields) * otherwise setting this to true is acceptable. * * @param cache A boolean telling if we should cache the fields. - * @throws IOException If there is an error while caching the fields. */ - public void setCacheFields( boolean cache ) throws IOException + public void setCacheFields(boolean cache) { if( cache ) { fieldCache = new HashMap(); - // fixme: this code does not cache non-terminal fields or their kids - List fields = getFields(); - for (PDField field : fields) + + for (PDField field : getFieldTree()) { fieldCache.put(field.getFullyQualifiedName(), field); } @@ -234,56 +414,25 @@ public boolean isCachingFields() * * @param fullyQualifiedName The name of the field to get. * @return The field with that name of null if one was not found. - * @throws IOException If there is an error getting the field type. */ - public PDField getField(String fullyQualifiedName) throws IOException + public PDField getField(String fullyQualifiedName) { - PDField retval = null; + // get the field from the cache if there is one. if( fieldCache != null ) { - retval = fieldCache.get(fullyQualifiedName); + return fieldCache.get(fullyQualifiedName); } - else - { - String[] nameSubSection = fullyQualifiedName.split("\\."); - COSArray fields = (COSArray) dictionary.getDictionaryObject(COSName.FIELDS); - for (int i = 0; i < fields.size() && retval == null; i++) + // get the field from the field tree + for (PDField field : getFieldTree()) + { + if (field.getFullyQualifiedName().compareTo(fullyQualifiedName) == 0) { - COSDictionary element = (COSDictionary) fields.getObject(i); - if( element != null ) - { - COSString fieldName = - (COSString)element.getDictionaryObject( COSName.T ); - if (fieldName.getString().equals(fullyQualifiedName) || - fieldName.getString().equals( nameSubSection[0] ) ) - { - PDField root = PDField.fromDictionary(this, element, null); - - if (root != null) - { - if (nameSubSection.length > 1) - { - PDField kid = root.findKid(nameSubSection, 1); - if (kid != null) - { - retval = kid; - } - else - { - retval = root; - } - } - else - { - retval = root; - } - } - } - } + return field; } } - return retval; + + return null; } /** @@ -293,8 +442,7 @@ public PDField getField(String fullyQualifiedName) throws IOException */ public String getDefaultAppearance() { - COSString defaultAppearance = (COSString) dictionary.getItem(COSName.DA); - return defaultAppearance.getString(); + return dictionary.getString(COSName.DA, ""); } /** @@ -340,7 +488,7 @@ public PDResources getDefaultResources() COSDictionary dr = (COSDictionary) dictionary.getDictionaryObject(COSName.DR); if( dr != null ) { - retval = new PDResources( dr ); + retval = new PDResources(dr, document.getResourceCache()); } return retval; } @@ -354,7 +502,7 @@ public void setDefaultResources( PDResources dr ) { dictionary.setItem(COSName.DR, dr); } - + /** * This will tell if the AcroForm has XFA content. * @@ -372,7 +520,7 @@ public boolean hasXFA() */ public boolean xfaIsDynamic() { - return hasXFA() && getFields().isEmpty(); + return hasXFA() && getFields().isEmpty(); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDButton.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDButton.java index 3cef30e29..94b1f5058 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDButton.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDButton.java @@ -19,7 +19,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; @@ -29,6 +31,7 @@ import com.tom_roush.pdfbox.pdmodel.common.COSArrayList; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceEntry; /** * A button field represents an interactive control on the screen @@ -42,10 +45,12 @@ public abstract class PDButton extends PDTerminalField * A Ff flag. If set, the field is a set of radio buttons */ static final int FLAG_RADIO = 1 << 15; + /** * A Ff flag. If set, the field is a pushbutton. */ static final int FLAG_PUSHBUTTON = 1 << 16; + /** * A Ff flag. If set, radio buttons individual fields, using the same * value for the on state will turn on and off in unison. @@ -53,14 +58,14 @@ public abstract class PDButton extends PDTerminalField static final int FLAG_RADIOS_IN_UNISON = 1 << 25; /** - * @see PDField#PDField(PDAcroForm) (PDAcroForm) + * @see PDField#PDField(PDAcroForm) * * @param acroForm The acroform. */ public PDButton(PDAcroForm acroForm) { super(acroForm); - dictionary.setItem(COSName.FT, COSName.BTN); + getCOSObject().setItem(COSName.FT, COSName.BTN); } /** @@ -82,7 +87,7 @@ public PDButton(PDAcroForm acroForm) */ public boolean isPushButton() { - return dictionary.getFlag(COSName.FF, FLAG_PUSHBUTTON); + return getCOSObject().getFlag(COSName.FF, FLAG_PUSHBUTTON); } /** @@ -92,7 +97,7 @@ public boolean isPushButton() */ public void setPushButton(boolean pushbutton) { - dictionary.setFlag(COSName.FF, FLAG_PUSHBUTTON, pushbutton); + getCOSObject().setFlag(COSName.FF, FLAG_PUSHBUTTON, pushbutton); } /** @@ -102,7 +107,7 @@ public void setPushButton(boolean pushbutton) */ public boolean isRadioButton() { - return dictionary.getFlag(COSName.FF, FLAG_RADIO); + return getCOSObject().getFlag(COSName.FF, FLAG_RADIO); } /** @@ -112,7 +117,93 @@ public boolean isRadioButton() */ public void setRadioButton(boolean radiobutton) { - dictionary.setFlag(COSName.FF, FLAG_RADIO, radiobutton); + getCOSObject().setFlag(COSName.FF, FLAG_RADIO, radiobutton); + } + + /** + * Returns the selected value. May be empty if NoToggleToOff is set but there is no value + * selected. + * + * @return A non-null string. + */ + public String getValue() + { + COSBase value = getInheritableAttribute(COSName.V); + if (value instanceof COSName) + { + return ((COSName)value).getName(); + } + else + { + return ""; + } + } + + /** + * Sets the selected option given its name. + * + * @param value Name of option to select + * + * @throws IOException if the value could not be set + * @throws IllegalArgumentException if the value is not a valid option. + */ + @Override + public void setValue(String value) throws IOException + { + checkValue(value); + getCOSObject().setName(COSName.V, value); + // update the appearance state (AS) + for (PDAnnotationWidget widget : getWidgets()) + { + PDAppearanceEntry appearanceEntry = widget.getAppearance().getNormalAppearance(); + if (((COSDictionary)appearanceEntry.getCOSObject()).containsKey(value)) + { + widget.getCOSObject().setName(COSName.AS, value); + } + else + { + widget.getCOSObject().setItem(COSName.AS, COSName.Off); + } + } + applyChange(); + } + + + /** + * Returns the default value, if any. + * + * @return A non-null string. + */ + public String getDefaultValue() + { + COSBase value = getInheritableAttribute(COSName.DV); + if (value instanceof COSName) + { + return ((COSName)value).getName(); + } + else + { + return ""; + } + } + + /** + * Sets the default value. + * + * @param value Name of option to select + * + * @throws IllegalArgumentException if the value is not a valid option. + */ + public void setDefaultValue(String value) + { + checkValue(value); + getCOSObject().setName(COSName.DV, value); + } + + @Override + public String getValueAsString() + { + return getValue(); } /** @@ -161,11 +252,11 @@ public void setExportValues(List values) if (values != null && !values.isEmpty()) { cosValues = COSArrayList.convertStringListToCOSStringCOSArray(values); - dictionary.setItem(COSName.OPT, cosValues); + getCOSObject().setItem(COSName.OPT, cosValues); } else { - dictionary.removeItem(COSName.OPT); + getCOSObject().removeItem(COSName.OPT); } } @@ -181,6 +272,76 @@ void constructAppearances() throws IOException throw new UnsupportedOperationException( "Appearance generation is not implemented yet, see PDFBOX-2849"); } + else + { + PDAppearanceEntry appearanceEntry = widget.getAppearance().getNormalAppearance(); + String value = getValue(); + if (((COSDictionary)appearanceEntry.getCOSObject()).containsKey(value)) + { + widget.getCOSObject().setName(COSName.AS, value); + } + else + { + widget.getCOSObject().setItem(COSName.AS, COSName.Off); + } + } + } + } + + /** + * Get the values to set individual buttons within a group to the on state. + * + *

    The On value could be an arbitrary string as long as it is within the limitations of + * a PDF name object. The Off value shall always be 'Off'. If not set or not part of the normal + * appearance keys 'Off' is the default

    + * + * @return the potential values setting the check box to the On state. + * If an empty Set is returned there is no appearance definition. + */ + public Set getOnValues() + { + // we need a set as the field can appear multiple times + Set onValues = new HashSet(); + + List widgets = this.getWidgets(); + for (PDAnnotationWidget widget : widgets) + { + PDAppearanceDictionary apDictionary = widget.getAppearance(); + if (apDictionary != null) + { + PDAppearanceEntry normalAppearance = apDictionary.getNormalAppearance(); + if (normalAppearance != null) + { + Set entries = normalAppearance.getSubDictionary().keySet(); + for (COSName entry : entries) + { + if (COSName.Off.compareTo(entry) != 0) + { + onValues.add(entry.getName()); + } + } + } + } + } + return onValues; + } + + /** + * Checks value. + * + * @param value Name of radio button to select + * + * @throws IllegalArgumentException if the value is not a valid option. + */ + void checkValue(String value) throws IllegalArgumentException + { + Set onValues = getOnValues(); + if (COSName.Off.getName().compareTo(value) != 0 && !onValues.contains(value)) + { + throw new IllegalArgumentException( + "value '" + value + "' is not a valid option for the field " + + getFullyQualifiedName() + ", valid values are: " + onValues + " and " + + COSName.Off.getName()); } } -} \ No newline at end of file +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDCheckBox.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDCheckBox.java new file mode 100644 index 000000000..98d1c65dc --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDCheckBox.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.interactive.form; + +import java.io.IOException; +import java.util.Set; + +import com.tom_roush.pdfbox.cos.COSDictionary; +import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceEntry; + +/** + * A check box toggles between two states, on and off. + * + * @author Ben Litchfield + * @author sug + */ +public final class PDCheckBox extends PDButton +{ + /** + * @see PDField#PDField(PDAcroForm) + * + * @param acroForm The acroform. + */ + public PDCheckBox(PDAcroForm acroForm) + { + super(acroForm); + } + + /** + * Constructor. + * + * @param acroForm The form that this field is part of. + * @param field the PDF object to represent as a field. + * @param parent the parent node of the node + */ + PDCheckBox(PDAcroForm acroForm, COSDictionary field, PDNonTerminalField parent) + { + super(acroForm, field, parent); + } + + /** + * This will tell if this radio button is currently checked or not. + * This is equivalent to calling {@link #getValue()}. + * + * @return true If this field is checked. + */ + public boolean isChecked() + { + return getValue().compareTo(getOnValue()) == 0; + } + + /** + * Checks the check box. + * + * @throws IOException if the appearance couldn't be generated. + */ + public void check() throws IOException + { + setValue(getOnValue()); + } + + /** + * Unchecks the check box. + * + * @throws IOException if the appearance couldn't be generated. + */ + public void unCheck() throws IOException + { + setValue(COSName.Off.getName()); + } + + /** + * Get the value which sets the check box to the On state. + * + *

    The On value should be 'Yes' but other values are possible + * so we need to look for that. On the other hand the Off value shall + * always be 'Off'. If not set or not part of the normal appearance keys + * 'Off' is the default

    + * + * @return the value setting the check box to the On state. + * If an empty string is returned there is no appearance definition. + */ + public String getOnValue() + { + PDAnnotationWidget widget = this.getWidgets().get(0); + PDAppearanceDictionary apDictionary = widget.getAppearance(); + + String onValue = ""; + if (apDictionary != null) + { + PDAppearanceEntry normalAppearance = apDictionary.getNormalAppearance(); + if (normalAppearance != null) + { + Set entries = normalAppearance.getSubDictionary().keySet(); + for (COSName entry : entries) + { + if (COSName.Off.compareTo(entry) != 0) + { + onValue = entry.getName(); + } + } + } + } + return onValue; + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDCheckbox.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDCheckbox.java deleted file mode 100644 index 57f7831a0..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDCheckbox.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.interactive.form; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import com.tom_roush.pdfbox.cos.COSBase; -import com.tom_roush.pdfbox.cos.COSDictionary; -import com.tom_roush.pdfbox.cos.COSName; -import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; -import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; -import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceEntry; - -/** - * A check box toggles between two states, on and off. - * - * @author Ben Litchfield - * @author sug - */ -public final class PDCheckbox extends PDButton -{ - /** - * @see PDField#PDField(PDAcroForm) - * - * @param acroForm The acroform. - */ - public PDCheckbox(PDAcroForm acroForm) - { - super(acroForm); - } - - /** - * Constructor. - * - * @param acroForm The form that this field is part of. - * @param field the PDF object to represent as a field. - * @param parent the parent node of the node - */ - public PDCheckbox(PDAcroForm acroForm, COSDictionary field, PDNonTerminalField parent) - { - super(acroForm, field, parent); - } - - /** - * This will tell if this radio button is currently checked or not. - * This is equivalent to calling {@link #getValue()}. - * - * @return true If this field is checked. - */ - public boolean isChecked() - { - return getValue().compareTo(getOnValue()) == 0; - } - - /** - * Checks the check box. - * - * @throws IOException if the appearance couldn't be generated. - */ - public void check() throws IOException - { - setValue(getOnValue()); - } - - /** - * Unchecks the check box. - * - * @throws IOException if the appearance couldn't be generated. - */ - public void unCheck() throws IOException - { - setValue(COSName.Off.getName()); - } - - /** - * Returns the fields value entry. - * - * @return the fields value entry. - */ - public String getValue() - { - // the dictionary shall be a name object but it might not be - // so don't assume it is. - COSBase value = getInheritableAttribute(COSName.V); - if (value instanceof COSName) - { - return ((COSName) value).getName(); - } - else - { - return ""; - } - } - - /** - * Returns the default value, if any. - * - * @return the fields default value. - */ - public String getDefaultValue() - { - // the dictionary shall be a name object but it might not be - // so don't assume it is. - COSBase value = getInheritableAttribute(COSName.DV); - if (value instanceof COSName) - { - return ((COSName) value).getName(); - } - else - { - return ""; - } - } - - @Override - public String getValueAsString() - { - return getValue(); - } - - /** - * Sets the checked value of this field. - * - *

    To retrieve the potential On value use {@link #getOnValue()} or - * {@link #getOnValues()}. The Off value shall always be 'Off'.

    - * - * @param value matching the On or Off state of the checkbox. - * @throws IOException if the appearance couldn't be generated. - * @throws IllegalArgumentException if the value is not a valid option for the checkbox. - */ - public void setValue(String value) throws IOException - { - if (value.compareTo(getOnValue()) != 0 && value.compareTo(COSName.Off.getName()) != 0) - { - throw new IllegalArgumentException( - value + " is not a valid option for the checkbox " + getFullyQualifiedName()); - } - else - { - // Update the field value and the appearance state. - // Both are necessary to work properly with different viewers. - COSName name = COSName.getPDFName(value); - dictionary.setItem(COSName.V, name); - dictionary.setItem(COSName.AS, name); - } - - applyChange(); - } - - /** - * Sets the default value. - * - * @see #setValue(String) - * @param value matching the On or Off state of the checkbox. - */ - public void setDefaultValue(String value) - { - if (value.compareTo(getOnValue()) != 0 && value.compareTo(COSName.Off.getName()) != 0) - { - throw new IllegalArgumentException( - value + " is not a valid option for the checkbox " + getFullyQualifiedName()); - } - else - { - dictionary.setName(COSName.DV, value); - } - } - - /** - * Get the value which sets the check box to the On state. - * - *

    The On value should be 'Yes' but other values are possible - * so we need to look for that. On the other hand the Off value shall - * always be 'Off'. If not set or not part of the normal appearance keys - * 'Off' is the default

    - * - * @return the value setting the check box to the On state. - * If an empty string is returned there is no appearance definition. - * @throws IOException if the value could not be set - */ - public String getOnValue() - { - PDAnnotationWidget widget = this.getWidgets().get(0); - PDAppearanceDictionary apDictionary = widget.getAppearance(); - - String onValue = ""; - if (apDictionary != null) - { - PDAppearanceEntry normalAppearance = apDictionary.getNormalAppearance(); - if (normalAppearance != null) - { - Set entries = normalAppearance.getSubDictionary().keySet(); - for (COSName entry : entries) - { - if (COSName.Off.compareTo(entry) != 0) - { - onValue = entry.getName(); - } - } - } - } - return onValue; - } - - /** - * Get the values which sets the check box to the On state. - * - *

    This is a convenience function to provide a similar method to - * {@link PDRadioButton}

    - * - * @return the value setting the check box to the On state. - * If an empty List is returned there is no appearance definition. - * @throws IOException if the value could not be set - * @see #getOnValue() - */ - public Set getOnValues() - { - String onValue = getOnValue(); - - if (onValue.isEmpty()) - { - return Collections.emptySet(); - } - else - { - Set onValues = new HashSet<>(); - onValues.add(onValue); - return onValues; - } - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDChoice.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDChoice.java index b64a662c2..6ca85a2be 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDChoice.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDChoice.java @@ -40,6 +40,7 @@ public abstract class PDChoice extends PDVariableText { static final int FLAG_COMBO = 1 << 17; + private static final int FLAG_SORT = 1 << 19; private static final int FLAG_MULTI_SELECT = 1 << 21; private static final int FLAG_DO_NOT_SPELL_CHECK = 1 << 22; @@ -53,7 +54,7 @@ public abstract class PDChoice extends PDVariableText public PDChoice(PDAcroForm acroForm) { super(acroForm); - dictionary.setItem(COSName.FT, COSName.CH); + getCOSObject().setItem(COSName.FT, COSName.CH); } /** @@ -61,7 +62,7 @@ public PDChoice(PDAcroForm acroForm) * * @param acroForm The form that this field is part of. * @param field the PDF object to represent as a field. - * @param parent the parent node of the node to be created + * @param parent the parent node of the node */ PDChoice(PDAcroForm acroForm, COSDictionary field, PDNonTerminalField parent) { @@ -84,11 +85,10 @@ public PDChoice(PDAcroForm acroForm) *

    * * @return List containing the export values. - * @return COSArray containing all options. */ public List getOptions() { - COSBase values = dictionary.getDictionaryObject(COSName.OPT); + COSBase values = getCOSObject().getDictionaryObject(COSName.OPT); return FieldUtils.getPairableItems(values, 0); } @@ -115,12 +115,12 @@ public void setOptions(List displayValues) { Collections.sort(displayValues); } - dictionary.setItem(COSName.OPT, + getCOSObject().setItem(COSName.OPT, COSArrayList.convertStringListToCOSStringCOSArray(displayValues)); } else { - dictionary.removeItem(COSName.OPT); + getCOSObject().removeItem(COSName.OPT); } } @@ -154,10 +154,12 @@ public void setOptions(List exportValues, List displayValues) else { List keyValuePairs = FieldUtils.toKeyValueList(exportValues, displayValues); + if (isSort()) { FieldUtils.sortByValue(keyValuePairs); } + COSArray options = new COSArray(); for (int i = 0; i exportValues, List displayValues) entry.add(new COSString(keyValuePairs.get(i).getValue())); options.add(entry); } - dictionary.setItem(COSName.OPT, options); + getCOSObject().setItem(COSName.OPT, options); } } else { - dictionary.removeItem(COSName.OPT); + getCOSObject().removeItem(COSName.OPT); } } @@ -189,7 +191,7 @@ public void setOptions(List exportValues, List displayValues) */ public List getOptionsDisplayValues() { - COSBase values = dictionary.getDictionaryObject(COSName.OPT); + COSBase values = getCOSObject().getDictionaryObject(COSName.OPT); return FieldUtils.getPairableItems(values, 1); } @@ -223,7 +225,7 @@ public List getOptionsExportValues() */ public List getSelectedOptionsIndex() { - COSBase value = dictionary.getDictionaryObject(COSName.I); + COSBase value = getCOSObject().getDictionaryObject(COSName.I); if (value != null) { return COSArrayList.convertIntegerCOSArrayToList((COSArray) value); @@ -255,11 +257,11 @@ public void setSelectedOptionsIndex(List values) throw new IllegalArgumentException( "Setting the indices is not allowed for choice fields not allowing multiple selections."); } - dictionary.setItem(COSName.I, COSArrayList.converterToCOSArray(values)); + getCOSObject().setItem(COSName.I, COSArrayList.converterToCOSArray(values)); } else { - dictionary.removeItem(COSName.I); + getCOSObject().removeItem(COSName.I); } } @@ -276,7 +278,7 @@ public void setSelectedOptionsIndex(List values) */ public boolean isSort() { - return dictionary.getFlag(COSName.FF, FLAG_SORT); + return getCOSObject().getFlag(COSName.FF, FLAG_SORT); } /** @@ -287,7 +289,7 @@ public boolean isSort() */ public void setSort( boolean sort ) { - dictionary.setFlag(COSName.FF, FLAG_SORT, sort); + getCOSObject().setFlag(COSName.FF, FLAG_SORT, sort); } /** @@ -297,7 +299,7 @@ public void setSort( boolean sort ) */ public boolean isMultiSelect() { - return dictionary.getFlag(COSName.FF, FLAG_MULTI_SELECT); + return getCOSObject().getFlag(COSName.FF, FLAG_MULTI_SELECT); } /** @@ -307,7 +309,7 @@ public boolean isMultiSelect() */ public void setMultiSelect( boolean multiSelect ) { - dictionary.setFlag(COSName.FF, FLAG_MULTI_SELECT, multiSelect); + getCOSObject().setFlag(COSName.FF, FLAG_MULTI_SELECT, multiSelect); } /** @@ -317,7 +319,7 @@ public void setMultiSelect( boolean multiSelect ) */ public boolean isDoNotSpellCheck() { - return dictionary.getFlag(COSName.FF, FLAG_DO_NOT_SPELL_CHECK); + return getCOSObject().getFlag(COSName.FF, FLAG_DO_NOT_SPELL_CHECK); } /** @@ -327,7 +329,7 @@ public boolean isDoNotSpellCheck() */ public void setDoNotSpellCheck( boolean doNotSpellCheck ) { - dictionary.setFlag(COSName.FF, FLAG_DO_NOT_SPELL_CHECK, doNotSpellCheck); + getCOSObject().setFlag(COSName.FF, FLAG_DO_NOT_SPELL_CHECK, doNotSpellCheck); } /** @@ -337,7 +339,7 @@ public void setDoNotSpellCheck( boolean doNotSpellCheck ) */ public boolean isCommitOnSelChange() { - return dictionary.getFlag(COSName.FF, FLAG_COMMIT_ON_SEL_CHANGE); + return getCOSObject().getFlag(COSName.FF, FLAG_COMMIT_ON_SEL_CHANGE); } /** @@ -347,7 +349,7 @@ public boolean isCommitOnSelChange() */ public void setCommitOnSelChange( boolean commitOnSelChange ) { - dictionary.setFlag(COSName.FF, FLAG_COMMIT_ON_SEL_CHANGE, commitOnSelChange); + getCOSObject().setFlag(COSName.FF, FLAG_COMMIT_ON_SEL_CHANGE, commitOnSelChange); } /** @@ -357,7 +359,7 @@ public void setCommitOnSelChange( boolean commitOnSelChange ) */ public boolean isCombo() { - return dictionary.getFlag(COSName.FF, FLAG_COMBO); + return getCOSObject().getFlag(COSName.FF, FLAG_COMBO); } /** @@ -367,7 +369,7 @@ public boolean isCombo() */ public void setCombo( boolean combo ) { - dictionary.setFlag(COSName.FF, FLAG_COMBO, combo); + getCOSObject().setFlag(COSName.FF, FLAG_COMBO, combo); } /** @@ -376,9 +378,10 @@ public void setCombo( boolean combo ) * @param value The name of the selected item. * @throws IOException if the value could not be set */ + @Override public void setValue(String value) throws IOException { - dictionary.setString(COSName.V, value); + getCOSObject().setString(COSName.V, value); // remove I key for single valued choice field setSelectedOptionsIndex(null); @@ -394,7 +397,7 @@ public void setValue(String value) throws IOException */ public void setDefaultValue(String value) throws IOException { - dictionary.setString(COSName.DV, value); + getCOSObject().setString(COSName.DV, value); } /** @@ -415,13 +418,13 @@ public void setValue(List values) throws IOException { throw new IllegalArgumentException("The values are not contained in the selectable options."); } - dictionary + getCOSObject() .setItem(COSName.V, COSArrayList.convertStringListToCOSStringCOSArray(values)); updateSelectedOptionsIndex(values); } else { - dictionary.removeItem(COSName.V); + getCOSObject().removeItem(COSName.V); } applyChange(); } @@ -453,7 +456,7 @@ public List getDefaultValue() */ private List getValueFor(COSName name) { - COSBase value = dictionary.getDictionaryObject(name); + COSBase value = getCOSObject().getDictionaryObject(name); if (value instanceof COSString) { List array = new ArrayList(); @@ -491,5 +494,6 @@ private void updateSelectedOptionsIndex(List values) setSelectedOptionsIndex(indices); } + @Override abstract void constructAppearances() throws IOException; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDComboBox.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDComboBox.java index e21bd4616..13fe68d2d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDComboBox.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDComboBox.java @@ -35,11 +35,11 @@ public final class PDComboBox extends PDChoice /** * @see PDField#PDField(PDAcroForm) * - * @param acroform The acroform. + * @param acroForm The acroForm. */ - public PDComboBox(PDAcroForm acroform) + public PDComboBox(PDAcroForm acroForm) { - super(acroform); + super(acroForm); setCombo(true); } @@ -62,7 +62,7 @@ public PDComboBox(PDAcroForm acroform) */ public boolean isEdit() { - return dictionary.getFlag(COSName.FF, FLAG_EDIT); + return getCOSObject().getFlag(COSName.FF, FLAG_EDIT); } /** @@ -72,7 +72,7 @@ public boolean isEdit() */ public void setEdit(boolean edit) { - dictionary.setFlag(COSName.FF, FLAG_EDIT, edit); + getCOSObject().setFlag(COSName.FF, FLAG_EDIT, edit); } @Override diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDDefaultAppearanceString.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDDefaultAppearanceString.java index 4b38cc5df..6e89b8f46 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDDefaultAppearanceString.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDDefaultAppearanceString.java @@ -16,19 +16,28 @@ */ package com.tom_roush.pdfbox.pdmodel.interactive.form; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import com.tom_roush.pdfbox.contentstream.operator.Operator; +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSNumber; +import com.tom_roush.pdfbox.cos.COSObject; import com.tom_roush.pdfbox.cos.COSString; import com.tom_roush.pdfbox.pdfparser.PDFStreamParser; import com.tom_roush.pdfbox.pdmodel.PDPageContentStream; import com.tom_roush.pdfbox.pdmodel.PDResources; import com.tom_roush.pdfbox.pdmodel.font.PDFont; +import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColor; +import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; +import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceColorSpace; +import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceRGB; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; -import java.io.IOException; -import java.util.List; - /** * Represents a default appearance string, as found in the /DA entry of free text annotations. * @@ -46,9 +55,13 @@ class PDDefaultAppearanceString */ private static final float DEFAULT_FONT_SIZE = 12; - private final List tokens; private final PDResources defaultResources; + private COSName fontName; + private PDFont font; + private float fontSize = DEFAULT_FONT_SIZE; + private PDColor fontColor; + /** * Constructor for reading an existing DA string. * @@ -69,79 +82,195 @@ class PDDefaultAppearanceString throw new IllegalArgumentException("/DR is a required entry"); } - PDFStreamParser parser = new PDFStreamParser(defaultAppearance.getBytes()); - parser.parse(); - tokens = parser.getTokens(); - this.defaultResources = defaultResources; + processAppearanceStringOperators(defaultAppearance.getBytes()); } /** - * Returns the font size. + * Processes the operators of the given content stream. + * + * @param content the content to parse. + * @throws IOException if there is an error reading or parsing the content stream. */ - public float getFontSize() + private void processAppearanceStringOperators(byte[] content) throws IOException { - if (!tokens.isEmpty()) + List arguments = new ArrayList(); + PDFStreamParser parser = new PDFStreamParser(content); + Object token = parser.parseNextToken(); + while (token != null) { - // daString looks like "BMC /Helv 3.4 Tf EMC" - // use the fontsize of the default existing apperance stream - int fontIndex = tokens.indexOf(Operator.getOperator("Tf")); - if (fontIndex != -1) + if (token instanceof COSObject) { - return ((COSNumber) tokens.get(fontIndex - 1)).floatValue(); + arguments.add(((COSObject)token).getObject()); } + else if (token instanceof Operator) + { + processOperator((Operator)token, arguments); + arguments = new ArrayList(); + } + else + { + arguments.add((COSBase)token); + } + token = parser.parseNextToken(); } - - return DEFAULT_FONT_SIZE; } /** - * w in an appearance stream represents the lineWidth. + * This is used to handle an operation. * - * @return the linewidth + * @param operator The operation to perform. + * @param operands The list of arguments. + * @throws IOException If there is an error processing the operation. */ - public float getLineWidth() + private void processOperator(Operator operator, List operands) throws IOException { - float retval = 0f; - if (tokens != null) + String name = operator.getName(); + + if ("Tf".equals(name)) { - int btIndex = tokens.indexOf(Operator.getOperator("BT")); - int wIndex = tokens.indexOf(Operator.getOperator("w")); - // the w should only be used if it is before the first BT. - if (wIndex > 0 && (wIndex < btIndex || btIndex == -1)) - { - retval = ((COSNumber) tokens.get(wIndex - 1)).floatValue(); - } + processSetFont(operands); + } + else if ("rg".equals(name)) + { + processSetFontColor(operands); } - return retval; } /** - * Returns the font. + * Process the set font and font size operator. * - * @throws IOException If the font could not be found. + * @param operands the font name and size + * @throws IOException in case there are missing operators or the font is not within the resources */ - public PDFont getFont() throws IOException + private void processSetFont(List operands) throws IOException { - COSName name = getFontResourceName(); - PDFont font = defaultResources.getFont(name); + if (operands.size() < 2) + { + throw new IOException( + "Missing operands for set font operator " + Arrays.toString(operands.toArray())); + } + + COSBase base0 = operands.get(0); + COSBase base1 = operands.get(1); + if (!(base0 instanceof COSName)) + { + return; + } + if (!(base1 instanceof COSNumber)) + { + return; + } + COSName fontName = (COSName)base0; + + PDFont font = defaultResources.getFont(fontName); + float fontSize = ((COSNumber)base1).floatValue(); // todo: handle cases where font == null with special mapping logic (see PDFBOX-2661) if (font == null) { - throw new IOException("Could not find font: /" + name.getName()); + throw new IOException("Could not find font: /" + fontName.getName()); } + setFontName(fontName); + setFont(font); + setFontSize(fontSize); + } + /** + * Process the font color operator. + * + * This is assumed to be an RGB color. + * + * @param operands the color components + * + * @throws IOException in case of the color components not matching + */ + private void processSetFontColor(List operands) throws IOException + { + PDColorSpace colorSpace = PDDeviceRGB.INSTANCE; + if (colorSpace instanceof PDDeviceColorSpace && + operands.size() < colorSpace.getNumberOfComponents()) + { + throw new IOException("Missing operands for set non stroking color operator " + + Arrays.toString(operands.toArray())); + } + COSArray array = new COSArray(); + array.addAll(operands); + setFontColor(new PDColor(array, colorSpace)); + } + + /** + * Get the font name + * + * @return the font name to use for resource lookup + */ + COSName getFontName() + { + return fontName; + } + + /** + * Set the font name. + * + * @param fontName the font name to use for resource lookup + */ + void setFontName(COSName fontName) + { + this.fontName = fontName; + } + + /** + * Returns the font. + */ + PDFont getFont() throws IOException + { return font; } /** - * Returns the name of the font in the Resources. + * Set the font. + * + * @param font the font to use. + */ + void setFont(PDFont font) + { + this.font = font; + } + + /** + * Returns the font size. + */ + public float getFontSize() + { + return fontSize; + } + + /** + * Set the font size. + * + * @param fontSize the font size. */ - private COSName getFontResourceName() + void setFontSize(float fontSize) { - int setFontOperatorIndex = tokens.indexOf(Operator.getOperator("Tf")); - return (COSName) tokens.get(setFontOperatorIndex - 2); + this.fontSize = fontSize; + } + + /** + * Returns the font color + */ + PDColor getFontColor() + { + return fontColor; + } + + /** + * Set the font color. + * + * @param fontColor the fontColor to use. + */ + void setFontColor(PDColor fontColor) + { + this.fontColor = fontColor; } /** @@ -155,7 +284,11 @@ void writeTo(PDPageContentStream contents, float zeroFontSize) throws IOExceptio fontSize = zeroFontSize; } contents.setFont(getFont(), fontSize); - // todo: set more state... + + if (getFontColor() != null) + { + contents.setNonStrokingColor(getFontColor()); + } } /** @@ -172,9 +305,7 @@ void copyNeededResourcesTo(PDAppearanceStream appearanceStream) throws IOExcepti appearanceStream.setResources(streamResources); } - // fonts - COSName fontName = getFontResourceName(); - if (streamResources.getFont(fontName) == null) + if (streamResources.getFont(getFontName()) == null) { streamResources.put(fontName, getFont()); } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDField.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDField.java index 4d16c8de7..22af48503 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDField.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDField.java @@ -16,6 +16,9 @@ */ package com.tom_roush.pdfbox.pdmodel.interactive.form; +import java.io.IOException; +import java.util.List; + import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; @@ -23,8 +26,7 @@ import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; import com.tom_roush.pdfbox.pdmodel.fdf.FDFField; import com.tom_roush.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions; - -import java.io.IOException; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; /** * A field in an interactive form. @@ -35,22 +37,9 @@ public abstract class PDField implements COSObjectable private static final int FLAG_REQUIRED = 1 << 1; private static final int FLAG_NO_EXPORT = 1 << 2; - /** - * Creates a COSField subclass from the given COS field. This is for reading fields from PDFs. - * - * @param form the form that the field is part of - * @param field the dictionary representing a field element - * @param parent the parent node of the node to be created, or null if root. - * @return a new PDField instance - */ - static PDField fromDictionary(PDAcroForm form, COSDictionary field, PDNonTerminalField parent) - { - return PDFieldFactory.createField(form, field, parent); - } - - protected final PDAcroForm acroForm; - protected final PDNonTerminalField parent; - protected final COSDictionary dictionary; + private final PDAcroForm acroForm; + private final PDNonTerminalField parent; + private final COSDictionary dictionary; /** * Constructor. @@ -76,6 +65,20 @@ static PDField fromDictionary(PDAcroForm form, COSDictionary field, PDNonTermina this.parent = parent; } + /** + * Creates a COSField subclass from the given COS field. This is for reading fields from PDFs. + * + * @param form the form that the field is part of + * @param field the dictionary representing a field element + * @param parent the parent node of the node to be created, or null if root. + * + * @return a new PDField instance + */ + static PDField fromDictionary(PDAcroForm form, COSDictionary field, PDNonTerminalField parent) + { + return PDFieldFactory.createField(form, field, parent); + } + /** * Returns the given attribute, inheriting from parent nodes if necessary. * @@ -102,7 +105,7 @@ else if (parent != null) * 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. * - * @return The Field type. + * @return The list of widget annotations. */ public abstract String getFieldType(); @@ -113,6 +116,26 @@ else if (parent != null) */ public abstract String getValueAsString(); + /** + * Sets the value of the field. + * + * @param value the new field value. + * + * @throws IOException if the value could not be set + */ + public abstract void setValue(String value) throws IOException; + + + /** + * Returns the widget annotations associated with this field. + * + * For {@link PDNonTerminalField} the list will be empty as non terminal fields + * have no visual representation in the form. + * + * @return A non-null string. + */ + public abstract List getWidgets(); + /** * sets the field to be read-only. * @@ -124,6 +147,7 @@ public void setReadOnly(boolean readonly) } /** + * * @return true if the field is readonly */ public boolean isReadOnly() @@ -142,6 +166,7 @@ public void setRequired(boolean required) } /** + * * @return true if the field is required */ public boolean isRequired() @@ -160,6 +185,7 @@ public void setNoExport(boolean noExport) } /** + * * @return true if the field is not to be exported. */ public boolean isNoExport() diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDFieldFactory.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDFieldFactory.java index 71b504388..43b8f7786 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDFieldFactory.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDFieldFactory.java @@ -25,15 +25,15 @@ */ final class PDFieldFactory { - private PDFieldFactory() - { - } - private static final String FIELD_TYPE_TEXT = "Tx"; private static final String FIELD_TYPE_BUTTON = "Btn"; private static final String FIELD_TYPE_CHOICE = "Ch"; private static final String FIELD_TYPE_SIGNATURE = "Sig"; + private PDFieldFactory() + { + } + /** * Creates a COSField subclass from the given field. * @@ -103,7 +103,7 @@ else if ((flags & PDButton.FLAG_PUSHBUTTON) != 0) } else { - return new PDCheckbox(form, field, parent); + return new PDCheckBox(form, field, parent); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDFieldTree.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDFieldTree.java new file mode 100644 index 000000000..7d689d503 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDFieldTree.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.pdmodel.interactive.form; + +import java.util.ArrayDeque; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; + +/** + * The field tree. + */ +public class PDFieldTree implements Iterable +{ + private final PDAcroForm acroForm; + + /** + * Constructor for reading. + * + * @param acroForm the AcroForm containing the fields. + */ + public PDFieldTree(PDAcroForm acroForm) + { + if (acroForm == null) + { + throw new IllegalArgumentException("root cannot be null"); + } + this.acroForm = acroForm; + } + + /** + * Returns an iterator which walks all fields in the tree, in order. + */ + @Override + public Iterator iterator() + { + return new FieldIterator(acroForm); + } + + /** + * Iterator which walks all fields in the tree, in order. + */ + private static final class FieldIterator implements Iterator + { + private final Queue queue = new ArrayDeque(); + + private FieldIterator(PDAcroForm form) + { + List fields = form.getFields(); + for (PDField field : fields) + { + enqueueKids(field); + } + } + + @Override + public boolean hasNext() + { + return !queue.isEmpty(); + } + + @Override + public PDField next() + { + if (!hasNext()) + { + throw new NoSuchElementException(); + } + + return queue.poll(); + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + + private void enqueueKids(PDField node) + { + queue.add(node); + if (node instanceof PDNonTerminalField) + { + List kids = ((PDNonTerminalField)node).getChildren(); + for (PDField kid : kids) + { + enqueueKids(kid); + } + } + } + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDListBox.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDListBox.java index dfe8b3114..cb9379339 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDListBox.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDListBox.java @@ -58,7 +58,7 @@ public PDListBox(PDAcroForm acroForm) */ public int getTopIndex() { - return dictionary.getInt(COSName.TI, 0); + return getCOSObject().getInt(COSName.TI, 0); } /** @@ -70,11 +70,11 @@ public void setTopIndex(Integer topIndex) { if (topIndex != null) { - dictionary.setInt(COSName.TI, topIndex); + getCOSObject().setInt(COSName.TI, topIndex); } else { - dictionary.removeItem(COSName.TI); + getCOSObject().removeItem(COSName.TI); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDNonTerminalField.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDNonTerminalField.java index 36f8f6a89..eb4ee2449 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDNonTerminalField.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDNonTerminalField.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import com.tom_roush.pdfbox.cos.COSArray; @@ -28,6 +29,7 @@ import com.tom_roush.pdfbox.pdmodel.common.COSArrayList; import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; import com.tom_roush.pdfbox.pdmodel.fdf.FDFField; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; /** * A non terminal field in an interactive form. @@ -58,7 +60,7 @@ public PDNonTerminalField(PDAcroForm acroForm) * @param field the PDF object to represent as a field. * @param parent the parent node of the node to be created */ - public PDNonTerminalField(PDAcroForm acroForm, COSDictionary field, PDNonTerminalField parent) + PDNonTerminalField(PDAcroForm acroForm, COSDictionary field, PDNonTerminalField parent) { super(acroForm, field, parent); } @@ -67,7 +69,7 @@ public PDNonTerminalField(PDAcroForm acroForm, COSDictionary field, PDNonTermina public int getFieldFlags() { int retval = 0; - COSInteger ff = (COSInteger) dictionary.getDictionaryObject(COSName.FF); + COSInteger ff = (COSInteger)getCOSObject().getDictionaryObject(COSName.FF); if (ff != null) { retval = ff.intValue(); @@ -127,11 +129,11 @@ FDFField exportFDF() throws IOException public List getChildren() { List children = new ArrayList(); - COSArray kids = (COSArray) dictionary.getDictionaryObject(COSName.KIDS); + COSArray kids = (COSArray)getCOSObject().getDictionaryObject(COSName.KIDS); for (int i = 0; i < kids.size(); i++) { - PDField field = PDField - .fromDictionary(acroForm, (COSDictionary) kids.getObject(i), this); + PDField field = PDField.fromDictionary(getAcroForm(), (COSDictionary)kids.getObject(i), + this); if (field != null) { children.add(field); @@ -148,33 +150,33 @@ public List getChildren() public void setChildren(List children) { COSArray kidsArray = COSArrayList.converterToCOSArray(children); - dictionary.setItem(COSName.KIDS, kidsArray); + getCOSObject().setItem(COSName.KIDS, kidsArray); } /** - * {@inheritDoc} + * @inheritDoc *

    Note: while non-terminal fields do inherit field values, this method returns * the local value, without inheritance. */ @Override public String getFieldType() { - return dictionary.getNameAsString(COSName.FT); + return getCOSObject().getNameAsString(COSName.FT); } /** - * {@inheritDoc} + * @inheritDoc * *

    Note: while non-terminal fields do inherit field values, this method returns * the local value, without inheritance. */ public COSBase getValue() { - return dictionary.getDictionaryObject(COSName.V); + return getCOSObject().getDictionaryObject(COSName.V); } /** - * {@inheritDoc} + * @inheritDoc * *

    Note: while non-terminal fields do inherit field values, this method returns * the local value, without inheritance. @@ -182,7 +184,7 @@ public COSBase getValue() @Override public String getValueAsString() { - COSBase fieldValue = dictionary.getDictionaryObject(COSName.V); + COSBase fieldValue = getCOSObject().getDictionaryObject(COSName.V); return fieldValue != null ? fieldValue.toString() : ""; } @@ -195,7 +197,21 @@ public String getValueAsString() */ public void setValue(COSBase object) throws IOException { - dictionary.setItem(COSName.V, object); + getCOSObject().setItem(COSName.V, object); + // todo: propagate change event to children? + // todo: construct appearances of children? + } + + /** + * Sets the plain text value of this field. + * + * @param value Plain text + * + * @throws IOException if the value could not be set + */ + public void setValue(String value) throws IOException + { + getCOSObject().setString(COSName.V, value); // todo: propagate change event to children? // todo: construct appearances of children? } @@ -209,7 +225,7 @@ public void setValue(COSBase object) throws IOException */ public COSBase getDefaultValue() { - return dictionary.getDictionaryObject(COSName.DV); + return getCOSObject().getDictionaryObject(COSName.DV); } /** @@ -221,6 +237,12 @@ public COSBase getDefaultValue() */ public void setDefaultValue(COSBase value) { - dictionary.setItem(COSName.V, value); + getCOSObject().setItem(COSName.V, value); + } + + @Override + public List getWidgets() + { + return Collections.emptyList(); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDPushButton.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDPushButton.java index 85e34a619..70ce325e7 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDPushButton.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDPushButton.java @@ -16,11 +16,11 @@ */ package com.tom_roush.pdfbox.pdmodel.interactive.form; -import com.tom_roush.pdfbox.cos.COSDictionary; - import java.util.Collections; import java.util.List; +import com.tom_roush.pdfbox.cos.COSDictionary; + /** * A pushbutton is a purely interactive control that responds immediately to user * input without retaining a permanent value. @@ -29,8 +29,8 @@ */ public class PDPushButton extends PDButton { - /** - * @see PDField#PDfield(PDAcroForm) + /** + * @see PDField#PDField(PDAcroForm) * * @param acroForm The acroform. */ @@ -45,7 +45,7 @@ public PDPushButton(PDAcroForm acroForm) * * @param acroForm The form that this field is part of. * @param field the PDF object to represent as a field. - * @param parent the parent node of the node to be created + * @param parent the parent node of the node */ PDPushButton(PDAcroForm acroForm, COSDictionary field, PDNonTerminalField parent) { @@ -69,9 +69,20 @@ public void setExportValues(List values) } @Override - public String getValueAsString() + public String getValue() + { + return ""; + } + + @Override + public String getDefaultValue() { - // PushButton fields don't support the "V" entry. return ""; } + + @Override + public String getValueAsString() + { + return getValue(); + } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDRadioButton.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDRadioButton.java index 823e57a8c..21cad8df9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDRadioButton.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDRadioButton.java @@ -18,16 +18,11 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Set; -import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; -import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; -import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; -import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAppearanceEntry; /** * Radio button fields contain a set of related buttons that can each be on or off. @@ -37,13 +32,14 @@ public final class PDRadioButton extends PDButton { /** - * An Ff flag. + * A Ff flag. */ private static final int FLAG_NO_TOGGLE_TO_OFF = 1 << 14; /** - * @param acroForm The acroform. * @see PDField#PDField(PDAcroForm) + * + * @param acroForm The acroform. */ public PDRadioButton(PDAcroForm acroForm) { @@ -73,15 +69,16 @@ public PDRadioButton(PDAcroForm acroForm) */ public void setRadiosInUnison(boolean radiosInUnison) { - dictionary.setFlag(COSName.FF, FLAG_RADIOS_IN_UNISON, radiosInUnison); + getCOSObject().setFlag(COSName.FF, FLAG_RADIOS_IN_UNISON, radiosInUnison); } /** + * * @return true If the flag is set for radios in unison. */ public boolean isRadiosInUnison() { - return dictionary.getFlag(COSName.FF, FLAG_RADIOS_IN_UNISON); + return getCOSObject().getFlag(COSName.FF, FLAG_RADIOS_IN_UNISON); } /** @@ -101,7 +98,7 @@ public boolean isRadiosInUnison() */ public List getSelectedExportValues() throws IOException { - List onValues = getSelectableOnValues(); + Set onValues = getOnValues(); List exportValues = getExportValues(); List selectedExportValues = new ArrayList(); if (exportValues.isEmpty()) @@ -123,156 +120,4 @@ public List getSelectedExportValues() throws IOException return selectedExportValues; } } - - /** - * Returns the selected value. May be empty if NoToggleToOff is set but there is no value - * selected. - * - * @return A non-null string. - */ - public String getValue() - { - COSBase value = getInheritableAttribute(COSName.V); - if (value instanceof COSName) - { - return ((COSName) value).getName(); - } - else - { - return ""; - } - } - - /** - * Returns the default value, if any. - * - * @return A non-null string. - */ - public String getDefaultValue() - { - COSBase value = getInheritableAttribute(COSName.DV); - if (value instanceof COSName) - { - return ((COSName) value).getName(); - } - else - { - return ""; - } - } - - @Override - public String getValueAsString() - { - return getValue(); - } - - /** - * Sets the selected radio button, given its name. - * - * @param value Name of radio button to select - * @throws IOException if the value could not be set - * @throws IllegalArgumentException if the value is not a valid option. - */ - public void setValue(String value) throws IOException - { - checkValue(value); - dictionary.setName(COSName.V, value); - // update the appearance state (AS) - for (PDAnnotationWidget widget : getWidgets()) - { - PDAppearanceEntry appearanceEntry = widget.getAppearance().getNormalAppearance(); - if (((COSDictionary) appearanceEntry.getCOSObject()).containsKey(value)) - { - widget.getCOSObject().setName(COSName.AS, value); - } - else - { - widget.getCOSObject().setItem(COSName.AS, COSName.Off); - } - } - applyChange(); - } - - /** - * Sets the default value. - * - * @param value Name of radio button to select - * @throws IOException if the value could not be set - * @throws IllegalArgumentException if the value is not a valid option. - */ - public void setDefaultValue(String value) - { - checkValue(value); - dictionary.setName(COSName.DV, value); - } - - /** - * Get the values to set individual radio buttons to the on state. - * - *

    The On value could be an arbitrary string as long as it is within the limitations of - * a PDF name object. The Off value shall always be 'Off'. If not set or not part of the normal - * appearance keys 'Off' is the default

    - * - * @return the value setting the check box to the On state. - * If an empty string is returned there is no appearance definition. - */ - public Set getOnValues() - { - // we need a set as the radio buttons can appear multiple times - Set onValues = new HashSet(); - onValues.addAll(getSelectableOnValues()); - return onValues; - } - - /** - * Checks value. - * - * @param value Name of radio button to select - * @throws IllegalArgumentException if the value is not a valid option. - */ - private void checkValue(String value) throws IllegalArgumentException - { - Set onValues = getOnValues(); - if (COSName.Off.getName().compareTo(value) != 0 && !onValues.contains(value)) - { - throw new IllegalArgumentException("value '" + value - + "' is not a valid option for the radio button " + getFullyQualifiedName() - + ", valid values are: " + onValues + " and " + COSName.Off.getName()); - } - } - - /** - * Get all potential ON values. - * - * @return the ON values. - */ - private List getSelectableOnValues() - { - List widgets = this.getWidgets(); - // we need a set as the radio buttons can appear multiple times - List onValues = new ArrayList(); - - for (PDAnnotationWidget widget : widgets) - { - PDAppearanceDictionary apDictionary = widget.getAppearance(); - - if (apDictionary != null) - { - PDAppearanceEntry normalAppearance = apDictionary.getNormalAppearance(); - if (normalAppearance != null) - { - Set entries = normalAppearance.getSubDictionary().keySet(); - for (COSName entry : entries) - { - if (COSName.Off.compareTo(entry) != 0) - { - onValues.add(entry.getName()); - } - } - } - } - } - return onValues; - } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDSignatureField.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDSignatureField.java index 4e413eb6b..3dfc36ab0 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDSignatureField.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDSignatureField.java @@ -45,7 +45,7 @@ public class PDSignatureField extends PDTerminalField public PDSignatureField(PDAcroForm acroForm) throws IOException { super(acroForm); - dictionary.setItem(COSName.FT, COSName.SIG); + getCOSObject().setItem(COSName.FT, COSName.SIG); getWidgets().get(0).setLocked(true); getWidgets().get(0).setPrinted(true); setPartialName(generatePartialName()); @@ -73,7 +73,7 @@ private String generatePartialName() String fieldName = "Signature"; Set sigNames = new HashSet(); // fixme: this ignores non-terminal fields, so will miss any descendant signatures - for (PDField field : acroForm.getFields()) + for (PDField field : getAcroForm().getFields()) { if (field instanceof PDSignatureField) { @@ -101,7 +101,7 @@ public void setSignature(PDSignature value) throws IOException } /** - * Get the signature dictionary. + * Get the signature getCOSObject(). * * @return the signature dictionary */ @@ -117,10 +117,28 @@ public PDSignature getSignature() */ public void setValue(PDSignature value) throws IOException { - dictionary.setItem(COSName.V, value); + getCOSObject().setItem(COSName.V, value); applyChange(); } + /** + * Sets the value of this field. + * + * This will throw an UnsupportedOperationException if used as the signature fields + * value can't be set using a String + * + * @param value the plain text value. + * + * @throws UnsupportedOperationException in all cases! + */ + @Override + public void setValue(String value) throws UnsupportedOperationException + { + throw new UnsupportedOperationException( + "Signature fields don't support setting the value as String " + + "- use setValue(PDSignature value) instead"); + } + /** * Sets the default value of this field to be the given signature. * @@ -128,7 +146,7 @@ public void setValue(PDSignature value) throws IOException */ public void setDefaultValue(PDSignature value) throws IOException { - dictionary.setItem(COSName.DV, value); + getCOSObject().setItem(COSName.DV, value); } /** @@ -138,7 +156,7 @@ public void setDefaultValue(PDSignature value) throws IOException */ public PDSignature getValue() { - COSBase value = dictionary.getDictionaryObject(COSName.V); + COSBase value = getCOSObject().getDictionaryObject(COSName.V); if (value == null) { return null; @@ -153,7 +171,7 @@ public PDSignature getValue() */ public PDSignature getDefaultValue() { - COSBase value = dictionary.getDictionaryObject(COSName.DV); + COSBase value = getCOSObject().getDictionaryObject(COSName.DV); if (value == null) { return null; @@ -177,7 +195,7 @@ public String getValueAsString() */ public PDSeedValue getSeedValue() { - COSDictionary dict = (COSDictionary) dictionary.getDictionaryObject(COSName.SV); + COSDictionary dict = (COSDictionary)getCOSObject().getDictionaryObject(COSName.SV); PDSeedValue sv = null; if (dict != null) { @@ -197,7 +215,7 @@ public void setSeedValue(PDSeedValue sv) { if (sv != null) { - dictionary.setItem(COSName.SV, sv); + getCOSObject().setItem(COSName.SV, sv); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDTerminalField.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDTerminalField.java index d2abfe7b4..94a1db0bd 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDTerminalField.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDTerminalField.java @@ -16,6 +16,10 @@ */ package com.tom_roush.pdfbox.pdmodel.interactive.form; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + import com.tom_roush.pdfbox.cos.COSArray; import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; @@ -26,10 +30,6 @@ import com.tom_roush.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - /** * A field in an interactive form. * Fields may be one of four types: button, text, choice, or signature. @@ -67,21 +67,21 @@ protected PDTerminalField(PDAcroForm acroForm) */ public void setActions(PDFormFieldAdditionalActions actions) { - dictionary.setItem(COSName.AA, actions); + getCOSObject().setItem(COSName.AA, actions); } @Override public int getFieldFlags() { int retval = 0; - COSInteger ff = (COSInteger) dictionary.getDictionaryObject(COSName.FF); + COSInteger ff = (COSInteger)getCOSObject().getDictionaryObject(COSName.FF); if (ff != null) { retval = ff.intValue(); } - else if (parent != null) + else if (getParent() != null) { - retval = parent.getFieldFlags(); + retval = getParent().getFieldFlags(); } return retval; } @@ -89,10 +89,10 @@ else if (parent != null) @Override public String getFieldType() { - String fieldType = dictionary.getNameAsString(COSName.FT); - if (fieldType == null && parent != null) + String fieldType = getCOSObject().getNameAsString(COSName.FT); + if (fieldType == null && getParent() != null) { - fieldType = parent.getFieldType(); + fieldType = getParent().getFieldType(); } return fieldType; } @@ -146,7 +146,7 @@ FDFField exportFDF() throws IOException { FDFField fdfField = new FDFField(); fdfField.setPartialFieldName(getPartialName()); - fdfField.setValue(dictionary.getDictionaryObject(COSName.V)); + fdfField.setValue(getCOSObject().getDictionaryObject(COSName.V)); // fixme: the old code which was here assumed that Kids were PDField instances, // which is never true. They're annotation widgets. @@ -159,14 +159,15 @@ FDFField exportFDF() throws IOException * * @return The list of widget annotations. */ + @Override public List getWidgets() { List widgets = new ArrayList(); - COSArray kids = (COSArray) dictionary.getDictionaryObject(COSName.KIDS); + COSArray kids = (COSArray)getCOSObject().getDictionaryObject(COSName.KIDS); if (kids == null) { // the field itself is a widget - widgets.add(new PDAnnotationWidget(dictionary)); + widgets.add(new PDAnnotationWidget(getCOSObject())); } else if (kids.size() > 0) { @@ -191,7 +192,7 @@ else if (kids.size() > 0) public void setWidgets(List children) { COSArray kidsArray = COSArrayList.converterToCOSArray(children); - dictionary.setItem(COSName.KIDS, kidsArray); + getCOSObject().setItem(COSName.KIDS, kidsArray); } /** @@ -216,12 +217,12 @@ public PDAnnotationWidget getWidget() */ protected final void applyChange() throws IOException { - if(!acroForm.getNeedAppearances()) + if(!getAcroForm().getNeedAppearances()) { constructAppearances(); } + // if we supported JavaScript we would raise a field changed event here } - // if we supported JavaScript we would raise a field changed event here /** * Constructs appearance streams and appearance dictionaries for all widget annotations. diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDTextField.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDTextField.java index 4bfd40f4e..cf81b287f 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDTextField.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDTextField.java @@ -45,7 +45,7 @@ public final class PDTextField extends PDVariableText public PDTextField(PDAcroForm acroForm) { super(acroForm); - dictionary.setItem(COSName.FT, COSName.TX); + getCOSObject().setItem(COSName.FT, COSName.TX); } /** @@ -55,7 +55,7 @@ public PDTextField(PDAcroForm acroForm) * @param field the PDF object to represent as a field. * @param parent the parent node of the node */ - public PDTextField(PDAcroForm acroForm, COSDictionary field, PDNonTerminalField parent) + PDTextField(PDAcroForm acroForm, COSDictionary field, PDNonTerminalField parent) { super(acroForm, field, parent); } @@ -65,7 +65,7 @@ public PDTextField(PDAcroForm acroForm, COSDictionary field, PDNonTerminalField */ public boolean isMultiline() { - return dictionary.getFlag(COSName.FF, FLAG_MULTILINE); + return getCOSObject().getFlag(COSName.FF, FLAG_MULTILINE); } /** @@ -75,7 +75,7 @@ public boolean isMultiline() */ public void setMultiline( boolean multiline ) { - dictionary.setFlag(COSName.FF, FLAG_MULTILINE, multiline); + getCOSObject().setFlag(COSName.FF, FLAG_MULTILINE, multiline); } /** @@ -83,7 +83,7 @@ public void setMultiline( boolean multiline ) */ public boolean isPassword() { - return dictionary.getFlag(COSName.FF, FLAG_PASSWORD); + return getCOSObject().getFlag(COSName.FF, FLAG_PASSWORD); } /** @@ -93,7 +93,7 @@ public boolean isPassword() */ public void setPassword( boolean password ) { - dictionary.setFlag(COSName.FF, FLAG_PASSWORD, password); + getCOSObject().setFlag(COSName.FF, FLAG_PASSWORD, password); } /** @@ -101,7 +101,7 @@ public void setPassword( boolean password ) */ public boolean isFileSelect() { - return dictionary.getFlag(COSName.FF, FLAG_FILE_SELECT); + return getCOSObject().getFlag(COSName.FF, FLAG_FILE_SELECT); } /** @@ -111,7 +111,7 @@ public boolean isFileSelect() */ public void setFileSelect( boolean fileSelect ) { - dictionary.setFlag(COSName.FF, FLAG_FILE_SELECT, fileSelect); + getCOSObject().setFlag(COSName.FF, FLAG_FILE_SELECT, fileSelect); } /** @@ -119,7 +119,7 @@ public void setFileSelect( boolean fileSelect ) */ public boolean doNotSpellCheck() { - return dictionary.getFlag(COSName.FF, FLAG_DO_NOT_SPELL_CHECK); + return getCOSObject().getFlag(COSName.FF, FLAG_DO_NOT_SPELL_CHECK); } /** @@ -129,7 +129,7 @@ public boolean doNotSpellCheck() */ public void setDoNotSpellCheck( boolean doNotSpellCheck ) { - dictionary.setFlag(COSName.FF, FLAG_DO_NOT_SPELL_CHECK, doNotSpellCheck); + getCOSObject().setFlag(COSName.FF, FLAG_DO_NOT_SPELL_CHECK, doNotSpellCheck); } /** @@ -137,7 +137,7 @@ public void setDoNotSpellCheck( boolean doNotSpellCheck ) */ public boolean doNotScroll() { - return dictionary.getFlag(COSName.FF, FLAG_DO_NOT_SCROLL); + return getCOSObject().getFlag(COSName.FF, FLAG_DO_NOT_SCROLL); } /** @@ -147,7 +147,7 @@ public boolean doNotScroll() */ public void setDoNotScroll( boolean doNotScroll ) { - dictionary.setFlag(COSName.FF, FLAG_DO_NOT_SCROLL, doNotScroll); + getCOSObject().setFlag(COSName.FF, FLAG_DO_NOT_SCROLL, doNotScroll); } /** @@ -155,7 +155,7 @@ public void setDoNotScroll( boolean doNotScroll ) */ public boolean isComb() { - return dictionary.getFlag(COSName.FF, FLAG_COMB); + return getCOSObject().getFlag(COSName.FF, FLAG_COMB); } /** @@ -165,7 +165,7 @@ public boolean isComb() */ public void setComb( boolean comb ) { - dictionary.setFlag(COSName.FF, FLAG_COMB, comb); + getCOSObject().setFlag(COSName.FF, FLAG_COMB, comb); } /** @@ -173,7 +173,7 @@ public void setComb( boolean comb ) */ public boolean isRichText() { - return dictionary.getFlag(COSName.FF, FLAG_RICH_TEXT); + return getCOSObject().getFlag(COSName.FF, FLAG_RICH_TEXT); } /** @@ -183,7 +183,7 @@ public boolean isRichText() */ public void setRichText( boolean richText ) { - dictionary.setFlag(COSName.FF, FLAG_RICH_TEXT, richText); + getCOSObject().setFlag(COSName.FF, FLAG_RICH_TEXT, richText); } /** @@ -193,7 +193,7 @@ public void setRichText( boolean richText ) */ public int getMaxLen() { - return dictionary.getInt(COSName.MAX_LEN); + return getCOSObject().getInt(COSName.MAX_LEN); } /** @@ -203,7 +203,7 @@ public int getMaxLen() */ public void setMaxLen(int maxLen) { - dictionary.setInt(COSName.MAX_LEN, maxLen); + getCOSObject().setInt(COSName.MAX_LEN, maxLen); } /** @@ -212,9 +212,10 @@ public void setMaxLen(int maxLen) * @param value Plain text * @throws IOException if the value could not be set */ + @Override public void setValue(String value) throws IOException { - dictionary.setString(COSName.V, value); + getCOSObject().setString(COSName.V, value); applyChange(); } @@ -226,7 +227,7 @@ public void setValue(String value) throws IOException */ public void setDefaultValue(String value) throws IOException { - dictionary.setString(COSName.DV, value); + getCOSObject().setString(COSName.DV, value); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDVariableText.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDVariableText.java index 827109a79..3ea9fbdcf 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDVariableText.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDVariableText.java @@ -105,7 +105,7 @@ PDDefaultAppearanceString getDefaultAppearanceString() throws IOException */ public void setDefaultAppearance(String daValue) { - dictionary.setString(COSName.DA, daValue); + getCOSObject().setString(COSName.DA, daValue); } /** @@ -118,7 +118,7 @@ public void setDefaultAppearance(String daValue) */ public String getDefaultStyleString() { - COSString defaultStyleString = (COSString) dictionary.getDictionaryObject(COSName.DS); + COSString defaultStyleString = (COSString)getCOSObject().getDictionaryObject(COSName.DS); return defaultStyleString.getString(); } @@ -133,11 +133,11 @@ public void setDefaultStyleString(String defaultStyleString) { if (defaultStyleString != null) { - dictionary.setItem(COSName.DS, new COSString(defaultStyleString)); + getCOSObject().setItem(COSName.DS, new COSString(defaultStyleString)); } else { - dictionary.removeItem(COSName.DS); + getCOSObject().removeItem(COSName.DS); } } @@ -173,13 +173,14 @@ public int getQ() */ public void setQ( int q ) { - dictionary.setInt(COSName.Q, q); + getCOSObject().setInt(COSName.Q, q); } /** * Get the fields rich text value. * * @return the rich text value string + * @throws IOException if the field dictionary entry is not a text type */ public String getRichTextValue() throws IOException { @@ -192,7 +193,6 @@ public String getRichTextValue() throws IOException *

    * Setting the rich text value will not generate the appearance * for the field. - * *
    * You can set {@link PDAcroForm#setNeedAppearances(Boolean)} to * signal a conforming reader to generate the appearance stream. @@ -205,18 +205,16 @@ public String getRichTextValue() throws IOException */ public void setRichTextValue(String richTextValue) { - // TODO stream instead of string if (richTextValue != null) { - dictionary.setItem(COSName.RV, new COSString(richTextValue)); + getCOSObject().setItem(COSName.RV, new COSString(richTextValue)); } else { - dictionary.removeItem(COSName.RV); + getCOSObject().removeItem(COSName.RV); } } - /** * Get a text as text stream. * diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFA.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFA.java deleted file mode 100644 index 284292ce2..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFA.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.pdmodel.interactive.form; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import com.tom_roush.pdfbox.cos.COSArray; -import com.tom_roush.pdfbox.cos.COSBase; -import com.tom_roush.pdfbox.cos.COSStream; -import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; -import org.w3c.dom.Document; -import org.xml.sax.SAXException; - -/** - * This class represents an XML Forms Architecture Data packet. - * - * @author Ben Litchfield - * @version $Revision: 1.2 $ - */ -public class PDXFA implements COSObjectable -{ - private COSBase xfa; - - /** - * Constructor. - * - * @param xfaBase The xfa resource. - */ - public PDXFA( COSBase xfaBase ) - { - xfa = xfaBase; - } - - /** - * {@inheritDoc} - */ - public COSBase getCOSObject() - { - return xfa; - } - - - /** - * Get the XFA content as byte array. - * - * The XFA is either a stream containing the entire XFA resource - * or an array specifying individual packets that together make - * up the XFA resource. - * - * A packet is a pair of a string and stream. The string contains - * the name of the XML element and the stream contains the complete - * text of this XML element. Each packet represents a complete XML - * element, with the exception of the first and last packet, - * which specify begin and end tags for the xdp:xdp element. - * [IS0 32000-1:2008: 12.7.8] - * - * @return the XFA content - * @throws IOException - */ - public byte[] getBytes() throws IOException - { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - InputStream is = null; - byte[] xfaBytes = null; - - try - { - // handle the case if the XFA is split into individual parts - if (this.getCOSObject() instanceof COSArray) - { - xfaBytes = new byte[1024]; - COSArray cosArray = (COSArray) this.getCOSObject(); - for (int i = 1; i < cosArray.size(); i += 2) - { - COSBase cosObj = cosArray.getObject(i); - if (cosObj instanceof COSStream) - { - is = ((COSStream) cosObj).createInputStream(); - int nRead = 0; - while ((nRead = is.read(xfaBytes, 0, xfaBytes.length)) != -1) - { - baos.write(xfaBytes, 0, nRead); - } - baos.flush(); - } - } - // handle the case if the XFA is represented as a single stream - } - else if (xfa.getCOSObject() instanceof COSStream) - { - xfaBytes = new byte[1024]; - is = ((COSStream) xfa.getCOSObject()).createInputStream(); - int nRead = 0; - while ((nRead = is.read(xfaBytes, 0, xfaBytes.length)) != -1) - { - baos.write(xfaBytes, 0, nRead); - } - baos.flush(); - } - } - finally - { - if (is != null) - { - is.close(); - } - if (baos != null) - { - baos.close(); - } - } - return baos.toByteArray(); - } - - /** - * Get the XFA content as W3C document. - * - * @see #getBytes() - * - * @return the XFA content - * - * @throws ParserConfigurationException parser exception. - * @throws SAXException parser exception. - * @throws IOException if something went wrong when reading the XFA content. - * - */ - public Document getDocument() throws ParserConfigurationException, SAXException, IOException - { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document xfaDocument = builder.parse(new ByteArrayInputStream(this.getBytes())); - return xfaDocument; - } -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFAResource.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFAResource.java index 2c028b5f5..3117ac529 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFAResource.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PDXFAResource.java @@ -40,6 +40,11 @@ */ public final class PDXFAResource implements COSObjectable { + /** + * The default buffer size + */ + private static final int BUFFER_SIZE = 1024; + private final COSBase xfa; /** @@ -89,7 +94,7 @@ public byte[] getBytes() throws IOException // handle the case if the XFA is split into individual parts if (this.getCOSObject() instanceof COSArray) { - xfaBytes = new byte[1024]; + xfaBytes = new byte[BUFFER_SIZE]; COSArray cosArray = (COSArray) this.getCOSObject(); for (int i = 1; i < cosArray.size(); i += 2) { @@ -109,7 +114,7 @@ public byte[] getBytes() throws IOException } else if (xfa.getCOSObject() instanceof COSStream) { - xfaBytes = new byte[1024]; + xfaBytes = new byte[BUFFER_SIZE]; is = ((COSStream) xfa.getCOSObject()).createInputStream(); int nRead; while ((nRead = is.read(xfaBytes, 0, xfaBytes.length)) != -1) @@ -146,7 +151,6 @@ public Document getDocument() throws ParserConfigurationException, SAXException, DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); - Document xfaDocument = builder.parse(new ByteArrayInputStream(this.getBytes())); - return xfaDocument; + return builder.parse(new ByteArrayInputStream(this.getBytes())); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PlainText.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PlainText.java index d27a89dc0..0339da7c5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PlainText.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PlainText.java @@ -194,6 +194,7 @@ List getLines(PDFont font, float fontSize, float width) throws IOException start = end; end = iterator.next(); } + textLine.setWidth(textLine.calculateWidth(font, fontSize)); textLines.add(textLine); return textLines; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java index d67cad489..135148bd8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java @@ -40,7 +40,7 @@ enum TextAlign private final int alignment; - TextAlign(int alignment) + private TextAlign(int alignment) { this.alignment = alignment; } @@ -63,9 +63,15 @@ public static TextAlign valueOf(int alignment) } } + /** + * The scaling factor for font units to PDF units + */ + private static final int FONTSCALE = 1000; + private final AppearanceStyle appearanceStyle; private final boolean wrapLines; private final float width; + private final PDPageContentStream contents; private final PlainText textContent; private final TextAlign textAlignment; @@ -165,6 +171,7 @@ public void format() throws IOException { if (textContent != null && !textContent.getParagraphs().isEmpty()) { + boolean isFirstParagraph = true; for (Paragraph paragraph : textContent.getParagraphs()) { if (wrapLines) @@ -174,14 +181,15 @@ public void format() throws IOException appearanceStyle.getFontSize(), width ); - processLines(lines); + processLines(lines, isFirstParagraph); + isFirstParagraph = false; } else { float startOffset = 0f; float lineWidth = appearanceStyle.getFont().getStringWidth( - paragraph.getText()) * appearanceStyle.getFontSize() / 1000f; + paragraph.getText()) * appearanceStyle.getFontSize() / FONTSCALE; if (lineWidth < width) { @@ -215,7 +223,7 @@ public void format() throws IOException * @param lines the lines to process. * @throws IOException if there is an error writing to the stream. */ - private void processLines(List lines) throws IOException + private void processLines(List lines, boolean isFirstParagraph) throws IOException { float wordWidth = 0f; @@ -244,12 +252,9 @@ private void processLines(List lines) throws IOException } float offset = -lastPos + startOffset + horizontalOffset; - if (lines.indexOf(line) == 0) + if (lines.indexOf(line) == 0 && isFirstParagraph) { contents.newLineAtOffset(offset, verticalOffset); - // reset the initial verticalOffset - verticalOffset = 0f; - horizontalOffset = 0f; } else { @@ -257,7 +262,7 @@ private void processLines(List lines) throws IOException verticalOffset = verticalOffset - appearanceStyle.getLeading(); contents.newLineAtOffset(offset, -appearanceStyle.getLeading()); } - lastPos = startOffset; + lastPos += offset; List words = line.getWords(); for (Word word : words) diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionDirection.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionDirection.java index 1fb2a149b..5f320a04d 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionDirection.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionDirection.java @@ -58,7 +58,7 @@ public COSBase getCOSBase() private final int degrees; - PDTransitionDirection(int degrees) + private PDTransitionDirection(int degrees) { this.degrees = degrees; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionMotion.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionMotion.java index 99ae6c357..892bcbbf9 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionMotion.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionMotion.java @@ -32,5 +32,5 @@ public enum PDTransitionMotion /** * Outward from the center of the page */ - O + O; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionStyle.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionStyle.java index 0a98005bd..56c73cffe 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionStyle.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/pagenavigation/PDTransitionStyle.java @@ -25,5 +25,5 @@ */ public enum PDTransitionStyle { - Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade + Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/viewerpreferences/PDViewerPreferences.java b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/viewerpreferences/PDViewerPreferences.java index 2ef1c4c1f..a542497fd 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/viewerpreferences/PDViewerPreferences.java +++ b/library/src/main/java/com/tom_roush/pdfbox/pdmodel/interactive/viewerpreferences/PDViewerPreferences.java @@ -16,10 +16,8 @@ */ package com.tom_roush.pdfbox.pdmodel.interactive.viewerpreferences; -import com.tom_roush.pdfbox.cos.COSBase; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; - import com.tom_roush.pdfbox.pdmodel.common.COSObjectable; /** @@ -58,7 +56,7 @@ public class PDViewerPreferences implements COSObjectable /** * Enumeration containing all valid values for NonFullScreenPageMode. */ - public enum NON_FULL_SCREEN_PAGE_MODE + public static enum NON_FULL_SCREEN_PAGE_MODE { /** * From PDF Reference: "Neither document outline nor thumbnail images visible". @@ -93,7 +91,7 @@ public enum NON_FULL_SCREEN_PAGE_MODE /** * Enumeration containing all valid values for ReadingDirection. */ - public enum READING_DIRECTION + public static enum READING_DIRECTION { /** * left to right. @@ -138,7 +136,7 @@ public enum READING_DIRECTION /** * Enumeration containing all valid values for boundaries. */ - public enum BOUNDARY + public static enum BOUNDARY { /** * use media box as boundary. @@ -165,7 +163,7 @@ public enum BOUNDARY /** * Enumeration containing all valid values for duplex. */ - public enum DUPLEX + public static enum DUPLEX { /** * simplex printing. @@ -184,7 +182,7 @@ public enum DUPLEX /** * Enumeration containing all valid values for printscaling. */ - public enum PRINT_SCALING + public static enum PRINT_SCALING { /** * no scaling. @@ -214,7 +212,7 @@ public PDViewerPreferences( COSDictionary dic ) * @return The underlying info dictionary. */ @Override - public COSBase getCOSObject() + public COSDictionary getCOSObject() { return prefs; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/rendering/CIDType0Glyph2D.java b/library/src/main/java/com/tom_roush/pdfbox/rendering/CIDType0Glyph2D.java index e6b68daa8..b4208214c 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/rendering/CIDType0Glyph2D.java +++ b/library/src/main/java/com/tom_roush/pdfbox/rendering/CIDType0Glyph2D.java @@ -35,6 +35,7 @@ final class CIDType0Glyph2D implements Glyph2D private final Map cache = new HashMap(); private final PDCIDFontType0 font; private final String fontName; + /** * Constructor. * @@ -48,28 +49,30 @@ final class CIDType0Glyph2D implements Glyph2D @Override public Path getPathForCharacterCode(int code) { - if (cache.containsKey(code)) - { - return cache.get(code); - } - try + Path path = cache.get(code); + if (path == null) { - if (!font.hasGlyph(code)) + try { - int cid = font.getParent().codeToCID(code); - String cidHex = String.format("%04x", cid); - Log.w("PdfBox-Android", "No glyph for " + code + " (CID " + cidHex + ") in font " + fontName); + if (!font.hasGlyph(code)) + { + int cid = font.getParent().codeToCID(code); + String cidHex = String.format("%04x", cid); + Log.w("PdfBox-Android", + "No glyph for " + code + " (CID " + cidHex + ") in font " + fontName); + } + path = font.getPath(code); +// cache.put(code, path); TODO: PdfBox-Android + return path; + } + catch (IOException e) + { + // TODO: escalate this error? + Log.w("PdfBox-Android", "Glyph rendering failed", e); + return new Path(); } - Path path = font.getPath(code); - cache.put(code, path); - return path; - } - catch (IOException e) - { - // TODO: escalate this error? - Log.w("PdfBox-Android", "Glyph rendering failed", e); - return new Path(); } + return path; } @Override public void dispose() diff --git a/library/src/main/java/com/tom_roush/pdfbox/rendering/Glyph2D.java b/library/src/main/java/com/tom_roush/pdfbox/rendering/Glyph2D.java index 05d210d5e..08d22d7dd 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/rendering/Glyph2D.java +++ b/library/src/main/java/com/tom_roush/pdfbox/rendering/Glyph2D.java @@ -24,7 +24,8 @@ * This interface is implemented by several font specific classes which is called to get the * general path of a single glyph of the represented font most likely to render it. */ -interface Glyph2D { +interface Glyph2D +{ /** * Returns the path describing the glyph for the given character code. * @@ -33,6 +34,7 @@ interface Glyph2D { * @return the GeneralPath for the given character code */ Path getPathForCharacterCode(int code) throws IOException; + /** * Remove all cached resources. */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/rendering/ImageType.java b/library/src/main/java/com/tom_roush/pdfbox/rendering/ImageType.java new file mode 100644 index 000000000..c89944c67 --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/rendering/ImageType.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.rendering; + +import android.graphics.Bitmap; + +/** + * Image type for rendering. + */ +public enum ImageType +{ + /** + * Black or white. + */ + BINARY + { + @Override + Bitmap.Config toBitmapConfig() + { + return Bitmap.Config.ALPHA_8; // TODO: PdfBox-Android Need to take care with this + } + }, + + /** + * Shades of gray + */ + GRAY + { + @Override + Bitmap.Config toBitmapConfig() + { + return Bitmap.Config.ALPHA_8; + } + }, + + /** + * Red, Green, Blue + */ + RGB + { + @Override // TODO: PdfBox-Android 565? + Bitmap.Config toBitmapConfig() + { + return Bitmap.Config.ARGB_8888; + } + }, + + /** + * Alpha, Red, Green, Blue + */ + ARGB + { + @Override + Bitmap.Config toBitmapConfig() + { + return Bitmap.Config.ARGB_8888; + } + }; + + abstract Bitmap.Config toBitmapConfig(); +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/rendering/PDFRenderer.java b/library/src/main/java/com/tom_roush/pdfbox/rendering/PDFRenderer.java index acb241717..25edff070 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/rendering/PDFRenderer.java +++ b/library/src/main/java/com/tom_roush/pdfbox/rendering/PDFRenderer.java @@ -30,34 +30,51 @@ /** * Renders a PDF document to an AWT BufferedImage. * This class may be overridden in order to perform custom rendering. - * @author John Hewson - * @author Andreas Lehmkühler * + * @author John Hewson */ public class PDFRenderer { - protected final PDDocument document; - // TODO keep rendering state such as caches here - - /** - * Creates a new PDFRenderer. - * @param document the document to render - */ - public PDFRenderer(PDDocument document) - { - this.document = document; - } - - /** - * Returns the given page as an RGB image at 72 DPI - * @param pageIndex the zero-based index of the page to be converted. - * @return the rendered page image - * @throws IOException if the PDF cannot be read - */ - public Bitmap renderImage(int pageIndex) throws IOException - { - return renderImage(pageIndex, 1, Bitmap.Config.ARGB_8888); - } + protected final PDDocument document; + // TODO keep rendering state such as caches here + + /** + * Creates a new PDFRenderer. + * + * @param document the document to render + */ + public PDFRenderer(PDDocument document) + { + this.document = document; + } + + /** + * Returns the given page as an RGB image at 72 DPI + * + * @param pageIndex the zero-based index of the page to be converted. + * + * @return the rendered page image + * @throws IOException if the PDF cannot be read + */ + public Bitmap renderImage(int pageIndex) throws IOException + { + return renderImage(pageIndex, 1); + } + + /** + * Returns the given page as an RGB image at the given scale. + * A scale of 1 will render at 72 DPI. + * + * @param pageIndex the zero-based index of the page to be converted + * @param scale the scaling factor, where 1 = 72 DPI + * + * @return the rendered page image + * @throws IOException if the PDF cannot be read + */ + public Bitmap renderImage(int pageIndex, float scale) throws IOException + { + return renderImage(pageIndex, scale, ImageType.RGB); + } /** * Returns the given page as an RGB image at the given DPI. @@ -68,7 +85,7 @@ public Bitmap renderImage(int pageIndex) throws IOException */ public Bitmap renderImageWithDPI(int pageIndex, float dpi) throws IOException { - return renderImage(pageIndex, dpi / 72f, Bitmap.Config.ARGB_8888); + return renderImage(pageIndex, dpi / 72f, ImageType.RGB); } /** @@ -79,23 +96,25 @@ public Bitmap renderImageWithDPI(int pageIndex, float dpi) throws IOException * @return the rendered page image * @throws IOException if the PDF cannot be read */ - public Bitmap renderImageWithDPI(int pageIndex, float dpi, Bitmap.Config imageType) + public Bitmap renderImageWithDPI(int pageIndex, float dpi, ImageType imageType) throws IOException { return renderImage(pageIndex, dpi / 72f, imageType); } - - /** - * Returns the given page as an RGB image at the given scale. - * @param pageIndex the zero-based index of the page to be converted - * @param scale the scaling factor, where 1 = 72 DPI - * @param config the bitmap config to create - * @return the rendered page image - * @throws IOException if the PDF cannot be read - */ - public Bitmap renderImage(int pageIndex, float scale, Bitmap.Config config) throws IOException - { - PDPage page = document.getPage(pageIndex); + + /** + * Returns the given page as an RGB image at the given scale. + * + * @param pageIndex the zero-based index of the page to be converted + * @param scale the scaling factor, where 1 = 72 DPI + * @param imageType the type of image to return + * + * @return the rendered page image + * @throws IOException if the PDF cannot be read + */ + public Bitmap renderImage(int pageIndex, float scale, ImageType imageType) throws IOException + { + PDPage page = document.getPage(pageIndex); PDRectangle cropbBox = page.getCropBox(); float widthPt = cropbBox.getWidth(); @@ -108,32 +127,56 @@ public Bitmap renderImage(int pageIndex, float scale, Bitmap.Config config) thro Bitmap image; if (rotationAngle == 90 || rotationAngle == 270) { - image = Bitmap.createBitmap(heightPx, widthPx, config); + image = Bitmap.createBitmap(heightPx, widthPx, imageType.toBitmapConfig()); } else { - image = Bitmap.createBitmap(widthPx, heightPx, config); + image = Bitmap.createBitmap(widthPx, heightPx, imageType.toBitmapConfig()); } // use a transparent background if the imageType supports alpha Paint paint = new Paint(); Canvas canvas = new Canvas(image); - if (config != Bitmap.Config.ARGB_8888) + if (imageType == ImageType.ARGB) + { + paint.setColor(Color.TRANSPARENT); + } + else { paint.setColor(Color.WHITE); - paint.setStyle(Paint.Style.FILL); - canvas.drawRect(0, 0, image.getWidth(), image.getHeight(), paint); - paint.reset(); } + paint.setStyle(Paint.Style.FILL); + canvas.drawRect(0, 0, image.getWidth(), image.getHeight(), paint); + paint.reset(); renderPage(page, paint, canvas, image.getWidth(), image.getHeight(), scale, scale); return image; - } - - // renders a page to the given graphics - public void renderPage(PDPage page, Paint paint, Canvas canvas, int width, int height, float scaleX, - float scaleY) throws IOException + } + + /** + * Renders a given page to an AWT Graphics2D instance. + * + * @param pageIndex the zero-based index of the page to be converted + * @param paint the Paint that will be used to draw the page + * @param canvas the Canvas on which to draw the page + * @param scale the scale to draw the page at + * + * @throws IOException if the PDF cannot be read + */ + public void renderPageToGraphics(int pageIndex, Paint paint, Canvas canvas, float scale) + throws IOException + { + PDPage page = document.getPage(pageIndex); + // TODO need width/wight calculations? should these be in PageDrawer? + PDRectangle adjustedCropBox = page.getCropBox(); + renderPage(page, paint, canvas, (int)adjustedCropBox.getWidth(), + (int)adjustedCropBox.getHeight(), scale, scale); + } + + // renders a page to the given graphics + private void renderPage(PDPage page, Paint paint, Canvas canvas, int width, int height, + float scaleX, float scaleY) throws IOException { canvas.scale(scaleX, scaleY); @@ -142,20 +185,20 @@ public void renderPage(PDPage page, Paint paint, Canvas canvas, int width, int h if (rotationAngle != 0) { - float translateX = 0; - float translateY = 0; + float translateX = 0; + float translateY = 0; switch (rotationAngle) { - case 90: - translateX = cropBox.getHeight(); - break; - case 270: - translateY = cropBox.getWidth(); - break; - case 180: - translateX = cropBox.getWidth(); - translateY = cropBox.getHeight(); - break; + case 90: + translateX = cropBox.getHeight(); + break; + case 270: + translateY = cropBox.getWidth(); + break; + case 180: + translateX = cropBox.getWidth(); + translateY = cropBox.getHeight(); + break; } canvas.translate(translateX, translateY); canvas.rotate((float) Math.toRadians(rotationAngle)); diff --git a/library/src/main/java/com/tom_roush/pdfbox/rendering/PageDrawer.java b/library/src/main/java/com/tom_roush/pdfbox/rendering/PageDrawer.java index 4e5d4b258..dd22af7ec 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/rendering/PageDrawer.java +++ b/library/src/main/java/com/tom_roush/pdfbox/rendering/PageDrawer.java @@ -27,9 +27,19 @@ import android.graphics.Region; import android.util.Log; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.tom_roush.harmony.awt.geom.AffineTransform; import com.tom_roush.pdfbox.contentstream.PDFGraphicsStreamEngine; +import com.tom_roush.pdfbox.cos.COSArray; +import com.tom_roush.pdfbox.cos.COSBase; +import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; +import com.tom_roush.pdfbox.cos.COSNumber; import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; +import com.tom_roush.pdfbox.pdmodel.common.function.PDFunction; import com.tom_roush.pdfbox.pdmodel.font.PDCIDFontType0; import com.tom_roush.pdfbox.pdmodel.font.PDCIDFontType2; import com.tom_roush.pdfbox.pdmodel.font.PDFont; @@ -40,20 +50,19 @@ import com.tom_roush.pdfbox.pdmodel.graphics.PDLineDashPattern; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColor; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; -import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; +import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceGray; +import com.tom_roush.pdfbox.pdmodel.graphics.form.PDTransparencyGroup; import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImage; import com.tom_roush.pdfbox.pdmodel.graphics.shading.PDShading; import com.tom_roush.pdfbox.pdmodel.graphics.state.PDGraphicsState; import com.tom_roush.pdfbox.pdmodel.graphics.state.PDSoftMask; import com.tom_roush.pdfbox.pdmodel.graphics.state.RenderingMode; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup; +import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; import com.tom_roush.pdfbox.util.Matrix; import com.tom_roush.pdfbox.util.Vector; -import com.tom_roush.harmony.awt.geom.AffineTransform; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; /** * Paints a page in a PDF document to a Canvas context. May be subclassed to provide custom @@ -70,15 +79,15 @@ public class PageDrawer extends PDFGraphicsStreamEngine // parent document renderer - note: this is needed for not-yet-implemented resource caching private final PDFRenderer renderer; - // the graphics device to draw to, xform is the initial transform of the device (i.e. DPI) - Paint paint; - Canvas canvas; - private AffineTransform xform; + // the graphics device to draw to, xform is the initial transform of the device (i.e. DPI) + Paint paint; + Canvas canvas; + private AffineTransform xform; - // the page box to draw (usually the crop box but may be another) - private PDRectangle pageSize; + // the page box to draw (usually the crop box but may be another) + private PDRectangle pageSize; - // clipping winding rule used for the clipping path + // clipping winding rule used for the clipping path private Path.FillType clipWindingRule = null; private Path linePath = new Path(); @@ -93,17 +102,17 @@ public class PageDrawer extends PDFGraphicsStreamEngine private PointF currentPoint = new PointF(); - /** - * Constructor. - * - * @param parameters Parameters for page drawing. - * @throws IOException If there is an error loading properties from the file. - */ + /** + * Constructor. + * + * @param parameters Parameters for page drawing. + * @throws IOException If there is an error loading properties from the file. + */ public PageDrawer(PageDrawerParameters parameters) throws IOException - { + { super(parameters.getPage()); this.renderer = parameters.getRenderer(); - } + } /** * Returns the parent renderer. @@ -141,44 +150,45 @@ private void setRenderingHints() paint.setAntiAlias(true); } - /** - * Draws the page to the requested canvas. + /** + * Draws the page to the requested canvas. * * @param p The paint. - * @param c The canvas to draw onto. - * @param pageSize The size of the page to draw. - * @throws IOException If there is an IO error while drawing the page. - */ - public void drawPage(Paint p, Canvas c, PDRectangle pageSize) throws IOException - { - paint = p; - canvas = c; - xform = new AffineTransform(canvas.getMatrix()); - this.pageSize = pageSize; + * @param c The canvas to draw onto. + * @param pageSize The size of the page to draw. + * @throws IOException If there is an IO error while drawing the page. + */ + public void drawPage(Paint p, Canvas c, PDRectangle pageSize) throws IOException + { + paint = p; + canvas = c; + xform = new AffineTransform(canvas.getMatrix()); + this.pageSize = pageSize; - setRenderingHints(); + setRenderingHints(); - canvas.translate(0, pageSize.getHeight()); - canvas.scale(1, -1); + canvas.translate(0, pageSize.getHeight()); + canvas.scale(1, -1); - paint.setStrokeCap(Paint.Cap.BUTT); - paint.setStrokeJoin(Paint.Join.MITER); - paint.setStrokeWidth(1.0f); + paint.setStrokeCap(Paint.Cap.BUTT); + paint.setStrokeJoin(Paint.Join.MITER); + paint.setStrokeWidth(1.0f); // FIXME: PdfBox-Android: create set stroke method? - // adjust for non-(0,0) crop box - canvas.translate(-pageSize.getLowerLeftX(), -pageSize.getLowerLeftY()); + // adjust for non-(0,0) crop box + canvas.translate(-pageSize.getLowerLeftX(), -pageSize.getLowerLeftY()); + canvas.save(); - processPage(getPage()); + processPage(getPage()); - for (PDAnnotation annotation : getPage().getAnnotations()) - { - showAnnotation(annotation); - } + for (PDAnnotation annotation : getPage().getAnnotations()) + { + showAnnotation(annotation); + } // graphics = null; - } + } - /** + /** * Draws the pattern stream to the requested context. * * @param g The graphics context to draw onto. @@ -255,7 +265,7 @@ public void drawPage(Paint p, Canvas c, PDRectangle pageSize) throws IOException // } TODO: PdfBox-Android // returns an integer for color that Android understands from the PDColor - // TODO: alpha? + // TODO: alpha? private int getColor(PDColor color) throws IOException { PDColorSpace colorSpace = color.getColorSpace(); float[] floats = colorSpace.toRGB(color.getComponents()); @@ -317,7 +327,7 @@ private void endTextClip() @Override protected void showFontGlyph(Matrix textRenderingMatrix, PDFont font, int code, String unicode, - Vector displacement) throws IOException + Vector displacement) throws IOException { AffineTransform at = textRenderingMatrix.createAffineTransform(); at.concatenate(font.getFontMatrix().createAffineTransform()); @@ -337,7 +347,7 @@ protected void showFontGlyph(Matrix textRenderingMatrix, PDFont font, int code, * @throws IOException if something went wrong */ private void drawGlyph2D(Glyph2D glyph2D, PDFont font, int code, Vector displacement, - AffineTransform at) throws IOException + AffineTransform at) throws IOException { PDGraphicsState state = getGraphicsState(); RenderingMode renderingMode = state.getTextState().getRenderingMode(); @@ -350,7 +360,7 @@ private void drawGlyph2D(Glyph2D glyph2D, PDFont font, int code, Vector displace { float fontWidth = font.getWidthFromFont(code); if (fontWidth > 0 && // ignore spaces - Math.abs(fontWidth - displacement.getX() * 1000) > 0.0001) + Math.abs(fontWidth - displacement.getX() * 1000) > 0.0001) { float pdfWidth = displacement.getX() * 1000; at.scale(pdfWidth / fontWidth, 1); @@ -365,7 +375,7 @@ private void drawGlyph2D(Glyph2D glyph2D, PDFont font, int code, Vector displace { // graphics.setComposite(state.getNonStrokingJavaComposite()); // graphics.setPaint(getNonStrokingPaint()); - paint.setColor(getNonStrokingColor()); + paint.setColor(getNonStrokingColor()); setClip(); // graphics.fill(glyph); paint.setStyle(Paint.Style.FILL); @@ -378,7 +388,7 @@ private void drawGlyph2D(Glyph2D glyph2D, PDFont font, int code, Vector displace { // graphics.setComposite(state.getStrokingJavaComposite()); // graphics.setPaint(getStrokingPaint()); - paint.setColor(getStrokingColor()); + paint.setColor(getStrokingColor()); // graphics.setStroke(getStroke()); setClip(); // graphics.draw(glyph); @@ -391,7 +401,7 @@ private void drawGlyph2D(Glyph2D glyph2D, PDFont font, int code, Vector displace if (renderingMode.isClip()) { // textClippingArea.add(new Area(glyph)); - } + } // FIXME: PdfBox-Android: check this commented out stuff } } @@ -404,13 +414,13 @@ private void drawGlyph2D(Glyph2D glyph2D, PDFont font, int code, Vector displace */ private Glyph2D createGlyph2D(PDFont font) throws IOException { + Glyph2D glyph2D = fontGlyph2D.get(font); // Is there already a Glyph2D for the given font? - if (fontGlyph2D.containsKey(font)) + if (glyph2D != null) { - return fontGlyph2D.get(font); + return glyph2D; } - Glyph2D glyph2D = null; if (font instanceof PDTrueTypeFont) { PDTrueTypeFont ttfFont = (PDTrueTypeFont)font; @@ -448,7 +458,7 @@ else if (type0Font.getDescendantFont() instanceof PDCIDFontType0) // cache the Glyph2D instance if (glyph2D != null) { -// fontGlyph2D.put(font, glyph2D); TODO: use caching + fontGlyph2D.put(font, glyph2D); } if (glyph2D == null) @@ -535,7 +545,7 @@ private int getStrokingColor() throws IOException { // { // return getPaint(getGraphicsState().getNonStrokingColor()); // } TODO: PdfBox-Android - + private int getNonStrokingColor() throws IOException { return getColor(getGraphicsState().getNonStrokingColor()); } @@ -562,11 +572,11 @@ private void setStroke() // apply the CTM for (int i = 0; i < dashArray.length; ++i) { - // minimum line dash width avoids JVM crash, see PDFBOX-2373 + // minimum line dash width avoids JVM crash, see PDFBOX-2373, PDFBOX-2929, PDFBOX-3204 float w = transformWidth(dashArray[i]); if (w != 0) { - dashArray[i] = Math.max(w, 0.016f); + dashArray[i] = Math.max(w, 0.035f); } } phaseStart = (int) transformWidth(phaseStart); @@ -595,7 +605,7 @@ public void strokePath() throws IOException setStroke(); setClip(); - paint.setARGB(255, 0, 0, 0); // TODO set the correct color from graphics state. + paint.setARGB(255, 0, 0, 0); // TODO set the correct color from graphics state. FIXME: 2.0, isn't this done below? paint.setStyle(Paint.Style.STROKE); paint.setColor(getStrokingColor()); setClip(); @@ -607,93 +617,43 @@ public void strokePath() throws IOException public void fillPath(Path.FillType windingRule) throws IOException { // graphics.setComposite(getGraphicsState().getNonStrokingJavaComposite()); - paint.setColor(getNonStrokingColor()); - setClip(); - linePath.setFillType(windingRule); + paint.setColor(getNonStrokingColor()); + setClip(); + linePath.setFillType(windingRule); // disable anti-aliasing for rectangular paths, this is a workaround to avoid small stripes // which occur when solid fills are used to simulate piecewise gradients, see PDFBOX-2302 // note that we ignore paths with a width/height under 1 as these are fills used as strokes, // see PDFBOX-1658 for an example -// RectF bounds = new RectF((); -// linePath.computeBounds(bounds, true); -// boolean noAntiAlias = isRectangular(linePath) && bounds.width() > 1 && bounds.height() > 1; -// if (noAntiAlias) + RectF bounds = new RectF(); + linePath.computeBounds(bounds, true); + boolean noAntiAlias = false;//isRectangular(linePath) && bounds.width() > 1 && bounds.height() > 1; FIXME + if (noAntiAlias) { // graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // RenderingHints.VALUE_ANTIALIAS_OFF); } - // TODO: PdfBox-Android: Commit 7f5861f9559e30ad68f0bcbce5b9dd2e7c2621b5 ? - paint.setStyle(Paint.Style.FILL); - canvas.drawPath(linePath, paint); + canvas.drawPath(linePath, paint); linePath.reset(); -// if (noAntiAlias) + if (noAntiAlias) { // JDK 1.7 has a bug where rendering hints are reset by the above call to // the setRenderingHint method, so we re-set all hints, see PDFBOX-2302 - setRenderingHints(); + setRenderingHints(); } } /** * Returns true if the given path is rectangular. */ -// private boolean isRectangular(GeneralPath path) -// { -// PathIterator iter = path.getPathIterator(null); -// double[] coords = new double[6]; -// int count = 0; -// int[] xs = new int[4]; -// int[] ys = new int[4]; -// while (!iter.isDone()) -// { -// switch(iter.currentSegment(coords)) -// { -// case PathIterator.SEG_MOVETO: -// if (count == 0) -// { -// xs[count] = (int)Math.floor(coords[0]); -// ys[count] = (int)Math.floor(coords[1]); -// } -// else -// { -// return false; -// } -// count++; -// break; -// -// case PathIterator.SEG_LINETO: -// if (count < 4) -// { -// xs[count] = (int)Math.floor(coords[0]); -// ys[count] = (int)Math.floor(coords[1]); -// } -// else -// { -// return false; -// } -// count++; -// break; -// -// case PathIterator.SEG_CUBICTO: -// return false; -// -// case PathIterator.SEG_CLOSE: -// break; -// } -// iter.next(); -// } -// -// if (count == 4) -// { -// return xs[0] == xs[1] || xs[0] == xs[2] || -// ys[0] == ys[1] || ys[0] == ys[3]; -// } -// return false; -// } TODO: PdfBox-Android + private boolean isRectangular(Path path) + { + RectF rect = null; + return path.isRect(rect); + } /** * Fills and then strokes the path. @@ -745,7 +705,7 @@ public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) @Override public PointF getCurrentPoint() { - return currentPoint; + return currentPoint; } @Override @@ -757,11 +717,11 @@ public void closePath() @Override public void endPath() { -// if (clipWindingRule != -1) +// if (clipWindingRule != null) FIXME: 2.0, makes things worse // { -// linePath.setWindingRule(clipWindingRule); +// linePath.setFillType(clipWindingRule); // getGraphicsState().intersectClippingPath(linePath); -// clipWindingRule = -1; +// clipWindingRule = null; // } linePath.reset(); } @@ -774,17 +734,17 @@ public void drawImage(PDImage pdImage) throws IOException if (!pdImage.getInterpolate()) { - boolean isScaledUp = pdImage.getWidth() < Math.round(at.getScaleX()) || - pdImage.getHeight() < Math.round(at.getScaleY()); + boolean isScaledUp = pdImage.getWidth() < Math.round(at.getScaleX()) || + pdImage.getHeight() < Math.round(at.getScaleY()); - // if the image is scaled down, we use smooth interpolation, eg PDFBOX-2364 - // only when scaled up do we use nearest neighbour, eg PDFBOX-2302 / mori-cvpr01.pdf - // stencils are excluded from this rule (see survey.pdf) - if (isScaledUp || pdImage.isStencil()) - { + // if the image is scaled down, we use smooth interpolation, eg PDFBOX-2364 + // only when scaled up do we use nearest neighbour, eg PDFBOX-2302 / mori-cvpr01.pdf + // stencils are excluded from this rule (see survey.pdf) + if (isScaledUp || pdImage.isStencil()) + { // graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, // RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); - } + } } if (pdImage.isStencil()) @@ -798,18 +758,18 @@ public void drawImage(PDImage pdImage) throws IOException else { // draw the image - drawBufferedImage(pdImage.getImage(), at); + drawBitmap(pdImage.getImage(), at); } if (!pdImage.getInterpolate()) { - // JDK 1.7 has a bug where rendering hints are reset by the above call to - // the setRenderingHint method, so we re-set all hints, see PDFBOX-2302 - setRenderingHints(); + // JDK 1.7 has a bug where rendering hints are reset by the above call to + // the setRenderingHint method, so we re-set all hints, see PDFBOX-2302 + setRenderingHints(); } } - private void drawBufferedImage(Bitmap image, AffineTransform at) throws IOException + private void drawBitmap(Bitmap image, AffineTransform at) throws IOException { // graphics.setComposite(getGraphicsState().getNonStrokingJavaComposite()); setClip(); @@ -829,15 +789,96 @@ private void drawBufferedImage(Bitmap image, AffineTransform at) throws IOExcept } else { + COSBase transfer = getGraphicsState().getTransfer(); + if (transfer instanceof COSArray || transfer instanceof COSDictionary) + { + image = applyTransferFunction(image, transfer); + } + int width = image.getWidth(); int height = image.getHeight(); AffineTransform imageTransform = new AffineTransform(at); - imageTransform.scale((1.0f / width), (-1.0f / height)); + imageTransform.scale(1.0f / width, -1.0f / height); imageTransform.translate(0, -height); canvas.drawBitmap(image, imageTransform.toMatrix(), paint); } } + private Bitmap applyTransferFunction(Bitmap image, COSBase transfer) throws IOException + { + Bitmap bim = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888); + + // prepare transfer functions (either one per color or one for all) + // and maps (actually arrays[256] to be faster) to avoid calculating values several times + Integer rMap[], gMap[], bMap[]; + PDFunction rf, gf, bf; + if (transfer instanceof COSArray) + { + COSArray ar = (COSArray) transfer; + rf = PDFunction.create(ar.getObject(0)); + gf = PDFunction.create(ar.getObject(1)); + bf = PDFunction.create(ar.getObject(2)); + rMap = new Integer[256]; + gMap = new Integer[256]; + bMap = new Integer[256]; + } + else + { + rf = PDFunction.create(transfer); + gf = rf; + bf = rf; + rMap = new Integer[256]; + gMap = rMap; + bMap = rMap; + } + + // apply the transfer function to each color, but keep alpha + float input[] = new float[1]; + int[] pixels = new int[image.getWidth() * image.getHeight()]; + image.getPixels(pixels, 0, image.getWidth(), 0, 0, image.getWidth(), image.getHeight()); + for (int pixelIdx = 0; pixelIdx < image.getWidth() * image.getHeight(); pixelIdx++) + { + int rgb = pixels[pixelIdx]; + int ri = (rgb >> 16) & 0xFF; + int gi = (rgb >> 8) & 0xFF; + int bi = rgb & 0xFF; + int ro, go, bo; + if (rMap[ri] != null) + { + ro = rMap[ri]; + } + else + { + input[0] = (ri & 0xFF) / 255f; + ro = (int) (rf.eval(input)[0] * 255); + rMap[ri] = ro; + } + if (gMap[gi] != null) + { + go = gMap[gi]; + } + else + { + input[0] = (gi & 0xFF) / 255f; + go = (int) (gf.eval(input)[0] * 255); + gMap[gi] = go; + } + if (bMap[bi] != null) + { + bo = bMap[bi]; + } + else + { + input[0] = (bi & 0xFF) / 255f; + bo = (int) (bf.eval(input)[0] * 255); + bMap[bi] = bo; + } + pixels[pixelIdx] = (rgb & 0xFF000000) | (ro << 16) | (go << 8) | bo; + } + bim.setPixels(pixels, 0, image.getWidth(), 0, 0, image.getWidth(), image.getHeight()); + return bim; + } + @Override public void shadingFill(COSName shadingName) throws IOException { @@ -855,26 +896,200 @@ public void shadingFill(COSName shadingName) throws IOException @Override public void showAnnotation(PDAnnotation annotation) throws IOException { -// lastClip = null; - //TODO support more annotation flags (Invisible, NoZoom, NoRotate) + lastClip = null; + // TODO support more annotation flags (Invisible, NoZoom, NoRotate) + // Example for NoZoom can be found in p5 of PDFBOX-2348 // int deviceType = graphics.getDeviceConfiguration().getDevice().getType(); // if (deviceType == GraphicsDevice.TYPE_PRINTER && !annotation.isPrinted()) // { // return; // } Shouldn't be needed - if (/*deviceType == GraphicsDevice.TYPE_RASTER_SCREEN && */annotation.isNoView()) - { - return; - } - if (annotation.isHidden()) - { - return; - } - super.showAnnotation(annotation); + if (/*deviceType == GraphicsDevice.TYPE_RASTER_SCREEN && */annotation.isNoView()) + { + return; + } + if (annotation.isHidden()) + { + return; + } + super.showAnnotation(annotation); + + if (annotation.getAppearance() == null) + { + if (annotation instanceof PDAnnotationLink) + { + drawAnnotationLinkBorder((PDAnnotationLink) annotation); + } + + if (annotation instanceof PDAnnotationMarkup && annotation.getSubtype().equals(PDAnnotationMarkup.SUB_TYPE_INK)) + { + drawAnnotationInk((PDAnnotationMarkup) annotation); + } + } + } + + private static class AnnotationBorder + { + private float[] dashArray = null; + private boolean underline = false; + private float width = 0; + private PDColor color; + } + + // return border info. BorderStyle must be provided as parameter because + // method is not available in the base class + private AnnotationBorder getAnnotationBorder(PDAnnotation annotation, + PDBorderStyleDictionary borderStyle) + { + AnnotationBorder ab = new AnnotationBorder(); + COSArray border = annotation.getBorder(); + if (borderStyle == null) + { + if (border.get(2) instanceof COSNumber) + { + ab.width = ((COSNumber) border.getObject(2)).floatValue(); + } + if (border.size() > 3) + { + COSBase base3 = border.getObject(3); + if (base3 instanceof COSArray) + { + ab.dashArray = ((COSArray) base3).toFloatArray(); + } + } + } + else + { + ab.width = borderStyle.getWidth(); + if (borderStyle.getStyle().equals(PDBorderStyleDictionary.STYLE_DASHED)) + { + ab.dashArray = borderStyle.getDashStyle().getDashArray(); + } + if (borderStyle.getStyle().equals(PDBorderStyleDictionary.STYLE_UNDERLINE)) + { + ab.underline = true; + } + } + ab.color = annotation.getColor(); + if (ab.color == null) + { + // spec is unclear, but black seems to be the right thing to do + ab.color = new PDColor(new float[] { 0 }, PDDeviceGray.INSTANCE); + } + if (ab.dashArray != null) + { + boolean allZero = true; + for (float f : ab.dashArray) + { + if (f != 0) + { + allZero = false; + break; + } + } + if (allZero) + { + ab.dashArray = null; + } + } + return ab; + } + + private void drawAnnotationLinkBorder(PDAnnotationLink link) throws IOException + { + Log.e("PdfBox-Android", "Hey! We drew an annotation link border!"); + AnnotationBorder ab = getAnnotationBorder(link, link.getBorderStyle()); + if (ab.width == 0) + { + return; + } + PDRectangle rectangle = link.getRectangle(); + + Paint strokePaint = new Paint(paint); + strokePaint.setColor(getColor(ab.color)); + setStroke(strokePaint, ab.width, Paint.Cap.BUTT, Paint.Join.MITER, 10, ab.dashArray, 0); + canvas.restore(); + if (ab.underline) + { + canvas.drawLine(rectangle.getLowerLeftX(), rectangle.getLowerLeftY(), + rectangle.getLowerLeftX() + rectangle.getWidth(), rectangle.getLowerLeftY(), + strokePaint); + } + else + { + canvas.drawRect(rectangle.getLowerLeftX(), rectangle.getLowerLeftY(), + rectangle.getWidth(), rectangle.getHeight(), strokePaint); + } + } + + private void drawAnnotationInk(PDAnnotationMarkup inkAnnotation) throws IOException + { + Log.e("PdfBox-Android", "Hey! We drew an annotation ink!"); + if (!inkAnnotation.getCOSObject().containsKey(COSName.INKLIST)) + { + return; + } + //TODO there should be an InkAnnotation class with a getInkList method + COSBase base = inkAnnotation.getCOSObject().getDictionaryObject(COSName.INKLIST); + if (!(base instanceof COSArray)) + { + return; + } + // PDF spec does not mention /Border for ink annotations, but it is used if /BS is not available + AnnotationBorder ab = getAnnotationBorder(inkAnnotation, inkAnnotation.getBorderStyle()); + if (ab.width == 0) + { + return; + } + Paint strokePaint = new Paint(paint); + strokePaint.setColor(getColor(ab.color)); + setStroke(strokePaint, ab.width, Paint.Cap.BUTT, Paint.Join.MITER, 10, ab.dashArray, 0); + canvas.restore(); + COSArray pathsArray = (COSArray) base; + for (COSBase baseElement : (Iterable) pathsArray.toList()) + { + if (!(baseElement instanceof COSArray)) + { + continue; + } + COSArray pathArray = (COSArray) baseElement; + int nPoints = pathArray.size() / 2; + + // "When drawn, the points shall be connected by straight lines or curves + // in an implementation-dependent way" - we do lines. + Path path = new Path(); + for (int i = 0; i < nPoints; ++i) + { + COSBase bx = pathArray.getObject(i * 2); + COSBase by = pathArray.getObject(i * 2 + 1); + if (bx instanceof COSNumber && by instanceof COSNumber) + { + float x = ((COSNumber) bx).floatValue(); + float y = ((COSNumber) by).floatValue(); + if (i == 0) + { + path.moveTo(x, y); + } + else + { + path.lineTo(x, y); + } + } + } + canvas.drawPath(path, strokePaint); + } + } + + public void setStroke(Paint p, float width, Paint.Cap cap, Paint.Join join, float miterLimit, float[] dash, float dash_phase) { + p.setStrokeWidth(width); + p.setStrokeCap(cap); + p.setStrokeJoin(join); + p.setStrokeMiter(miterLimit); + p.setPathEffect(new DashPathEffect(dash, dash_phase)); } @Override - public void showTransparencyGroup(PDFormXObject form) throws IOException + public void showTransparencyGroup(PDTransparencyGroup form) throws IOException { TransparencyGroup group = new TransparencyGroup(form, false); @@ -925,17 +1140,17 @@ private final class TransparencyGroup /** * Creates a buffered image for a transparency group result. */ - private TransparencyGroup(PDFormXObject form, boolean isSoftMask) throws IOException + private TransparencyGroup(PDTransparencyGroup form, boolean isSoftMask) throws IOException { // Graphics2D g2dOriginal = graphics; // Area lastClipOriginal = lastClip; // get the CTM x Form Matrix transform -// Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); -// Matrix transform = Matrix.concatenate(ctm, form.getMatrix()); + Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); + Matrix transform = Matrix.concatenate(ctm, form.getMatrix()); // transform the bbox -// Path transformedBox = form.getBBox().transform(transform); + Path transformedBox = form.getBBox().transform(transform); // clip the bbox to prevent giant bboxes from consuming all memory // Area clip = (Area)getGraphicsState().getCurrentClippingPath().clone(); @@ -974,7 +1189,7 @@ private TransparencyGroup(PDFormXObject form, boolean isSoftMask) throws IOExcep { if (isSoftMask) { -// processSoftMask(form); + processSoftMask(form); } else { @@ -983,7 +1198,7 @@ private TransparencyGroup(PDFormXObject form, boolean isSoftMask) throws IOExcep } finally { -// lastClip = lastClipOriginal; +// lastClip = lastClipOriginal; // graphics.dispose(); // graphics = g2dOriginal; } diff --git a/library/src/main/java/com/tom_roush/pdfbox/rendering/TTFGlyph2D.java b/library/src/main/java/com/tom_roush/pdfbox/rendering/TTFGlyph2D.java index 16633fb01..8e0d2e948 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/rendering/TTFGlyph2D.java +++ b/library/src/main/java/com/tom_roush/pdfbox/rendering/TTFGlyph2D.java @@ -113,12 +113,8 @@ private int getGIDForCharacterCode(int code) throws IOException */ public Path getPathForGID(int gid, int code) throws IOException { - Path glyphPath; - if (glyphs.containsKey(gid)) - { - glyphPath = glyphs.get(gid); - } - else + Path glyphPath = glyphs.get(gid); + if (glyphPath == null) { if (gid == 0 || gid >= ttf.getMaximumProfile().getNumGlyphs()) { @@ -134,17 +130,20 @@ public Path getPathForGID(int gid, int code) throws IOException Log.w("PdfBox-Android", "No glyph for " + code + " in font " + font.getName()); } } + Path glyph = vectorFont.getPath(code); + // Acrobat only draws GID 0 for embedded or "Standard 14" fonts, see PDFBOX-2372 if (gid == 0 && !font.isEmbedded() && !font.isStandard14()) { glyph = null; } + if (glyph == null) { // empty glyph (e.g. space, newline) glyphPath = new Path(); - glyphs.put(gid, glyphPath); +// glyphs.put(gid, glyphPath); TODO: PdfBox-Android } else { @@ -154,10 +153,11 @@ public Path getPathForGID(int gid, int code) throws IOException AffineTransform atScale = AffineTransform.getScaleInstance(scale, scale); glyphPath.transform(atScale.toMatrix()); } - glyphs.put(gid, glyphPath); +// glyphs.put(gid, glyphPath); TODO: PdfBox-Android } } - return glyphPath != null ? new Path(glyphPath) : null; // todo: expensive + // todo: expensive + return new Path(glyphPath); } @Override diff --git a/library/src/main/java/com/tom_roush/pdfbox/rendering/Type1Glyph2D.java b/library/src/main/java/com/tom_roush/pdfbox/rendering/Type1Glyph2D.java index 8bd4c016e..aa0ddb040 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/rendering/Type1Glyph2D.java +++ b/library/src/main/java/com/tom_roush/pdfbox/rendering/Type1Glyph2D.java @@ -42,37 +42,42 @@ final class Type1Glyph2D implements Glyph2D { this.font = font; } + @Override public Path getPathForCharacterCode(int code) { // cache - if (cache.containsKey(code)) - { - return cache.get(code); - } - // fetch - try + Path path = cache.get(code); + if (path == null) { - String name = font.getEncoding().getName(code); - if (!font.hasGlyph(name)) + // fetch + try { - Log.w("PdfBox-Android", "No glyph for " + code + " (" + name + ") in font " + font.getName()); + String name = font.getEncoding().getName(code); + if (!font.hasGlyph(name)) + { + Log.w("PdfBox-Android", + "No glyph for " + code + " (" + name + ") in font " + font.getName()); + } + // todo: can this happen? should it be encapsulated? + path = font.getPath(name); + if (path == null) + { + path = font.getPath(".notdef"); + } +// cache.put(code, path); TODO: PdfBox-Android + return path; } - // todo: can this happen? should it be encapsulated? - Path path = font.getPath(name); - if (path == null) + catch (IOException e) { - path = font.getPath(".notdef"); + // todo: escalate this error? + Log.e("PdfBox-Android", "Glyph rendering failed", e); + return new Path(); } - cache.put(code, path); - return path; - } - catch (IOException e) - { - Log.e("PdfBox-Android", "Glyph rendering failed", e); // todo: escalate this error? - return new Path(); } + return path; } + @Override public void dispose() { diff --git a/library/src/main/java/com/tom_roush/pdfbox/text/PDFMarkedContentExtractor.java b/library/src/main/java/com/tom_roush/pdfbox/text/PDFMarkedContentExtractor.java index c30709d8d..7223b5d89 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/text/PDFMarkedContentExtractor.java +++ b/library/src/main/java/com/tom_roush/pdfbox/text/PDFMarkedContentExtractor.java @@ -25,6 +25,7 @@ import com.tom_roush.pdfbox.contentstream.operator.markedcontent.BeginMarkedContentSequence; import com.tom_roush.pdfbox.contentstream.operator.markedcontent.BeginMarkedContentSequenceWithProperties; +import com.tom_roush.pdfbox.contentstream.operator.markedcontent.DrawObject; import com.tom_roush.pdfbox.contentstream.operator.markedcontent.EndMarkedContentSequence; import com.tom_roush.pdfbox.cos.COSDictionary; import com.tom_roush.pdfbox.cos.COSName; @@ -38,10 +39,11 @@ */ public class PDFMarkedContentExtractor extends PDFTextStreamEngine { - private boolean suppressDuplicateOverlappingText = true; - private List markedContents = new ArrayList(); - private Stack currentMarkedContents = new Stack(); - private Map> characterListMapping = new HashMap>(); + private final boolean suppressDuplicateOverlappingText = true; + private final List markedContents = new ArrayList(); + private final Stack currentMarkedContents = new Stack(); + private final Map> characterListMapping = + new HashMap>(); /** * Instantiate a new PDFTextStripper object. @@ -61,6 +63,7 @@ public PDFMarkedContentExtractor(String encoding) throws IOException addOperator(new BeginMarkedContentSequenceWithProperties()); addOperator(new BeginMarkedContentSequence()); addOperator(new EndMarkedContentSequence()); + addOperator(new DrawObject()); // todo: DP - Marked Content Point // todo: MP - Marked Content Point with Properties } diff --git a/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStreamEngine.java b/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStreamEngine.java index a7026bb8b..0b12a94a5 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStreamEngine.java +++ b/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStreamEngine.java @@ -21,6 +21,8 @@ import java.io.IOException; import java.io.InputStream; +import com.tom_roush.fontbox.ttf.TrueTypeFont; +import com.tom_roush.fontbox.util.BoundingBox; import com.tom_roush.pdfbox.contentstream.PDFStreamEngine; import com.tom_roush.pdfbox.contentstream.operator.DrawObject; import com.tom_roush.pdfbox.contentstream.operator.state.Concatenate; @@ -46,8 +48,13 @@ import com.tom_roush.pdfbox.contentstream.operator.text.ShowTextLineAndSpace; import com.tom_roush.pdfbox.pdmodel.PDPage; import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; +import com.tom_roush.pdfbox.pdmodel.font.PDCIDFont; +import com.tom_roush.pdfbox.pdmodel.font.PDCIDFontType2; import com.tom_roush.pdfbox.pdmodel.font.PDFont; +import com.tom_roush.pdfbox.pdmodel.font.PDFontDescriptor; import com.tom_roush.pdfbox.pdmodel.font.PDSimpleFont; +import com.tom_roush.pdfbox.pdmodel.font.PDTrueTypeFont; +import com.tom_roush.pdfbox.pdmodel.font.PDType0Font; import com.tom_roush.pdfbox.pdmodel.font.PDType3Font; import com.tom_roush.pdfbox.pdmodel.font.encoding.GlyphList; import com.tom_roush.pdfbox.pdmodel.graphics.state.PDGraphicsState; @@ -66,8 +73,8 @@ class PDFTextStreamEngine extends PDFStreamEngine { private int pageRotation; private PDRectangle pageSize; + private Matrix translateMatrix; private final GlyphList glyphList; - private Matrix legacyCTM; /** * Constructor. @@ -120,14 +127,18 @@ public void processPage(PDPage page) throws IOException { this.pageRotation = page.getRotation(); this.pageSize = page.getCropBox(); - super.processPage(page); - } - @Override - protected void showText(byte[] string) throws IOException - { - legacyCTM = getGraphicsState().getCurrentTransformationMatrix().clone(); - super.showText(string); + if (pageSize.getLowerLeftX() == 0 && pageSize.getLowerLeftY() == 0) + { + translateMatrix = null; + } + else + { + // translation matrix for cropbox + translateMatrix = Matrix.getTranslateInstance(-pageSize.getLowerLeftX(), + -pageSize.getLowerLeftY()); + } + super.processPage(page); } /** @@ -142,21 +153,73 @@ protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, Stri // PDGraphicsState state = getGraphicsState(); - Matrix ctm = legacyCTM; + Matrix ctm = state.getCurrentTransformationMatrix(); float fontSize = state.getTextState().getFontSize(); float horizontalScaling = state.getTextState().getHorizontalScaling() / 100f; Matrix textMatrix = getTextMatrix(); + BoundingBox bbox = font.getBoundingBox(); + if (bbox.getLowerLeftY() < Short.MIN_VALUE) + { + // PDFBOX-2158 and PDFBOX-3130 + // files by Salmat eSolutions / ClibPDF Library + bbox.setLowerLeftY(-(bbox.getLowerLeftY() + 65536)); + } // 1/2 the bbox is used as the height todo: why? - float glyphHeight = font.getBoundingBox().getHeight() / 2; + float glyphHeight = bbox.getHeight() / 2; + + // sometimes the bbox has very high values, but CapHeight is OK + PDFontDescriptor fontDescriptor = font.getFontDescriptor(); + if (fontDescriptor != null) + { + float capHeight = fontDescriptor.getCapHeight(); + if (capHeight != 0 && capHeight < glyphHeight) + { + glyphHeight = capHeight; + } + } // transformPoint from glyph space -> text space - float height = font.getFontMatrix().transformPoint(0, glyphHeight).y; + float height; + if (font instanceof PDType3Font) + { + height = font.getFontMatrix().transformPoint(0, glyphHeight).y; + } + else + { + height = glyphHeight / 1000; + } + float displacementX = displacement.getX(); + // the sorting algorithm is based on the width of the character. As the displacement + // for vertical characters doesn't provide any suitable value for it, we have to + // calculate our own + if (font.isVertical()) + { + displacementX = font.getWidth(code) / 1000; + // there may be an additional scaling factor for true type fonts + TrueTypeFont ttf = null; + if (font instanceof PDTrueTypeFont) + { + ttf = ((PDTrueTypeFont)font).getTrueTypeFont(); + } + else if (font instanceof PDType0Font) + { + PDCIDFont cidFont = ((PDType0Font)font).getDescendantFont(); + if (cidFont instanceof PDCIDFontType2) + { + ttf = ((PDCIDFontType2)cidFont).getTrueTypeFont(); + } + } + if (ttf != null && ttf.getUnitsPerEm() != 1000) + { + displacementX *= 1000f / ttf.getUnitsPerEm(); + } + } // (modified) combined displacement, this is calculated *without* taking the character // spacing and word spacing into account, due to legacy code in TextStripper - float tx = displacement.getX() * fontSize * horizontalScaling; - float ty = 0; // todo: support vertical writing mode + float tx = displacementX * fontSize * horizontalScaling; + float ty = displacement.getY() * fontSize; // (modified) combined displacement matrix Matrix td = Matrix.getTranslateInstance(tx, ty); @@ -180,16 +243,10 @@ protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, Stri // Text or Disp to represent if the values are in text or disp units (no glyph units are // saved). - float fontSizeText = getGraphicsState().getTextState().getFontSize(); - float horizontalScalingText = getGraphicsState().getTextState().getHorizontalScaling()/100f; - //Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); - float glyphSpaceToTextSpaceFactor = 1 / 1000f; if (font instanceof PDType3Font) { - // This will typically be 1000 but in the case of a type3 font - // this might be a different number - glyphSpaceToTextSpaceFactor = 1f / font.getFontMatrix().getScaleX(); + glyphSpaceToTextSpaceFactor = font.getFontMatrix().getScaleX(); } float spaceWidthText = 0; @@ -215,8 +272,7 @@ protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, Stri } // the space width has to be transformed into display units - float spaceWidthDisplay = spaceWidthText * fontSizeText * horizontalScalingText * - textRenderingMatrix.getScalingFactorX() * ctm.getScalingFactorX(); + float spaceWidthDisplay = spaceWidthText * textRenderingMatrix.getScalingFactorX(); // use our additional glyph list for Unicode mapping unicode = font.toUnicode(code, glyphList); @@ -239,11 +295,25 @@ protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, Stri } } + // adjust for cropbox if needed + Matrix translatedTextRenderingMatrix; + if (translateMatrix == null) + { + translatedTextRenderingMatrix = textRenderingMatrix; + } + else + { + translatedTextRenderingMatrix = Matrix.concatenate(translateMatrix, + textRenderingMatrix); + nextX -= pageSize.getLowerLeftX(); + nextY -= pageSize.getLowerLeftY(); + } + processTextPosition(new TextPosition(pageRotation, pageSize.getWidth(), - pageSize.getHeight(), textRenderingMatrix, nextX, nextY, + pageSize.getHeight(), translatedTextRenderingMatrix, nextX, nextY, dyDisplay, dxDisplay, spaceWidthDisplay, unicode, new int[] { code } , font, fontSize, - (int)(fontSize * textRenderingMatrix.getScalingFactorX()))); + (int)(fontSize * textMatrix.getScalingFactorX()))); } /** diff --git a/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStripper.java b/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStripper.java index 98941ef07..c5368006a 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStripper.java +++ b/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStripper.java @@ -16,9 +16,15 @@ */ package com.tom_roush.pdfbox.text; +import android.util.Log; + import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; import java.io.StringWriter; import java.io.Writer; +import java.text.Bidi; import java.text.Normalizer; import java.util.ArrayList; import java.util.Collections; @@ -29,9 +35,9 @@ import java.util.Map; import java.util.SortedMap; import java.util.SortedSet; +import java.util.StringTokenizer; import java.util.TreeMap; import java.util.TreeSet; -import java.util.Vector; import java.util.regex.Pattern; import com.tom_roush.pdfbox.pdmodel.PDDocument; @@ -40,11 +46,12 @@ import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; import com.tom_roush.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; import com.tom_roush.pdfbox.pdmodel.interactive.pagenavigation.PDThreadBead; +import com.tom_roush.pdfbox.util.PDFBoxResourceLoader; import com.tom_roush.pdfbox.util.QuickSort; /** * 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 + * formatting and such. Please note; it is up to clients of this class to verify that * a specific user has the correct permissions to extract text from the PDF document. * * The basic flow of this process is that we get a document and use a series of @@ -57,13 +64,12 @@ public class PDFTextStripper extends PDFTextStreamEngine { private static float defaultIndentThreshold = 2.0f; private static float defaultDropThreshold = 2.5f; - private static final boolean useCustomQuickSort; // enable the ability to set the default indent/drop thresholds // with -D system properties: - // pdftextstripper.indent - // pdftextstripper.drop + // pdftextstripper.indent + // pdftextstripper.drop static { String strDrop = null, strIndent = null; @@ -102,25 +108,36 @@ public class PDFTextStripper extends PDFTextStreamEngine // ignore and use default } } + } - // We can't get a Java version, so we'll assume its higher and use quicksort - // // check if we need to use the custom quicksort algorithm as a - // // workaround to the transitivity issue of TextPositionComparator: - // // https://issues.apache.org/jira/browse/PDFBOX-1512 - // boolean is16orLess = false; - // try - // { - // String[] versionComponents = System.getProperty("java.version").split("\\."); - // int javaMajorVersion = Integer.parseInt(versionComponents[0]); - // int javaMinorVersion = Integer.parseInt(versionComponents[1]); - // is16orLess = javaMajorVersion == 1 && javaMinorVersion <= 6; - // } - // catch (SecurityException x) - // { - // // when run in an applet ignore and use default - // // assume 1.7 or higher so that quicksort is used - // } - useCustomQuickSort = true; // !is16orLess; + static + { + // check if we need to use the custom quicksort algorithm as a + // workaround to the PDFBOX-1512 transitivity issue of TextPositionComparator: + boolean is16orLess = false; + try + { + String version = System.getProperty("java.specification.version"); + StringTokenizer st = new StringTokenizer(version, "."); + int majorVersion = Integer.parseInt(st.nextToken()); + int minorVersion = 0; + if (st.hasMoreTokens()) + { + minorVersion = Integer.parseInt(st.nextToken()); + } + is16orLess = majorVersion == 1 && minorVersion <= 6; + } + catch (SecurityException x) + { + // when run in an applet ignore and use default + // assume 1.7 or higher so that quicksort is used + } + catch (NumberFormatException nfe) + { + // should never happen, but if it does, + // assume 1.7 or higher so that quicksort is used + } + useCustomQuickSort = !is16orLess; } /** @@ -159,13 +176,13 @@ public class PDFTextStripper extends PDFTextStreamEngine private float spacingTolerance = .5f; private float averageCharTolerance = .3f; - private List pageArticles = null; + private List beadRectangles = null; /** - * The charactersByArticle is used to extract text by article divisions. For example + * The charactersByArticle is used to extract text by article divisions. For example * a PDF that has two columns like a newspaper, we want to extract the first column and - * then the second column. In this example the PDF would have 2 beads(or articles), one for - * each column. The size of the charactersByArticle would be 5, because not all text on the + * then the second column. In this example the PDF would have 2 beads(or articles), one for + * each column. The size of the charactersByArticle would be 5, because not all text on the * screen will fall into one of the articles. The five divisions are shown below * * Text before first article @@ -176,7 +193,7 @@ public class PDFTextStripper extends PDFTextStreamEngine * * Most PDFs won't have any beads, so charactersByArticle will contain a single entry. */ - protected Vector> charactersByArticle = new Vector>(); + protected ArrayList> charactersByArticle = new ArrayList<>(); private Map>> characterListMapping = new HashMap>>(); @@ -199,7 +216,7 @@ public PDFTextStripper() throws IOException } /** - * This will return the text of a document. See writeText.
    + * This will return the text of a document. See writeText.
    * NOTE: The document must not be encrypted when coming into this method. * * @param doc The document to get the text from. @@ -261,13 +278,11 @@ public void writeText(PDDocument doc, Writer outputStream) throws IOException */ protected void processPages(PDPageTree pages) throws IOException { - PDPageTree pagesTree = document.getPages(); - PDPage startBookmarkPage = startBookmark == null ? null : startBookmark.findDestinationPage(document); if (startBookmarkPage != null) { - startBookmarkPageNumber = pagesTree.indexOf(startBookmarkPage) + 1; + startBookmarkPageNumber = pages.indexOf(startBookmarkPage) + 1; } else { @@ -279,7 +294,7 @@ protected void processPages(PDPageTree pages) throws IOException : endBookmark.findDestinationPage(document); if (endBookmarkPage != null) { - endBookmarkPageNumber = pagesTree.indexOf(endBookmarkPage) + 1; + endBookmarkPageNumber = pages.indexOf(endBookmarkPage) + 1; } else { @@ -292,7 +307,7 @@ protected void processPages(PDPageTree pages) throws IOException startBookmark.getCOSObject() == endBookmark.getCOSObject()) { // this is a special case where both the start and end bookmark - // are the same but point to nothing. In this case + // are the same but point to nothing. In this case // we will not extract any text. startBookmarkPageNumber = 0; endBookmarkPageNumber = 0; @@ -347,23 +362,32 @@ public void processPage(PDPage page) throws IOException (endBookmarkPageNumber == -1 || currentPageNo <= endBookmarkPageNumber)) { startPage(page); - pageArticles = page.getThreadBeads(); - int numberOfArticleSections = 1 + pageArticles.size() * 2; - if (!shouldSeparateByBeads) + + int numberOfArticleSections = 1; + if (shouldSeparateByBeads) { - numberOfArticleSections = 1; + fillBeadRectangles(page); + numberOfArticleSections += beadRectangles.size() * 2; } int originalSize = charactersByArticle.size(); - charactersByArticle.setSize(numberOfArticleSections); - for (int i = 0; i < numberOfArticleSections; i++) + charactersByArticle.ensureCapacity(numberOfArticleSections); + int lastIndex = Math.max(numberOfArticleSections, originalSize); + for (int i = 0; i < lastIndex; i++) { - if (numberOfArticleSections < originalSize) + if (i < originalSize) { charactersByArticle.get(i).clear(); } else { - charactersByArticle.set(i, new ArrayList()); + if (numberOfArticleSections < originalSize) + { + charactersByArticle.remove(i); + } + else + { + charactersByArticle.add(new ArrayList()); + } } } characterListMapping.clear(); @@ -373,9 +397,46 @@ public void processPage(PDPage page) throws IOException } } + private void fillBeadRectangles(PDPage page) + { + beadRectangles = new ArrayList(); + for (PDThreadBead bead : page.getThreadBeads()) + { + if (bead == null) + { + // can't skip, because of null entry handling in processTextPosition() + beadRectangles.add(null); + continue; + } + + PDRectangle rect = bead.getRectangle(); + + // bead rectangle is in PDF coordinates (y=0 is bottom), + // glyphs are in image coordinates (y=0 is top), + // so we must flip + PDRectangle mediaBox = page.getMediaBox(); + float upperRightY = mediaBox.getUpperRightY() - rect.getLowerLeftY(); + float lowerLeftY = mediaBox.getUpperRightY() - rect.getUpperRightY(); + rect.setLowerLeftY(lowerLeftY); + rect.setUpperRightY(upperRightY); + + // adjust for cropbox + PDRectangle cropBox = page.getCropBox(); + if (cropBox.getLowerLeftX() != 0 || cropBox.getLowerLeftY() != 0) + { + rect.setLowerLeftX(rect.getLowerLeftX() - cropBox.getLowerLeftX()); + rect.setLowerLeftY(rect.getLowerLeftY() - cropBox.getLowerLeftY()); + rect.setUpperRightX(rect.getUpperRightX() - cropBox.getLowerLeftX()); + rect.setUpperRightY(rect.getUpperRightY() - cropBox.getLowerLeftY()); + } + + beadRectangles.add(rect); + } + } + /** * Start a new article, which is typically defined as a column - * on a single page (also referred to as a bead). This assumes + * on a single page (also referred to as a bead). This assumes * that the primary direction of text is left to right. * Default implementation is to do nothing. Subclasses * may provide additional information. @@ -390,7 +451,7 @@ protected void startArticle() throws IOException /** * Start a new article, which is typically defined as a column * on a single page (also referred to as a bead). - * Default implementation is to do nothing. Subclasses + * Default implementation is to do nothing. Subclasses * may provide additional information. * * @param isLTR true if primary direction of text is left to right. @@ -402,7 +463,7 @@ protected void startArticle(boolean isLTR) throws IOException } /** - * End an article. Default implementation is to do nothing. Subclasses + * End an article. Default implementation is to do nothing. Subclasses * may provide additional information. * * @throws IOException If there is any error writing to the stream. @@ -413,7 +474,7 @@ protected void endArticle() throws IOException } /** - * Start a new page. Default implementation is to do nothing. Subclasses + * Start a new page. Default implementation is to do nothing. Subclasses * may provide additional information. * * @param page The page we are about to process. @@ -426,7 +487,7 @@ protected void startPage(PDPage page) throws IOException } /** - * End a page. Default implementation is to do nothing. Subclasses + * End a page. Default implementation is to do nothing. Subclasses * may provide additional information. * * @param page The page we are about to process. @@ -489,59 +550,16 @@ protected void writePage() throws IOException } } 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 - // stored first, followed by the second character from the right etc. - // However, PDF stores the text in presentation form, which is left to - // right. We need to do some normalization to convert the PDF data to - // the proper logical output format. - // - // Note that if we did not sort the text, then the output of reversing the - // text is undefined and can sometimes produce worse output then not trying - // to reverse the order. Sorting should be done for these languages. - - // First step is to determine if we have any right to left text, and - // if so, is it dominant. - int ltrCount = 0; - int rtlCount = 0; - - while (textIter.hasNext()) - { - TextPosition position = textIter.next(); - String stringValue = position.getUnicode(); - for (int a = 0; a < stringValue.length(); a++) - { - byte dir = Character.getDirectionality(stringValue.charAt(a)); - if (dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT || - dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING || - dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE) - { - ltrCount++; - } - else if (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT || - dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC || - dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING || - dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE) - { - rtlCount++; - } - } - } - // choose the dominant direction - boolean isRtlDominant = rtlCount > ltrCount; - startArticle(!isRtlDominant); + startArticle(); startOfArticle = true; - // we will later use this to skip reordering - boolean hasRtl = rtlCount > 0; // Now cycle through to print the text. // We queue up a line at a time before we print so that we can convert // the line from presentation form to logical form (if needed). List line = new ArrayList(); - textIter = textList.iterator(); // start from the beginning again + textIter = textList.iterator(); // start from the beginning again // PDF files don't always store spaces. We will need to guess where we should add // spaces based on the distances between TextPositions. Historically, this was done // based on the size of the space character provided by the font. In general, this @@ -650,18 +668,18 @@ else if (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT || startOfArticle = false; } // RDD - Here we determine whether this text object is on the current - // line. We use the lastBaselineFontSize to handle the superscript + // line. We use the lastBaselineFontSize to handle the superscript // case, and the size of the current font to handle the subscript case. // Text must overlap with the last rendered baseline text by at least // a small amount in order to be considered as being on the same line. // XXX BC: In theory, this check should really check if the next char is in // full range seen in this line. This is what I tried to do with minYTopForLine, - // but this caused a lot of regression test failures. So, I'm leaving it be for + // but this caused a lot of regression test failures. So, I'm leaving it be for // now if (!overlap(positionY, positionHeight, maxYForLine, maxHeightForLine)) { - writeLine(normalize(line, isRtlDominant, hasRtl), isRtlDominant); + writeLine(normalize(line)); line.clear(); lastLineStartPosition = handleLineSeparation(current, lastPosition, lastLineStartPosition, @@ -686,7 +704,7 @@ else if (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT || 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. + // end position of the text. We use it in computing our metrics below. endOfLastTextX = positionX + positionWidth; // add it to the list @@ -694,7 +712,7 @@ else if (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT || { if (startOfPage && lastPosition == null) { - writeParagraphStart();//not sure this is correct for RTL? + writeParagraphStart(); // not sure this is correct for RTL? } line.add(new LineItem(position)); } @@ -714,7 +732,7 @@ else if (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT || // print the final line if (line.size() > 0) { - writeLine(normalize(line, isRtlDominant, hasRtl), isRtlDominant); + writeLine(normalize(line)); writeParagraphEnd(); } endArticle(); @@ -731,6 +749,7 @@ private boolean overlap(float y1, float height1, float y2, float height2) /** * Write the line separator value to the output stream. + * * @throws IOException If there is a problem writing out the lineseparator to the document. */ protected void writeLineSeparator() throws IOException @@ -740,6 +759,7 @@ protected void writeLineSeparator() throws IOException /** * Write the word separator value to the output stream. + * * @throws IOException If there is a problem writing out the wordseparator to the document. */ protected void writeWordSeparator() throws IOException @@ -818,13 +838,13 @@ protected void processTextPosition(TextPosition text) 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 + // text. This value is used to determine whether subsequent text rendered // on the same line overwrites the current text. // // We subtract any positive padding to handle cases where extreme amounts // of padding are applied, then backed off (not sure why this is done, but there // are cases where the padding is on the order of 10x the character width, and - // the TJ just backs up to compensate after each character). Also, we subtract + // the TJ just backs up to compensate after each character). Also, we subtract // an amount to allow for kerning (a percentage of the width of the last // character). boolean suppressCharacter = false; @@ -864,12 +884,11 @@ protected void processTextPosition(TextPosition text) float y = text.getY(); if (shouldSeparateByBeads) { - for (int i = 0; i < pageArticles.size() && foundArticleDivisionIndex == -1; i++) + for (int i = 0; i < beadRectangles.size() && foundArticleDivisionIndex == -1; i++) { - PDThreadBead bead = pageArticles.get(i); - if (bead != null) + PDRectangle rect = beadRectangles.get(i); + if (rect != null) { - PDRectangle rect = bead.getRectangle(); if (rect.contains(x, y)) { foundArticleDivisionIndex = i * 2 + 1; @@ -926,8 +945,8 @@ else if (notFoundButFirstAboveArticleDivisionIndex != -1) List textList = charactersByArticle.get(articleDivisionIndex); // In the wild, some PDF encoded documents put diacritics (accents on - // top of characters) into a separate Tj element. When displaying them - // graphically, the two chunks get overlayed. With text output though, + // top of characters) into a separate Tj element. When displaying them + // graphically, the two chunks get overlayed. With text output though, // we need to do the overlay. This code recombines the diacritic with // its associated character if the two are consecutive. if (textList.isEmpty()) @@ -962,9 +981,9 @@ else if (previousTextPosition.isDiacritic() && text.contains(previousTextPositio } /** - * This is the page that the text extraction will start on. The pages start - * at page 1. For example in a 5 page PDF document, if the start page is 1 - * then all pages will be extracted. If the start page is 4 then pages 4 and 5 + * This is the page that the text extraction will start on. The pages start + * at page 1. For example in a 5 page PDF document, if the start page is 1 + * then all pages will be extracted. If the start page is 4 then pages 4 and 5 * will be extracted. The default value is 1. * * @return Value of property startPage. @@ -985,7 +1004,7 @@ public void setStartPage(int startPageValue) } /** - * This will get the last page that will be extracted. This is inclusive, + * This will get the last page that will be extracted. This is inclusive, * for example if a 5 page PDF an endPage value of 5 would extract the * entire document, an end page of 2 would extract pages 1 and 2. This defaults * to Integer.MAX_VALUE such that all pages of the pdf will be extracted. @@ -1008,7 +1027,7 @@ public void setEndPage(int endPageValue) } /** - * Set the desired line separator for output text. The line.separator + * Set the desired line separator for output text. The line.separator * system property is used if the line separator preference is not set * explicitly using this method. * @@ -1040,9 +1059,9 @@ public String getWordSeparator() } /** - * Set the desired word separator for output text. The PDFBox text extraction + * Set the desired word separator for output text. The PDFBox text extraction * algorithm will output a space character if there is enough space between - * two words. By default a space character is used. If you need and accurate + * two words. By default a space character is used. If you need and accurate * count of characters that are found in a PDF document then you might want to * set the word separator to the empty string. * @@ -1082,8 +1101,8 @@ protected Writer getOutput() } /** - * Character strings are grouped by articles. It is quite common that there - * will only be a single article. This returns a List that contains List objects, + * Character strings are grouped by articles. It is quite common that there + * will only be a single article. This returns a List that contains List objects, * the inner lists will contain TextPosition objects. * * @return A double List of TextPositions for all text strings on the page. @@ -1095,7 +1114,7 @@ protected List> getCharactersByArticle() /** * By default the text stripper will attempt to remove text that overlapps each other. - * Word paints the same character several times in order to make it look bold. By setting + * Word paints the same character several times in order to make it look bold. By setting * this to false all text will be extracted, which means that certain sections will be * duplicated, but better performance will be noticed. * @@ -1148,7 +1167,7 @@ public void setEndBookmark(PDOutlineItem aEndBookmark) } /** - * Get the bookmark where text extraction should start, inclusive. Default is null. + * Get the bookmark where text extraction should start, inclusive. Default is null. * * @return The starting bookmark. */ @@ -1169,6 +1188,7 @@ public void setStartBookmark(PDOutlineItem aStartBookmark) /** * This will tell if the text stripper should add some more text formatting. + * * @return true if some more text formatting will be added */ public boolean getAddMoreFormatting() @@ -1199,12 +1219,12 @@ public boolean getSortByPosition() /** * The order of the text tokens in a PDF file may not be in the same - * as they appear visually on the screen. For example, a PDF writer may + * as they appear visually on the screen. For example, a PDF writer may * write out all text by font, so all bold or larger text, then make a second * pass and write out the normal text.
    * The default is to not sort by position.
    *
    - * A PDF writer could choose to write each character in a different order. By + * A PDF writer could choose to write each character in a different order. By * default PDFBox does not sort the text tokens before processing them due to * performance reasons. * @@ -1217,7 +1237,7 @@ public void setSortByPosition(boolean newSortByPosition) /** * Get the current space width-based tolerance value that is being used - * to estimate where spaces in text should be added. Note that the + * to estimate where spaces in text should be added. Note that the * default value for this has been determined from trial and error. * * @return The current tolerance / scaling factor @@ -1229,7 +1249,7 @@ public float getSpacingTolerance() /** * Set the space width-based tolerance value that is used - * to estimate where spaces in text should be added. Note that the + * to estimate where spaces in text should be added. Note that the * default value for this has been determined from trial and error. * Setting this value larger will reduce the number of spaces added. * @@ -1242,7 +1262,7 @@ public void setSpacingTolerance(float spacingToleranceValue) /** * Get the current character width-based tolerance value that is being used - * to estimate where spaces in text should be added. Note that the + * to estimate where spaces in text should be added. Note that the * default value for this has been determined from trial and error. * * @return The current tolerance / scaling factor @@ -1254,7 +1274,7 @@ public float getAverageCharTolerance() /** * Set the character width-based tolerance value that is used - * to estimate where spaces in text should be added. Note that the + * to estimate where spaces in text should be added. Note that the * default value for this has been determined from trial and error. * Setting this value larger will reduce the number of spaces added. * @@ -1265,13 +1285,13 @@ public void setAverageCharTolerance(float averageCharToleranceValue) averageCharTolerance = averageCharToleranceValue; } - /** * returns the multiple of whitespace character widths * for the current text which the current * line start can be indented from the previous line start * beyond which the current line start is considered * to be a paragraph start. + * * @return the number of whitespace character widths to use * when detecting paragraph indents. */ @@ -1285,7 +1305,7 @@ public float getIndentThreshold() * for the current text which the current * line start can be indented from the previous line start * beyond which the current line start is considered - * to be a paragraph start. The default value is 2.0. + * to be a paragraph start. The default value is 2.0. * * @param indentThresholdValue the number of whitespace character widths to use * when detecting paragraph indents. @@ -1300,6 +1320,7 @@ public void setIndentThreshold(float indentThresholdValue) * of the max height of the current characters * beyond which the current line start is considered * to be a paragraph start. + * * @return the character height multiple for * max allowed whitespace between lines in * the same paragraph. @@ -1326,6 +1347,7 @@ public void setDropThreshold(float dropThresholdValue) /** * Returns the string which will be used at the beginning of a paragraph. + * * @return the paragraph start string */ public String getParagraphStart() @@ -1335,6 +1357,7 @@ public String getParagraphStart() /** * Sets the string which will be used at the beginning of a paragraph. + * * @param s the paragraph start string */ public void setParagraphStart(String s) @@ -1344,6 +1367,7 @@ public void setParagraphStart(String s) /** * Returns the string which will be used at the end of a paragraph. + * * @return the paragraph end string */ public String getParagraphEnd() @@ -1353,6 +1377,7 @@ public String getParagraphEnd() /** * Sets the string which will be used at the end of a paragraph. + * * @param s the paragraph end string */ public void setParagraphEnd(String s) @@ -1360,9 +1385,9 @@ public void setParagraphEnd(String s) paragraphEnd = s; } - /** * Returns the string which will be used at the beginning of a page. + * * @return the page start string */ public String getPageStart() @@ -1372,6 +1397,7 @@ public String getPageStart() /** * Sets the string which will be used at the beginning of a page. + * * @param pageStartValue the page start string */ public void setPageStart(String pageStartValue) @@ -1381,6 +1407,7 @@ public void setPageStart(String pageStartValue) /** * Returns the string which will be used at the end of a page. + * * @return the page end string */ public String getPageEnd() @@ -1390,6 +1417,7 @@ public String getPageEnd() /** * Sets the string which will be used at the end of a page. + * * @param pageEndValue the page end string */ public void setPageEnd(String pageEndValue) @@ -1399,6 +1427,7 @@ public void setPageEnd(String pageEndValue) /** * Returns the string which will be used at the beginning of an article. + * * @return the article start string */ public String getArticleStart() @@ -1408,6 +1437,7 @@ public String getArticleStart() /** * Sets the string which will be used at the beginning of an article. + * * @param articleStartValue the article start string */ public void setArticleStart(String articleStartValue) @@ -1417,6 +1447,7 @@ public void setArticleStart(String articleStartValue) /** * Returns the string which will be used at the end of an article. + * * @return the article end string */ public String getArticleEnd() @@ -1426,6 +1457,7 @@ public String getArticleEnd() /** * Sets the string which will be used at the end of an article. + * * @param articleEndValue the article end string */ public void setArticleEnd(String articleEndValue) @@ -1454,6 +1486,10 @@ private PositionWrapper handleLineSeparation(PositionWrapper current, { if (lastPosition.isArticleStart()) { + if (lastPosition.isLineStart()) + { + writeLineSeparator(); + } writeParagraphStart(); } else @@ -1487,7 +1523,7 @@ private PositionWrapper handleLineSeparation(PositionWrapper current, * This method sets the isParagraphStart and isHangingIndent flags on the current * position object.

    * - * @param position the current text position. This may have its isParagraphStart + * @param position the current text position. This may have its isParagraphStart * or isHangingIndent flags set upon return. * @param lastPosition the previous text position (should not be null). * @param lastLineStartPosition the last text position that followed a line separator, or null. @@ -1573,11 +1609,13 @@ private float multiplyFloat(float value1, float value2) // to avoid wrong results when comparing with another float return Math.round(value1 * value2 * 1000) / 1000f; } + /** * writes the paragraph separator string to the output. + * * @throws IOException if something went wrong */ - protected void writeParagraphSeparator()throws IOException + protected void writeParagraphSeparator() throws IOException { writeParagraphEnd(); writeParagraphStart(); @@ -1585,6 +1623,7 @@ protected void writeParagraphSeparator()throws IOException /** * Write something (if defined) at the start of a paragraph. + * * @throws IOException if something went wrong */ protected void writeParagraphStart() throws IOException @@ -1600,6 +1639,7 @@ protected void writeParagraphStart() throws IOException /** * Write something (if defined) at the end of a paragraph. + * * @throws IOException if something went wrong */ protected void writeParagraphEnd() throws IOException @@ -1614,18 +1654,20 @@ protected void writeParagraphEnd() throws IOException /** * Write something (if defined) at the start of a page. + * * @throws IOException if something went wrong */ - protected void writePageStart()throws IOException + protected void writePageStart() throws IOException { output.write(getPageStart()); } /** * Write something (if defined) at the end of a page. + * * @throws IOException if something went wrong */ - protected void writePageEnd()throws IOException + protected void writePageEnd() throws IOException { output.write(getPageEnd()); } @@ -1633,9 +1675,9 @@ protected void writePageEnd()throws IOException /** * returns the list item Pattern object that matches * the text at the specified PositionWrapper or null - * if the text does not match such a pattern. The list + * if the text does not match such a pattern. The list * of Patterns tested against is given by the - * {@link #getListItemPatterns()} method. To add to + * {@link #getListItemPatterns()} method. To add to * the list, simply override that method (if sub-classing) * or explicitly supply your own list using * {@link #setListItemPatterns(List)}. @@ -1669,6 +1711,7 @@ private Pattern matchListItemPattern(PositionWrapper pw) }; private List listOfPatterns = null; + /** * use to supply a different set of regular expression * patterns for matching list item starts. @@ -1682,7 +1725,7 @@ protected void setListItemPatterns(List patterns) /** * returns a list of regular expression Patterns representing - * different common list item formats. For example + * different common list item formats. For example * numbered items of form: *
      *
    1. some text
    2. @@ -1697,6 +1740,7 @@ protected void setListItemPatterns(List patterns) * or "\[\\d+\]" (matches "[1]", "[2]", ...). *

      * This method returns a list of such regular expression Patterns. + * * @return a list of Pattern objects. */ protected List getListItemPatterns() @@ -1723,6 +1767,7 @@ protected List getListItemPatterns() * should be strict in general, and all will be * used with case sensitivity on. *

      + * * @param string the string to be searched * @param patterns list of patterns * @return matching pattern @@ -1741,11 +1786,11 @@ protected static Pattern matchPattern(String string, List patterns) /** * Write a list of string containing a whole line of a document. + * * @param line a list with the words of the given line - * @param isRtlDominant determines if rtl or ltl is dominant * @throws IOException if something went wrong */ - private void writeLine(List line, boolean isRtlDominant) + private void writeLine(List line) throws IOException { int numberOfStrings = line.size(); @@ -1762,42 +1807,197 @@ private void writeLine(List line, boolean isRtlDominant) /** * Normalize the given list of TextPositions. + * * @param line list of TextPositions - * @param isRtlDominant determines if rtl or ltl is dominant - * @param hasRtl determines if lines contains rtl formatted text(parts) * @return a list of strings, one string for every word */ - private List normalize(List line, boolean isRtlDominant, - boolean hasRtl) + private List normalize(List line) { List 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) + + for (LineItem item : line) + { + lineBuilder = normalizeAdd(normalized, lineBuilder, wordPositions, item); + } + + if (lineBuilder.length() > 0) { - int numberOfPositions = line.size(); - for (int i = numberOfPositions - 1; i >= 0; i--) + normalized.add(createWord(lineBuilder.toString(), wordPositions)); + } + return normalized; + } + + /** + * Handles the LTR and RTL direction of the given words. The whole implementation stands and falls with the given + * word. If the word is a full line, the results will be the best. If the word contains of single words or + * characters, the order of the characters in a word or words in a line may wrong, due to RTL and LTR marks and + * characters! + * + * Based on http://www.nesterovsky-bros.com/weblog/2013/07/28/VisualToLogicalConversionInJava.aspx + * + * @param word The word that shall be processed + * + * @return new word with the correct direction of the containing characters + */ + private String handleDirection(String word) + { + Bidi bidi = new Bidi(word, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + + // if there is pure LTR text no need to process further + if (!bidi.isMixed() && bidi.getBaseLevel() == Bidi.DIRECTION_LEFT_TO_RIGHT) + { + return word; + } + + // collect individual bidi information + int runCount = bidi.getRunCount(); + byte[] levels = new byte[runCount]; + Integer[] runs = new Integer[runCount]; + + for (int i = 0; i < runCount; i++) + { + levels[i] = (byte)bidi.getRunLevel(i); + runs[i] = i; + } + + // reorder individual parts based on their levels + Bidi.reorderVisually(levels, 0, runs, 0, runCount); + + // collect the parts based on the direction within the run + StringBuilder result = new StringBuilder(); + + for (int i = 0; i < runCount; i++) + { + int index = runs[i]; + int start = bidi.getRunStart(index); + int end = bidi.getRunLimit(index); + + int level = levels[index]; + + if ((level & 1) != 0) + { + for (; --end >= start; ) + { + char character = word.charAt(end); + if (Character.isMirrored(word.codePointAt(end))) + { + if (MIRRORING_CHAR_MAP.containsKey(character)) + { + result.append(MIRRORING_CHAR_MAP.get(character)); + } + else + { + result.append(character); + } + } + else + { + result.append(character); + } + } + } + else { - lineBuilder = normalizeAdd(normalized, lineBuilder, wordPositions, line.get(i)); + result.append(word, start, end); } } - else + + return result.toString(); + } + + private static Map MIRRORING_CHAR_MAP = + new HashMap(); + + static + { + String path = "com/tom_roush/pdfbox/resources/text/BidiMirroring.txt"; + InputStream input = null; + if (!PDFBoxResourceLoader.isReady()) { - for (LineItem item : line) + // Fallback + input = PDFTextStripper.class.getClassLoader().getResourceAsStream(path); + } + try + { + if (PDFBoxResourceLoader.isReady()) { - lineBuilder = normalizeAdd(normalized, lineBuilder, wordPositions, item); + input = PDFBoxResourceLoader.getStream(path); } + parseBidiFile(input); } - if (lineBuilder.length() > 0) + catch (IOException e) { - normalized.add(createWord(lineBuilder.toString(), wordPositions)); + Log.w("PdfBox-Android", + "Could not parse BidiMirroring.txt, mirroring char map will be empty: " + + e.getMessage()); + } + finally + { + try + { + input.close(); + } + catch (IOException e) + { + Log.e("PdfBox-Android", "Could not close BidiMirroring.txt ", e); + } } - return normalized; } + ; + /** - * Used within {@link #normalize(List, boolean, boolean)} to create a single + * This method parses the bidi file provided as inputstream. + * + * @param inputStream - The bidi file as inputstream + * + * @throws IOException if any line could not be read by the LineNumberReader + */ + private static void parseBidiFile(InputStream inputStream) throws IOException + { + LineNumberReader rd = new LineNumberReader(new InputStreamReader(inputStream)); + + do + { + String s = rd.readLine(); + if (s == null) + { + break; + } + + int comment = s.indexOf('#'); // ignore comments + if (comment != -1) + { + s = s.substring(0, comment); + } + + if (s.length() < 2) + { + continue; + } + + StringTokenizer st = new StringTokenizer(s, ";"); + int nFields = st.countTokens(); + Character[] fields = new Character[nFields]; + for (int i = 0; i < nFields; i++) + { + fields[i] = (char)Integer.parseInt(st.nextToken().trim(), 16); + } + + if (fields.length == 2) + { + // initialize the MIRRORING_CHAR_MAP + MIRRORING_CHAR_MAP.put(fields[0], fields[1]); + } + + } + while (true); + } + + /** + * Used within {@link #normalize(List)} to create a single * {@link WordWithTextPositions} entry. */ private WordWithTextPositions createWord(String word, List wordPositions) @@ -1850,17 +2050,18 @@ private String normalizeWord(String word) } if (builder == null) { - return word; + return handleDirection(word); } else { builder.append(word.substring(p, q)); - return builder.toString(); + return handleDirection(builder.toString()); } } /** - * Used within {@link #normalize(List, boolean, boolean)} to handle a {@link TextPosition}. + * Used within {@link #normalize(List)} to handle a {@link TextPosition}. + * * @return The StringBuilder that must be used when calling this method. */ private StringBuilder normalizeAdd(List normalized, @@ -1952,10 +2153,11 @@ public List getTextPositions() *

      * This is implemented as a wrapper since the TextPosition * class doesn't provide complete access to its - * state fields to subclasses. Also, conceptually TextPosition is + * state fields to subclasses. Also, conceptually TextPosition is * immutable while these flags need to be set post-creation so * it makes sense to put these flags in this separate class. *

      + * * @author m.martinez@ll.mit.edu */ private static final class PositionWrapper @@ -1968,8 +2170,19 @@ private static final class PositionWrapper private TextPosition position = null; + /** + * Constructs a PositionWrapper around the specified TextPosition object. + * + * @param position the text position. + */ + PositionWrapper(TextPosition position) + { + this.position = position; + } + /** * Returns the underlying TextPosition object. + * * @return the text position */ public TextPosition getTextPosition() @@ -1990,7 +2203,6 @@ public void setLineStart() this.isLineStart = true; } - public boolean isParagraphStart() { return isParagraphStart; @@ -2004,13 +2216,11 @@ public void setParagraphStart() this.isParagraphStart = true; } - public boolean isArticleStart() { return isArticleStart; } - /** * Sets the isArticleStart() flag to true. */ @@ -2044,14 +2254,5 @@ public void setHangingIndent() { this.isHangingIndent = true; } - - /** - * Constructs a PositionWrapper around the specified TextPosition object. - * @param position the text position - */ - public PositionWrapper(TextPosition position) - { - this.position = position; - } } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStripperByArea.java b/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStripperByArea.java index de438a7a1..ecf8ed9b8 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStripperByArea.java +++ b/library/src/main/java/com/tom_roush/pdfbox/text/PDFTextStripperByArea.java @@ -25,7 +25,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Vector; import com.tom_roush.pdfbox.pdmodel.PDPage; @@ -38,8 +37,8 @@ public class PDFTextStripperByArea extends PDFTextStripper { private final List regions = new ArrayList(); private final Map regionArea = new HashMap(); - private final Map>> regionCharacterList = - new HashMap>>(); + private final Map>> regionCharacterList = + new HashMap>>(); private final Map regionText = new HashMap(); /** @@ -55,9 +54,11 @@ public PDFTextStripperByArea() throws IOException /** * This method does nothing in this derived class, because beads and regions are incompatible. Beads are * ignored when stripping by area. + * + * @param aShouldSeparateByBeads The new grouping of beads. */ @Override - public void setShouldSeparateByBeads(boolean aShouldSeparateByBeads) + public final void setShouldSeparateByBeads(boolean aShouldSeparateByBeads) { } @@ -73,6 +74,17 @@ public void addRegion( String regionName, RectF rect ) regionArea.put( regionName, rect ); } + /** + * Delete a region to group text by. If the region does not exist, this method does nothing. + * + * @param regionName The name of the region to delete. + */ + public void removeRegion(String regionName) + { + regions.remove(regionName); + regionArea.remove(regionName); + } + /** * Get the list of regions that have been setup. * @@ -111,7 +123,7 @@ public void extractRegions( PDPage page ) throws IOException //reset the stored text for the region so this class //can be reused. String regionName = regionIter.next(); - Vector> regionCharactersByArticle = new Vector>(); + ArrayList> regionCharactersByArticle = new ArrayList>(); regionCharactersByArticle.add( new ArrayList() ); regionCharacterList.put( regionName, regionCharactersByArticle ); regionText.put( regionName, new StringWriter() ); diff --git a/library/src/main/java/com/tom_roush/pdfbox/text/TextPosition.java b/library/src/main/java/com/tom_roush/pdfbox/text/TextPosition.java index 2862a65af..1af6e398b 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/text/TextPosition.java +++ b/library/src/main/java/com/tom_roush/pdfbox/text/TextPosition.java @@ -33,6 +33,7 @@ public final class TextPosition { private static final Map DIACRITICS = createDiacritics(); + // 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 @@ -40,7 +41,7 @@ public final class TextPosition // normalization. private static Map createDiacritics() { - HashMap map = new HashMap(); + Map map = new HashMap(31); map.put(0x0060, "\u0300"); map.put(0x02CB, "\u0300"); map.put(0x0027, "\u0301"); @@ -100,6 +101,7 @@ private static Map createDiacritics() // mutable private float[] widths; private String unicode; + private float direction = -1; /** * Constructor. @@ -191,36 +193,43 @@ public Matrix getTextMatrix() */ public float getDir() { - float a = textMatrix.getScaleY(); - float b = textMatrix.getShearY(); - float c = textMatrix.getScaleX(); - float d = textMatrix.getShearX(); - - // 12 0 left to right - // 0 12 - if (a > 0 && Math.abs(b) < d && Math.abs(c) < a && d > 0) - { - return 0; - } - // -12 0 right to left (upside down) - // 0 -12 - else if (a < 0 && Math.abs(b) < Math.abs(d) && Math.abs(c) < Math.abs(a) && d < 0) - { - return 180; - } - // 0 12 up - // -12 0 - else if (Math.abs(a) < Math.abs(c) && b > 0 && c < 0 && Math.abs(d) < b) + if (direction < 0) { - return 90; - } - // 0 -12 down - // 12 0 - else if (Math.abs(a) < c && b < 0 && c > 0 && Math.abs(d) < Math.abs(b)) - { - return 270; + float a = textMatrix.getScaleY(); + float b = textMatrix.getShearY(); + float c = textMatrix.getShearX(); + float d = textMatrix.getScaleX(); + + // 12 0 left to right + // 0 12 + if (a > 0 && Math.abs(b) < d && Math.abs(c) < a && d > 0) + { + direction = 0; + } + // -12 0 right to left (upside down) + // 0 -12 + else if (a < 0 && Math.abs(b) < Math.abs(d) && Math.abs(c) < Math.abs(a) && d < 0) + { + direction = 180; + } + // 0 12 up + // -12 0 + else if (Math.abs(a) < Math.abs(c) && b > 0 && c < 0 && Math.abs(d) < b) + { + direction = 90; + } + // 0 -12 down + // 12 0 + else if (Math.abs(a) < c && b < 0 && c > 0 && Math.abs(d) < Math.abs(b)) + { + direction = 270; + } + else + { + direction = 0; + } } - return 0; + return direction; } /** @@ -246,7 +255,7 @@ else if (rotation == 180) } else if (rotation == 270) { - return pageHeight - textMatrix.getTranslateX(); + return pageHeight - textMatrix.getTranslateY(); } return 0; } @@ -470,10 +479,11 @@ public float[] getIndividualWidths() public boolean contains(TextPosition tp2) { double thisXstart = getXDirAdj(); - double thisXend = getXDirAdj() + getWidthDirAdj(); + double thisWidth = getWidthDirAdj(); + double thisXend = thisXstart + thisWidth; double tp2Xstart = tp2.getXDirAdj(); - double tp2Xend = tp2.getXDirAdj() + tp2.getWidthDirAdj(); + double tp2Xend = tp2Xstart + tp2.getWidthDirAdj(); // no X overlap at all so return as soon as possible if (tp2Xend <= thisXstart || tp2Xstart >= thisXend) @@ -483,8 +493,9 @@ public boolean contains(TextPosition tp2) // no Y overlap at all so return as soon as possible. Note: 0.0 is in the upper left and // y-coordinate is top of TextPosition - if (tp2.getYDirAdj() + tp2.getHeightDir() < getYDirAdj() || - tp2.getYDirAdj() > getYDirAdj() + getHeightDir()) + double thisYstart = getYDirAdj(); + double tp2Ystart = tp2.getYDirAdj(); + if (tp2Ystart + tp2.getHeightDir() < thisYstart || tp2Ystart > thisYstart + getHeightDir()) { return false; } @@ -494,13 +505,13 @@ public boolean contains(TextPosition tp2) else if (tp2Xstart > thisXstart && tp2Xend > thisXend) { double overlap = thisXend - tp2Xstart; - double overlapPercent = overlap/getWidthDirAdj(); + double overlapPercent = overlap/thisWidth; return overlapPercent > .15; } else if (tp2Xstart < thisXstart && tp2Xend < thisXend) { double overlap = tp2Xend - thisXstart; - double overlapPercent = overlap/getWidthDirAdj(); + double overlapPercent = overlap/thisWidth; return overlapPercent > .15; } return true; @@ -634,6 +645,7 @@ private String combineDiacritic(String str) { // Unicode contains special combining forms of the diacritic characters which we want to use int codePoint = str.codePointAt(0); + // convert the characters not defined in the Unicode spec if (DIACRITICS.containsKey(codePoint)) { diff --git a/library/src/main/java/com/tom_roush/pdfbox/util/Matrix.java b/library/src/main/java/com/tom_roush/pdfbox/util/Matrix.java index fa85bf04f..e895e8b6c 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/util/Matrix.java +++ b/library/src/main/java/com/tom_roush/pdfbox/util/Matrix.java @@ -93,7 +93,6 @@ public Matrix(AffineTransform at) single[7] = (float)at.getTranslateY(); } - /** * This method resets the numbers in this Matrix to the original values, which are * the values that a newly constructed Matrix would have. @@ -136,7 +135,6 @@ public void setFromAffineTransform( AffineTransform af ) single[7] = (float)af.getTranslateY(); } - /** * This will get a matrix value at some point. * @@ -261,7 +259,7 @@ public void rotate(double theta) } /** - * This will take the current matrix and multipy it with a matrix that is passed in. + * This will take the current matrix and multiply it with a matrix that is passed in. * * @param b The matrix to multiply by. * @@ -457,7 +455,9 @@ public Matrix extractTranslating() * @param tx The x translating operator. * @param ty The y translating operator. * @return A new matrix with just the x/y translating. + * @deprecated Use {@link #getTranslateInstance} instead. */ + @Deprecated public static Matrix getTranslatingInstance(float tx, float ty) { return getTranslateInstance(tx, ty); @@ -576,7 +576,7 @@ public float getScalingFactorY() } /** - * * Returns the x-scaling element of this matrix. + * Returns the x-scaling element of this matrix. */ public float getScaleX() { @@ -625,6 +625,7 @@ public float getTranslateY() /** * Get the x position in the matrix. This method is deprecated as it is incorrectly named. + * * @return The x-position. * @deprecated Use {@link #getTranslateX} instead */ @@ -636,6 +637,7 @@ public float getXPosition() /** * Get the y position. This method is deprecated as it is incorrectly named. + * * @return The y position. * @deprecated Use {@link #getTranslateY} instead */ diff --git a/library/src/main/java/com/tom_roush/pdfbox/util/PDFHighlighter.java b/library/src/main/java/com/tom_roush/pdfbox/util/PDFHighlighter.java deleted file mode 100644 index c731821f8..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/util/PDFHighlighter.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.util; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.tom_roush.pdfbox.pdmodel.PDDocument; -import com.tom_roush.pdfbox.pdmodel.PDPage; -import com.tom_roush.pdfbox.text.PDFTextStripper; - - -/** - * Highlighting of words in a PDF document with an XML file. - * - * @author slagraulet (slagraulet@cardiweb.com) - * @author Ben Litchfield - * - * @see - * Adobe Highlight File Format - */ -public class PDFHighlighter extends PDFTextStripper -{ - private Writer highlighterOutput = null; - //private Color highlightColor = Color.YELLOW; - - private String[] searchedWords; - private ByteArrayOutputStream textOS = null; - private Writer textWriter = null; - private static final String ENCODING = "UTF-16"; - - /** - * Default constructor. - * - * @throws IOException If there is an error constructing this class. - */ - public PDFHighlighter() throws IOException - { - super(); - super.setLineSeparator( "" ); - super.setWordSeparator( "" ); - super.setShouldSeparateByBeads( false ); - super.setSuppressDuplicateOverlappingText( false ); - } - - /** - * Generate an XML highlight string based on the PDF. - * - * @param pdDocument The PDF to find words in. - * @param highlightWord The word to search for. - * @param xmlOutput The resulting output xml file. - * - * @throws IOException If there is an error reading from the PDF, or writing to the XML. - */ - public void generateXMLHighlight(PDDocument pdDocument, String highlightWord, Writer xmlOutput ) throws IOException - { - generateXMLHighlight( pdDocument, new String[] { highlightWord }, xmlOutput ); - } - - /** - * Generate an XML highlight string based on the PDF. - * - * @param pdDocument The PDF to find words in. - * @param sWords The words to search for. - * @param xmlOutput The resulting output xml file. - * - * @throws IOException If there is an error reading from the PDF, or writing to the XML. - */ - public void generateXMLHighlight(PDDocument pdDocument, String[] sWords, Writer xmlOutput ) throws IOException - { - highlighterOutput = xmlOutput; - searchedWords = sWords; - highlighterOutput.write("\n\n\n"); - textOS = new ByteArrayOutputStream(); - textWriter = new OutputStreamWriter( textOS, ENCODING); - writeText(pdDocument, textWriter); - highlighterOutput.write("\n\n"); - highlighterOutput.flush(); - } - - /** - * {@inheritDoc} - */ - protected void endPage( PDPage pdPage ) throws IOException - { - textWriter.flush(); - - String page = new String( textOS.toByteArray(), ENCODING ); - textOS.reset(); - //page = page.replaceAll( "\n", "" ); - //page = page.replaceAll( "\r", "" ); - //page = CCRStringUtil.stripChar(page, '\n'); - //page = CCRStringUtil.stripChar(page, '\r'); - - // Traitement des listes � puces (caract�res sp�ciaux) - if (page.indexOf('a') != -1) - { - page = page.replaceAll("a[0-9]{1,3}", "."); - } - for (String searchedWord : searchedWords) - { - Pattern pattern = Pattern.compile(searchedWord, Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(page); - while( matcher.find() ) - { - int begin = matcher.start(); - int end = matcher.end(); - highlighterOutput.write(" \n"); - } - } - } - - /** - * Command line application. - * - * @param args The command line arguments to the application. - * - * @throws IOException If there is an error generating the highlight file. - */ - public static void main(String[] args) throws IOException - { - PDFHighlighter xmlExtractor = new PDFHighlighter(); - PDDocument doc = null; - try - { - if( args.length < 2 ) - { - usage(); - } - String[] highlightStrings = new String[ args.length - 1]; - System.arraycopy( args, 1, highlightStrings, 0, highlightStrings.length ); - doc = PDDocument.load( new File(args[0]) ); - - xmlExtractor.generateXMLHighlight( - doc, - highlightStrings, - new OutputStreamWriter( System.out ) ); - } - finally - { - if( doc != null ) - { - doc.close(); - } - } - } - - private static void usage() - { - System.err.println( "usage: java " + PDFHighlighter.class.getName() + " word1 word2 word3 ..." ); - System.exit( 1 ); - } - - - /** - * Get the color to highlight the strings with. Default is Color.YELLOW. - * - * @return The color to highlight strings with. - */ - /*public Color getHighlightColor() - { - return highlightColor; - }**/ - - /** - * Get the color to highlight the strings with. Default is Color.YELLOW. - * - * @param color The color to highlight strings with. - */ - /*public void setHighlightColor(Color color) - { - this.highlightColor = color; - }**/ - - /** - * Set the highlight color using HTML like rgb string. The string must be 6 characters long. - * - * @param color The color to use for highlighting. Should be in the format of "FF0000". - */ - /*public void setHighlightColor( String color ) - { - highlightColor = Color.decode( color ); - }**/ - - /** - * Get the highlight color as an HTML like string. This will return a string of six characters. - * - * @return The current highlight color. For example FF0000 - */ - /*public String getHighlightColorAsString() - { - //BJL: kudos to anyone that has a cleaner way of doing this! - String red = Integer.toHexString( highlightColor.getRed() ); - String green = Integer.toHexString( highlightColor.getGreen() ); - String blue = Integer.toHexString( highlightColor.getBlue() ); - - return (red.length() < 2 ? "0" + red : red) + - (green.length() < 2 ? "0" + green : green) + - (blue.length() < 2 ? "0" + blue : blue); - }**/ -} diff --git a/library/src/main/java/com/tom_roush/pdfbox/util/QuickSort.java b/library/src/main/java/com/tom_roush/pdfbox/util/QuickSort.java index 5e9ea4465..31ebd4f50 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/util/QuickSort.java +++ b/library/src/main/java/com/tom_roush/pdfbox/util/QuickSort.java @@ -16,15 +16,18 @@ */ package com.tom_roush.pdfbox.util; +import java.util.ArrayDeque; import java.util.Comparator; +import java.util.Deque; import java.util.List; /** * see http://de.wikipedia.org/wiki/Quicksort. - * - * @author UWe Pachler + * + * @author Uwe Pachler + * @author Manuel Aristaran */ -public class QuickSort +public final class QuickSort { private QuickSort() @@ -33,6 +36,7 @@ private QuickSort() private static final Comparator objComp = new Comparator() { + @Override public int compare(Comparable object1, Comparable object2) { return object1.compareTo(object2); @@ -52,7 +56,7 @@ public static void sort(List list, Comparator cmp) { return; } - quicksort(list, cmp, 0, size - 1); + quicksort(list, cmp); } /** @@ -65,49 +69,65 @@ public static void sort(List list) sort(list, (Comparator) objComp); } - private static void quicksort(List list, Comparator cmp, int left, int right) + private static void quicksort(List list, Comparator cmp) { - if (left < right) + Deque stack = new ArrayDeque(); + stack.push(0); + stack.push(list.size()); + while (!stack.isEmpty()) { - int splitter = split(list, cmp, left, right); - quicksort(list, cmp, left, splitter - 1); - quicksort(list, cmp, splitter + 1, right); + int right = stack.pop(); + int left = stack.pop(); + if (right - left < 2) + { + continue; + } + int p = left + ((right - left) / 2); + p = partition(list, cmp, p, left, right); + + stack.push(p + 1); + stack.push(right); + + stack.push(left); + stack.push(p); } } - private static void swap(List list, int i, int j) + private static int partition(List list, Comparator cmp, int p, int start, int end) { - T tmp = list.get(i); - list.set(i, list.get(j)); - list.set(j, tmp); - } + int l = start; + int h = end - 2; + T piv = list.get(p); + swap(list, p, end - 1); - private static int split(List list, Comparator cmp, int left, int right) - { - int i = left; - int j = right - 1; - T pivot = list.get(right); - do + while (l < h) { - while (cmp.compare(list.get(i), pivot) <= 0 && i < right) + if (cmp.compare(list.get(l), piv) <= 0) { - ++i; + l++; } - while (cmp.compare(pivot, list.get(j)) <= 0 && j > left) + else if (cmp.compare(piv, list.get(h)) <= 0) { - --j; + h--; } - if (i < j) + else { - swap(list, i, j); + swap(list, l, h); } - - } while (i < j); - - if (cmp.compare(pivot, list.get(i)) < 0) + } + int idx = h; + if (cmp.compare(list.get(h), piv) < 0) { - swap(list, i, right); + idx++; } - return i; + swap(list, end - 1, idx); + return idx; + } + + private static void swap(List list, int i, int j) + { + T tmp = list.get(i); + list.set(i, list.get(j)); + list.set(j, tmp); } } diff --git a/library/src/main/java/com/tom_roush/pdfbox/util/XMLUtil.java b/library/src/main/java/com/tom_roush/pdfbox/util/XMLUtil.java deleted file mode 100644 index e8fb52466..000000000 --- a/library/src/main/java/com/tom_roush/pdfbox/util/XMLUtil.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.tom_roush.pdfbox.util; - -import java.io.IOException; -import java.io.InputStream; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.Text; - -/** - * This class with handle some simple XML operations. - * - * @author Ben Litchfield - * @version $Revision: 1.3 $ - */ -public class XMLUtil -{ - /** - * Utility class, should not be instantiated. - * - */ - private XMLUtil() - { - } - - /** - * This will parse an XML stream and create a DOM document. - * - * @param is The stream to get the XML from. - * @return The DOM document. - * @throws IOException It there is an error creating the dom. - */ - public static Document parse( InputStream is ) throws IOException - { - try - { - DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = builderFactory.newDocumentBuilder(); - return builder.parse( is ); - } - catch( Exception e ) - { - IOException thrown = new IOException( e.getMessage() ); - throw thrown; - } - } - - /** - * This will get the text value of an element. - * - * @param node The node to get the text value for. - * @return The text of the node. - */ - public static String getNodeValue( Element node ) - { - String retval = ""; - NodeList children = node.getChildNodes(); - for( int i=0; i the type of value to store for byte sequences + * @author Drew Noakes + * + * code taken from https://github.com/drewnoakes/metadata-extractor + * + * 2016-01-04 + * + * latest commit number 73f1a48 + * + * Stores values using a prefix tree (aka 'trie', i.e. reTRIEval data structure). + */ +class ByteTrie +{ + /** + * A node in the trie. Has children and may have an associated value. + */ + static class ByteTrieNode + { + private final Map> children = new HashMap>(); + private T value = null; + + public void setValue(T value) + { + if (this.value != null) + { + throw new IllegalStateException("Value already set for this trie node"); + } + this.value = value; + } + + public T getValue() + { + return value; + } + } + + private final ByteTrieNode root = new ByteTrieNode(); + private int maxDepth; + + /** + * Return the most specific value stored for this byte sequence. If not found, returns + * null or a default values as specified by calling + * {@link ByteTrie#setDefaultValue}. + * + * @param bytes + * @return + */ + public T find(byte[] bytes) + { + ByteTrieNode node = root; + T val = node.getValue(); + for (byte b : bytes) + { + ByteTrieNode child = node.children.get(b); + if (child == null) + { + break; + } + node = child; + if (node.getValue() != null) + { + val = node.getValue(); + } + } + return val; + } + + /** + * Store the given value at the specified path. + * + * @param value + * @param parts + */ + public void addPath(T value, byte[]... parts) + { + int depth = 0; + ByteTrieNode node = root; + for (byte[] part : parts) + { + for (byte b : part) + { + ByteTrieNode child = node.children.get(b); + if (child == null) + { + child = new ByteTrieNode(); + node.children.put(b, child); + } + node = child; + depth++; + } + } + node.setValue(value); + maxDepth = Math.max(maxDepth, depth); + } + + /** + * Sets the default value to use in {@link ByteTrie#find(byte[])} when no path matches. + * + * @param defaultValue + */ + public void setDefaultValue(T defaultValue) + { + root.setValue(defaultValue); + } + + /** + * Gets the maximum depth stored in this trie. + * + * @return + */ + public int getMaxDepth() + { + return maxDepth; + } +} diff --git a/library/src/main/java/com/tom_roush/pdfbox/exceptions/OutlineNotLocalException.java b/library/src/main/java/com/tom_roush/pdfbox/util/filetypedetector/FileType.java similarity index 54% rename from library/src/main/java/com/tom_roush/pdfbox/exceptions/OutlineNotLocalException.java rename to library/src/main/java/com/tom_roush/pdfbox/util/filetypedetector/FileType.java index 0b2439709..21b36f510 100644 --- a/library/src/main/java/com/tom_roush/pdfbox/exceptions/OutlineNotLocalException.java +++ b/library/src/main/java/com/tom_roush/pdfbox/util/filetypedetector/FileType.java @@ -14,28 +14,49 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.tom_roush.pdfbox.exceptions; - -import java.io.IOException; +package com.tom_roush.pdfbox.util.filetypedetector; /** - * This exception will be thrown when a local destination(page within the same PDF) is required - * but the bookmark(PDOutlineItem) refers to an external destination or an action that does not - * point to a page. + * @author Drew Noakes + * + * code taken from https://github.com/drewnoakes/metadata-extractor + * + * 2016-01-04 + * + * latest commit number 73f1a48 * - * @author Ben Litchfield - * @version $Revision: 1.2 $ + * Enumeration of supported image file formats. */ -public class OutlineNotLocalException extends IOException +public enum FileType { + UNKNOWN, JPEG, TIFF, PSD, PNG, BMP, GIF, ICO, PCX, RIFF, /** - * Constructor. - * - * @param msg An error message. + * Sony camera raw. + */ + ARW, + /** + * Canon camera raw, version 1. + */ + CRW, + /** + * Canon camera raw, version 2. + */ + CR2, + /** + * Nikon camera raw. + */ + NEF, + /** + * Olympus camera raw. + */ + ORF, + /** + * FujiFilm camera raw. + */ + RAF, + /** + * Panasonic camera raw. */ - public OutlineNotLocalException( String msg ) - { - super( msg ); - } -} + RW2 +} \ No newline at end of file diff --git a/library/src/main/java/com/tom_roush/pdfbox/util/filetypedetector/FileTypeDetector.java b/library/src/main/java/com/tom_roush/pdfbox/util/filetypedetector/FileTypeDetector.java new file mode 100644 index 000000000..56612bc6b --- /dev/null +++ b/library/src/main/java/com/tom_roush/pdfbox/util/filetypedetector/FileTypeDetector.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.util.filetypedetector; + +import java.io.BufferedInputStream; +import java.io.IOException; + +import com.tom_roush.pdfbox.util.Charsets; + +/** + * @author Drew Noakes + * + * code taken from https://github.com/drewnoakes/metadata-extractor + * + * 2016-01-04 + * + * latest commit number 73f1a48 + * + * Examines the a file's first bytes and estimates the file's type. + */ +public final class FileTypeDetector +{ + private static final ByteTrie root; + + static + { + root = new ByteTrie(); + root.setDefaultValue(FileType.UNKNOWN); + + // https://en.wikipedia.org/wiki/List_of_file_signatures + + root.addPath(FileType.JPEG, new byte[] {(byte)0xff, (byte)0xd8}); + root.addPath(FileType.TIFF, "II".getBytes(Charsets.ISO_8859_1), new byte[] {0x2a, 0x00}); + root.addPath(FileType.TIFF, "MM".getBytes(Charsets.ISO_8859_1), new byte[] {0x00, 0x2a}); + root.addPath(FileType.PSD, "8BPS".getBytes(Charsets.ISO_8859_1)); + root.addPath(FileType.PNG, + new byte[] {(byte)0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, + 0x0D, 0x49, 0x48, 0x44, 0x52}); + // TODO technically there are other very rare magic numbers for OS/2 BMP files... + root.addPath(FileType.BMP, "BM".getBytes(Charsets.ISO_8859_1)); + root.addPath(FileType.GIF, "GIF87a".getBytes(Charsets.ISO_8859_1)); + root.addPath(FileType.GIF, "GIF89a".getBytes(Charsets.ISO_8859_1)); + root.addPath(FileType.ICO, new byte[] {0x00, 0x00, 0x01, 0x00}); + // multiple PCX versions, explicitly listed + root.addPath(FileType.PCX, new byte[] {0x0A, 0x00, 0x01}); + root.addPath(FileType.PCX, new byte[] {0x0A, 0x02, 0x01}); + root.addPath(FileType.PCX, new byte[] {0x0A, 0x03, 0x01}); + root.addPath(FileType.PCX, new byte[] {0x0A, 0x05, 0x01}); + root.addPath(FileType.RIFF, "RIFF".getBytes(Charsets.ISO_8859_1)); + + root.addPath(FileType.ARW, "II".getBytes(Charsets.ISO_8859_1), + new byte[] {0x2a, 0x00, 0x08, 0x00}); + root.addPath(FileType.CRW, "II".getBytes(Charsets.ISO_8859_1), + new byte[] {0x1a, 0x00, 0x00, 0x00}, "HEAPCCDR".getBytes(Charsets.ISO_8859_1)); + root.addPath(FileType.CR2, "II".getBytes(Charsets.ISO_8859_1), + new byte[] {0x2a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x43, 0x52}); + root.addPath(FileType.NEF, "MM".getBytes(Charsets.ISO_8859_1), + new byte[] {0x00, 0x2a, 0x00, 0x00, 0x00, (byte)0x80, 0x00}); + root.addPath(FileType.ORF, "IIRO".getBytes(Charsets.ISO_8859_1), + new byte[] {(byte)0x08, 0x00}); + root.addPath(FileType.ORF, "IIRS".getBytes(Charsets.ISO_8859_1), + new byte[] {(byte)0x08, 0x00}); + root.addPath(FileType.RAF, "FUJIFILMCCD-RAW".getBytes(Charsets.ISO_8859_1)); + root.addPath(FileType.RW2, "II".getBytes(Charsets.ISO_8859_1), new byte[] {0x55, 0x00}); + } + + private FileTypeDetector() throws Exception + { + } + + /** + * Examines the a file's first bytes and estimates the file's type. + *

      + * Requires a {@link BufferedInputStream} in order to mark and reset the stream to the position + * at which it was provided to this method once completed. + *

      + * Requires the stream to contain at least eight bytes. + * + * @param inputStream a buffered input stream of the file to examine. + * @return the file type. + * @throws IOException if an IO error occurred or the input stream ended unexpectedly. + */ + public static FileType detectFileType(final BufferedInputStream inputStream) throws IOException + { + if (!inputStream.markSupported()) + { + throw new IOException("Stream must support mark/reset"); + } + + int maxByteCount = root.getMaxDepth(); + + inputStream.mark(maxByteCount); + + byte[] bytes = new byte[maxByteCount]; + int bytesRead = inputStream.read(bytes); + + if (bytesRead == -1) + { + throw new IOException("Stream ended before file's magic number could be determined."); + } + + inputStream.reset(); + + //noinspection ConstantConditions + return root.find(bytes); + } +} diff --git a/library/src/main/resources/com/tom_roush/pdfbox/resources/text/BidiMirroring.txt b/library/src/main/resources/com/tom_roush/pdfbox/resources/text/BidiMirroring.txt new file mode 100644 index 000000000..fbc60f1ab --- /dev/null +++ b/library/src/main/resources/com/tom_roush/pdfbox/resources/text/BidiMirroring.txt @@ -0,0 +1,604 @@ +# BidiMirroring-8.0.0.txt +# Date: 2015-01-20, 18:30:00 GMT [KW, LI] +# +# Bidi_Mirroring_Glyph Property +# +# This file is an informative contributory data file in the +# Unicode Character Database. +# +# Copyright (c) 1991-2015 Unicode, Inc. +# For terms of use, see http://www.unicode.org/terms_of_use.html +# +# This data file lists characters that have the Bidi_Mirrored=Yes property +# value, for which there is another Unicode character that typically has a glyph +# that is the mirror image of the original character's glyph. +# +# The repertoire covered by the file is Unicode 8.0.0. +# +# The file contains a list of lines with mappings from one code point +# to another one for character-based mirroring. +# Note that for "real" mirroring, a rendering engine needs to select +# appropriate alternative glyphs, and that many Unicode characters do not +# have a mirror-image Unicode character. +# +# Each mapping line contains two fields, separated by a semicolon (';'). +# Each of the two fields contains a code point represented as a +# variable-length hexadecimal value with 4 to 6 digits. +# A comment indicates where the characters are "BEST FIT" mirroring. +# +# Code points for which Bidi_Mirrored=Yes, but for which no appropriate +# characters exist with mirrored glyphs, are +# listed as comments at the end of the file. +# +# Formally, the default value of the Bidi_Mirroring_Glyph property +# for each code point is , unless a mapping to +# some other character is specified in this data file. When a code +# point has the default value for the Bidi_Mirroring_Glyph property, +# that means that no other character exists whose glyph is suitable +# for character-based mirroring. +# +# For information on bidi mirroring, see UAX #9: Unicode Bidirectional Algorithm, +# at http://www.unicode.org/unicode/reports/tr9/ +# +# This file was originally created by Markus Scherer. +# Extended for Unicode 3.2, 4.0, 4.1, 5.0, 5.1, 5.2, and 6.0 by Ken Whistler, +# and for subsequent versions by Ken Whistler and Laurentiu Iancu. +# +# ############################################################ +# +# Property: Bidi_Mirroring_Glyph +# +# @missing: 0000..10FFFF; + +0028; 0029 # LEFT PARENTHESIS +0029; 0028 # RIGHT PARENTHESIS +003C; 003E # LESS-THAN SIGN +003E; 003C # GREATER-THAN SIGN +005B; 005D # LEFT SQUARE BRACKET +005D; 005B # RIGHT SQUARE BRACKET +007B; 007D # LEFT CURLY BRACKET +007D; 007B # RIGHT CURLY BRACKET +00AB; 00BB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +00BB; 00AB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +0F3A; 0F3B # TIBETAN MARK GUG RTAGS GYON +0F3B; 0F3A # TIBETAN MARK GUG RTAGS GYAS +0F3C; 0F3D # TIBETAN MARK ANG KHANG GYON +0F3D; 0F3C # TIBETAN MARK ANG KHANG GYAS +169B; 169C # OGHAM FEATHER MARK +169C; 169B # OGHAM REVERSED FEATHER MARK +2039; 203A # SINGLE LEFT-POINTING ANGLE QUOTATION MARK +203A; 2039 # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +2045; 2046 # LEFT SQUARE BRACKET WITH QUILL +2046; 2045 # RIGHT SQUARE BRACKET WITH QUILL +207D; 207E # SUPERSCRIPT LEFT PARENTHESIS +207E; 207D # SUPERSCRIPT RIGHT PARENTHESIS +208D; 208E # SUBSCRIPT LEFT PARENTHESIS +208E; 208D # SUBSCRIPT RIGHT PARENTHESIS +2208; 220B # ELEMENT OF +2209; 220C # NOT AN ELEMENT OF +220A; 220D # SMALL ELEMENT OF +220B; 2208 # CONTAINS AS MEMBER +220C; 2209 # DOES NOT CONTAIN AS MEMBER +220D; 220A # SMALL CONTAINS AS MEMBER +2215; 29F5 # DIVISION SLASH +223C; 223D # TILDE OPERATOR +223D; 223C # REVERSED TILDE +2243; 22CD # ASYMPTOTICALLY EQUAL TO +2252; 2253 # APPROXIMATELY EQUAL TO OR THE IMAGE OF +2253; 2252 # IMAGE OF OR APPROXIMATELY EQUAL TO +2254; 2255 # COLON EQUALS +2255; 2254 # EQUALS COLON +2264; 2265 # LESS-THAN OR EQUAL TO +2265; 2264 # GREATER-THAN OR EQUAL TO +2266; 2267 # LESS-THAN OVER EQUAL TO +2267; 2266 # GREATER-THAN OVER EQUAL TO +2268; 2269 # [BEST FIT] LESS-THAN BUT NOT EQUAL TO +2269; 2268 # [BEST FIT] GREATER-THAN BUT NOT EQUAL TO +226A; 226B # MUCH LESS-THAN +226B; 226A # MUCH GREATER-THAN +226E; 226F # [BEST FIT] NOT LESS-THAN +226F; 226E # [BEST FIT] NOT GREATER-THAN +2270; 2271 # [BEST FIT] NEITHER LESS-THAN NOR EQUAL TO +2271; 2270 # [BEST FIT] NEITHER GREATER-THAN NOR EQUAL TO +2272; 2273 # [BEST FIT] LESS-THAN OR EQUIVALENT TO +2273; 2272 # [BEST FIT] GREATER-THAN OR EQUIVALENT TO +2274; 2275 # [BEST FIT] NEITHER LESS-THAN NOR EQUIVALENT TO +2275; 2274 # [BEST FIT] NEITHER GREATER-THAN NOR EQUIVALENT TO +2276; 2277 # LESS-THAN OR GREATER-THAN +2277; 2276 # GREATER-THAN OR LESS-THAN +2278; 2279 # [BEST FIT] NEITHER LESS-THAN NOR GREATER-THAN +2279; 2278 # [BEST FIT] NEITHER GREATER-THAN NOR LESS-THAN +227A; 227B # PRECEDES +227B; 227A # SUCCEEDS +227C; 227D # PRECEDES OR EQUAL TO +227D; 227C # SUCCEEDS OR EQUAL TO +227E; 227F # [BEST FIT] PRECEDES OR EQUIVALENT TO +227F; 227E # [BEST FIT] SUCCEEDS OR EQUIVALENT TO +2280; 2281 # [BEST FIT] DOES NOT PRECEDE +2281; 2280 # [BEST FIT] DOES NOT SUCCEED +2282; 2283 # SUBSET OF +2283; 2282 # SUPERSET OF +2284; 2285 # [BEST FIT] NOT A SUBSET OF +2285; 2284 # [BEST FIT] NOT A SUPERSET OF +2286; 2287 # SUBSET OF OR EQUAL TO +2287; 2286 # SUPERSET OF OR EQUAL TO +2288; 2289 # [BEST FIT] NEITHER A SUBSET OF NOR EQUAL TO +2289; 2288 # [BEST FIT] NEITHER A SUPERSET OF NOR EQUAL TO +228A; 228B # [BEST FIT] SUBSET OF WITH NOT EQUAL TO +228B; 228A # [BEST FIT] SUPERSET OF WITH NOT EQUAL TO +228F; 2290 # SQUARE IMAGE OF +2290; 228F # SQUARE ORIGINAL OF +2291; 2292 # SQUARE IMAGE OF OR EQUAL TO +2292; 2291 # SQUARE ORIGINAL OF OR EQUAL TO +2298; 29B8 # CIRCLED DIVISION SLASH +22A2; 22A3 # RIGHT TACK +22A3; 22A2 # LEFT TACK +22A6; 2ADE # ASSERTION +22A8; 2AE4 # TRUE +22A9; 2AE3 # FORCES +22AB; 2AE5 # DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE +22B0; 22B1 # PRECEDES UNDER RELATION +22B1; 22B0 # SUCCEEDS UNDER RELATION +22B2; 22B3 # NORMAL SUBGROUP OF +22B3; 22B2 # CONTAINS AS NORMAL SUBGROUP +22B4; 22B5 # NORMAL SUBGROUP OF OR EQUAL TO +22B5; 22B4 # CONTAINS AS NORMAL SUBGROUP OR EQUAL TO +22B6; 22B7 # ORIGINAL OF +22B7; 22B6 # IMAGE OF +22C9; 22CA # LEFT NORMAL FACTOR SEMIDIRECT PRODUCT +22CA; 22C9 # RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT +22CB; 22CC # LEFT SEMIDIRECT PRODUCT +22CC; 22CB # RIGHT SEMIDIRECT PRODUCT +22CD; 2243 # REVERSED TILDE EQUALS +22D0; 22D1 # DOUBLE SUBSET +22D1; 22D0 # DOUBLE SUPERSET +22D6; 22D7 # LESS-THAN WITH DOT +22D7; 22D6 # GREATER-THAN WITH DOT +22D8; 22D9 # VERY MUCH LESS-THAN +22D9; 22D8 # VERY MUCH GREATER-THAN +22DA; 22DB # LESS-THAN EQUAL TO OR GREATER-THAN +22DB; 22DA # GREATER-THAN EQUAL TO OR LESS-THAN +22DC; 22DD # EQUAL TO OR LESS-THAN +22DD; 22DC # EQUAL TO OR GREATER-THAN +22DE; 22DF # EQUAL TO OR PRECEDES +22DF; 22DE # EQUAL TO OR SUCCEEDS +22E0; 22E1 # [BEST FIT] DOES NOT PRECEDE OR EQUAL +22E1; 22E0 # [BEST FIT] DOES NOT SUCCEED OR EQUAL +22E2; 22E3 # [BEST FIT] NOT SQUARE IMAGE OF OR EQUAL TO +22E3; 22E2 # [BEST FIT] NOT SQUARE ORIGINAL OF OR EQUAL TO +22E4; 22E5 # [BEST FIT] SQUARE IMAGE OF OR NOT EQUAL TO +22E5; 22E4 # [BEST FIT] SQUARE ORIGINAL OF OR NOT EQUAL TO +22E6; 22E7 # [BEST FIT] LESS-THAN BUT NOT EQUIVALENT TO +22E7; 22E6 # [BEST FIT] GREATER-THAN BUT NOT EQUIVALENT TO +22E8; 22E9 # [BEST FIT] PRECEDES BUT NOT EQUIVALENT TO +22E9; 22E8 # [BEST FIT] SUCCEEDS BUT NOT EQUIVALENT TO +22EA; 22EB # [BEST FIT] NOT NORMAL SUBGROUP OF +22EB; 22EA # [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP +22EC; 22ED # [BEST FIT] NOT NORMAL SUBGROUP OF OR EQUAL TO +22ED; 22EC # [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL +22F0; 22F1 # UP RIGHT DIAGONAL ELLIPSIS +22F1; 22F0 # DOWN RIGHT DIAGONAL ELLIPSIS +22F2; 22FA # ELEMENT OF WITH LONG HORIZONTAL STROKE +22F3; 22FB # ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22F4; 22FC # SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22F6; 22FD # ELEMENT OF WITH OVERBAR +22F7; 22FE # SMALL ELEMENT OF WITH OVERBAR +22FA; 22F2 # CONTAINS WITH LONG HORIZONTAL STROKE +22FB; 22F3 # CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22FC; 22F4 # SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +22FD; 22F6 # CONTAINS WITH OVERBAR +22FE; 22F7 # SMALL CONTAINS WITH OVERBAR +2308; 2309 # LEFT CEILING +2309; 2308 # RIGHT CEILING +230A; 230B # LEFT FLOOR +230B; 230A # RIGHT FLOOR +2329; 232A # LEFT-POINTING ANGLE BRACKET +232A; 2329 # RIGHT-POINTING ANGLE BRACKET +2768; 2769 # MEDIUM LEFT PARENTHESIS ORNAMENT +2769; 2768 # MEDIUM RIGHT PARENTHESIS ORNAMENT +276A; 276B # MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT +276B; 276A # MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT +276C; 276D # MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT +276D; 276C # MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT +276E; 276F # HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT +276F; 276E # HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT +2770; 2771 # HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT +2771; 2770 # HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT +2772; 2773 # LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT +2773; 2772 # LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT +2774; 2775 # MEDIUM LEFT CURLY BRACKET ORNAMENT +2775; 2774 # MEDIUM RIGHT CURLY BRACKET ORNAMENT +27C3; 27C4 # OPEN SUBSET +27C4; 27C3 # OPEN SUPERSET +27C5; 27C6 # LEFT S-SHAPED BAG DELIMITER +27C6; 27C5 # RIGHT S-SHAPED BAG DELIMITER +27C8; 27C9 # REVERSE SOLIDUS PRECEDING SUBSET +27C9; 27C8 # SUPERSET PRECEDING SOLIDUS +27CB; 27CD # MATHEMATICAL RISING DIAGONAL +27CD; 27CB # MATHEMATICAL FALLING DIAGONAL +27D5; 27D6 # LEFT OUTER JOIN +27D6; 27D5 # RIGHT OUTER JOIN +27DD; 27DE # LONG RIGHT TACK +27DE; 27DD # LONG LEFT TACK +27E2; 27E3 # WHITE CONCAVE-SIDED DIAMOND WITH LEFTWARDS TICK +27E3; 27E2 # WHITE CONCAVE-SIDED DIAMOND WITH RIGHTWARDS TICK +27E4; 27E5 # WHITE SQUARE WITH LEFTWARDS TICK +27E5; 27E4 # WHITE SQUARE WITH RIGHTWARDS TICK +27E6; 27E7 # MATHEMATICAL LEFT WHITE SQUARE BRACKET +27E7; 27E6 # MATHEMATICAL RIGHT WHITE SQUARE BRACKET +27E8; 27E9 # MATHEMATICAL LEFT ANGLE BRACKET +27E9; 27E8 # MATHEMATICAL RIGHT ANGLE BRACKET +27EA; 27EB # MATHEMATICAL LEFT DOUBLE ANGLE BRACKET +27EB; 27EA # MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET +27EC; 27ED # MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET +27ED; 27EC # MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET +27EE; 27EF # MATHEMATICAL LEFT FLATTENED PARENTHESIS +27EF; 27EE # MATHEMATICAL RIGHT FLATTENED PARENTHESIS +2983; 2984 # LEFT WHITE CURLY BRACKET +2984; 2983 # RIGHT WHITE CURLY BRACKET +2985; 2986 # LEFT WHITE PARENTHESIS +2986; 2985 # RIGHT WHITE PARENTHESIS +2987; 2988 # Z NOTATION LEFT IMAGE BRACKET +2988; 2987 # Z NOTATION RIGHT IMAGE BRACKET +2989; 298A # Z NOTATION LEFT BINDING BRACKET +298A; 2989 # Z NOTATION RIGHT BINDING BRACKET +298B; 298C # LEFT SQUARE BRACKET WITH UNDERBAR +298C; 298B # RIGHT SQUARE BRACKET WITH UNDERBAR +298D; 2990 # LEFT SQUARE BRACKET WITH TICK IN TOP CORNER +298E; 298F # RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +298F; 298E # LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +2990; 298D # RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER +2991; 2992 # LEFT ANGLE BRACKET WITH DOT +2992; 2991 # RIGHT ANGLE BRACKET WITH DOT +2993; 2994 # LEFT ARC LESS-THAN BRACKET +2994; 2993 # RIGHT ARC GREATER-THAN BRACKET +2995; 2996 # DOUBLE LEFT ARC GREATER-THAN BRACKET +2996; 2995 # DOUBLE RIGHT ARC LESS-THAN BRACKET +2997; 2998 # LEFT BLACK TORTOISE SHELL BRACKET +2998; 2997 # RIGHT BLACK TORTOISE SHELL BRACKET +29B8; 2298 # CIRCLED REVERSE SOLIDUS +29C0; 29C1 # CIRCLED LESS-THAN +29C1; 29C0 # CIRCLED GREATER-THAN +29C4; 29C5 # SQUARED RISING DIAGONAL SLASH +29C5; 29C4 # SQUARED FALLING DIAGONAL SLASH +29CF; 29D0 # LEFT TRIANGLE BESIDE VERTICAL BAR +29D0; 29CF # VERTICAL BAR BESIDE RIGHT TRIANGLE +29D1; 29D2 # BOWTIE WITH LEFT HALF BLACK +29D2; 29D1 # BOWTIE WITH RIGHT HALF BLACK +29D4; 29D5 # TIMES WITH LEFT HALF BLACK +29D5; 29D4 # TIMES WITH RIGHT HALF BLACK +29D8; 29D9 # LEFT WIGGLY FENCE +29D9; 29D8 # RIGHT WIGGLY FENCE +29DA; 29DB # LEFT DOUBLE WIGGLY FENCE +29DB; 29DA # RIGHT DOUBLE WIGGLY FENCE +29F5; 2215 # REVERSE SOLIDUS OPERATOR +29F8; 29F9 # BIG SOLIDUS +29F9; 29F8 # BIG REVERSE SOLIDUS +29FC; 29FD # LEFT-POINTING CURVED ANGLE BRACKET +29FD; 29FC # RIGHT-POINTING CURVED ANGLE BRACKET +2A2B; 2A2C # MINUS SIGN WITH FALLING DOTS +2A2C; 2A2B # MINUS SIGN WITH RISING DOTS +2A2D; 2A2E # PLUS SIGN IN LEFT HALF CIRCLE +2A2E; 2A2D # PLUS SIGN IN RIGHT HALF CIRCLE +2A34; 2A35 # MULTIPLICATION SIGN IN LEFT HALF CIRCLE +2A35; 2A34 # MULTIPLICATION SIGN IN RIGHT HALF CIRCLE +2A3C; 2A3D # INTERIOR PRODUCT +2A3D; 2A3C # RIGHTHAND INTERIOR PRODUCT +2A64; 2A65 # Z NOTATION DOMAIN ANTIRESTRICTION +2A65; 2A64 # Z NOTATION RANGE ANTIRESTRICTION +2A79; 2A7A # LESS-THAN WITH CIRCLE INSIDE +2A7A; 2A79 # GREATER-THAN WITH CIRCLE INSIDE +2A7D; 2A7E # LESS-THAN OR SLANTED EQUAL TO +2A7E; 2A7D # GREATER-THAN OR SLANTED EQUAL TO +2A7F; 2A80 # LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE +2A80; 2A7F # GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE +2A81; 2A82 # LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE +2A82; 2A81 # GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE +2A83; 2A84 # LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT +2A84; 2A83 # GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT +2A8B; 2A8C # LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN +2A8C; 2A8B # GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN +2A91; 2A92 # LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL +2A92; 2A91 # GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL +2A93; 2A94 # LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL +2A94; 2A93 # GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL +2A95; 2A96 # SLANTED EQUAL TO OR LESS-THAN +2A96; 2A95 # SLANTED EQUAL TO OR GREATER-THAN +2A97; 2A98 # SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE +2A98; 2A97 # SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE +2A99; 2A9A # DOUBLE-LINE EQUAL TO OR LESS-THAN +2A9A; 2A99 # DOUBLE-LINE EQUAL TO OR GREATER-THAN +2A9B; 2A9C # DOUBLE-LINE SLANTED EQUAL TO OR LESS-THAN +2A9C; 2A9B # DOUBLE-LINE SLANTED EQUAL TO OR GREATER-THAN +2AA1; 2AA2 # DOUBLE NESTED LESS-THAN +2AA2; 2AA1 # DOUBLE NESTED GREATER-THAN +2AA6; 2AA7 # LESS-THAN CLOSED BY CURVE +2AA7; 2AA6 # GREATER-THAN CLOSED BY CURVE +2AA8; 2AA9 # LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL +2AA9; 2AA8 # GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL +2AAA; 2AAB # SMALLER THAN +2AAB; 2AAA # LARGER THAN +2AAC; 2AAD # SMALLER THAN OR EQUAL TO +2AAD; 2AAC # LARGER THAN OR EQUAL TO +2AAF; 2AB0 # PRECEDES ABOVE SINGLE-LINE EQUALS SIGN +2AB0; 2AAF # SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN +2AB3; 2AB4 # PRECEDES ABOVE EQUALS SIGN +2AB4; 2AB3 # SUCCEEDS ABOVE EQUALS SIGN +2ABB; 2ABC # DOUBLE PRECEDES +2ABC; 2ABB # DOUBLE SUCCEEDS +2ABD; 2ABE # SUBSET WITH DOT +2ABE; 2ABD # SUPERSET WITH DOT +2ABF; 2AC0 # SUBSET WITH PLUS SIGN BELOW +2AC0; 2ABF # SUPERSET WITH PLUS SIGN BELOW +2AC1; 2AC2 # SUBSET WITH MULTIPLICATION SIGN BELOW +2AC2; 2AC1 # SUPERSET WITH MULTIPLICATION SIGN BELOW +2AC3; 2AC4 # SUBSET OF OR EQUAL TO WITH DOT ABOVE +2AC4; 2AC3 # SUPERSET OF OR EQUAL TO WITH DOT ABOVE +2AC5; 2AC6 # SUBSET OF ABOVE EQUALS SIGN +2AC6; 2AC5 # SUPERSET OF ABOVE EQUALS SIGN +2ACD; 2ACE # SQUARE LEFT OPEN BOX OPERATOR +2ACE; 2ACD # SQUARE RIGHT OPEN BOX OPERATOR +2ACF; 2AD0 # CLOSED SUBSET +2AD0; 2ACF # CLOSED SUPERSET +2AD1; 2AD2 # CLOSED SUBSET OR EQUAL TO +2AD2; 2AD1 # CLOSED SUPERSET OR EQUAL TO +2AD3; 2AD4 # SUBSET ABOVE SUPERSET +2AD4; 2AD3 # SUPERSET ABOVE SUBSET +2AD5; 2AD6 # SUBSET ABOVE SUBSET +2AD6; 2AD5 # SUPERSET ABOVE SUPERSET +2ADE; 22A6 # SHORT LEFT TACK +2AE3; 22A9 # DOUBLE VERTICAL BAR LEFT TURNSTILE +2AE4; 22A8 # VERTICAL BAR DOUBLE LEFT TURNSTILE +2AE5; 22AB # DOUBLE VERTICAL BAR DOUBLE LEFT TURNSTILE +2AEC; 2AED # DOUBLE STROKE NOT SIGN +2AED; 2AEC # REVERSED DOUBLE STROKE NOT SIGN +2AF7; 2AF8 # TRIPLE NESTED LESS-THAN +2AF8; 2AF7 # TRIPLE NESTED GREATER-THAN +2AF9; 2AFA # DOUBLE-LINE SLANTED LESS-THAN OR EQUAL TO +2AFA; 2AF9 # DOUBLE-LINE SLANTED GREATER-THAN OR EQUAL TO +2E02; 2E03 # LEFT SUBSTITUTION BRACKET +2E03; 2E02 # RIGHT SUBSTITUTION BRACKET +2E04; 2E05 # LEFT DOTTED SUBSTITUTION BRACKET +2E05; 2E04 # RIGHT DOTTED SUBSTITUTION BRACKET +2E09; 2E0A # LEFT TRANSPOSITION BRACKET +2E0A; 2E09 # RIGHT TRANSPOSITION BRACKET +2E0C; 2E0D # LEFT RAISED OMISSION BRACKET +2E0D; 2E0C # RIGHT RAISED OMISSION BRACKET +2E1C; 2E1D # LEFT LOW PARAPHRASE BRACKET +2E1D; 2E1C # RIGHT LOW PARAPHRASE BRACKET +2E20; 2E21 # LEFT VERTICAL BAR WITH QUILL +2E21; 2E20 # RIGHT VERTICAL BAR WITH QUILL +2E22; 2E23 # TOP LEFT HALF BRACKET +2E23; 2E22 # TOP RIGHT HALF BRACKET +2E24; 2E25 # BOTTOM LEFT HALF BRACKET +2E25; 2E24 # BOTTOM RIGHT HALF BRACKET +2E26; 2E27 # LEFT SIDEWAYS U BRACKET +2E27; 2E26 # RIGHT SIDEWAYS U BRACKET +2E28; 2E29 # LEFT DOUBLE PARENTHESIS +2E29; 2E28 # RIGHT DOUBLE PARENTHESIS +3008; 3009 # LEFT ANGLE BRACKET +3009; 3008 # RIGHT ANGLE BRACKET +300A; 300B # LEFT DOUBLE ANGLE BRACKET +300B; 300A # RIGHT DOUBLE ANGLE BRACKET +300C; 300D # [BEST FIT] LEFT CORNER BRACKET +300D; 300C # [BEST FIT] RIGHT CORNER BRACKET +300E; 300F # [BEST FIT] LEFT WHITE CORNER BRACKET +300F; 300E # [BEST FIT] RIGHT WHITE CORNER BRACKET +3010; 3011 # LEFT BLACK LENTICULAR BRACKET +3011; 3010 # RIGHT BLACK LENTICULAR BRACKET +3014; 3015 # LEFT TORTOISE SHELL BRACKET +3015; 3014 # RIGHT TORTOISE SHELL BRACKET +3016; 3017 # LEFT WHITE LENTICULAR BRACKET +3017; 3016 # RIGHT WHITE LENTICULAR BRACKET +3018; 3019 # LEFT WHITE TORTOISE SHELL BRACKET +3019; 3018 # RIGHT WHITE TORTOISE SHELL BRACKET +301A; 301B # LEFT WHITE SQUARE BRACKET +301B; 301A # RIGHT WHITE SQUARE BRACKET +FE59; FE5A # SMALL LEFT PARENTHESIS +FE5A; FE59 # SMALL RIGHT PARENTHESIS +FE5B; FE5C # SMALL LEFT CURLY BRACKET +FE5C; FE5B # SMALL RIGHT CURLY BRACKET +FE5D; FE5E # SMALL LEFT TORTOISE SHELL BRACKET +FE5E; FE5D # SMALL RIGHT TORTOISE SHELL BRACKET +FE64; FE65 # SMALL LESS-THAN SIGN +FE65; FE64 # SMALL GREATER-THAN SIGN +FF08; FF09 # FULLWIDTH LEFT PARENTHESIS +FF09; FF08 # FULLWIDTH RIGHT PARENTHESIS +FF1C; FF1E # FULLWIDTH LESS-THAN SIGN +FF1E; FF1C # FULLWIDTH GREATER-THAN SIGN +FF3B; FF3D # FULLWIDTH LEFT SQUARE BRACKET +FF3D; FF3B # FULLWIDTH RIGHT SQUARE BRACKET +FF5B; FF5D # FULLWIDTH LEFT CURLY BRACKET +FF5D; FF5B # FULLWIDTH RIGHT CURLY BRACKET +FF5F; FF60 # FULLWIDTH LEFT WHITE PARENTHESIS +FF60; FF5F # FULLWIDTH RIGHT WHITE PARENTHESIS +FF62; FF63 # [BEST FIT] HALFWIDTH LEFT CORNER BRACKET +FF63; FF62 # [BEST FIT] HALFWIDTH RIGHT CORNER BRACKET + +# The following characters have no appropriate mirroring character. +# For these characters it is up to the rendering system +# to provide mirrored glyphs. + +# 2140; DOUBLE-STRUCK N-ARY SUMMATION +# 2201; COMPLEMENT +# 2202; PARTIAL DIFFERENTIAL +# 2203; THERE EXISTS +# 2204; THERE DOES NOT EXIST +# 2211; N-ARY SUMMATION +# 2216; SET MINUS +# 221A; SQUARE ROOT +# 221B; CUBE ROOT +# 221C; FOURTH ROOT +# 221D; PROPORTIONAL TO +# 221F; RIGHT ANGLE +# 2220; ANGLE +# 2221; MEASURED ANGLE +# 2222; SPHERICAL ANGLE +# 2224; DOES NOT DIVIDE +# 2226; NOT PARALLEL TO +# 222B; INTEGRAL +# 222C; DOUBLE INTEGRAL +# 222D; TRIPLE INTEGRAL +# 222E; CONTOUR INTEGRAL +# 222F; SURFACE INTEGRAL +# 2230; VOLUME INTEGRAL +# 2231; CLOCKWISE INTEGRAL +# 2232; CLOCKWISE CONTOUR INTEGRAL +# 2233; ANTICLOCKWISE CONTOUR INTEGRAL +# 2239; EXCESS +# 223B; HOMOTHETIC +# 223E; INVERTED LAZY S +# 223F; SINE WAVE +# 2240; WREATH PRODUCT +# 2241; NOT TILDE +# 2242; MINUS TILDE +# 2244; NOT ASYMPTOTICALLY EQUAL TO +# 2245; APPROXIMATELY EQUAL TO +# 2246; APPROXIMATELY BUT NOT ACTUALLY EQUAL TO +# 2247; NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO +# 2248; ALMOST EQUAL TO +# 2249; NOT ALMOST EQUAL TO +# 224A; ALMOST EQUAL OR EQUAL TO +# 224B; TRIPLE TILDE +# 224C; ALL EQUAL TO +# 225F; QUESTIONED EQUAL TO +# 2260; NOT EQUAL TO +# 2262; NOT IDENTICAL TO +# 228C; MULTISET +# 22A7; MODELS +# 22AA; TRIPLE VERTICAL BAR RIGHT TURNSTILE +# 22AC; DOES NOT PROVE +# 22AD; NOT TRUE +# 22AE; DOES NOT FORCE +# 22AF; NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE +# 22B8; MULTIMAP +# 22BE; RIGHT ANGLE WITH ARC +# 22BF; RIGHT TRIANGLE +# 22F5; ELEMENT OF WITH DOT ABOVE +# 22F8; ELEMENT OF WITH UNDERBAR +# 22F9; ELEMENT OF WITH TWO HORIZONTAL STROKES +# 22FF; Z NOTATION BAG MEMBERSHIP +# 2320; TOP HALF INTEGRAL +# 2321; BOTTOM HALF INTEGRAL +# 27C0; THREE DIMENSIONAL ANGLE +# 27CC; LONG DIVISION +# 27D3; LOWER RIGHT CORNER WITH DOT +# 27D4; UPPER LEFT CORNER WITH DOT +# 27DC; LEFT MULTIMAP +# 299B; MEASURED ANGLE OPENING LEFT +# 299C; RIGHT ANGLE VARIANT WITH SQUARE +# 299D; MEASURED RIGHT ANGLE WITH DOT +# 299E; ANGLE WITH S INSIDE +# 299F; ACUTE ANGLE +# 29A0; SPHERICAL ANGLE OPENING LEFT +# 29A1; SPHERICAL ANGLE OPENING UP +# 29A2; TURNED ANGLE +# 29A3; REVERSED ANGLE +# 29A4; ANGLE WITH UNDERBAR +# 29A5; REVERSED ANGLE WITH UNDERBAR +# 29A6; OBLIQUE ANGLE OPENING UP +# 29A7; OBLIQUE ANGLE OPENING DOWN +# 29A8; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT +# 29A9; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT +# 29AA; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT +# 29AB; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT +# 29AC; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP +# 29AD; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP +# 29AE; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN +# 29AF; MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN +# 29C2; CIRCLE WITH SMALL CIRCLE TO THE RIGHT +# 29C3; CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT +# 29C9; TWO JOINED SQUARES +# 29CE; RIGHT TRIANGLE ABOVE LEFT TRIANGLE +# 29DC; INCOMPLETE INFINITY +# 29E1; INCREASES AS +# 29E3; EQUALS SIGN AND SLANTED PARALLEL +# 29E4; EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE +# 29E5; IDENTICAL TO AND SLANTED PARALLEL +# 29E8; DOWN-POINTING TRIANGLE WITH LEFT HALF BLACK +# 29E9; DOWN-POINTING TRIANGLE WITH RIGHT HALF BLACK +# 29F4; RULE-DELAYED +# 29F6; SOLIDUS WITH OVERBAR +# 29F7; REVERSE SOLIDUS WITH HORIZONTAL STROKE +# 2A0A; MODULO TWO SUM +# 2A0B; SUMMATION WITH INTEGRAL +# 2A0C; QUADRUPLE INTEGRAL OPERATOR +# 2A0D; FINITE PART INTEGRAL +# 2A0E; INTEGRAL WITH DOUBLE STROKE +# 2A0F; INTEGRAL AVERAGE WITH SLASH +# 2A10; CIRCULATION FUNCTION +# 2A11; ANTICLOCKWISE INTEGRATION +# 2A12; LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE +# 2A13; LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE +# 2A14; LINE INTEGRATION NOT INCLUDING THE POLE +# 2A15; INTEGRAL AROUND A POINT OPERATOR +# 2A16; QUATERNION INTEGRAL OPERATOR +# 2A17; INTEGRAL WITH LEFTWARDS ARROW WITH HOOK +# 2A18; INTEGRAL WITH TIMES SIGN +# 2A19; INTEGRAL WITH INTERSECTION +# 2A1A; INTEGRAL WITH UNION +# 2A1B; INTEGRAL WITH OVERBAR +# 2A1C; INTEGRAL WITH UNDERBAR +# 2A1E; LARGE LEFT TRIANGLE OPERATOR +# 2A1F; Z NOTATION SCHEMA COMPOSITION +# 2A20; Z NOTATION SCHEMA PIPING +# 2A21; Z NOTATION SCHEMA PROJECTION +# 2A24; PLUS SIGN WITH TILDE ABOVE +# 2A26; PLUS SIGN WITH TILDE BELOW +# 2A29; MINUS SIGN WITH COMMA ABOVE +# 2A3E; Z NOTATION RELATIONAL COMPOSITION +# 2A57; SLOPING LARGE OR +# 2A58; SLOPING LARGE AND +# 2A6A; TILDE OPERATOR WITH DOT ABOVE +# 2A6B; TILDE OPERATOR WITH RISING DOTS +# 2A6C; SIMILAR MINUS SIMILAR +# 2A6D; CONGRUENT WITH DOT ABOVE +# 2A6F; ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT +# 2A70; APPROXIMATELY EQUAL OR EQUAL TO +# 2A73; EQUALS SIGN ABOVE TILDE OPERATOR +# 2A74; DOUBLE COLON EQUAL +# 2A7B; LESS-THAN WITH QUESTION MARK ABOVE +# 2A7C; GREATER-THAN WITH QUESTION MARK ABOVE +# 2A85; LESS-THAN OR APPROXIMATE +# 2A86; GREATER-THAN OR APPROXIMATE +# 2A87; LESS-THAN AND SINGLE-LINE NOT EQUAL TO +# 2A88; GREATER-THAN AND SINGLE-LINE NOT EQUAL TO +# 2A89; LESS-THAN AND NOT APPROXIMATE +# 2A8A; GREATER-THAN AND NOT APPROXIMATE +# 2A8D; LESS-THAN ABOVE SIMILAR OR EQUAL +# 2A8E; GREATER-THAN ABOVE SIMILAR OR EQUAL +# 2A8F; LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN +# 2A90; GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN +# 2A9D; SIMILAR OR LESS-THAN +# 2A9E; SIMILAR OR GREATER-THAN +# 2A9F; SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN +# 2AA0; SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN +# 2AA3; DOUBLE NESTED LESS-THAN WITH UNDERBAR +# 2AB1; PRECEDES ABOVE SINGLE-LINE NOT EQUAL TO +# 2AB2; SUCCEEDS ABOVE SINGLE-LINE NOT EQUAL TO +# 2AB5; PRECEDES ABOVE NOT EQUAL TO +# 2AB6; SUCCEEDS ABOVE NOT EQUAL TO +# 2AB7; PRECEDES ABOVE ALMOST EQUAL TO +# 2AB8; SUCCEEDS ABOVE ALMOST EQUAL TO +# 2AB9; PRECEDES ABOVE NOT ALMOST EQUAL TO +# 2ABA; SUCCEEDS ABOVE NOT ALMOST EQUAL TO +# 2AC7; SUBSET OF ABOVE TILDE OPERATOR +# 2AC8; SUPERSET OF ABOVE TILDE OPERATOR +# 2AC9; SUBSET OF ABOVE ALMOST EQUAL TO +# 2ACA; SUPERSET OF ABOVE ALMOST EQUAL TO +# 2ACB; SUBSET OF ABOVE NOT EQUAL TO +# 2ACC; SUPERSET OF ABOVE NOT EQUAL TO +# 2ADC; FORKING +# 2AE2; VERTICAL BAR TRIPLE RIGHT TURNSTILE +# 2AE6; LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL +# 2AEE; DOES NOT DIVIDE WITH REVERSED NEGATION SLASH +# 2AF3; PARALLEL WITH TILDE OPERATOR +# 2AFB; TRIPLE SOLIDUS BINARY RELATION +# 2AFD; DOUBLE SOLIDUS OPERATOR +# 1D6DB; MATHEMATICAL BOLD PARTIAL DIFFERENTIAL +# 1D715; MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL +# 1D74F; MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL +# 1D789; MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL +# 1D7C3; MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL + +# EOF diff --git a/library/src/test/java/com/tom_roush/pdfbox/filter/PredictorTest.java b/library/src/test/java/com/tom_roush/pdfbox/filter/PredictorTest.java new file mode 100644 index 000000000..746fe9571 --- /dev/null +++ b/library/src/test/java/com/tom_roush/pdfbox/filter/PredictorTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2015 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tom_roush.pdfbox.filter; + +import org.junit.Assert; +import org.junit.Test; + +import static com.tom_roush.pdfbox.filter.Predictor.calcSetBitSeq; +import static com.tom_roush.pdfbox.filter.Predictor.getBitSeq; + +/** + * @author Tilman Hausherr + */ +public class PredictorTest +{ + /** + * Test of getBitSeq method, of class Predictor. + */ + @Test + public void testGetBitSeq() + { + Assert.assertEquals(Integer.parseInt("11111111", 2), + getBitSeq(Integer.parseInt("11111111", 2), 0, 8)); + Assert.assertEquals(Integer.parseInt("00000000", 2), + getBitSeq(Integer.parseInt("00000000", 2), 0, 8)); + Assert.assertEquals(Integer.parseInt("1", 2), + getBitSeq(Integer.parseInt("11111111", 2), 0, 1)); + Assert.assertEquals(Integer.parseInt("0", 2), + getBitSeq(Integer.parseInt("00000000", 2), 0, 1)); + Assert.assertEquals(Integer.parseInt("001", 2), + getBitSeq(Integer.parseInt("00110001", 2), 0, 3)); + Assert.assertEquals(Integer.parseInt("10101010", 2), + getBitSeq(Integer.parseInt("10101010", 2), 0, 8)); + Assert.assertEquals(Integer.parseInt("10", 2), + getBitSeq(Integer.parseInt("10101010", 2), 0, 2)); + Assert.assertEquals(Integer.parseInt("01", 2), + getBitSeq(Integer.parseInt("10101010", 2), 1, 2)); + Assert.assertEquals(Integer.parseInt("10", 2), + getBitSeq(Integer.parseInt("10101010", 2), 2, 2)); + Assert.assertEquals(Integer.parseInt("101", 2), + getBitSeq(Integer.parseInt("10101010", 2), 3, 3)); + Assert.assertEquals(Integer.parseInt("1010101", 2), + getBitSeq(Integer.parseInt("10101010", 2), 1, 7)); + Assert.assertEquals(Integer.parseInt("01", 2), + getBitSeq(Integer.parseInt("10101010", 2), 3, 2)); + Assert.assertEquals(Integer.parseInt("00110001", 2), + getBitSeq(Integer.parseInt("00110001", 2), 0, 8)); + Assert.assertEquals(Integer.parseInt("10001", 2), + getBitSeq(Integer.parseInt("00110001", 2), 0, 5)); + Assert.assertEquals(Integer.parseInt("0011", 2), + getBitSeq(Integer.parseInt("00110001", 2), 4, 4)); + Assert.assertEquals(Integer.parseInt("110", 2), + getBitSeq(Integer.parseInt("00110001", 2), 3, 3)); + Assert.assertEquals(Integer.parseInt("00", 2), + getBitSeq(Integer.parseInt("00110001", 2), 6, 2)); + Assert.assertEquals(Integer.parseInt("1111", 2), + getBitSeq(Integer.parseInt("11110000", 2), 4, 4)); + Assert.assertEquals(Integer.parseInt("11", 2), + getBitSeq(Integer.parseInt("11110000", 2), 6, 2)); + Assert.assertEquals(Integer.parseInt("0000", 2), + getBitSeq(Integer.parseInt("11110000", 2), 0, 4)); + } + + /** + * Test of calcSetBitSeq method, of class Predictor. + */ + @Test + public void testCalcSetBitSeq() + { + Assert.assertEquals(Integer.parseInt("00000000", 2), + calcSetBitSeq(Integer.parseInt("11111111", 2), 0, 8, 0)); + Assert.assertEquals(Integer.parseInt("00000001", 2), + calcSetBitSeq(Integer.parseInt("11111111", 2), 0, 8, 1)); + Assert.assertEquals(Integer.parseInt("11111111", 2), + calcSetBitSeq(Integer.parseInt("11111111", 2), 0, 1, 1)); + Assert.assertEquals(Integer.parseInt("11111101", 2), + calcSetBitSeq(Integer.parseInt("11111111", 2), 0, 2, 1)); + Assert.assertEquals(Integer.parseInt("11111001", 2), + calcSetBitSeq(Integer.parseInt("11111111", 2), 0, 3, 1)); + Assert.assertEquals(Integer.parseInt("00000001", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 0, 2, 1)); + Assert.assertEquals(Integer.parseInt("11110001", 2), + calcSetBitSeq(Integer.parseInt("11111111", 2), 0, 4, 1)); + Assert.assertEquals(Integer.parseInt("11100011", 2), + calcSetBitSeq(Integer.parseInt("11111111", 2), 1, 4, 1)); + Assert.assertEquals(Integer.parseInt("00000010", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 1, 1, 1)); + Assert.assertEquals(Integer.parseInt("11111111", 2), + calcSetBitSeq(Integer.parseInt("11111111", 2), 7, 1, 1)); + Assert.assertEquals(Integer.parseInt("01111111", 2), + calcSetBitSeq(Integer.parseInt("11111111", 2), 7, 1, 0)); + Assert.assertEquals(Integer.parseInt("10000000", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 7, 1, 1)); + Assert.assertEquals(Integer.parseInt("00000000", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 7, 1, 0)); + Assert.assertEquals(Integer.parseInt("01000000", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 6, 1, 1)); + Assert.assertEquals(Integer.parseInt("00000000", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 6, 1, 0)); + Assert.assertEquals(Integer.parseInt("00110000", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 3, 3, 6)); + Assert.assertEquals(Integer.parseInt("01100000", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 4, 3, 6)); + Assert.assertEquals(Integer.parseInt("11000000", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 5, 3, 6)); + Assert.assertEquals(Integer.parseInt("11111111", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 0, 8, 0xFF)); + Assert.assertEquals(Integer.parseInt("11111111", 2), + calcSetBitSeq(Integer.parseInt("11111111", 2), 0, 8, 0xFF)); + Assert.assertEquals(0x7E, calcSetBitSeq(0xA5, 0, 8, 0xD9 + 0xA5)); + + // check truncation + Assert.assertEquals(Integer.parseInt("00000010", 2), + calcSetBitSeq(Integer.parseInt("00000000", 2), 1, 1, 3)); + } +} diff --git a/library/src/test/java/com/tom_roush/pdfbox/io/TestRandomAccessBuffer.java b/library/src/test/java/com/tom_roush/pdfbox/io/TestRandomAccessBuffer.java index 90c357014..8dbbd48e9 100644 --- a/library/src/test/java/com/tom_roush/pdfbox/io/TestRandomAccessBuffer.java +++ b/library/src/test/java/com/tom_roush/pdfbox/io/TestRandomAccessBuffer.java @@ -181,7 +181,7 @@ public void testArrayReadWrite() throws IOException // read the last 5 bytes from the first and the first 5 bytes // from the second chunk and sum them up. The result should be "5" byteArray = new byte[10]; - buffer.read(byteArray); + buffer.read(byteArray, 0, byteArray.length); int result = 0; for (int i = 0; i < 10; i++) { @@ -194,13 +194,14 @@ public void testArrayReadWrite() throws IOException // read the last 5 bytes from the second and the first 5 bytes // from the third chunk and sum them up. The result should be "15" byteArray = new byte[10]; - buffer.read(byteArray,0, byteArray.length); + buffer.read(byteArray); result = 0; for ( int i=0;i < 10;i++ ) { result += byteArray[i]; } + assertEquals(15, result); buffer.close(); } @@ -295,4 +296,37 @@ public void testPDFBOX1490() throws Exception buffer.seek(buffer.getPosition()); buffer.close(); } + + public void testPDFBOX2969() throws Exception + { + // create buffer with non-default chunk size + // by providing an array with unusual size + // (larger than RandomAccessBuffer.DEFAULT_CHUNK_SIZE) + int chunkSize = (CHUNK_SIZE << 4) + 3; + byte[] byteArray = new byte[chunkSize]; + + com.tom_roush.pdfbox.io.RandomAccessBuffer buffer = + new com.tom_roush.pdfbox.io.RandomAccessBuffer(byteArray); + + // fill completely + for (int i = 0; i < chunkSize; i++) + { + buffer.write(1); + } + + // create clone + com.tom_roush.pdfbox.io.RandomAccessBuffer bufferClone = buffer.clone(); + + // read all from both + buffer.seek(0); + int bufRead = buffer.read(new byte[(int)buffer.length()]); + + bufferClone.seek(0); + int bufCloneRead = bufferClone.read(new byte[(int)bufferClone.length()]); + + assertEquals(bufRead, bufCloneRead); + + buffer.close(); + bufferClone.close(); + } } diff --git a/library/src/test/java/com/tom_roush/pdfbox/multipdf/TestLayerUtility.java b/library/src/test/java/com/tom_roush/pdfbox/multipdf/TestLayerUtility.java index 581857d57..5392b9faf 100644 --- a/library/src/test/java/com/tom_roush/pdfbox/multipdf/TestLayerUtility.java +++ b/library/src/test/java/com/tom_roush/pdfbox/multipdf/TestLayerUtility.java @@ -16,6 +16,9 @@ */ package com.tom_roush.pdfbox.multipdf; +import java.io.File; +import java.io.IOException; + import com.tom_roush.harmony.awt.AWTColor; import com.tom_roush.harmony.awt.geom.AffineTransform; import com.tom_roush.pdfbox.cos.COSName; @@ -34,9 +37,6 @@ import junit.framework.TestCase; -import java.io.File; -import java.io.IOException; - /** * Tests the {@link com.tom_roush.pdfbox.multipdf.LayerUtility} class. */ @@ -134,7 +134,8 @@ private File createMainPDF() throws IOException }; //Setup page content stream and paint background/title - PDPageContentStream contentStream = new PDPageContentStream(doc, page, false, false); + PDPageContentStream contentStream = new PDPageContentStream(doc, page, + PDPageContentStream.AppendMode.OVERWRITE, false); PDFont font = PDType1Font.HELVETICA_BOLD; contentStream.beginText(); contentStream.newLineAtOffset(50, 720); @@ -180,7 +181,8 @@ private File createOverlay1() throws IOException } //Setup page content stream and paint background/title - PDPageContentStream contentStream = new PDPageContentStream(doc, page, false, false); + PDPageContentStream contentStream = new PDPageContentStream(doc, page, + PDPageContentStream.AppendMode.OVERWRITE, false); PDFont font = PDType1Font.HELVETICA_BOLD; contentStream.setNonStrokingColor(AWTColor.LIGHT_GRAY); contentStream.beginText(); diff --git a/library/src/test/java/com/tom_roush/pdfbox/pdfparser/TestPDFParser.java b/library/src/test/java/com/tom_roush/pdfbox/pdfparser/TestPDFParser.java index bceed584c..d3a122e22 100644 --- a/library/src/test/java/com/tom_roush/pdfbox/pdfparser/TestPDFParser.java +++ b/library/src/test/java/com/tom_roush/pdfbox/pdfparser/TestPDFParser.java @@ -21,18 +21,21 @@ package com.tom_roush.pdfbox.pdfparser; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.net.URISyntaxException; + import com.tom_roush.pdfbox.cos.COSDocument; +import com.tom_roush.pdfbox.io.MemoryUsageSetting; import com.tom_roush.pdfbox.io.RandomAccessBufferedFileInputStream; import com.tom_roush.pdfbox.io.RandomAccessRead; +import com.tom_roush.pdfbox.io.ScratchFile; +import com.tom_roush.pdfbox.pdmodel.PDDocument; import org.junit.Before; import org.junit.Test; -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.net.URISyntaxException; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -86,8 +89,10 @@ public void testPDFParserFile() throws IOException try { executeParserTest(new RandomAccessBufferedFileInputStream( - new File(getClass().getResource(PATH_OF_PDF).toURI())), false); - } catch (URISyntaxException e) + new File(getClass().getResource(PATH_OF_PDF).toURI())), + MemoryUsageSetting.setupMainMemoryOnly()); + } + catch (URISyntaxException e) { e.printStackTrace(); } @@ -98,7 +103,7 @@ public void testPDFParserInputStream() throws IOException { executeParserTest( new RandomAccessBufferedFileInputStream(getClass().getResourceAsStream(PATH_OF_PDF)), - false); + MemoryUsageSetting.setupMainMemoryOnly()); } @Test @@ -107,8 +112,10 @@ public void testPDFParserFileScratchFile() throws IOException try { executeParserTest(new RandomAccessBufferedFileInputStream( - new File(getClass().getResource(PATH_OF_PDF).toURI())), true); - } catch (URISyntaxException e) + new File(getClass().getResource(PATH_OF_PDF).toURI())), + MemoryUsageSetting.setupTempFileOnly()); + } + catch (URISyntaxException e) { e.printStackTrace(); } @@ -119,13 +126,23 @@ public void testPDFParserInputStreamScratchFile() throws IOException { executeParserTest( new RandomAccessBufferedFileInputStream(getClass().getResourceAsStream(PATH_OF_PDF)), - true); + MemoryUsageSetting.setupTempFileOnly()); + } + + @Test + public void testPDFParserMissingCatalog() throws IOException + { + // PDFBOX-3060 + PDDocument.load(TestPDFParser.class + .getResourceAsStream("/pdfbox/com/tom_roush/pdfbox/pdfparser/MissingCatalog.pdf")) + .close(); } - private void executeParserTest(RandomAccessRead source, boolean useScratchFile) + private void executeParserTest(RandomAccessRead source, MemoryUsageSetting memUsageSetting) throws IOException { - PDFParser pdfParser = new PDFParser(source, useScratchFile); + ScratchFile scratchFile = new ScratchFile(memUsageSetting); + PDFParser pdfParser = new PDFParser(source, scratchFile); pdfParser.parse(); COSDocument doc = pdfParser.getDocument(); assertNotNull(doc); diff --git a/library/src/test/java/com/tom_roush/pdfbox/pdmodel/TestFDF.java b/library/src/test/java/com/tom_roush/pdfbox/pdmodel/TestFDF.java index 9b64d65dc..c4a40ffd6 100644 --- a/library/src/test/java/com/tom_roush/pdfbox/pdmodel/TestFDF.java +++ b/library/src/test/java/com/tom_roush/pdfbox/pdmodel/TestFDF.java @@ -16,6 +16,10 @@ */ package com.tom_roush.pdfbox.pdmodel; +import java.io.File; +import java.io.IOException; +import java.util.List; + import com.tom_roush.pdfbox.contentstream.PDContentStream; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.cos.COSString; @@ -34,10 +38,6 @@ import junit.framework.TestCase; import junit.framework.TestSuite; -import java.io.File; -import java.io.IOException; -import java.util.List; - /** * This will test the FDF algorithms in PDFBox. * @@ -104,14 +104,14 @@ public void testFDFfdeb() throws Exception String expected = "/Tx BMC " + - "BT " + - "/Helv 9 Tf " + - " 0 g " + - " 2 1.985585 Td " + - "2.07698 0 Td " + - "(2) Tj " + - "ET " + - "EMC"; + "BT " + + "/Helv 9 Tf " + + " 0 g " + + " 2 1.985585 Td " + + "2.07698 0 Td " + + "(2) Tj " + + "ET " + + "EMC"; testContentStreams( fdeb, field, expected ); } @@ -144,39 +144,39 @@ public void testFDFPDFWithLotsOfFields() throws Exception feld2.setValue( "Benjamin" ); String expected = - "1 1 0.8000000119 rg " + - " 0 0 127.5 19.8299999237 re " + - " f " + - " 0 0 0 RG " + - " 1 w " + - " 0.5 0.5 126.5 18.8299999237 re " + - " S " + - " 0.5 g " + - " 1 1 m " + - " 1 18.8299999237 l " + - " 126.5 18.8299999237 l " + - " 125.5 17.8299999237 l " + - " 2 17.8299999237 l " + - " 2 2 l " + - " 1 1 l " + - " f " + - " 0.75 g " + - " 1 1 m " + - " 126.5 1 l " + - " 126.5 18.8299999237 l " + - " 125.5 17.8299999237 l " + - " 125.5 2 l " + - " 2 2 l " + - " 1 1 l " + - " f " + - " /Tx BMC " + - "BT " + - "/Helv 14 Tf " + - " 0 0 0 rg " + - " 4 4.721 Td " + - "(Benjamin) Tj " + - "ET " + - "EMC"; + "1 1 0.8000000119 rg " + + " 0 0 127.5 19.8299999237 re " + + " f " + + " 0 0 0 RG " + + " 1 w " + + " 0.5 0.5 126.5 18.8299999237 re " + + " S " + + " 0.5 g " + + " 1 1 m " + + " 1 18.8299999237 l " + + " 126.5 18.8299999237 l " + + " 125.5 17.8299999237 l " + + " 2 17.8299999237 l " + + " 2 2 l " + + " 1 1 l " + + " f " + + " 0.75 g " + + " 1 1 m " + + " 126.5 1 l " + + " 126.5 18.8299999237 l " + + " 125.5 17.8299999237 l " + + " 125.5 2 l " + + " 2 2 l " + + " 1 1 l " + + " f " + + " /Tx BMC " + + "BT " + + "/Helv 14 Tf " + + " 0 0 0 rg " + + " 4 4.721 Td " + + "(Benjamin) Tj " + + "ET " + + "EMC"; testContentStreams( fdeb, feld2, expected ); diff --git a/library/src/test/java/com/tom_roush/pdfbox/pdmodel/TestPDDocument.java b/library/src/test/java/com/tom_roush/pdfbox/pdmodel/TestPDDocument.java index cbcba3e10..cfcf25ff1 100644 --- a/library/src/test/java/com/tom_roush/pdfbox/pdmodel/TestPDDocument.java +++ b/library/src/test/java/com/tom_roush/pdfbox/pdmodel/TestPDDocument.java @@ -16,25 +16,28 @@ */ package com.tom_roush.pdfbox.pdmodel; -import com.tom_roush.pdfbox.io.IOUtils; - -import junit.framework.TestCase; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; 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.io.PrintWriter; import java.util.Arrays; +import com.tom_roush.pdfbox.io.IOUtils; + +import junit.framework.TestCase; + /** * Testcase introduced with PDFBOX-1581. * */ public class TestPDDocument extends TestCase { - private File testResultsDir = new File("target/test-output"); + private final File testResultsDir = new File("target/test-output"); @Override protected void setUp() throws Exception @@ -195,4 +198,53 @@ public void testVersions() throws IOException assertEquals("1.5", document.getDocumentCatalog().getVersion()); document.close(); } + + /** + * Test whether a bad file can be deleted after load() failed. + * + * @throws java.io.FileNotFoundException + */ + public void testDeleteBadFile() throws FileNotFoundException + { + File f = new File("test.pdf"); + PrintWriter pw = new PrintWriter(new FileOutputStream(f)); + pw.write("