aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Pepping <spepping@apache.org>2011-09-29 09:49:24 +0000
committerSimon Pepping <spepping@apache.org>2011-09-29 09:49:24 +0000
commit3dae5bcd7ea906930ff37a51d11f6c7e74c821a7 (patch)
tree549cc4036c7706eea7ffcc40f0c061392c8d6d82
parentd00fe07f590200f20567656182345a026d5d693a (diff)
parent0a4a0fdf2e28fd2f685104efcde2cde6b7b57124 (diff)
downloadxmlgraphics-fop-3dae5bcd7ea906930ff37a51d11f6c7e74c821a7.tar.gz
xmlgraphics-fop-3dae5bcd7ea906930ff37a51d11f6c7e74c821a7.zip
Merged /xmlgraphics/fop/trunk:r1149494-1177230
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_ComplexScripts@1177244 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--build.xml9
-rw-r--r--src/documentation/content/xdocs/compliance.ihtml8
-rw-r--r--src/documentation/content/xdocs/favicon.ico (renamed from src/documentation/content/favicon.ico)bin766 -> 766 bytes
-rw-r--r--src/documentation/content/xdocs/trunk/configuration.xml5
-rw-r--r--src/documentation/content/xdocs/trunk/output.xml6
-rw-r--r--src/documentation/content/xdocs/trunk/pdfencryption.xml83
-rw-r--r--src/documentation/content/xdocs/trunk/running.xml4
-rw-r--r--src/documentation/skinconf.xml3
-rw-r--r--src/java/org/apache/fop/accessibility/StructureTreeBuilder.java2
-rw-r--r--src/java/org/apache/fop/afp/AFPDataObjectFactory.java4
-rw-r--r--src/java/org/apache/fop/afp/fonts/AFPBase12FontCollection.java8
-rw-r--r--src/java/org/apache/fop/afp/fonts/CharacterSet.java65
-rw-r--r--src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java73
-rw-r--r--src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java2
-rw-r--r--src/java/org/apache/fop/afp/fonts/CharactersetEncoder.java213
-rw-r--r--src/java/org/apache/fop/afp/fonts/FopCharacterSet.java6
-rw-r--r--src/java/org/apache/fop/afp/ioca/IDEStructureParameter.java2
-rw-r--r--src/java/org/apache/fop/afp/modca/AbstractAFPObject.java59
-rw-r--r--src/java/org/apache/fop/afp/modca/AbstractNamedAFPObject.java2
-rw-r--r--src/java/org/apache/fop/afp/modca/AbstractTripletStructuredObject.java27
-rw-r--r--src/java/org/apache/fop/afp/modca/IncludeObject.java174
-rw-r--r--src/java/org/apache/fop/afp/modca/TagLogicalElement.java2
-rw-r--r--src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java48
-rw-r--r--src/java/org/apache/fop/afp/ptoca/TextDataInfoProducer.java4
-rw-r--r--src/java/org/apache/fop/afp/util/AFPResourceUtil.java60
-rw-r--r--src/java/org/apache/fop/cli/CommandLineOptions.java16
-rw-r--r--src/java/org/apache/fop/fonts/FontDescriptor.java5
-rw-r--r--src/java/org/apache/fop/fonts/LazyFont.java12
-rw-r--r--src/java/org/apache/fop/fonts/MultiByteFont.java36
-rw-r--r--src/java/org/apache/fop/fonts/SingleByteFont.java6
-rw-r--r--src/java/org/apache/fop/fonts/truetype/FontFileReader.java7
-rw-r--r--src/java/org/apache/fop/fonts/truetype/GlyfTable.java231
-rw-r--r--src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java202
-rw-r--r--src/java/org/apache/fop/pdf/FileIDGenerator.java124
-rw-r--r--src/java/org/apache/fop/pdf/PDFDocument.java48
-rw-r--r--src/java/org/apache/fop/pdf/PDFEncryption.java12
-rw-r--r--src/java/org/apache/fop/pdf/PDFEncryptionJCE.java730
-rw-r--r--src/java/org/apache/fop/pdf/PDFEncryptionManager.java10
-rw-r--r--src/java/org/apache/fop/pdf/PDFEncryptionParams.java109
-rw-r--r--src/java/org/apache/fop/pdf/PDFFactory.java37
-rw-r--r--src/java/org/apache/fop/pdf/PDFObject.java2
-rw-r--r--src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java7
-rw-r--r--src/java/org/apache/fop/render/bitmap/BitmapRendererConfigurator.java10
-rw-r--r--src/java/org/apache/fop/render/java2d/Base14FontCollection.java35
-rw-r--r--src/java/org/apache/fop/render/java2d/InstalledFontCollection.java19
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java28
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DRenderer.java6
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DUtil.java6
-rw-r--r--src/java/org/apache/fop/render/java2d/SystemFontMetricsMapper.java31
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java9
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java30
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFEventProducer.java10
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFEventProducer.xml1
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java47
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java90
-rw-r--r--src/java/org/apache/fop/render/ps/PSImageHandlerGraphics2D.java93
-rw-r--r--src/java/org/apache/fop/render/rtf/RTFHandler.java6
-rw-r--r--src/java/org/apache/fop/svg/AbstractFOPTranscoder.java1
-rw-r--r--status.xml37
-rw-r--r--test/java/org/apache/fop/StandardTestSuite.java10
-rw-r--r--test/java/org/apache/fop/UtilityCodeTestSuite.java6
-rw-r--r--test/java/org/apache/fop/afp/AFPResourceUtilTestCase.java100
-rw-r--r--test/java/org/apache/fop/afp/AFPTestSuite.java45
-rw-r--r--test/java/org/apache/fop/afp/expected_named_resource.afpbin0 -> 21494 bytes
-rw-r--r--test/java/org/apache/fop/afp/expected_resource.afpbin0 -> 21511 bytes
-rw-r--r--test/java/org/apache/fop/afp/fonts/CharactersetEncoderTest.java108
-rw-r--r--test/java/org/apache/fop/afp/modca/AbstractAFPObjectTestCase.java245
-rw-r--r--test/java/org/apache/fop/afp/modca/AbstractNamedAFPObjectTestCase.java60
-rw-r--r--test/java/org/apache/fop/afp/modca/AbstractStructuredObjectTestCase.java63
-rw-r--r--test/java/org/apache/fop/afp/modca/AbstractTripletStructuredObjectTestCase.java154
-rw-r--r--test/java/org/apache/fop/afp/modca/IncludeObjectTestCase.java129
-rw-r--r--test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java190
-rw-r--r--test/java/org/apache/fop/pdf/FileIDGeneratorTestCase.java106
-rw-r--r--test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java492
-rw-r--r--test/java/org/apache/fop/pdf/PDFFactoryTestCase.java58
-rw-r--r--test/java/org/apache/fop/render/pdf/PDFRendererConfiguratorTestCase.java146
-rw-r--r--test/java/org/apache/fop/render/pdf/RenderPDFTestSuite.java45
-rw-r--r--test/resources/org/apache/fop/render/pdf/above128.xconf10
-rw-r--r--test/resources/org/apache/fop/render/pdf/below40.xconf10
-rw-r--r--test/resources/org/apache/fop/render/pdf/correct.xconf10
-rw-r--r--test/resources/org/apache/fop/render/pdf/roundDown.xconf10
-rw-r--r--test/resources/org/apache/fop/render/pdf/roundUp.xconf10
82 files changed, 3899 insertions, 1030 deletions
diff --git a/build.xml b/build.xml
index 5d1c1d73a..805323c17 100644
--- a/build.xml
+++ b/build.xml
@@ -749,6 +749,7 @@ list of possible build targets.
<include name="**/*.xml"/>
<include name="**/*.fo"/>
<include name="**/BidiTestData*.ser"/>
+ <include name="**/*.afp"/>
</fileset>
<fileset dir="${build.dir}/test-gensrc">
<include name="**/*.xml"/>
@@ -991,7 +992,13 @@ list of possible build targets.
<test name="org.apache.fop.text.linebreak.LineBreakStatusTest" todir="${junit.reports.dir}"/>
</junit>
</target>
- <target name="junit-reduced" depends="junit-userconfig, junit-basic, junit-transcoder, junit-text-linebreak, junit-fotree, junit-complexscripts"/>
+ <target name="junit-render-pdf" depends="junit-compile">
+ <echo message="Running tests for the render pdf package"/>
+ <junit-run title="render-pdf" testsuite="org.apache.fop.render.pdf.RenderPDFTestSuite"
+ outfile="TEST-render-pdf"/>
+ </target>
+ <target name="junit-reduced" depends="junit-userconfig, junit-basic, junit-transcoder,
+ junit-text-linebreak, junit-fotree, junit-render-pdf, junit-complexscripts"/>
<target name="junit-full" depends="junit-reduced, junit-layout, junit-area-tree-xml-format, junit-intermediate-format"/>
<target name="junit" depends="junit-full" description="Runs all of FOP's JUnit tests" if="junit.present">
<fail><condition><or><isset property="fop.junit.error"/><isset property="fop.junit.failure"/><not><isset property="hyphenation.present"/></not></or></condition>
diff --git a/src/documentation/content/xdocs/compliance.ihtml b/src/documentation/content/xdocs/compliance.ihtml
index b2ea6ed37..54a1b9955 100644
--- a/src/documentation/content/xdocs/compliance.ihtml
+++ b/src/documentation/content/xdocs/compliance.ihtml
@@ -3669,10 +3669,10 @@
"http://www.w3.org/TR/xsl/#format">&sect;7.26.1</a></td>
<td><a name="fo-property-format" id="fo-property-format">format</a></td>
<td class="basic">Basic</td>
- <td class="yes">yes</td>
- <td class="yes">yes</td>
- <td class="yes">yes</td>
- <td>&nbsp;</td>
+ <td class="partial">partial</td>
+ <td class="partial">partial</td>
+ <td class="partial">partial</td>
+ <td>only values '0*1', 'a', 'A', 'i', 'I' supported</td>
</tr>
<tr>
<td align="center"><a href=
diff --git a/src/documentation/content/favicon.ico b/src/documentation/content/xdocs/favicon.ico
index 161bcf784..161bcf784 100644
--- a/src/documentation/content/favicon.ico
+++ b/src/documentation/content/xdocs/favicon.ico
Binary files differ
diff --git a/src/documentation/content/xdocs/trunk/configuration.xml b/src/documentation/content/xdocs/trunk/configuration.xml
index 26707edbf..e5469d037 100644
--- a/src/documentation/content/xdocs/trunk/configuration.xml
+++ b/src/documentation/content/xdocs/trunk/configuration.xml
@@ -367,12 +367,17 @@
<source><![CDATA[
<renderer mime="application/pdf">
<encryption-params>
+ <encryption-length>128</encryption-length>
<user-password>testuserpass</user-password>
<owner-password>testownerpass</owner-password>
<noprint/>
<nocopy/>
<noedit/>
<noannotations/>
+ <nofillinforms/>
+ <noaccesscontent/>
+ <noassembledoc/>
+ <noprinthq/>
</encryption-params>
</renderer>]]></source>
diff --git a/src/documentation/content/xdocs/trunk/output.xml b/src/documentation/content/xdocs/trunk/output.xml
index c71f6abf8..7b245eb07 100644
--- a/src/documentation/content/xdocs/trunk/output.xml
+++ b/src/documentation/content/xdocs/trunk/output.xml
@@ -743,6 +743,12 @@ Note that the value of the encoding attribute in the example is the double-byte
<a href="fonts.html#embedding"><code>referenced-fonts</code> section of the configuration file</a>.
However, the default fonts shown above will not be embedded.
</p>
+ <p>
+ For double byte EBCDIC encoded character sets, there is an optional tag that must be set to prevent
+ characters from being miscoded. This defaults to "false" if not specified.</p>
+ <source><![CDATA[
+ <afp-font type="CIDKeyed" codepage="T10835 " encoding="Cp937" characterset="CZTKAI" ebcdic-dbcs="true"/>]]>
+ </source>
</section>
<section id="afp-renderer-resolution-config">
<title>Output Resolution</title>
diff --git a/src/documentation/content/xdocs/trunk/pdfencryption.xml b/src/documentation/content/xdocs/trunk/pdfencryption.xml
index 22d965057..3562bb591 100644
--- a/src/documentation/content/xdocs/trunk/pdfencryption.xml
+++ b/src/documentation/content/xdocs/trunk/pdfencryption.xml
@@ -69,10 +69,45 @@
supplied, viewing the content is not restricted.
</p>
<p>
- Further restrictions can be imposed by using the <code>-noprint</code>,
- <code>-nocopy</code>, <code>-noedit</code> and
- <code>-noannotations</code> options, which disable printing, copying
- text, editing in Adobe Acrobat and making annotations, respectively.
+ Further restrictions can be imposed by using the following command-line options:
+ <table>
+ <tr>
+ <th>Option</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td><code>-noprint</code></td>
+ <td>disable printing</td>
+ </tr>
+ <tr>
+ <td><code>-nocopy</code></td>
+ <td>disable copy/paste of content</td>
+ </tr>
+ <tr>
+ <td><code>-noedit</code></td>
+ <td>disable editing in Adobe Acrobat</td>
+ </tr>
+ <tr>
+ <td><code>-noannotations</code></td>
+ <td>disable editing of annotations</td>
+ </tr>
+ <tr>
+ <td><code>-nofillinforms</code></td>
+ <td>disable filling in forms</td>
+ </tr>
+ <tr>
+ <td><code>-noaccesscontent</code></td>
+ <td>disable text and graphics extraction for accessibility purposes</td>
+ </tr>
+ <tr>
+ <td><code>-noassembledoc</code></td>
+ <td>disable assembling documents</td>
+ </tr>
+ <tr>
+ <td><code>-noprinthq</code></td>
+ <td>disable high quality printing</td>
+ </tr>
+ </table>
</p>
</section>
<section>
@@ -89,6 +124,12 @@
<th>Default</th>
</tr>
<tr>
+ <td>encryption-length</td>
+ <td>The encryption length in bit</td>
+ <td>Any multiple of 8 between 40 and 128</td>
+ <td>40</td>
+ </tr>
+ <tr>
<td>ownerPassword</td>
<td>The owner password</td>
<td>String</td>
@@ -114,7 +155,7 @@
</tr>
<tr>
<td>allowEditContent</td>
- <td>Allows/disallows editing of content</td>
+ <td>Allows/disallows editing in Adobe Acrobat</td>
<td>"TRUE" or "FALSE"</td>
<td>"TRUE"</td>
</tr>
@@ -124,6 +165,30 @@
<td>"TRUE" or "FALSE"</td>
<td>"TRUE"</td>
</tr>
+ <tr>
+ <td>allowFillInForms</td>
+ <td>Allows/disallows filling in forms</td>
+ <td>"TRUE" or "FALSE"</td>
+ <td>"TRUE"</td>
+ </tr>
+ <tr>
+ <td>allowAccessContent</td>
+ <td>Allows/disallows text and graphics extraction for accessibility purposes</td>
+ <td>"TRUE" or "FALSE"</td>
+ <td>"TRUE"</td>
+ </tr>
+ <tr>
+ <td>allowAssembleDocument</td>
+ <td>Allows/disallows assembling document</td>
+ <td>"TRUE" or "FALSE"</td>
+ <td>"TRUE"</td>
+ </tr>
+ <tr>
+ <td>allowPrintHq</td>
+ <td>Allows/disallows high quality printing</td>
+ <td>"TRUE" or "FALSE"</td>
+ <td>"TRUE"</td>
+ </tr>
</table>
<note>
Encryption is enabled as soon as one of these options is set.
@@ -151,6 +216,10 @@ Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent);
<li>allowCopyContent: true if copying content is allowed</li>
<li>allowEditContent: true if editing content is allowed</li>
<li>allowEditAnnotations: true if editing annotations is allowed</li>
+ <li>allowFillInForms: true if filling in forms is allowed.</li>
+ <li>allowAccessContent: true if extracting text and graphics is allowed</li>
+ <li>allowAssembleDocument: true if assembling document is allowed</li>
+ <li>allowPrintHq: true if printing to high quality is allowed</li>
</ol>
<p>
Alternatively, you can set each value separately in the Map provided by
@@ -163,6 +232,10 @@ Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent);
<li>nocopy: Boolean or "true"/"false"</li>
<li>noedit: Boolean or "true"/"false"</li>
<li>noannotations: Boolean or "true"/"false"</li>
+ <li>nofillinforms: Boolean or "true"/"false"</li>
+ <li>noaccesscontent: Boolean or "true"/"false"</li>
+ <li>noassembledoc: Boolean or "true"/"false"</li>
+ <li>noprinthq: Boolean or "true"/"false"</li>
</ol>
</section>
<section>
diff --git a/src/documentation/content/xdocs/trunk/running.xml b/src/documentation/content/xdocs/trunk/running.xml
index 11dc2848a..49ba7efd8 100644
--- a/src/documentation/content/xdocs/trunk/running.xml
+++ b/src/documentation/content/xdocs/trunk/running.xml
@@ -128,6 +128,10 @@ Fop [options] [-fo|-xml] infile [-xsl file] [-awt|-pdf|-mif|-rtf|-tiff|-png|-pcl
-nocopy PDF file will be encrypted without copy content permission
-noedit PDF file will be encrypted without edit content permission
-noannotations PDF file will be encrypted without edit annotation permission
+ -nofillinforms PDF file will be encrypted without fill in forms permission
+ -noaccesscontent PDF file will be encrypted without extract text and graphics permission
+ -noassembledoc PDF file will be encrypted without assemble the document permission
+ -noprinthq PDF file will be encrypted without print high quality permission
-a enables accessibility features (Tagged PDF etc., default off)
-pdfprofile prof PDF file will be generated with the specified profile
(Examples for prof: PDF/A-1b or PDF/X-3:2003)
diff --git a/src/documentation/skinconf.xml b/src/documentation/skinconf.xml
index 318cf99cf..f7d0b2de9 100644
--- a/src/documentation/skinconf.xml
+++ b/src/documentation/skinconf.xml
@@ -94,7 +94,7 @@ See main/fresh-site/src/documentation/skinconf.xml for details.
<copyright-link>http://www.apache.org/licenses/</copyright-link>
<!-- Optional text trademark statement below the copyright statement -->
<trademark-statement>
- Apache, Apache Forrest, the Apache feather logo, and the Apache Forrest
+ Apache, Apache FOP, the Apache feather logo, and the Apache FOP
logos are trademarks of The Apache Software Foundation. All other marks mentioned may be trademarks or registered trademarks of their respective owners.
</trademark-statement>
<!-- Some skins use this to form a 'breadcrumb trail' of links.
@@ -109,6 +109,7 @@ See main/fresh-site/src/documentation/skinconf.xml for details.
<trail location="alt">
<link1 name="The Apache Software Foundation" href="http://www.apache.org/"/>
<link2 name="Apache XML Graphics Project" href="http://xmlgraphics.apache.org/"/>
+ <link3 name="" href=""/>
</trail>
<!-- Configure the TOC, i.e. the Table of Contents.
@max-depth
diff --git a/src/java/org/apache/fop/accessibility/StructureTreeBuilder.java b/src/java/org/apache/fop/accessibility/StructureTreeBuilder.java
index cd9aa011a..036502e99 100644
--- a/src/java/org/apache/fop/accessibility/StructureTreeBuilder.java
+++ b/src/java/org/apache/fop/accessibility/StructureTreeBuilder.java
@@ -31,7 +31,7 @@ import org.apache.fop.util.DelegatingContentHandler;
/**
* Helper class that re-builds a structure tree from what is stored in an
- * intermediate XML file (IF XML or Area Tree XML).
+ * intermediate XML file (IF XML or Area Tree XML).
*/
public final class StructureTreeBuilder {
diff --git a/src/java/org/apache/fop/afp/AFPDataObjectFactory.java b/src/java/org/apache/fop/afp/AFPDataObjectFactory.java
index d7e8ea5f9..615c22b17 100644
--- a/src/java/org/apache/fop/afp/AFPDataObjectFactory.java
+++ b/src/java/org/apache/fop/afp/AFPDataObjectFactory.java
@@ -124,8 +124,10 @@ public class AFPDataObjectFactory {
break;
case 4:
case 8:
+ //A grayscale image
ideStruct = content.needIDEStructureParameter();
ideStruct.setBitsPerComponent(new int[] {bitsPerPixel});
+ ideStruct.setColorModel(IDEStructureParameter.COLOR_MODEL_YCBCR);
break;
case 24:
ideStruct = content.needIDEStructureParameter();
@@ -139,7 +141,7 @@ public class AFPDataObjectFactory {
throw new IllegalArgumentException("Unsupported number of bits per pixel: "
+ bitsPerPixel);
}
- if (imageObjectInfo.isSubtractive()) {
+ if (bitsPerPixel > 1 && imageObjectInfo.isSubtractive()) {
ideStruct = content.needIDEStructureParameter();
ideStruct.setSubtractive(imageObjectInfo.isSubtractive());
}
diff --git a/src/java/org/apache/fop/afp/fonts/AFPBase12FontCollection.java b/src/java/org/apache/fop/afp/fonts/AFPBase12FontCollection.java
index 417250df1..421945119 100644
--- a/src/java/org/apache/fop/afp/fonts/AFPBase12FontCollection.java
+++ b/src/java/org/apache/fop/afp/fonts/AFPBase12FontCollection.java
@@ -108,22 +108,22 @@ public class AFPBase12FontCollection implements FontCollection {
"Times New Roman", "TimesNewRoman", "serif", "any"};
font = createReferencedRasterFont("Times Roman");
- addCharacterSet(font, "CON200", new TimesRoman());
+ addCharacterSet(font, "C0N200", new TimesRoman());
num = addFontProperties(fontInfo, font, timesNames,
Font.STYLE_NORMAL, Font.WEIGHT_NORMAL, num);
font = createReferencedRasterFont("Times Roman Italic");
- addCharacterSet(font, "CON300", new TimesItalic());
+ addCharacterSet(font, "C0N300", new TimesItalic());
num = addFontProperties(fontInfo, font, timesNames,
Font.STYLE_ITALIC, Font.WEIGHT_NORMAL, num);
font = createReferencedRasterFont("Times Roman Bold");
- addCharacterSet(font, "CON400", new TimesBold());
+ addCharacterSet(font, "C0N400", new TimesBold());
num = addFontProperties(fontInfo, font, timesNames,
Font.STYLE_NORMAL, Font.WEIGHT_BOLD, num);
font = createReferencedRasterFont("Times Roman Italic Bold");
- addCharacterSet(font, "CON500", new TimesBoldItalic());
+ addCharacterSet(font, "C0N500", new TimesBoldItalic());
num = addFontProperties(fontInfo, font, timesNames,
Font.STYLE_ITALIC, Font.WEIGHT_BOLD, num);
diff --git a/src/java/org/apache/fop/afp/fonts/CharacterSet.java b/src/java/org/apache/fop/afp/fonts/CharacterSet.java
index 7123d4138..784588762 100644
--- a/src/java/org/apache/fop/afp/fonts/CharacterSet.java
+++ b/src/java/org/apache/fop/afp/fonts/CharacterSet.java
@@ -21,19 +21,13 @@ package org.apache.fop.afp.fonts;
import java.io.File;
import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CodingErrorAction;
-import java.nio.charset.UnsupportedCharsetException;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.apache.fop.afp.AFPConstants;
+import org.apache.fop.afp.fonts.CharactersetEncoder.EncodedChars;
import org.apache.fop.afp.util.ResourceAccessor;
import org.apache.fop.afp.util.SimpleResourceAccessor;
import org.apache.fop.afp.util.StringUtils;
@@ -70,16 +64,16 @@ public class CharacterSet {
/** The code page to which the character set relates */
- protected String codePage;
+ protected final String codePage;
/** The encoding used for the code page */
- protected String encoding;
+ protected final String encoding;
- /** The charset encoder corresponding to this encoding */
- private CharsetEncoder encoder;
+ /** The characterset encoder corresponding to this encoding */
+ private final CharactersetEncoder encoder;
/** The character set relating to the font */
- protected String name;
+ protected final String name;
/** The path to the installed fonts */
private ResourceAccessor accessor;
@@ -105,20 +99,22 @@ public class CharacterSet {
* {@link #CharacterSet(String, String, String, ResourceAccessor)} instead.
*/
public CharacterSet(String codePage, String encoding, String name, String path) {
- this(codePage, encoding, name,
+ this(codePage, encoding, false, name,
new SimpleResourceAccessor(path != null ? new File(path) : null));
}
/**
- * Constructor for the CharacterSetMetric object, the character set is used
- * to load the font information from the actual AFP font.
+ * Constructor for the CharacterSetMetric object, the character set is used to load the font
+ * information from the actual AFP font.
*
* @param codePage the code page identifier
* @param encoding the encoding of the font
+ * @param isEBDCS if this is an EBCDIC double byte character set.
* @param name the character set name
* @param accessor the resource accessor to load resource with
*/
- CharacterSet(String codePage, String encoding, String name, ResourceAccessor accessor) {
+ CharacterSet(String codePage, String encoding, boolean isEBDCS, String name,
+ ResourceAccessor accessor) {
if (name.length() > MAX_NAME_LEN) {
String msg = "Character set name '" + name + "' must be a maximum of "
+ MAX_NAME_LEN + " characters";
@@ -133,14 +129,7 @@ public class CharacterSet {
}
this.codePage = codePage;
this.encoding = encoding;
- try {
- this.encoder = Charset.forName(encoding).newEncoder();
- this.encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
- } catch (UnsupportedCharsetException uce) {
- //No nio-capable encoder available
- //This may happen with "Cp500" on Sun Java 1.4.2
- this.encoder = null;
- }
+ this.encoder = CharactersetEncoder.newInstance(encoding, isEBDCS);
this.accessor = accessor;
this.characterSetOrientations = new java.util.HashMap(4);
@@ -357,32 +346,8 @@ public class CharacterSet {
* @return the encoded characters
* @throws CharacterCodingException if the encoding operation fails
*/
- public byte[] encodeChars(CharSequence chars) throws CharacterCodingException {
- if (encoder != null) {
- ByteBuffer bb;
- // encode method is not thread safe
- synchronized (encoder) {
- bb = encoder.encode(CharBuffer.wrap(chars));
- }
- if (bb.hasArray()) {
- return bb.array();
- } else {
- bb.rewind();
- byte[] bytes = new byte[bb.remaining()];
- bb.get(bytes);
- return bytes;
- }
- } else {
- //Sun Java 1.4.2 compatibility
- byte[] bytes;
- try {
- bytes = chars.toString().getBytes(this.encoding);
- return bytes;
- } catch (UnsupportedEncodingException uee) {
- throw new UnsupportedOperationException(
- "Unsupported encoding: " + uee.getMessage());
- }
- }
+ public EncodedChars encodeChars(CharSequence chars) throws CharacterCodingException {
+ return encoder.encode(chars);
}
/**
diff --git a/src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java b/src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java
index d575e2ae1..16893a152 100644
--- a/src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java
+++ b/src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java
@@ -30,13 +30,11 @@ import java.util.WeakHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
-import org.apache.xmlgraphics.image.loader.util.SoftMapCache;
-
import org.apache.fop.afp.AFPConstants;
import org.apache.fop.afp.util.ResourceAccessor;
import org.apache.fop.afp.util.StructuredFieldReader;
import org.apache.fop.fonts.Typeface;
+import org.apache.xmlgraphics.image.loader.util.SoftMapCache;
/**
* The CharacterSetBuilder is responsible building the a CharacterSet instance that holds
@@ -181,9 +179,9 @@ public class CharacterSetBuilder {
}
/**
- * Load the font details and metrics into the CharacterSetMetric object,
- * this will use the actual afp code page and character set files to load
- * the object with the necessary metrics.
+ * Load the font details and metrics into the CharacterSetMetric object, this will use the
+ * actual afp code page and character set files to load the object with the necessary metrics.
+ *
* @param characterSetName name of the characterset
* @param codePageName name of the code page file
* @param encoding encoding name
@@ -191,9 +189,47 @@ public class CharacterSetBuilder {
* @return CharacterSet object
* @throws IOException if an I/O error occurs
*/
- public CharacterSet build(String characterSetName, String codePageName,
- String encoding, ResourceAccessor accessor) throws IOException {
+ public CharacterSet build(String characterSetName, String codePageName, String encoding,
+ ResourceAccessor accessor) throws IOException {
+ return processFont(characterSetName, codePageName, encoding, false, accessor);
+ }
+ /**
+ * Load the font details and metrics into the CharacterSetMetric object, this will use the
+ * actual afp code page and character set files to load the object with the necessary metrics.
+ * This method is to be used for double byte character sets (DBCS).
+ *
+ * @param characterSetName name of the characterset
+ * @param codePageName name of the code page file
+ * @param encoding encoding name
+ * @param isEDBCS if this is an EBCDIC double byte character set (DBCS)
+ * @param accessor used to load codepage and characterset
+ * @return CharacterSet object
+ * @throws IOException if an I/O error occurs
+ */
+ public CharacterSet buildDBCS(String characterSetName, String codePageName, String encoding,
+ boolean isEDBCS, ResourceAccessor accessor) throws IOException {
+ return processFont(characterSetName, codePageName, encoding, isEDBCS, accessor);
+ }
+
+ /**
+ * Load the font details and metrics into the CharacterSetMetric object, this will use the
+ * actual afp code page and character set files to load the object with the necessary metrics.
+ *
+ * @param characterSetName the CharacterSetMetric object to populate
+ * @param codePageName the name of the code page to use
+ * @param encoding name of the encoding in use
+ * @param typeface base14 font name
+ * @return CharacterSet object
+ * @throws IOException if an I/O error occurs
+ */
+ public CharacterSet build(String characterSetName, String codePageName, String encoding,
+ Typeface typeface) throws IOException {
+ return new FopCharacterSet(codePageName, encoding, characterSetName, typeface);
+ }
+
+ private CharacterSet processFont(String characterSetName, String codePageName, String encoding,
+ boolean isEDBCS, ResourceAccessor accessor) throws IOException {
// check for cached version of the characterset
String descriptor = characterSetName + "_" + encoding + "_" + codePageName;
CharacterSet characterSet = (CharacterSet)characterSetsCache.get(descriptor);
@@ -203,8 +239,8 @@ public class CharacterSetBuilder {
}
// characterset not in the cache, so recreating
- characterSet = new CharacterSet(
- codePageName, encoding, characterSetName, accessor);
+ characterSet = new CharacterSet(codePageName, encoding, isEDBCS, characterSetName,
+ accessor);
InputStream inputStream = null;
@@ -268,23 +304,6 @@ public class CharacterSetBuilder {
}
characterSetsCache.put(descriptor, characterSet);
return characterSet;
-
- }
-
- /**
- * Load the font details and metrics into the CharacterSetMetric object,
- * this will use the actual afp code page and character set files to load
- * the object with the necessary metrics.
- *
- * @param characterSetName the CharacterSetMetric object to populate
- * @param codePageName the name of the code page to use
- * @param encoding name of the encoding in use
- * @param typeface base14 font name
- * @return CharacterSet object
- */
- public CharacterSet build(String characterSetName, String codePageName,
- String encoding, Typeface typeface) {
- return new FopCharacterSet(codePageName, encoding, characterSetName, typeface);
}
/**
diff --git a/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java b/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java
index b7246f35b..7ac8ae96b 100644
--- a/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java
+++ b/src/java/org/apache/fop/afp/fonts/CharacterSetOrientation.java
@@ -107,7 +107,7 @@ public class CharacterSetOrientation {
* a character rotation other than 0, ascender height loses its
* meaning when the character is lying on its side or is upside down
* with respect to normal viewing orientation. For the general case,
- * Ascender Height is the character�s most positive y-axis value.
+ * Ascender Height is the character's most positive y-axis value.
* For bounded character boxes, for a given character having an
* ascender, ascender height and baseline offset are equal.
* @return the ascender value in millipoints
diff --git a/src/java/org/apache/fop/afp/fonts/CharactersetEncoder.java b/src/java/org/apache/fop/afp/fonts/CharactersetEncoder.java
new file mode 100644
index 000000000..19ad8751c
--- /dev/null
+++ b/src/java/org/apache/fop/afp/fonts/CharactersetEncoder.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.afp.fonts;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * An abstraction that wraps the encoding mechanism for encoding a Unicode character sequence into a
+ * specified format.
+ */
+public abstract class CharactersetEncoder {
+
+ private final CharsetEncoder encoder;
+
+ private CharactersetEncoder(String encoding) {
+ this.encoder = Charset.forName(encoding).newEncoder();
+ this.encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+ }
+
+ /**
+ * Tells whether or not this encoder can encode the given character.
+ *
+ * @param c the character
+ * @return true if, and only if, this encoder can encode the given character
+ * @throws IllegalStateException - If an encoding operation is already in progress
+ */
+ final boolean canEncode(char c) {
+ return encoder.canEncode(c);
+ }
+
+ /**
+ * Encodes a character sequence to a byte array.
+ *
+ * @param chars the character sequence
+ * @return the encoded character sequence
+ * @throws CharacterCodingException if the encoding operation fails
+ */
+ final EncodedChars encode(CharSequence chars) throws CharacterCodingException {
+ ByteBuffer bb;
+ // encode method is not thread safe
+ synchronized (encoder) {
+ bb = encoder.encode(CharBuffer.wrap(chars));
+ }
+ if (bb.hasArray()) {
+ return getEncodedChars(bb.array(), bb.limit());
+ } else {
+ bb.rewind();
+ byte[] bytes = new byte[bb.remaining()];
+ bb.get(bytes);
+ return getEncodedChars(bytes, bytes.length);
+ }
+ }
+
+ abstract EncodedChars getEncodedChars(byte[] byteArray, int length);
+
+ /**
+ * Encodes <code>chars</code> into a format specified by <code>encoding</code>.
+ *
+ * @param chars the character sequence
+ * @param encoding the encoding type
+ * @param isEDBCS if this encoding represents a double-byte character set
+ * @return encoded data
+ * @throws CharacterCodingException if encoding fails
+ */
+ public static EncodedChars encodeSBCS(CharSequence chars, String encoding, boolean isEDBCS)
+ throws CharacterCodingException {
+ CharactersetEncoder encoder = newInstance(encoding, isEDBCS);
+ return encoder.encode(chars);
+ }
+
+ /**
+ * The EBCDIC double byte encoder is used for encoding IBM format DBCS (double byte character
+ * sets) with an EBCDIC code-page. Given a double byte EBCDIC code page and a Unicode character
+ * sequence it will return its EBCDIC code-point, however, the "Shift In - Shift Out" operators
+ * are removed from the sequence of bytes. These are only used in Line Data.
+ */
+ private static final class EbcdicDoubleByteEncoder extends CharactersetEncoder {
+ private EbcdicDoubleByteEncoder(String encoding) {
+ super(encoding);
+ }
+ @Override
+ EncodedChars getEncodedChars(byte[] byteArray, int length) {
+ if (byteArray[0] == 0x0E && byteArray[length - 1] == 0x0F) {
+ return new EncodedChars(byteArray, 1, length - 2);
+ }
+ return new EncodedChars(byteArray);
+ }
+ }
+
+ /**
+ * The default encoder is used for encoding IBM format SBCS (single byte character sets), this
+ * the primary format for most Latin character sets. This can also be used for Unicode double-
+ * byte character sets (DBCS).
+ */
+ private static final class DefaultEncoder extends CharactersetEncoder {
+ private DefaultEncoder(String encoding) {
+ super(encoding);
+ }
+
+ @Override
+ EncodedChars getEncodedChars(byte[] byteArray, int length) {
+ return new EncodedChars(byteArray);
+ }
+ }
+
+ /**
+ * Returns an new instance of a {@link CharactersetEncoder}.
+ *
+ * @param encoding the encoding for the underlying character encoder
+ * @param isEbcdicDBCS whether or not this wraps a double-byte EBCDIC code page.
+ * @return the CharactersetEncoder
+ */
+ static CharactersetEncoder newInstance(String encoding, boolean isEbcdicDBCS) {
+ if (isEbcdicDBCS) {
+ return new EbcdicDoubleByteEncoder(encoding);
+ } else {
+ return new DefaultEncoder(encoding);
+ }
+ }
+
+ /**
+ * A container for encoded character bytes
+ */
+ public static final class EncodedChars {
+
+ private final byte[] bytes;
+
+ private final int offset;
+
+ private final int length;
+
+ private EncodedChars(byte[] bytes, int offset, int length) {
+ if (offset < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ if (length < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ if (offset + length > bytes.length) {
+ throw new IllegalArgumentException();
+ }
+
+ this.bytes = bytes;
+
+ this.offset = offset;
+
+ this.length = length;
+ }
+
+ private EncodedChars(byte[] bytes) {
+ this(bytes, 0, bytes.length);
+ }
+
+ /**
+ * write <code>length</code> bytes from <code>offset</code> to the output stream
+ *
+ * @param out output to write the bytes to
+ * @param offset the offset where to write
+ * @param length the length to write
+ * @throws IOException if an I/O error occurs
+ */
+ public void writeTo(OutputStream out, int offset, int length) throws IOException {
+ if (offset < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ if (length < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ if (offset + length > this.length) {
+ throw new IllegalArgumentException();
+ }
+
+ out.write(bytes, this.offset + offset, length);
+ }
+
+ /**
+ * The number of containing bytes.
+ *
+ * @return the length
+ */
+ public int getLength() {
+ return length;
+ }
+ }
+}
diff --git a/src/java/org/apache/fop/afp/fonts/FopCharacterSet.java b/src/java/org/apache/fop/afp/fonts/FopCharacterSet.java
index 716ca538f..6faf03d43 100644
--- a/src/java/org/apache/fop/afp/fonts/FopCharacterSet.java
+++ b/src/java/org/apache/fop/afp/fonts/FopCharacterSet.java
@@ -44,7 +44,7 @@ public class FopCharacterSet extends CharacterSet {
String name,
Typeface charSet) {
- super(codePage, encoding, name, (ResourceAccessor)null);
+ super(codePage, encoding, false, name, (ResourceAccessor) null);
this.charSet = charSet;
}
@@ -55,7 +55,7 @@ public class FopCharacterSet extends CharacterSet {
* a character rotation other than 0, ascender height loses its
* meaning when the character is lying on its side or is upside down
* with respect to normal viewing orientation. For the general case,
- * Ascender Height is the character�s most positive y-axis value.
+ * Ascender Height is the character's most positive y-axis value.
* For bounded character boxes, for a given character having an
* ascender, ascender height and baseline offset are equal.
* @return the ascender value in millipoints
@@ -135,4 +135,4 @@ public class FopCharacterSet extends CharacterSet {
return charSet.mapChar(c);
}
-} \ No newline at end of file
+}
diff --git a/src/java/org/apache/fop/afp/ioca/IDEStructureParameter.java b/src/java/org/apache/fop/afp/ioca/IDEStructureParameter.java
index b0cce02e1..9bcdeda88 100644
--- a/src/java/org/apache/fop/afp/ioca/IDEStructureParameter.java
+++ b/src/java/org/apache/fop/afp/ioca/IDEStructureParameter.java
@@ -46,7 +46,7 @@ public class IDEStructureParameter implements Streamable {
private boolean grayCoding = false;
/** the image color model */
- private byte colorModel = COLOR_MODEL_YCRCB;
+ private byte colorModel = COLOR_MODEL_RGB;
/** the array with the number of bits/IDE for each component */
private byte[] bitsPerIDE = new byte[] {(byte)1}; //1-bit by default
diff --git a/src/java/org/apache/fop/afp/modca/AbstractAFPObject.java b/src/java/org/apache/fop/afp/modca/AbstractAFPObject.java
index af679fa25..1dd14b124 100644
--- a/src/java/org/apache/fop/afp/modca/AbstractAFPObject.java
+++ b/src/java/org/apache/fop/afp/modca/AbstractAFPObject.java
@@ -39,13 +39,12 @@ import org.apache.fop.afp.util.BinaryUtils;
public abstract class AbstractAFPObject implements Streamable {
/** Static logging instance */
- protected static final Log LOG = LogFactory.getLog("org.apache.xmlgraphics.afp.modca");
+ protected static final Log LOG = LogFactory.getLog(AbstractAFPObject.class);
/** the structured field class id */
protected static final byte SF_CLASS = (byte)0xD3;
- /** the structure field header */
- static final byte[] SF_HEADER = new byte[] {
+ private static final byte[] SF_HEADER = new byte[] {
0x5A, // Structured field identifier
0x00, // Length byte 1
0x10, // Length byte 2
@@ -57,6 +56,9 @@ public abstract class AbstractAFPObject implements Streamable {
0x00, // Reserved
};
+ /** Length of bytes of a Structured Field Header */
+ protected static final int SF_HEADER_LENGTH = SF_HEADER.length;
+
/**
* Copies the template structured field data array to the given byte array
*
@@ -77,7 +79,7 @@ public abstract class AbstractAFPObject implements Streamable {
* @param category the category code
*/
protected static void copySF(byte[] data, byte clazz, byte type, byte category) {
- System.arraycopy(SF_HEADER, 0, data, 0, SF_HEADER.length);
+ System.arraycopy(SF_HEADER, 0, data, 0, SF_HEADER_LENGTH);
data[3] = clazz;
data[4] = type;
data[5] = category;
@@ -87,54 +89,23 @@ public abstract class AbstractAFPObject implements Streamable {
* Writes a collection of Streamable to the AFP Datastream.
*
* @param objects a list of AFPObjects
+ * @param <S> Streamable view of an AFPObject
* @param os The stream to write to
* @throws java.io.IOException an I/O exception of some sort has occurred.
*/
- protected void writeObjects(Collection/*<Streamable>*/ objects, OutputStream os)
- throws IOException {
- if (objects != null && objects.size() > 0) {
- Iterator it = objects.iterator();
+ protected <S extends Streamable> void writeObjects(Collection<S> objects, OutputStream os)
+ throws IOException {
+ if (objects != null) {
+ Iterator<S> it = objects.iterator();
while (it.hasNext()) {
- Object object = it.next();
- if (object instanceof Streamable) {
- ((Streamable)object).writeToStream(os);
- it.remove(); // once written, immediately remove the object
- }
+ Streamable s = it.next();
+ s.writeToStream(os);
+ it.remove(); // once written, immediately remove the object
}
}
}
/**
- * Reads data chunks from an InputStream
- * and then formats them with a structured header to a given OutputStream
- *
- * @param dataHeader the header data
- * @param lengthOffset offset of length field in data chunk
- * @param maxChunkLength the maximum chunk length
- * @param inputStream the InputStream to read from
- * @param outputStream the OutputStream to write to
- * @throws IOException thrown if an I/O exception of some sort has occurred.
- */
- protected static void copyChunks(byte[] dataHeader, int lengthOffset,
- int maxChunkLength, InputStream inputStream, OutputStream outputStream)
- throws IOException {
- int headerLen = dataHeader.length - lengthOffset;
- // length field is just before data so do not include in data length
- if (headerLen == 2) {
- headerLen = 0;
- }
- byte[] data = new byte[maxChunkLength];
- int numBytesRead = 0;
- while ((numBytesRead = inputStream.read(data, 0, maxChunkLength)) > 0) {
- byte[] len = BinaryUtils.convert(headerLen + numBytesRead, 2);
- dataHeader[lengthOffset] = len[0]; // Length byte 1
- dataHeader[lengthOffset + 1] = len[1]; // Length byte 2
- outputStream.write(dataHeader);
- outputStream.write(data, 0, numBytesRead);
- }
- }
-
- /**
* Writes data chunks to a given outputstream
*
* @param data the data byte array
@@ -186,7 +157,7 @@ public abstract class AbstractAFPObject implements Streamable {
* @param maxLength the maximum length allowed for the string
* @return a possibly truncated string
*/
- protected String truncate(String str, int maxLength) {
+ protected static String truncate(String str, int maxLength) {
if (str.length() > maxLength) {
str = str.substring(0, maxLength);
LOG.warn("truncated character string '"
diff --git a/src/java/org/apache/fop/afp/modca/AbstractNamedAFPObject.java b/src/java/org/apache/fop/afp/modca/AbstractNamedAFPObject.java
index b349e1f7d..83c720e58 100644
--- a/src/java/org/apache/fop/afp/modca/AbstractNamedAFPObject.java
+++ b/src/java/org/apache/fop/afp/modca/AbstractNamedAFPObject.java
@@ -90,7 +90,7 @@ public abstract class AbstractNamedAFPObject extends AbstractTripletStructuredOb
return nameBytes;
}
- /** {@inheritDoc} */
+ @Override
protected void copySF(byte[] data, byte type, byte category) {
super.copySF(data, type, category);
byte[] nameData = getNameBytes();
diff --git a/src/java/org/apache/fop/afp/modca/AbstractTripletStructuredObject.java b/src/java/org/apache/fop/afp/modca/AbstractTripletStructuredObject.java
index 4b269086e..662b344c1 100644
--- a/src/java/org/apache/fop/afp/modca/AbstractTripletStructuredObject.java
+++ b/src/java/org/apache/fop/afp/modca/AbstractTripletStructuredObject.java
@@ -22,7 +22,6 @@ package org.apache.fop.afp.modca;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
-import java.util.Iterator;
import java.util.List;
import org.apache.fop.afp.modca.Registry.ObjectType;
@@ -35,10 +34,10 @@ import org.apache.fop.afp.modca.triplets.Triplet;
/**
* A MODCA structured object base class providing support for Triplets
*/
-public class AbstractTripletStructuredObject extends AbstractStructuredObject {
+public abstract class AbstractTripletStructuredObject extends AbstractStructuredObject {
/** list of object triplets */
- protected List/*<Triplet>*/ triplets = new java.util.ArrayList/*<Triplet>*/();
+ protected List<AbstractTriplet> triplets = new java.util.ArrayList<AbstractTriplet>();
/**
* Returns the triplet data length
@@ -47,12 +46,8 @@ public class AbstractTripletStructuredObject extends AbstractStructuredObject {
*/
protected int getTripletDataLength() {
int dataLength = 0;
- if (hasTriplets()) {
- Iterator it = triplets.iterator();
- while (it.hasNext()) {
- AbstractTriplet triplet = (AbstractTriplet)it.next();
- dataLength += triplet.getDataLength();
- }
+ for (Triplet triplet : triplets) {
+ dataLength += triplet.getDataLength();
}
return dataLength;
}
@@ -85,11 +80,9 @@ public class AbstractTripletStructuredObject extends AbstractStructuredObject {
* @param tripletId the triplet identifier
*/
private AbstractTriplet getTriplet(byte tripletId) {
- Iterator it = getTriplets().iterator();
- while (it.hasNext()) {
- AbstractTriplet triplet = (AbstractTriplet)it.next();
- if (triplet.getId() == tripletId) {
- return triplet;
+ for (AbstractTriplet trip : triplets) {
+ if (trip.getId() == tripletId) {
+ return trip;
}
}
return null;
@@ -110,7 +103,7 @@ public class AbstractTripletStructuredObject extends AbstractStructuredObject {
*
* @param triplet the triplet to add
*/
- protected void addTriplet(Triplet triplet) {
+ protected void addTriplet(AbstractTriplet triplet) {
triplets.add(triplet);
}
@@ -119,14 +112,14 @@ public class AbstractTripletStructuredObject extends AbstractStructuredObject {
*
* @param tripletCollection a collection of triplets
*/
- public void addTriplets(Collection/*<Triplet>*/ tripletCollection) {
+ public void addTriplets(Collection<AbstractTriplet> tripletCollection) {
if (tripletCollection != null) {
triplets.addAll(tripletCollection);
}
}
/** @return the triplet list pertaining to this resource */
- protected List/*<Triplet>*/ getTriplets() {
+ protected List<AbstractTriplet> getTriplets() {
return triplets;
}
diff --git a/src/java/org/apache/fop/afp/modca/IncludeObject.java b/src/java/org/apache/fop/afp/modca/IncludeObject.java
index 46b4a31f2..68fa72688 100644
--- a/src/java/org/apache/fop/afp/modca/IncludeObject.java
+++ b/src/java/org/apache/fop/afp/modca/IncludeObject.java
@@ -57,7 +57,6 @@ public class IncludeObject extends AbstractNamedAFPObject {
/** the object referenced is of type image */
public static final byte TYPE_IMAGE = (byte)0xFB;
-
/** the object type referenced (default is other) */
private byte objectType = TYPE_OTHER;
@@ -68,7 +67,7 @@ public class IncludeObject extends AbstractNamedAFPObject {
private int yoaOset = 0;
/** the orientation of the referenced object */
- private int oaOrent = 0;
+ private ObjectAreaRotation oaOrent = ObjectAreaRotation.RIGHT_HANDED_0;
/** the X-axis origin defined in the object */
private int xocaOset = -1;
@@ -94,13 +93,7 @@ public class IncludeObject extends AbstractNamedAFPObject {
* The orientation (0,90, 180, 270)
*/
public void setObjectAreaOrientation(int orientation) {
- if (orientation == 0 || orientation == 90 || orientation == 180
- || orientation == 270) {
- this.oaOrent = orientation;
- } else {
- throw new IllegalArgumentException(
- "The orientation must be one of the values 0, 90, 180, 270");
- }
+ this.oaOrent = ObjectAreaRotation.objectAreaRotationFor(orientation);
}
/**
@@ -151,87 +144,16 @@ public class IncludeObject extends AbstractNamedAFPObject {
data[17] = 0x00; // reserved
data[18] = objectType;
- //XoaOset (object area)
- if (xoaOset > -1) {
- byte[] x = BinaryUtils.convert(xoaOset, 3);
- data[19] = x[0];
- data[20] = x[1];
- data[21] = x[2];
- } else {
- data[19] = (byte)0xFF;
- data[20] = (byte)0xFF;
- data[21] = (byte)0xFF;
- }
+ writeOsetTo(data, 19, xoaOset);
- // YoaOset (object area)
- if (yoaOset > -1) {
- byte[] y = BinaryUtils.convert(yoaOset, 3);
- data[22] = y[0];
- data[23] = y[1];
- data[24] = y[2];
- } else {
- data[22] = (byte)0xFF;
- data[23] = (byte)0xFF;
- data[24] = (byte)0xFF;
- }
+ writeOsetTo(data, 22, yoaOset);
- // XoaOrent/YoaOrent
- switch (oaOrent) {
- case -1: // use x/y axis orientation defined in object
- data[25] = (byte)0xFF; // x axis rotation
- data[26] = (byte)0xFF; //
- data[27] = (byte)0xFF; // y axis rotation
- data[28] = (byte)0xFF;
- break;
- case 90:
- data[25] = 0x2D;
- data[26] = 0x00;
- data[27] = 0x5A;
- data[28] = 0x00;
- break;
- case 180:
- data[25] = 0x5A;
- data[25] = 0x00;
- data[27] = (byte)0x87;
- data[28] = 0x00;
- break;
- case 270:
- data[25] = (byte)0x87;
- data[26] = 0x00;
- data[27] = 0x00;
- data[28] = 0x00;
- break;
- default: // 0 degrees
- data[25] = 0x00;
- data[26] = 0x00;
- data[27] = 0x2D;
- data[28] = 0x00;
- break;
- }
+ oaOrent.writeTo(data, 25);
- // XocaOset (object content)
- if (xocaOset > -1) {
- byte[] x = BinaryUtils.convert(xocaOset, 3);
- data[29] = x[0];
- data[30] = x[1];
- data[31] = x[2];
- } else {
- data[29] = (byte)0xFF;
- data[30] = (byte)0xFF;
- data[31] = (byte)0xFF;
- }
+ writeOsetTo(data, 29, xocaOset);
+
+ writeOsetTo(data, 32, yocaOset);
- // YocaOset (object content)
- if (yocaOset > -1) {
- byte[] y = BinaryUtils.convert(yocaOset, 3);
- data[32] = y[0];
- data[33] = y[1];
- data[34] = y[2];
- } else {
- data[32] = (byte)0xFF;
- data[33] = (byte)0xFF;
- data[34] = (byte)0xFF;
- }
// RefCSys (Reference coordinate system)
data[35] = 0x01; // Page or overlay coordinate system
@@ -242,6 +164,19 @@ public class IncludeObject extends AbstractNamedAFPObject {
writeTriplets(os);
}
+ private static void writeOsetTo(byte[] out, int offset, int oset) {
+ if (oset > -1) {
+ byte[] y = BinaryUtils.convert(oset, 3);
+ out[offset] = y[0];
+ out[offset + 1] = y[1];
+ out[offset + 2] = y[2];
+ } else {
+ out[offset] = (byte)0xFF;
+ out[offset + 1] = (byte)0xFF;
+ out[offset + 2] = (byte)0xFF;
+ }
+ }
+
private String getObjectTypeName() {
String objectTypeName = null;
if (objectType == TYPE_PAGE_SEGMENT) {
@@ -299,4 +234,69 @@ public class IncludeObject extends AbstractNamedAFPObject {
addTriplet(new MeasurementUnitsTriplet(xRes, xRes));
}
-} \ No newline at end of file
+ /**
+ * Represents the 4 bytes that specify the area rotation reference coordinate system
+ *
+ */
+ private enum ObjectAreaRotation {
+
+ RIGHT_HANDED_0(Rotation.ROTATION_0, Rotation.ROTATION_90),
+ RIGHT_HANDED_90(Rotation.ROTATION_90, Rotation.ROTATION_180),
+ RIGHT_HANDED_180(Rotation.ROTATION_180, Rotation.ROTATION_270),
+ RIGHT_HANDED_270(Rotation.ROTATION_270, Rotation.ROTATION_0);
+
+ /**
+ * The object area's X-axis rotation from the X axis of the reference coordinate system
+ */
+ private final Rotation xoaOrent;
+ /**
+ * The object area's Y-axis rotation from the Y axis of the reference coordinate system
+ */
+ private final Rotation yoaOrent;
+
+ public void writeTo(byte[] out, int offset) {
+ xoaOrent.writeTo(out, offset);
+ yoaOrent.writeTo(out, offset + 2);
+ }
+
+ ObjectAreaRotation(Rotation xoaOrent, Rotation yoaOrent) {
+ this.xoaOrent = xoaOrent;
+ this.yoaOrent = yoaOrent;
+ }
+
+ private static ObjectAreaRotation objectAreaRotationFor(int orientation) {
+ switch (orientation) {
+ case 0: return RIGHT_HANDED_0;
+ case 90: return RIGHT_HANDED_90;
+ case 180: return RIGHT_HANDED_180;
+ case 270: return RIGHT_HANDED_270;
+ default: throw new IllegalArgumentException(
+ "The orientation must be one of the values 0, 90, 180, 270");
+ }
+ }
+ }
+
+ /**
+ * Represents a rotation value
+ *
+ */
+ private enum Rotation {
+
+ ROTATION_0(0),
+ ROTATION_90(0x2D),
+ ROTATION_180(0x5A),
+ ROTATION_270(0x87);
+
+ private final byte firstByte;
+
+ public void writeTo(byte[] out, int offset) {
+ out[offset] = firstByte;
+ out[offset + 1] = (byte)0;
+ }
+
+ Rotation(int firstByte) {
+ this.firstByte = (byte) firstByte;
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/afp/modca/TagLogicalElement.java b/src/java/org/apache/fop/afp/modca/TagLogicalElement.java
index ab2c52143..706ceae3b 100644
--- a/src/java/org/apache/fop/afp/modca/TagLogicalElement.java
+++ b/src/java/org/apache/fop/afp/modca/TagLogicalElement.java
@@ -104,7 +104,7 @@ public class TagLogicalElement extends AbstractTripletStructuredObject {
setAttributeValue(value);
setAttributeQualifier(tleID, 1);
- byte[] data = new byte[SF_HEADER.length];
+ byte[] data = new byte[SF_HEADER_LENGTH];
copySF(data, Type.ATTRIBUTE, Category.PROCESS_ELEMENT);
int tripletDataLength = getTripletDataLength();
diff --git a/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java b/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java
index 2962dc76c..059753338 100644
--- a/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java
+++ b/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java
@@ -25,7 +25,7 @@ import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;
-
+import org.apache.fop.afp.fonts.CharactersetEncoder.EncodedChars;
import org.apache.xmlgraphics.java2d.color.CIELabColorSpace;
import org.apache.xmlgraphics.java2d.color.ColorUtil;
import org.apache.xmlgraphics.java2d.color.ColorWithAlternatives;
@@ -85,10 +85,6 @@ public abstract class PtocaBuilder implements PtocaConstants {
baout.writeTo(out);
}
- private void write(byte[] data, int offset, int length) {
- baout.write(data, offset, length);
- }
-
private void writeByte(int data) {
baout.write(data);
}
@@ -180,44 +176,34 @@ public abstract class PtocaBuilder implements PtocaConstants {
currentX = -1;
}
- private static final int TRANSPARENT_MAX_SIZE = 253;
-
/**
* The Transparent Data control sequence contains a sequence of code points
* that are presented without a scan for embedded control sequences. If the data is larger
* than fits in one chunk, additional chunks are automatically generated.
*
- * @param data The text data to add.
+ * @param encodedChars The encoded text data to add.
* @throws IOException if an I/O error occurs
*/
- public void addTransparentData(byte[] data) throws IOException {
- if (data.length <= TRANSPARENT_DATA_MAX_SIZE) {
- addTransparentDataChunk(data);
- } else {
- // data size greater than TRANSPARENT_MAX_SIZE, so slice
- int numTransData = data.length / TRANSPARENT_DATA_MAX_SIZE;
- int currIndex = 0;
- for (int transDataCnt = 0; transDataCnt < numTransData; transDataCnt++) {
- addTransparentDataChunk(data, currIndex, TRANSPARENT_DATA_MAX_SIZE);
- currIndex += TRANSPARENT_DATA_MAX_SIZE;
- }
- int left = data.length - currIndex;
- addTransparentDataChunk(data, currIndex, left);
+ public void addTransparentData(EncodedChars encodedChars) throws IOException {
+
+ // data size greater than TRANSPARENT_MAX_SIZE, so slice
+ int numTransData = encodedChars.getLength() / TRANSPARENT_DATA_MAX_SIZE;
+ int currIndex = 0;
+ for (int transDataCnt = 0; transDataCnt < numTransData; transDataCnt++) {
+ addTransparentDataChunk(encodedChars, currIndex, TRANSPARENT_DATA_MAX_SIZE);
+ currIndex += TRANSPARENT_DATA_MAX_SIZE;
}
- }
+ int left = encodedChars.getLength() - currIndex;
+ addTransparentDataChunk(encodedChars, currIndex, left);
- private void addTransparentDataChunk(byte[] data) throws IOException {
- addTransparentDataChunk(data, 0, data.length);
}
- private void addTransparentDataChunk(byte[] data, int offset, int length) throws IOException {
- if (length > TRANSPARENT_MAX_SIZE) {
- // Check that we are not exceeding the maximum length
- throw new IllegalArgumentException(
- "Transparent data is longer than " + TRANSPARENT_MAX_SIZE + " bytes");
- }
+
+
+ private void addTransparentDataChunk(EncodedChars encodedChars, int offset, int length)
+ throws IOException {
newControlSequence();
- write(data, offset, length);
+ encodedChars.writeTo(baout, offset, length);
commit(chained(TRN));
}
diff --git a/src/java/org/apache/fop/afp/ptoca/TextDataInfoProducer.java b/src/java/org/apache/fop/afp/ptoca/TextDataInfoProducer.java
index 7ae3028e8..f7ed5a85c 100644
--- a/src/java/org/apache/fop/afp/ptoca/TextDataInfoProducer.java
+++ b/src/java/org/apache/fop/afp/ptoca/TextDataInfoProducer.java
@@ -22,6 +22,7 @@ package org.apache.fop.afp.ptoca;
import java.io.IOException;
import org.apache.fop.afp.AFPTextDataInfo;
+import org.apache.fop.afp.fonts.CharactersetEncoder;
/**
* {@link PtocaProducer} implementation that interprets {@link AFPTextDataInfo} objects.
@@ -55,8 +56,7 @@ public class TextDataInfoProducer implements PtocaProducer, PtocaConstants {
// Add transparent data
String textString = textDataInfo.getString();
String encoding = textDataInfo.getEncoding();
- byte[] data = textString.getBytes(encoding);
- builder.addTransparentData(data);
+ builder.addTransparentData(CharactersetEncoder.encodeSBCS(textString, encoding, false));
}
}
diff --git a/src/java/org/apache/fop/afp/util/AFPResourceUtil.java b/src/java/org/apache/fop/afp/util/AFPResourceUtil.java
index 979376b3e..538f2b880 100644
--- a/src/java/org/apache/fop/afp/util/AFPResourceUtil.java
+++ b/src/java/org/apache/fop/afp/util/AFPResourceUtil.java
@@ -92,10 +92,13 @@ public final class AFPResourceUtil {
throws UnsupportedEncodingException {
//The first 8 bytes of the field data represent the resource name
byte[] nameBytes = new byte[8];
- System.arraycopy(field.getData(), 0, nameBytes, 0, 8);
- String asciiName;
- asciiName = new String(nameBytes, AFPConstants.EBCIDIC_ENCODING);
- return asciiName;
+
+ byte[] fieldData = field.getData();
+ if (fieldData.length < 8) {
+ throw new IllegalArgumentException("Field data does not contain a resource name");
+ }
+ System.arraycopy(fieldData, 0, nameBytes, 0, 8);
+ return new String(nameBytes, AFPConstants.EBCIDIC_ENCODING);
}
/**
@@ -128,12 +131,13 @@ public final class AFPResourceUtil {
public static void copyNamedResource(String name,
final InputStream in, final OutputStream out) throws IOException {
final MODCAParser parser = new MODCAParser(in);
- Collection resourceNames = new java.util.HashSet();
+ Collection<String> resourceNames = new java.util.HashSet<String>();
//Find matching "Begin" field
final UnparsedStructuredField fieldBegin;
while (true) {
- UnparsedStructuredField field = parser.readNextStructuredField();
+ final UnparsedStructuredField field = parser.readNextStructuredField();
+
if (field == null) {
throw new IOException("Requested resource '" + name
+ "' not found. Encountered resource names: " + resourceNames);
@@ -142,8 +146,10 @@ public final class AFPResourceUtil {
if (field.getSfTypeCode() != TYPE_CODE_BEGIN) { //0xA8=Begin
continue; //Not a "Begin" field
}
- String resourceName = getResourceName(field);
+ final String resourceName = getResourceName(field);
+
resourceNames.add(resourceName);
+
if (resourceName.equals(name)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Start of requested structured field found:\n"
@@ -170,45 +176,35 @@ public final class AFPResourceUtil {
if (wrapInResource) {
ResourceObject resourceObject = new ResourceObject(name) {
protected void writeContent(OutputStream os) throws IOException {
- copyStructuredFields(name, fieldBegin, parser, out);
+ copyNamedStructuredFields(name, fieldBegin, parser, out);
}
};
resourceObject.setType(ResourceObject.TYPE_PAGE_SEGMENT);
resourceObject.writeToStream(out);
} else {
- copyStructuredFields(name, fieldBegin, parser, out);
+ copyNamedStructuredFields(name, fieldBegin, parser, out);
}
}
- private static void copyStructuredFields(String name, UnparsedStructuredField fieldBegin,
- MODCAParser parser, OutputStream out) throws IOException {
- boolean inRequestedResource;
+ private static void copyNamedStructuredFields(final String name,
+ UnparsedStructuredField fieldBegin, MODCAParser parser,
+ OutputStream out) throws IOException {
- //The "Begin" field first
- out.write(MODCAParser.CARRIAGE_CONTROL_CHAR);
- fieldBegin.writeTo(out);
- UnparsedStructuredField field;
+ UnparsedStructuredField field = fieldBegin;
- //Then the rest of the fields until the corresponding "End" field
- inRequestedResource = true;
- do {
- field = parser.readNextStructuredField();
+ while (true) {
if (field == null) {
- break; //Unexpected EOF
- }
-
- if (field.getSfTypeCode() == TYPE_CODE_END) {
- String resourceName = getResourceName(field);
- if (resourceName.equals(name)) {
- inRequestedResource = false; //Signal end of loop
- }
+ throw new IOException("Ending structured field not found for resource " + name);
}
out.write(MODCAParser.CARRIAGE_CONTROL_CHAR);
field.writeTo(out);
- } while (inRequestedResource);
- if (inRequestedResource) {
- throw new IOException("Ending structured field not found for resource " + name);
+
+ if (field.getSfTypeCode() == TYPE_CODE_END
+ && fieldBegin.getSfCategoryCode() == field.getSfCategoryCode()
+ && name.equals(getResourceName(field))) {
+ break;
+ }
+ field = parser.readNextStructuredField();
}
}
-
}
diff --git a/src/java/org/apache/fop/cli/CommandLineOptions.java b/src/java/org/apache/fop/cli/CommandLineOptions.java
index 2a5c0d272..0d4c3790c 100644
--- a/src/java/org/apache/fop/cli/CommandLineOptions.java
+++ b/src/java/org/apache/fop/cli/CommandLineOptions.java
@@ -381,6 +381,14 @@ public class CommandLineOptions {
getPDFEncryptionParams().setAllowEditContent(false);
} else if (args[i].equals("-noannotations")) {
getPDFEncryptionParams().setAllowEditAnnotations(false);
+ } else if (args[i].equals("-nofillinforms")) {
+ getPDFEncryptionParams().setAllowFillInForms(false);
+ } else if (args[i].equals("-noaccesscontent")) {
+ getPDFEncryptionParams().setAllowAccessContent(false);
+ } else if (args[i].equals("-noassembledoc")) {
+ getPDFEncryptionParams().setAllowAssembleDocument(false);
+ } else if (args[i].equals("-noprinthq")) {
+ getPDFEncryptionParams().setAllowPrintHq(false);
} else if (args[i].equals("-version")) {
printVersion();
return false;
@@ -1181,6 +1189,14 @@ public class CommandLineOptions {
+ " -nocopy PDF file will be encrypted without copy content permission\n"
+ " -noedit PDF file will be encrypted without edit content permission\n"
+ " -noannotations PDF file will be encrypted without edit annotation permission\n"
+ + " -nofillinforms PDF file will be encrypted without"
+ + " fill in interactive form fields permission\n"
+ + " -noaccesscontent PDF file will be encrypted without"
+ + " extract text and graphics permission\n"
+ + " -noassembledoc PDF file will be encrypted without"
+ + " assemble the document permission\n"
+ + " -noprinthq PDF file will be encrypted without"
+ + " print high quality permission\n"
+ " -a enables accessibility features (Tagged PDF etc., default off)\n"
+ " -pdfprofile prof PDF file will be generated with the specified profile\n"
+ " (Examples for prof: PDF/A-1b or PDF/X-3:2003)\n\n"
diff --git a/src/java/org/apache/fop/fonts/FontDescriptor.java b/src/java/org/apache/fop/fonts/FontDescriptor.java
index e7c81c9f3..8aea105be 100644
--- a/src/java/org/apache/fop/fonts/FontDescriptor.java
+++ b/src/java/org/apache/fop/fonts/FontDescriptor.java
@@ -86,5 +86,10 @@ public interface FontDescriptor extends FontMetrics {
*/
boolean isEmbeddable();
+ /**
+ * Indicates whether this font is subset embedded.
+ * @return true if this font is subset embedded
+ */
+ boolean isSubsetEmbedded();
}
diff --git a/src/java/org/apache/fop/fonts/LazyFont.java b/src/java/org/apache/fop/fonts/LazyFont.java
index ef48cf877..ce7b1eec9 100644
--- a/src/java/org/apache/fop/fonts/LazyFont.java
+++ b/src/java/org/apache/fop/fonts/LazyFont.java
@@ -27,12 +27,10 @@ import java.util.Set;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
-import org.xml.sax.InputSource;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.apache.fop.apps.FOPException;
+import org.xml.sax.InputSource;
/**
* This class is used to defer the loading of a font until it is really used.
@@ -462,5 +460,13 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable,
}
}
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isSubsetEmbedded() {
+ load(true);
+ return realFont.isMultiByte();
+ }
+
}
diff --git a/src/java/org/apache/fop/fonts/MultiByteFont.java b/src/java/org/apache/fop/fonts/MultiByteFont.java
index 33e99ff67..285d754be 100644
--- a/src/java/org/apache/fop/fonts/MultiByteFont.java
+++ b/src/java/org/apache/fop/fonts/MultiByteFont.java
@@ -21,7 +21,6 @@ package org.apache.fop.fonts;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
-import java.text.DecimalFormat;
import java.util.Map;
import org.apache.commons.logging.Log;
@@ -38,16 +37,12 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
private static final Log log // CSOK: ConstantNameCheck
= LogFactory.getLog(MultiByteFont.class);
- private static int uniqueCounter = -1;
-
private String ttcName = null;
private String encoding = "Identity-H";
private int defaultWidth = 0;
private CIDFontType cidType = CIDFontType.CIDTYPE2;
- private String namePrefix = null; // Quasi unique prefix
-
private CIDSubset subset = new CIDSubset();
/**
@@ -77,26 +72,6 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
*/
public MultiByteFont() {
subset.setupFirstGlyph();
-
- // Create a quasiunique prefix for fontname
- synchronized (this.getClass()) {
- uniqueCounter++;
- if (uniqueCounter > 99999 || uniqueCounter < 0) {
- uniqueCounter = 0; //We need maximum 5 character then we start again
- }
- }
- DecimalFormat counterFormat = new DecimalFormat("00000");
- String cntString = counterFormat.format(uniqueCounter);
-
- //Subset prefix as described in chapter 5.5.3 of PDF 1.4
- StringBuffer sb = new StringBuffer("E");
- for (int i = 0, c = cntString.length(); i < c; i++) {
- //translate numbers to uppercase characters
- sb.append((char)(cntString.charAt(i) + (65 - 48)));
- }
- sb.append("+");
- namePrefix = sb.toString();
-
setFontType(FontType.TYPE0);
}
@@ -133,14 +108,10 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
this.cidType = cidType;
}
- private String getPrefixedFontName() {
- return namePrefix + FontUtil.stripWhiteSpace(super.getFontName());
- }
-
/** {@inheritDoc} */
public String getEmbedFontName() {
if (isEmbeddable()) {
- return getPrefixedFontName();
+ return FontUtil.stripWhiteSpace(super.getFontName());
} else {
return super.getFontName();
}
@@ -152,6 +123,11 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
}
/** {@inheritDoc} */
+ public boolean isSubsetEmbedded() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
public CIDSubset getCIDSubset() {
return this.subset;
}
diff --git a/src/java/org/apache/fop/fonts/SingleByteFont.java b/src/java/org/apache/fop/fonts/SingleByteFont.java
index 0605cd11e..d5901297f 100644
--- a/src/java/org/apache/fop/fonts/SingleByteFont.java
+++ b/src/java/org/apache/fop/fonts/SingleByteFont.java
@@ -63,7 +63,11 @@ public class SingleByteFont extends CustomFont {
}
/** {@inheritDoc} */
- @Override
+ public boolean isSubsetEmbedded() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
public String getEncodingName() {
return this.mapping.getName();
}
diff --git a/src/java/org/apache/fop/fonts/truetype/FontFileReader.java b/src/java/org/apache/fop/fonts/truetype/FontFileReader.java
index a6db7b6d0..56017d377 100644
--- a/src/java/org/apache/fop/fonts/truetype/FontFileReader.java
+++ b/src/java/org/apache/fop/fonts/truetype/FontFileReader.java
@@ -198,14 +198,15 @@ public class FontFileReader {
* @param val The value to write
* @throws IOException If EOF is reached
*/
- public final void writeTTFUShort(int pos, int val) throws IOException {
+ public final void writeTTFUShort(long pos, int val) throws IOException {
if ((pos + 2) > fsize) {
throw new java.io.EOFException("Reached EOF");
}
final byte b1 = (byte)((val >> 8) & 0xff);
final byte b2 = (byte)(val & 0xff);
- file[pos] = b1;
- file[pos + 1] = b2;
+ final int fileIndex = (int) pos;
+ file[fileIndex] = b1;
+ file[fileIndex + 1] = b2;
}
/**
diff --git a/src/java/org/apache/fop/fonts/truetype/GlyfTable.java b/src/java/org/apache/fop/fonts/truetype/GlyfTable.java
new file mode 100644
index 000000000..f26ac2ffd
--- /dev/null
+++ b/src/java/org/apache/fop/fonts/truetype/GlyfTable.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.fonts.truetype;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * This "glyf" table in a TrueType font file contains information that describes the glyphs. This
+ * class is responsible for creating a subset of the "glyf" table given a set of glyph indices.
+ */
+public class GlyfTable {
+
+ private final TTFMtxEntry[] mtxTab;
+
+ private final long tableOffset;
+
+ private final Set<Long> remappedComposites;
+
+ private final Map<Integer, Integer> subset;
+
+ private final FontFileReader in;
+
+ /** All the composite glyphs that appear in the subset. */
+ private Set<Integer> compositeGlyphs = new TreeSet<Integer>();
+
+ /** All the glyphs that are composed, but do not appear in the subset. */
+ private Set<Integer> composedGlyphs = new TreeSet<Integer>();
+
+ GlyfTable(FontFileReader in, TTFMtxEntry[] metrics, TTFDirTabEntry dirTableEntry,
+ Map<Integer, Integer> glyphs) throws IOException {
+ mtxTab = metrics;
+ tableOffset = dirTableEntry.getOffset();
+ remappedComposites = new HashSet<Long>();
+ this.subset = glyphs;
+ this.in = in;
+ }
+
+ private static enum GlyfFlags {
+
+ ARG_1_AND_2_ARE_WORDS(4, 2),
+ ARGS_ARE_XY_VALUES,
+ ROUND_XY_TO_GRID,
+ WE_HAVE_A_SCALE(2),
+ RESERVED,
+ MORE_COMPONENTS,
+ WE_HAVE_AN_X_AND_Y_SCALE(4),
+ WE_HAVE_A_TWO_BY_TWO(8),
+ WE_HAVE_INSTRUCTIONS,
+ USE_MY_METRICS,
+ OVERLAP_COMPOUND,
+ SCALED_COMPONENT_OFFSET,
+ UNSCALED_COMPONENT_OFFSET;
+
+ private final int bitMask;
+ private final int argsCountIfSet;
+ private final int argsCountIfNotSet;
+
+ private GlyfFlags(int argsCountIfSet, int argsCountIfNotSet) {
+ this.bitMask = 1 << this.ordinal();
+ this.argsCountIfSet = argsCountIfSet;
+ this.argsCountIfNotSet = argsCountIfNotSet;
+ }
+
+ private GlyfFlags(int argsCountIfSet) {
+ this(argsCountIfSet, 0);
+ }
+
+ private GlyfFlags() {
+ this(0, 0);
+ }
+
+ /**
+ * Calculates, from the given flags, the offset to the next glyph index.
+ *
+ * @param flags the glyph data flags
+ * @return offset to the next glyph if any, or 0
+ */
+ static int getOffsetToNextComposedGlyf(int flags) {
+ int offset = 0;
+ for (GlyfFlags flag : GlyfFlags.values()) {
+ offset += (flags & flag.bitMask) > 0 ? flag.argsCountIfSet : flag.argsCountIfNotSet;
+ }
+ return offset;
+ }
+
+ /**
+ * Checks the given flags to see if there is another composed glyph.
+ *
+ * @param flags the glyph data flags
+ * @return true if there is another composed glyph, otherwise false.
+ */
+ static boolean hasMoreComposites(int flags) {
+ return (flags & MORE_COMPONENTS.bitMask) > 0;
+ }
+ }
+
+ /**
+ * Populates the map of subset glyphs with all the glyphs that compose the glyphs in the subset.
+ * This also re-maps the indices of composed glyphs to their new index in the subset font.
+ *
+ * @throws IOException an I/O error
+ */
+ void populateGlyphsWithComposites() throws IOException {
+ for (int indexInOriginal : subset.keySet()) {
+ scanGlyphsRecursively(indexInOriginal);
+ }
+
+ addAllComposedGlyphsToSubset();
+
+ for (int compositeGlyph : compositeGlyphs) {
+ long offset = tableOffset + mtxTab[compositeGlyph].getOffset() + 10;
+ if (!remappedComposites.contains(offset)) {
+ remapComposite(offset);
+ }
+ }
+ }
+
+ /**
+ * Scans each glyph for any composed glyphs. This populates <code>compositeGlyphs</code> with
+ * all the composite glyphs being used in the subset. This also populates <code>newGlyphs</code>
+ * with any new glyphs that are composed and do not appear in the subset of glyphs.
+ *
+ * For example the double quote mark (") is often composed of two apostrophes ('), if an
+ * apostrophe doesn't appear in the glyphs in the subset, it will be included and will be added
+ * to newGlyphs.
+ *
+ * @param indexInOriginal the index of the glyph to test from the original font
+ * @throws IOException an I/O error
+ */
+ private void scanGlyphsRecursively(int indexInOriginal) throws IOException {
+ if (!subset.containsKey(indexInOriginal)) {
+ composedGlyphs.add(indexInOriginal);
+ }
+
+ if (isComposite(indexInOriginal)) {
+ compositeGlyphs.add(indexInOriginal);
+ Set<Integer> composedGlyphs = retrieveComposedGlyphs(indexInOriginal);
+ for (Integer composedGlyph : composedGlyphs) {
+ scanGlyphsRecursively(composedGlyph);
+ }
+ }
+ }
+
+ /**
+ * Adds to the subset, all the glyphs that are composed by a glyph, but do not appear themselves
+ * in the subset.
+ */
+ private void addAllComposedGlyphsToSubset() {
+ int newIndex = subset.size();
+ for (int composedGlyph : composedGlyphs) {
+ subset.put(composedGlyph, newIndex++);
+ }
+ }
+
+ /**
+ * Re-maps the index of composed glyphs in the original font to the index of the same glyph in
+ * the subset font.
+ *
+ * @param glyphOffset the offset of the composite glyph
+ * @throws IOException an I/O error
+ */
+ private void remapComposite(long glyphOffset) throws IOException {
+ long currentGlyphOffset = glyphOffset;
+
+ remappedComposites.add(currentGlyphOffset);
+
+ int flags = 0;
+ do {
+ flags = in.readTTFUShort(currentGlyphOffset);
+ int glyphIndex = in.readTTFUShort(currentGlyphOffset + 2);
+ Integer indexInSubset = subset.get(glyphIndex);
+ assert indexInSubset != null;
+ /*
+ * TODO: this should not be done here!! We're writing to the stream we're reading from,
+ * this is asking for trouble! What should happen is when the glyph data is copied from
+ * subset, the remapping should be done there. So the original stream is left untouched.
+ */
+ in.writeTTFUShort(currentGlyphOffset + 2, indexInSubset);
+
+ currentGlyphOffset += 4 + GlyfFlags.getOffsetToNextComposedGlyf(flags);
+ } while (GlyfFlags.hasMoreComposites(flags));
+ }
+
+ private boolean isComposite(int indexInOriginal) throws IOException {
+ int numberOfContours = in.readTTFShort(tableOffset + mtxTab[indexInOriginal].getOffset());
+ return numberOfContours < 0;
+ }
+
+ /**
+ * Reads a composite glyph at a given index and retrieves all the glyph indices of contingent
+ * composed glyphs.
+ *
+ * @param indexInOriginal the glyph index of the composite glyph
+ * @return the set of glyph indices this glyph composes
+ * @throws IOException an I/O error
+ */
+ private Set<Integer> retrieveComposedGlyphs(int indexInOriginal)
+ throws IOException {
+ Set<Integer> composedGlyphs = new HashSet<Integer>();
+ long offset = tableOffset + mtxTab[indexInOriginal].getOffset() + 10;
+ int flags = 0;
+ do {
+ flags = in.readTTFUShort(offset);
+ composedGlyphs.add(in.readTTFUShort(offset + 2));
+ offset += 4 + GlyfFlags.getOffsetToNextComposedGlyf(flags);
+ } while (GlyfFlags.hasMoreComposites(flags));
+
+ return composedGlyphs;
+ }
+}
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
index ee89d9303..665cf289d 100644
--- a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
+++ b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
@@ -21,8 +21,8 @@ package org.apache.fop.fonts.truetype;
import java.io.IOException;
import java.util.Iterator;
-import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
@@ -466,175 +466,6 @@ public class TTFSubSetFile extends TTFFile {
}
/**
- * Returns a List containing the glyph itself plus all glyphs
- * that this composite glyph uses
- */
- private List getIncludedGlyphs(FontFileReader in, int glyphOffset,
- Integer glyphIdx) throws IOException {
- List ret = new java.util.ArrayList();
- ret.add(glyphIdx);
- int offset = glyphOffset + (int)mtxTab[glyphIdx.intValue()].getOffset() + 10;
- Integer compositeIdx = null;
- int flags = 0;
- boolean moreComposites = true;
- while (moreComposites) {
- flags = in.readTTFUShort(offset);
- compositeIdx = new Integer(in.readTTFUShort(offset + 2));
- ret.add(compositeIdx);
-
- offset += 4;
- if ((flags & 1) > 0) {
- // ARG_1_AND_ARG_2_ARE_WORDS
- offset += 4;
- } else {
- offset += 2;
- }
-
- if ((flags & 8) > 0) {
- offset += 2; // WE_HAVE_A_SCALE
- } else if ((flags & 64) > 0) {
- offset += 4; // WE_HAVE_AN_X_AND_Y_SCALE
- } else if ((flags & 128) > 0) {
- offset += 8; // WE_HAVE_A_TWO_BY_TWO
- }
-
- if ((flags & 32) > 0) {
- moreComposites = true;
- } else {
- moreComposites = false;
- }
- }
-
- return ret;
- }
-
-
- /**
- * Rewrite all compositepointers in glyphindex glyphIdx
- *
- */
- private void remapComposite(FontFileReader in, Map glyphs,
- int glyphOffset,
- Integer glyphIdx) throws IOException {
- int offset = glyphOffset + (int)mtxTab[glyphIdx.intValue()].getOffset()
- + 10;
-
- Integer compositeIdx = null;
- int flags = 0;
- boolean moreComposites = true;
-
- while (moreComposites) {
- flags = in.readTTFUShort(offset);
- compositeIdx = new Integer(in.readTTFUShort(offset + 2));
- Integer newIdx = (Integer)glyphs.get(compositeIdx);
- if (newIdx == null) {
- // This errormessage would look much better
- // if the fontname was printed to
- //log.error("An embedded font "
- // + "contains bad glyph data. "
- // + "Characters might not display "
- // + "correctly.");
- moreComposites = false;
- continue;
- }
-
- in.writeTTFUShort(offset + 2, newIdx.intValue());
-
- offset += 4;
-
- if ((flags & 1) > 0) {
- // ARG_1_AND_ARG_2_ARE_WORDS
- offset += 4;
- } else {
- offset += 2;
- }
-
- if ((flags & 8) > 0) {
- offset += 2; // WE_HAVE_A_SCALE
- } else if ((flags & 64) > 0) {
- offset += 4; // WE_HAVE_AN_X_AND_Y_SCALE
- } else if ((flags & 128) > 0) {
- offset += 8; // WE_HAVE_A_TWO_BY_TWO
- }
-
- if ((flags & 32) > 0) {
- moreComposites = true;
- } else {
- moreComposites = false;
- }
- }
- }
-
-
- /**
- * Scan all the original glyphs for composite glyphs and add those glyphs
- * to the glyphmapping also rewrite the composite glyph pointers to the new
- * mapping
- */
- private void scanGlyphs(FontFileReader in,
- Map glyphs) throws IOException {
- TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf");
- Map newComposites = null;
- Map allComposites = new java.util.HashMap();
-
- int newIndex = glyphs.size();
-
- if (entry != null) {
- while (newComposites == null || newComposites.size() > 0) {
- // Inefficient to iterate through all glyphs
- newComposites = new java.util.HashMap();
-
- Iterator e = glyphs.keySet().iterator();
- while (e.hasNext()) {
- Integer origIndex = (Integer)e.next();
-
- if (in.readTTFShort(entry.getOffset()
- + mtxTab[origIndex.intValue()].getOffset()) < 0) {
- // origIndex is a composite glyph
- allComposites.put(origIndex, glyphs.get(origIndex));
- List composites
- = getIncludedGlyphs(in, (int)entry.getOffset(),
- origIndex);
-
- // Iterate through all composites pointed to
- // by this composite and check if they exists
- // in the glyphs map, add them if not.
- Iterator cps = composites.iterator();
- while (cps.hasNext()) {
- Integer cIdx = (Integer)cps.next();
- if (glyphs.get(cIdx) == null
- && newComposites.get(cIdx) == null) {
- newComposites.put(cIdx,
- new Integer(newIndex));
- newIndex++;
- }
- }
- }
- }
-
- // Add composites to glyphs
- Iterator m = newComposites.keySet().iterator();
- while (m.hasNext()) {
- Integer im = (Integer)m.next();
- glyphs.put(im, newComposites.get(im));
- }
- }
-
- // Iterate through all composites to remap their composite index
- Iterator ce = allComposites.keySet().iterator();
- while (ce.hasNext()) {
- remapComposite(in, glyphs, (int)entry.getOffset(),
- (Integer)ce.next());
- }
-
- } else {
- throw new IOException("Can't find glyf table");
- }
- }
-
-
-
- /**
* Returns a subset of the original font.
*
* @param in FontFileReader to read from
@@ -704,6 +535,17 @@ public class TTFSubSetFile extends TTFFile {
return ret;
}
+ private void scanGlyphs(FontFileReader in, Map<Integer, Integer> subsetGlyphs)
+ throws IOException {
+ TTFDirTabEntry glyfTableInfo = (TTFDirTabEntry) dirTabs.get("glyf");
+ if (glyfTableInfo == null) {
+ throw new IOException("Glyf table could not be found");
+ }
+
+ GlyfTable glyfTable = new GlyfTable(in, mtxTab, glyfTableInfo, subsetGlyphs);
+ glyfTable.populateGlyphsWithComposites();
+ }
+
/**
* writes a ISO-8859-1 string at the currentPosition
* updates currentPosition but not realSize
@@ -795,15 +637,15 @@ public class TTFSubSetFile extends TTFFile {
* Read a unsigned short value at given position
*/
private int readUShort(int pos) {
- int ret = (int)output[pos];
+ int ret = output[pos];
if (ret < 0) {
ret += 256;
}
ret = ret << 8;
- if ((int)output[pos + 1] < 0) {
- ret |= (int)output[pos + 1] + 256;
+ if (output[pos + 1] < 0) {
+ ret |= output[pos + 1] + 256;
} else {
- ret |= (int)output[pos + 1];
+ ret |= output[pos + 1];
}
return ret;
@@ -826,7 +668,7 @@ public class TTFSubSetFile extends TTFFile {
*/
private int maxPow2(int max) {
int i = 0;
- while (Math.pow(2, (double)i) < max) {
+ while (Math.pow(2, i) < max) {
i++;
}
@@ -834,7 +676,7 @@ public class TTFSubSetFile extends TTFFile {
}
private int log2(int num) {
- return (int)(Math.log((double)num) / Math.log(2));
+ return (int)(Math.log(num) / Math.log(2));
}
@@ -853,10 +695,10 @@ public class TTFSubSetFile extends TTFFile {
long sum = 0;
for (int i = 0; i < size; i += 4) {
- int l = (int)(output[start + i] << 24);
- l += (int)(output[start + i + 1] << 16);
- l += (int)(output[start + i + 2] << 16);
- l += (int)(output[start + i + 3] << 16);
+ int l = (output[start + i] << 24);
+ l += (output[start + i + 1] << 16);
+ l += (output[start + i + 2] << 16);
+ l += (output[start + i + 3] << 16);
sum += l;
if (sum > 0xffffffff) {
sum = sum - 0xffffffff;
diff --git a/src/java/org/apache/fop/pdf/FileIDGenerator.java b/src/java/org/apache/fop/pdf/FileIDGenerator.java
new file mode 100644
index 000000000..00aad4426
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/FileIDGenerator.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+
+/**
+ * A class to generate the File Identifier of a PDF document (the ID entry of the file
+ * trailer dictionary).
+ */
+abstract class FileIDGenerator {
+
+ abstract byte[] getOriginalFileID();
+
+ abstract byte[] getUpdatedFileID();
+
+ private static final class RandomFileIDGenerator extends FileIDGenerator {
+
+ private byte[] fileID;
+
+ private RandomFileIDGenerator() {
+ Random random = new Random();
+ fileID = new byte[16];
+ random.nextBytes(fileID);
+ }
+
+ @Override
+ byte[] getOriginalFileID() {
+ return fileID;
+ }
+
+ @Override
+ byte[] getUpdatedFileID() {
+ return fileID;
+ }
+
+ }
+
+ private static final class DigestFileIDGenerator extends FileIDGenerator {
+
+ private byte[] fileID;
+
+ private final PDFDocument document;
+
+ private final MessageDigest digest;
+
+ DigestFileIDGenerator(PDFDocument document) throws NoSuchAlgorithmException {
+ this.document = document;
+ this.digest = MessageDigest.getInstance("MD5");
+ }
+
+ @Override
+ byte[] getOriginalFileID() {
+ if (fileID == null) {
+ generateFileID();
+ }
+ return fileID;
+ }
+
+ @Override
+ byte[] getUpdatedFileID() {
+ return getOriginalFileID();
+ }
+
+ private void generateFileID() {
+ DateFormat df = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS");
+ digest.update(PDFDocument.encode(df.format(new Date())));
+ // Ignoring the filename here for simplicity even though it's recommended
+ // by the PDF spec
+ digest.update(PDFDocument.encode(String.valueOf(document.getCurrentFileSize())));
+ digest.update(document.getInfo().toPDF());
+ fileID = digest.digest();
+ }
+
+ }
+
+ /**
+ * Use this method when the file ID is needed before the document is finalized. The
+ * digest method recommended by the PDF Reference is based, among other things, on the
+ * file size.
+ *
+ * @return an instance that generates a random sequence of bytes for the File
+ * Identifier
+ */
+ static FileIDGenerator getRandomFileIDGenerator() {
+ return new RandomFileIDGenerator();
+ }
+
+ /**
+ * Returns an instance that generates a file ID using the digest method recommended by
+ * the PDF Reference. To properly follow the Reference, the size of the document must
+ * no longer change after this method is called.
+ *
+ * @param document the document whose File Identifier must be generated
+ * @return the generator
+ * @throws NoSuchAlgorithmException if the MD5 Digest algorithm is not available
+ */
+ static FileIDGenerator getDigestFileIDGenerator(PDFDocument document)
+ throws NoSuchAlgorithmException {
+ return new DigestFileIDGenerator(document);
+ }
+}
diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java
index 9268ae921..cbca3ea8f 100644
--- a/src/java/org/apache/fop/pdf/PDFDocument.java
+++ b/src/java/org/apache/fop/pdf/PDFDocument.java
@@ -25,10 +25,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
-import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -186,6 +183,8 @@ public class PDFDocument {
private boolean encodingOnTheFly = true;
+ private FileIDGenerator fileIDGenerator;
+
/**
* Creates an empty PDF document.
*
@@ -513,10 +512,10 @@ public class PDFDocument {
*/
public void setEncryption(PDFEncryptionParams params) {
getProfile().verifyEncryptionAllowed();
- this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params);
+ fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator();
+ this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params, this);
if (this.encryption != null) {
PDFObject pdfObject = (PDFObject)this.encryption;
- pdfObject.setDocument(this);
addTrailerObject(pdfObject);
} else {
log.warn(
@@ -979,27 +978,6 @@ public class PDFDocument {
this.position += bin.length;
}
- /** @return the "ID" entry for the file trailer */
- protected String getIDEntry() {
- try {
- MessageDigest digest = MessageDigest.getInstance("MD5");
- DateFormat df = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS");
- digest.update(encode(df.format(new Date())));
- //Ignoring the filename here for simplicity even though it's recommended by the PDF spec
- digest.update(encode(String.valueOf(this.position)));
- digest.update(getInfo().toPDF());
- byte[] res = digest.digest();
- String s = PDFText.toHex(res);
- return "/ID [" + s + " " + s + "]";
- } catch (NoSuchAlgorithmException e) {
- if (getProfile().isIDEntryRequired()) {
- throw new UnsupportedOperationException("MD5 not available: " + e.getMessage());
- } else {
- return ""; //Entry is optional if PDF/A or PDF/X are not active
- }
- }
- }
-
/**
* Write the trailer
*
@@ -1038,7 +1016,9 @@ public class PDFDocument {
if (this.isEncryptionActive()) {
pdf.append(this.encryption.getTrailerEntry());
} else {
- pdf.append(this.getIDEntry());
+ byte[] fileID = getFileIDGenerator().getOriginalFileID();
+ String fileIDAsString = PDFText.toHex(fileID);
+ pdf.append("/ID [" + fileIDAsString + " " + fileIDAsString + "]");
}
pdf.append("\n>>\nstartxref\n")
@@ -1089,4 +1069,18 @@ public class PDFDocument {
return pdfBytes.length;
}
+ long getCurrentFileSize() {
+ return position;
+ }
+
+ FileIDGenerator getFileIDGenerator() {
+ if (fileIDGenerator == null) {
+ try {
+ fileIDGenerator = FileIDGenerator.getDigestFileIDGenerator(this);
+ } catch (NoSuchAlgorithmException e) {
+ fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator();
+ }
+ }
+ return fileIDGenerator;
+ }
}
diff --git a/src/java/org/apache/fop/pdf/PDFEncryption.java b/src/java/org/apache/fop/pdf/PDFEncryption.java
index 5852df157..277cf0a94 100644
--- a/src/java/org/apache/fop/pdf/PDFEncryption.java
+++ b/src/java/org/apache/fop/pdf/PDFEncryption.java
@@ -25,18 +25,6 @@ package org.apache.fop.pdf;
public interface PDFEncryption {
/**
- * Returns the encryption parameters.
- * @return the encryption parameters
- */
- PDFEncryptionParams getParams();
-
- /**
- * Sets the encryption parameters.
- * @param params The parameterss to set
- */
- void setParams(PDFEncryptionParams params);
-
- /**
* Adds a PDFFilter to the PDFStream object
* @param stream the stream to add an encryption filter to
*/
diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
index 269f0639d..c9b9c58ba 100644
--- a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
+++ b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
@@ -19,13 +19,12 @@
package org.apache.fop.pdf;
-// Java
import java.io.IOException;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.Random;
+import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -35,310 +34,427 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
/**
- * class representing a /Filter /Standard object.
- *
+ * An implementation of the Standard Security Handler.
*/
-public class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
+public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
- private class EncryptionFilter extends PDFFilter {
- private PDFEncryptionJCE encryption;
- private int number;
- private int generation;
+ private final MessageDigest digest;
- /**
- * The constructor for the internal PDFEncryptionJCE filter
- * @param encryption The encryption object to use
- * @param number The number of the object to be encrypted
- * @param generation The generation of the object to be encrypted
- */
- public EncryptionFilter(PDFEncryptionJCE encryption,
- int number, int generation) {
- super();
- this.encryption = encryption;
- this.number = number;
- this.generation = generation;
- log.debug("new encryption filter for number "
- + number + " and generation " + generation);
+ private byte[] encryptionKey;
+
+ private String encryptionDictionary;
+
+ private class EncryptionInitializer {
+
+ private final PDFEncryptionParams encryptionParams;
+
+ private int encryptionLength;
+
+ private int version;
+
+ private int revision;
+
+ EncryptionInitializer(PDFEncryptionParams params) {
+ this.encryptionParams = new PDFEncryptionParams(params);
}
+ void init() {
+ encryptionLength = encryptionParams.getEncryptionLengthInBits();
+ determineEncryptionAlgorithm();
+ int permissions = Permission.computePermissions(encryptionParams);
+ EncryptionSettings encryptionSettings = new EncryptionSettings(
+ encryptionLength, permissions,
+ encryptionParams.getUserPassword(), encryptionParams.getOwnerPassword());
+ InitializationEngine initializationEngine = (revision == 2)
+ ? new Rev2Engine(encryptionSettings)
+ : new Rev3Engine(encryptionSettings);
+ initializationEngine.run();
+ encryptionDictionary = createEncryptionDictionary(getObjectID(), permissions,
+ initializationEngine.oValue, initializationEngine.uValue);
+ }
+
+ private void determineEncryptionAlgorithm() {
+ if (isVersion1Revision2Algorithm()) {
+ version = 1;
+ revision = 2;
+ } else {
+ version = 2;
+ revision = 3;
+ }
+ }
+
+ private boolean isVersion1Revision2Algorithm() {
+ return encryptionLength == 40
+ && encryptionParams.isAllowFillInForms()
+ && encryptionParams.isAllowAccessContent()
+ && encryptionParams.isAllowAssembleDocument()
+ && encryptionParams.isAllowPrintHq();
+ }
+
+ private String createEncryptionDictionary(final String objectId, final int permissions,
+ final byte[] oValue, final byte[] uValue) {
+ return objectId
+ + "<< /Filter /Standard\n"
+ + "/V " + version + "\n"
+ + "/R " + revision + "\n"
+ + "/Length " + encryptionLength + "\n"
+ + "/P " + permissions + "\n"
+ + "/O " + PDFText.toHex(oValue) + "\n"
+ + "/U " + PDFText.toHex(uValue) + "\n"
+ + ">>\n"
+ + "endobj\n";
+ }
+
+ }
+
+ private static enum Permission {
+
+ PRINT(3),
+ EDIT_CONTENT(4),
+ COPY_CONTENT(5),
+ EDIT_ANNOTATIONS(6),
+ FILL_IN_FORMS(9),
+ ACCESS_CONTENT(10),
+ ASSEMBLE_DOCUMENT(11),
+ PRINT_HQ(12);
+
+ private final int mask;
+
/**
- * Return a PDF string representation of the filter. In this
- * case no filter name is passed.
- * @return The filter name, blank in this case
+ * Creates a new permission.
+ *
+ * @param bit bit position for this permission, 1-based to match the PDF Reference
*/
- public String getName() {
- return "";
+ private Permission(int bit) {
+ mask = 1 << (bit - 1);
+ }
+
+ private int removeFrom(int permissions) {
+ return permissions - mask;
+ }
+
+ static int computePermissions(PDFEncryptionParams encryptionParams) {
+ int permissions = -4;
+
+ if (!encryptionParams.isAllowPrint()) {
+ permissions = PRINT.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowCopyContent()) {
+ permissions = COPY_CONTENT.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowEditContent()) {
+ permissions = EDIT_CONTENT.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowEditAnnotations()) {
+ permissions = EDIT_ANNOTATIONS.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowFillInForms()) {
+ permissions = FILL_IN_FORMS.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowAccessContent()) {
+ permissions = ACCESS_CONTENT.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowAssembleDocument()) {
+ permissions = ASSEMBLE_DOCUMENT.removeFrom(permissions);
+ }
+ if (!encryptionParams.isAllowPrintHq()) {
+ permissions = PRINT_HQ.removeFrom(permissions);
+ }
+ return permissions;
+ }
+ }
+
+ private static final class EncryptionSettings {
+
+ final int encryptionLength; // CSOK: VisibilityModifier
+
+ final int permissions; // CSOK: VisibilityModifier
+
+ final String userPassword; // CSOK: VisibilityModifier
+
+ final String ownerPassword; // CSOK: VisibilityModifier
+
+ EncryptionSettings(int encryptionLength, int permissions,
+ String userPassword, String ownerPassword) {
+ this.encryptionLength = encryptionLength;
+ this.permissions = permissions;
+ this.userPassword = userPassword;
+ this.ownerPassword = ownerPassword;
+ }
+
+ }
+
+ private abstract class InitializationEngine {
+
+ /** Padding for passwords. */
+ protected final byte[] padding = new byte[] {
+ (byte) 0x28, (byte) 0xBF, (byte) 0x4E, (byte) 0x5E,
+ (byte) 0x4E, (byte) 0x75, (byte) 0x8A, (byte) 0x41,
+ (byte) 0x64, (byte) 0x00, (byte) 0x4E, (byte) 0x56,
+ (byte) 0xFF, (byte) 0xFA, (byte) 0x01, (byte) 0x08,
+ (byte) 0x2E, (byte) 0x2E, (byte) 0x00, (byte) 0xB6,
+ (byte) 0xD0, (byte) 0x68, (byte) 0x3E, (byte) 0x80,
+ (byte) 0x2F, (byte) 0x0C, (byte) 0xA9, (byte) 0xFE,
+ (byte) 0x64, (byte) 0x53, (byte) 0x69, (byte) 0x7A};
+
+ protected final int encryptionLengthInBytes;
+
+ private final int permissions;
+
+ private byte[] oValue;
+
+ private byte[] uValue;
+
+ private final byte[] preparedUserPassword;
+
+ protected final String ownerPassword;
+
+ InitializationEngine(EncryptionSettings encryptionSettings) {
+ this.encryptionLengthInBytes = encryptionSettings.encryptionLength / 8;
+ this.permissions = encryptionSettings.permissions;
+ this.preparedUserPassword = preparePassword(encryptionSettings.userPassword);
+ this.ownerPassword = encryptionSettings.ownerPassword;
+ }
+
+ void run() {
+ oValue = computeOValue();
+ createEncryptionKey();
+ uValue = computeUValue();
}
/**
- * Return a parameter dictionary for this filter, or null
- * @return The parameter dictionary. In this case, null.
+ * Applies Algorithm 3.3 Page 79 of the PDF 1.4 Reference.
+ *
+ * @return the O value
*/
- public PDFObject getDecodeParms() {
- return null;
+ private byte[] computeOValue() {
+ // Step 1
+ byte[] md5Input = prepareMD5Input();
+ // Step 2
+ digest.reset();
+ byte[] hash = digest.digest(md5Input);
+ // Step 3
+ hash = computeOValueStep3(hash);
+ // Step 4
+ byte[] key = new byte[encryptionLengthInBytes];
+ System.arraycopy(hash, 0, key, 0, encryptionLengthInBytes);
+ // Steps 5, 6
+ byte[] encryptionResult = encryptWithKey(key, preparedUserPassword);
+ // Step 7
+ encryptionResult = computeOValueStep7(key, encryptionResult);
+ // Step 8
+ return encryptionResult;
}
/**
- * {@inheritDoc}
+ * Applies Algorithm 3.2 Page 78 of the PDF 1.4 Reference.
*/
- public OutputStream applyFilter(OutputStream out) throws IOException {
- return new CipherOutputStream(out,
- encryption.initCipher(number, generation));
+ private void createEncryptionKey() {
+ // Steps 1, 2
+ digest.reset();
+ digest.update(preparedUserPassword);
+ // Step 3
+ digest.update(oValue);
+ // Step 4
+ digest.update((byte) (permissions >>> 0));
+ digest.update((byte) (permissions >>> 8));
+ digest.update((byte) (permissions >>> 16));
+ digest.update((byte) (permissions >>> 24));
+ // Step 5
+ digest.update(getDocumentSafely().getFileIDGenerator().getOriginalFileID());
+ byte[] hash = digest.digest();
+ // Step 6
+ hash = createEncryptionKeyStep6(hash);
+ // Step 7
+ encryptionKey = new byte[encryptionLengthInBytes];
+ System.arraycopy(hash, 0, encryptionKey, 0, encryptionLengthInBytes);
}
- }
+ protected abstract byte[] computeUValue();
- private static final char [] PAD
- = {0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
- 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
- 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
- 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A};
-
- /** Value of PRINT permission */
- public static final int PERMISSION_PRINT = 4;
- /** Value of content editting permission */
- public static final int PERMISSION_EDIT_CONTENT = 8;
- /** Value of content extraction permission */
- public static final int PERMISSION_COPY_CONTENT = 16;
- /** Value of annotation editting permission */
- public static final int PERMISSION_EDIT_ANNOTATIONS = 32;
-
- // Encryption tools
- private MessageDigest digest = null;
- //private Cipher cipher = null;
- private Random random = new Random();
- // Control attributes
- private PDFEncryptionParams params;
- // Output attributes
- private byte[] fileID = null;
- private byte[] encryptionKey = null;
- private String dictionary = null;
+ /**
+ * Adds padding to the password as directed in page 78 of the PDF 1.4 Reference.
+ *
+ * @param password the password
+ * @return the password with additional padding if necessary
+ */
+ private byte[] preparePassword(String password) {
+ int finalLength = 32;
+ byte[] preparedPassword = new byte[finalLength];
+ byte[] passwordBytes = password.getBytes();
+ System.arraycopy(passwordBytes, 0, preparedPassword, 0, passwordBytes.length);
+ System.arraycopy(padding, 0, preparedPassword, passwordBytes.length,
+ finalLength - passwordBytes.length);
+ return preparedPassword;
+ }
- /**
- * Create a /Filter /Standard object.
- *
- * @param objnum the object's number
- */
- public PDFEncryptionJCE(int objnum) {
- /* generic creation of object */
- super();
- setObjectNumber(objnum);
- try {
- digest = MessageDigest.getInstance("MD5");
- //cipher = Cipher.getInstance("RC4");
- } catch (NoSuchAlgorithmException e) {
- throw new UnsupportedOperationException(e.getMessage());
- /*} catch (NoSuchPaddingException e) {
- throw new UnsupportedOperationException(e.getMessage());*/
+ private byte[] prepareMD5Input() {
+ if (ownerPassword.length() != 0) {
+ return preparePassword(ownerPassword);
+ } else {
+ return preparedUserPassword;
+ }
}
- }
- /**
- * Local factory method.
- * @param objnum PDF object number for the encryption object
- * @param params PDF encryption parameters
- * @return PDFEncryption the newly created PDFEncryption object
- */
- public static PDFEncryption make(int objnum, PDFEncryptionParams params) {
- PDFEncryptionJCE impl = new PDFEncryptionJCE(objnum);
- impl.setParams(params);
- impl.init();
- return impl;
- }
+ protected abstract byte[] computeOValueStep3(byte[] hash);
+ protected abstract byte[] computeOValueStep7(byte[] key, byte[] encryptionResult);
- /**
- * Returns the encryption parameters.
- * @return the encryption parameters
- */
- public PDFEncryptionParams getParams() {
- return this.params;
- }
+ protected abstract byte[] createEncryptionKeyStep6(byte[] hash);
- /**
- * Sets the encryption parameters.
- * @param params The parameterss to set
- */
- public void setParams(PDFEncryptionParams params) {
- this.params = params;
}
- // Internal procedures
+ private class Rev2Engine extends InitializationEngine {
- private byte[] prepPassword(String password) {
- byte[] obuffer = new byte[32];
- byte[] pbuffer = password.getBytes();
+ Rev2Engine(EncryptionSettings encryptionSettings) {
+ super(encryptionSettings);
+ }
- int i = 0;
- int j = 0;
+ @Override
+ protected byte[] computeOValueStep3(byte[] hash) {
+ return hash;
+ }
- while (i < obuffer.length && i < pbuffer.length) {
- obuffer[i] = pbuffer[i];
- i++;
+ @Override
+ protected byte[] computeOValueStep7(byte[] key, byte[] encryptionResult) {
+ return encryptionResult;
}
- while (i < obuffer.length) {
- obuffer[i++] = (byte) PAD[j++];
+
+ @Override
+ protected byte[] createEncryptionKeyStep6(byte[] hash) {
+ return hash;
+ }
+
+ @Override
+ protected byte[] computeUValue() {
+ return encryptWithKey(encryptionKey, padding);
}
- return obuffer;
}
- /**
- * Returns the document file ID
- * @return The file ID
- */
- public byte[] getFileID() {
- if (fileID == null) {
- fileID = new byte[16];
- random.nextBytes(fileID);
+ private class Rev3Engine extends InitializationEngine {
+
+ Rev3Engine(EncryptionSettings encryptionSettings) {
+ super(encryptionSettings);
}
- return fileID;
- }
+ @Override
+ protected byte[] computeOValueStep3(byte[] hash) {
+ for (int i = 0; i < 50; i++) {
+ hash = digest.digest(hash);
+ }
+ return hash;
+ }
- /**
- * This method returns the indexed file ID
- * @param index The index to access the file ID
- * @return The file ID
- */
- public String getFileID(int index) {
- if (index == 1) {
- return PDFText.toHex(getFileID());
+ @Override
+ protected byte[] computeOValueStep7(byte[] key, byte[] encryptionResult) {
+ return xorKeyAndEncrypt19Times(key, encryptionResult);
}
- byte[] id = new byte[16];
- random.nextBytes(id);
- return PDFText.toHex(id);
- }
+ @Override
+ protected byte[] createEncryptionKeyStep6(byte[] hash) {
+ for (int i = 0; i < 50; i++) {
+ digest.update(hash, 0, encryptionLengthInBytes);
+ hash = digest.digest();
+ }
+ return hash;
+ }
- private byte[] encryptWithKey(byte[] data, byte[] key) {
- try {
- final Cipher c = initCipher(key);
- return c.doFinal(data);
- } catch (IllegalBlockSizeException e) {
- throw new IllegalStateException(e.getMessage());
- } catch (BadPaddingException e) {
- throw new IllegalStateException(e.getMessage());
+ @Override
+ protected byte[] computeUValue() {
+ // Step 1 is encryptionKey
+ // Step 2
+ digest.reset();
+ digest.update(padding);
+ // Step 3
+ digest.update(getDocumentSafely().getFileIDGenerator().getOriginalFileID());
+ // Step 4
+ byte[] encryptionResult = encryptWithKey(encryptionKey, digest.digest());
+ // Step 5
+ encryptionResult = xorKeyAndEncrypt19Times(encryptionKey, encryptionResult);
+ // Step 6
+ byte[] uValue = new byte[32];
+ System.arraycopy(encryptionResult, 0, uValue, 0, 16);
+ // Add the arbitrary padding
+ Arrays.fill(uValue, 16, 32, (byte) 0);
+ return uValue;
}
- }
- private Cipher initCipher(byte[] key) {
- try {
- Cipher c = Cipher.getInstance("RC4");
- SecretKeySpec keyspec = new SecretKeySpec(key, "RC4");
- c.init(Cipher.ENCRYPT_MODE, keyspec);
- return c;
- } catch (InvalidKeyException e) {
- throw new IllegalStateException(e.getMessage());
- } catch (NoSuchAlgorithmException e) {
- throw new UnsupportedOperationException(e.getMessage());
- } catch (NoSuchPaddingException e) {
- throw new UnsupportedOperationException(e.getMessage());
+ private byte[] xorKeyAndEncrypt19Times(byte[] key, byte[] input) {
+ byte[] result = input;
+ byte[] encryptionKey = new byte[key.length];
+ for (int i = 1; i <= 19; i++) {
+ for (int j = 0; j < key.length; j++) {
+ encryptionKey[j] = (byte) (key[j] ^ i);
+ }
+ result = encryptWithKey(encryptionKey, result);
+ }
+ return result;
}
- }
- private Cipher initCipher(int number, int generation) {
- byte[] hash = calcHash(number, generation);
- int size = hash.length;
- hash = digest.digest(hash);
- byte[] key = calcKey(hash, size);
- return initCipher(key);
}
- private byte[] encryptWithHash(byte[] data, byte[] hash, int size) {
- hash = digest.digest(hash);
+ private class EncryptionFilter extends PDFFilter {
- byte[] key = calcKey(hash, size);
+ private int streamNumber;
- return encryptWithKey(data, key);
- }
+ private int streamGeneration;
- private byte[] calcKey(byte[] hash, int size) {
- byte[] key = new byte[size];
+ EncryptionFilter(int streamNumber, int streamGeneration) {
+ this.streamNumber = streamNumber;
+ this.streamGeneration = streamGeneration;
+ }
- for (int i = 0; i < size; i++) {
- key[i] = hash[i];
+ /**
+ * Returns a PDF string representation of this filter.
+ *
+ * @return the empty string
+ */
+ public String getName() {
+ return "";
}
- return key;
+
+ /**
+ * Returns a parameter dictionary for this filter.
+ *
+ * @return null, this filter has no parameters
+ */
+ public PDFObject getDecodeParms() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public OutputStream applyFilter(OutputStream out) throws IOException {
+ byte[] key = createEncryptionKey(streamNumber, streamGeneration);
+ Cipher cipher = initCipher(key);
+ return new CipherOutputStream(out, cipher);
+ }
+
}
- /**
- * This method initializes the encryption algorithms and values
- */
- public void init() {
- // Generate the owner value
- byte[] oValue;
- if (params.getOwnerPassword().length() > 0) {
- oValue = encryptWithHash(
- prepPassword(params.getUserPassword()),
- prepPassword(params.getOwnerPassword()), 5);
- } else {
- oValue = encryptWithHash(
- prepPassword(params.getUserPassword()),
- prepPassword(params.getUserPassword()), 5);
- }
-
- // Generate permissions value
- int permissions = -4;
-
- if (!params.isAllowPrint()) {
- permissions -= PERMISSION_PRINT;
- }
- if (!params.isAllowCopyContent()) {
- permissions -= PERMISSION_COPY_CONTENT;
- }
- if (!params.isAllowEditContent()) {
- permissions -= PERMISSION_EDIT_CONTENT;
- }
- if (!params.isAllowEditAnnotations()) {
- permissions -= PERMISSION_EDIT_ANNOTATIONS;
- }
-
- // Create the encrption key
- digest.update(prepPassword(params.getUserPassword()));
- digest.update(oValue);
- digest.update((byte) (permissions >>> 0));
- digest.update((byte) (permissions >>> 8));
- digest.update((byte) (permissions >>> 16));
- digest.update((byte) (permissions >>> 24));
- digest.update(getFileID());
-
- byte [] hash = digest.digest();
- this.encryptionKey = new byte[5];
-
- for (int i = 0; i < 5; i++) {
- this.encryptionKey[i] = hash[i];
- }
-
- // Create the user value
- byte[] uValue = encryptWithKey(prepPassword(""), this.encryptionKey);
-
- // Create the dictionary
- this.dictionary = getObjectID()
- + "<< /Filter /Standard\n"
- + "/V 1\n"
- + "/R 2\n"
- + "/Length 40\n"
- + "/P " + permissions + "\n"
- + "/O " + PDFText.toHex(oValue) + "\n"
- + "/U " + PDFText.toHex(uValue) + "\n"
- + ">>\n"
- + "endobj\n";
+ private PDFEncryptionJCE(int objectNumber, PDFEncryptionParams params, PDFDocument pdf) {
+ setObjectNumber(objectNumber);
+ try {
+ digest = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(e.getMessage());
+ }
+ setDocument(pdf);
+ EncryptionInitializer encryptionInitializer = new EncryptionInitializer(params);
+ encryptionInitializer.init();
}
/**
- * This method encrypts the passed data using the generated keys.
- * @param data The data to be encrypted
- * @param number The block number
- * @param generation The block generation
- * @return The encrypted data
+ * Creates and returns an encryption object.
+ *
+ * @param objectNumber the object number for the encryption dictionary
+ * @param params the encryption parameters
+ * @param pdf the PDF document to be encrypted
+ * @return the newly created encryption object
*/
- public byte[] encryptData(byte[] data, int number, int generation) {
- if (this.encryptionKey == null) {
- throw new IllegalStateException("PDF Encryption has not been initialized");
- }
- byte[] hash = calcHash(number, generation);
- return encryptWithHash(data, hash, hash.length);
+ public static PDFEncryption make(
+ int objectNumber, PDFEncryptionParams params, PDFDocument pdf) {
+ return new PDFEncryptionJCE(objectNumber, params, pdf);
}
/** {@inheritDoc} */
@@ -350,63 +466,95 @@ public class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
if (o == null) {
throw new IllegalStateException("No object number could be obtained for a PDF object");
}
- return encryptData(data, o.getObjectNumber(), o.getGeneration());
+ byte[] key = createEncryptionKey(o.getObjectNumber(), o.getGeneration());
+ return encryptWithKey(key, data);
}
- private byte[] calcHash(int number, int generation) {
- byte[] hash = new byte[this.encryptionKey.length + 5];
-
- int i = 0;
- while (i < this.encryptionKey.length) {
- hash[i] = this.encryptionKey[i]; i++;
- }
-
- hash[i++] = (byte) (number >>> 0);
- hash[i++] = (byte) (number >>> 8);
- hash[i++] = (byte) (number >>> 16);
- hash[i++] = (byte) (generation >>> 0);
- hash[i++] = (byte) (generation >>> 8);
- return hash;
- }
-
- /**
- * Creates PDFFilter for the encryption object
- * @param number The object number
- * @param generation The objects generation
- * @return The resulting filter
- */
- public PDFFilter makeFilter(int number, int generation) {
- return new EncryptionFilter(this, number, generation);
- }
-
- /**
- * Adds a PDFFilter to the PDFStream object
- * @param stream the stream to add an encryption filter to
- */
+ /** {@inheritDoc} */
public void applyFilter(AbstractPDFStream stream) {
stream.getFilterList().addFilter(
- this.makeFilter(stream.getObjectNumber(), stream.getGeneration()));
+ new EncryptionFilter(stream.getObjectNumber(), stream.getGeneration()));
}
/**
- * Represent the object in PDF
+ * Prepares the encryption dictionary for output to a PDF file.
*
- * @return the PDF
+ * @return the encryption dictionary as a byte array
*/
public byte[] toPDF() {
- if (this.dictionary == null) {
- throw new IllegalStateException("PDF Encryption has not been initialized");
+ assert encryptionDictionary != null;
+ return encode(this.encryptionDictionary);
+ }
+
+ /** {@inheritDoc} */
+ public String getTrailerEntry() {
+ PDFDocument doc = getDocumentSafely();
+ FileIDGenerator gen = doc.getFileIDGenerator();
+ return "/Encrypt " + getObjectNumber() + " "
+ + getGeneration() + " R\n"
+ + "/ID["
+ + PDFText.toHex(gen.getOriginalFileID())
+ + PDFText.toHex(gen.getUpdatedFileID())
+ + "]\n";
+ }
+
+ private static byte[] encryptWithKey(byte[] key, byte[] data) {
+ try {
+ final Cipher c = initCipher(key);
+ return c.doFinal(data);
+ } catch (IllegalBlockSizeException e) {
+ throw new IllegalStateException(e.getMessage());
+ } catch (BadPaddingException e) {
+ throw new IllegalStateException(e.getMessage());
}
+ }
- return encode(this.dictionary);
+ private static Cipher initCipher(byte[] key) {
+ try {
+ Cipher c = Cipher.getInstance("RC4");
+ SecretKeySpec keyspec = new SecretKeySpec(key, "RC4");
+ c.init(Cipher.ENCRYPT_MODE, keyspec);
+ return c;
+ } catch (InvalidKeyException e) {
+ throw new IllegalStateException(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(e);
+ } catch (NoSuchPaddingException e) {
+ throw new UnsupportedOperationException(e);
+ }
}
/**
- * {@inheritDoc}
+ * Applies Algorithm 3.1 from the PDF 1.4 Reference.
+ *
+ * @param objectNumber the object number
+ * @param generationNumber the generation number
+ * @return the key to use for encryption
*/
- public String getTrailerEntry() {
- return "/Encrypt " + getObjectNumber() + " "
- + getGeneration() + " R\n"
- + "/ID[" + getFileID(1) + getFileID(2) + "]\n";
+ private byte[] createEncryptionKey(int objectNumber, int generationNumber) {
+ // Step 1 passed in
+ // Step 2
+ byte[] md5Input = prepareMD5Input(objectNumber, generationNumber);
+ // Step 3
+ digest.reset();
+ byte[] hash = digest.digest(md5Input);
+ // Step 4
+ int keyLength = Math.min(16, md5Input.length);
+ byte[] key = new byte[keyLength];
+ System.arraycopy(hash, 0, key, 0, keyLength);
+ return key;
}
+
+ private byte[] prepareMD5Input(int objectNumber, int generationNumber) {
+ byte[] md5Input = new byte[encryptionKey.length + 5];
+ System.arraycopy(encryptionKey, 0, md5Input, 0, encryptionKey.length);
+ int i = encryptionKey.length;
+ md5Input[i++] = (byte) (objectNumber >>> 0);
+ md5Input[i++] = (byte) (objectNumber >>> 8);
+ md5Input[i++] = (byte) (objectNumber >>> 16);
+ md5Input[i++] = (byte) (generationNumber >>> 0);
+ md5Input[i++] = (byte) (generationNumber >>> 8);
+ return md5Input;
+ }
+
}
diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionManager.java b/src/java/org/apache/fop/pdf/PDFEncryptionManager.java
index 3f5ae2a3d..6e57b1518 100644
--- a/src/java/org/apache/fop/pdf/PDFEncryptionManager.java
+++ b/src/java/org/apache/fop/pdf/PDFEncryptionManager.java
@@ -109,16 +109,18 @@ public final class PDFEncryptionManager {
* Creates a new PDFEncryption instance if PDF encryption is available.
* @param objnum PDF object number
* @param params PDF encryption parameters
+ * @param pdf the PDF document to encrypt
* @return PDFEncryption the newly created instance, null if PDF encryption
* is unavailable.
*/
- public static PDFEncryption newInstance(int objnum, PDFEncryptionParams params) {
+ public static PDFEncryption newInstance(int objnum, PDFEncryptionParams params,
+ PDFDocument pdf) {
try {
- Class clazz = Class.forName("org.apache.fop.pdf.PDFEncryptionJCE");
+ Class<?> clazz = Class.forName("org.apache.fop.pdf.PDFEncryptionJCE");
Method makeMethod = clazz.getMethod("make",
- new Class[] {int.class, PDFEncryptionParams.class});
+ new Class[] {int.class, PDFEncryptionParams.class, PDFDocument.class});
Object obj = makeMethod.invoke(null,
- new Object[] {new Integer(objnum), params});
+ new Object[] {new Integer(objnum), params, pdf});
return (PDFEncryption)obj;
} catch (ClassNotFoundException e) {
if (checkAvailableAlgorithms()) {
diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionParams.java b/src/java/org/apache/fop/pdf/PDFEncryptionParams.java
index 9cc502c42..71dccd867 100644
--- a/src/java/org/apache/fop/pdf/PDFEncryptionParams.java
+++ b/src/java/org/apache/fop/pdf/PDFEncryptionParams.java
@@ -26,10 +26,17 @@ public class PDFEncryptionParams {
private String userPassword = ""; //May not be null
private String ownerPassword = ""; //May not be null
+
private boolean allowPrint = true;
private boolean allowCopyContent = true;
private boolean allowEditContent = true;
private boolean allowEditAnnotations = true;
+ private boolean allowFillInForms = true;
+ private boolean allowAccessContent = true;
+ private boolean allowAssembleDocument = true;
+ private boolean allowPrintHq = true;
+
+ private int encryptionLengthInBits = 40;
/**
* Creates a new instance.
@@ -61,6 +68,25 @@ public class PDFEncryptionParams {
}
/**
+ * Creates a copy of the given encryption parameters.
+ *
+ * @param source source encryption parameters
+ */
+ public PDFEncryptionParams(PDFEncryptionParams source) {
+ setUserPassword(source.getUserPassword());
+ setOwnerPassword(source.getOwnerPassword());
+ setAllowPrint(source.isAllowPrint());
+ setAllowCopyContent(source.isAllowCopyContent());
+ setAllowEditContent(source.isAllowEditContent());
+ setAllowEditAnnotations(source.isAllowEditAnnotations());
+ setAllowAssembleDocument(source.isAllowAssembleDocument());
+ setAllowAccessContent(source.isAllowAccessContent());
+ setAllowFillInForms(source.isAllowFillInForms());
+ setAllowPrintHq(source.isAllowPrintHq());
+ setEncryptionLengthInBits(source.getEncryptionLengthInBits());
+ }
+
+ /**
* Indicates whether copying content is allowed.
* @return true if copying is allowed
*/
@@ -93,6 +119,38 @@ public class PDFEncryptionParams {
}
/**
+ * Indicates whether revision 3 filling in forms is allowed.
+ * @return true if revision 3 filling in forms is allowed
+ */
+ public boolean isAllowFillInForms() {
+ return allowFillInForms;
+ }
+
+ /**
+ * Indicates whether revision 3 extracting text and graphics is allowed.
+ * @return true if revision 3 extracting text and graphics is allowed
+ */
+ public boolean isAllowAccessContent() {
+ return allowAccessContent;
+ }
+
+ /**
+ * Indicates whether revision 3 assembling document is allowed.
+ * @return true if revision 3 assembling document is allowed
+ */
+ public boolean isAllowAssembleDocument() {
+ return allowAssembleDocument;
+ }
+
+ /**
+ * Indicates whether revision 3 printing to high quality is allowed.
+ * @return true if revision 3 printing to high quality is allowed
+ */
+ public boolean isAllowPrintHq() {
+ return allowPrintHq;
+ }
+
+ /**
* Returns the owner password.
* @return the owner password, an empty string if no password applies
*/
@@ -133,7 +191,7 @@ public class PDFEncryptionParams {
}
/**
- * Sets the persmission for printing.
+ * Sets the permission for printing.
* @param allowPrint true if printing is allowed
*/
public void setAllowPrint(boolean allowPrint) {
@@ -141,6 +199,38 @@ public class PDFEncryptionParams {
}
/**
+ * Sets whether revision 3 filling in forms is allowed.
+ * @param allowFillInForms true if revision 3 filling in forms is allowed.
+ */
+ public void setAllowFillInForms(boolean allowFillInForms) {
+ this.allowFillInForms = allowFillInForms;
+ }
+
+ /**
+ * Sets whether revision 3 extracting text and graphics is allowed.
+ * @param allowAccessContent true if revision 3 extracting text and graphics is allowed
+ */
+ public void setAllowAccessContent(boolean allowAccessContent) {
+ this.allowAccessContent = allowAccessContent;
+ }
+
+ /**
+ * Sets whether revision 3 assembling document is allowed.
+ * @param allowAssembleDocument true if revision 3 assembling document is allowed
+ */
+ public void setAllowAssembleDocument(boolean allowAssembleDocument) {
+ this.allowAssembleDocument = allowAssembleDocument;
+ }
+
+ /**
+ * Sets whether revision 3 printing to high quality is allowed.
+ * @param allowPrintHq true if revision 3 printing to high quality is allowed
+ */
+ public void setAllowPrintHq(boolean allowPrintHq) {
+ this.allowPrintHq = allowPrintHq;
+ }
+
+ /**
* Sets the owner password.
* @param ownerPassword The owner password to set, null or an empty String
* if no password is applicable
@@ -166,4 +256,21 @@ public class PDFEncryptionParams {
}
}
+ /**
+ * Returns the encryption length.
+ * @return the encryption length
+ */
+ public int getEncryptionLengthInBits() {
+ return encryptionLengthInBits;
+ }
+
+ /**
+ * Sets the encryption length.
+ *
+ * @param encryptionLength the encryption length
+ */
+ public void setEncryptionLengthInBits(int encryptionLength) {
+ this.encryptionLengthInBits = encryptionLength;
+ }
+
}
diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java
index 23542a49e..64941be94 100644
--- a/src/java/org/apache/fop/pdf/PDFFactory.java
+++ b/src/java/org/apache/fop/pdf/PDFFactory.java
@@ -27,6 +27,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
+import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
@@ -63,6 +64,7 @@ import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFSubSetFile;
import org.apache.fop.fonts.type1.PFBData;
import org.apache.fop.fonts.type1.PFBParser;
+import org.apache.xmlgraphics.xmp.Metadata;
/**
* This class provides method to create and register PDF objects.
@@ -76,6 +78,8 @@ public class PDFFactory {
private Log log = LogFactory.getLog(PDFFactory.class);
+ private int subsetFontCounter = -1;
+
/**
* Creates a new PDFFactory.
* @param document the parent PDFDocument needed to register the generated
@@ -1377,10 +1381,15 @@ public class PDFFactory {
} else {
FontType fonttype = metrics.getFontType();
- PDFFontDescriptor pdfdesc = makeFontDescriptor(descriptor);
+ String fontPrefix = descriptor.isSubsetEmbedded() ? createSubsetFontPrefix() : "";
+
+ String subsetFontName = fontPrefix + basefont;
+
+ PDFFontDescriptor pdfdesc = makeFontDescriptor(descriptor, fontPrefix);
PDFFont font = null;
- font = PDFFont.createFont(fontname, fonttype, basefont, null);
+
+ font = PDFFont.createFont(fontname, fonttype, subsetFontName, null);
getDocument().registerObject(font);
if (fonttype == FontType.TYPE0) {
@@ -1395,8 +1404,7 @@ public class PDFFactory {
= new PDFCIDSystemInfo(cidMetrics.getRegistry(),
cidMetrics.getOrdering(),
cidMetrics.getSupplement());
- PDFCIDFont cidFont
- = new PDFCIDFont(basefont,
+ PDFCIDFont cidFont = new PDFCIDFont(subsetFontName,
cidMetrics.getCIDType(),
cidMetrics.getDefaultWidth(),
getSubsetWidths(cidMetrics), sysInfo,
@@ -1550,18 +1558,35 @@ public class PDFFactory {
return warray;
}
+ private String createSubsetFontPrefix() {
+ subsetFontCounter++;
+ DecimalFormat counterFormat = new DecimalFormat("00000");
+ String counterString = counterFormat.format(subsetFontCounter);
+
+ // Subset prefix as described in chapter 5.5.3 of PDF 1.4
+ StringBuffer sb = new StringBuffer("E");
+
+ for (char c : counterString.toCharArray()) {
+ // translate numbers to uppercase characters
+ sb.append((char) (c + ('A' - '0')));
+ }
+ sb.append("+");
+ return sb.toString();
+ }
+
/**
* make a /FontDescriptor object
*
* @param desc the font descriptor
+ * @param fontPrefix the String with which to prefix the font name
* @return the new PDF font descriptor
*/
- public PDFFontDescriptor makeFontDescriptor(FontDescriptor desc) {
+ private PDFFontDescriptor makeFontDescriptor(FontDescriptor desc, String fontPrefix) {
PDFFontDescriptor descriptor = null;
if (desc.getFontType() == FontType.TYPE0) {
// CID Font
- descriptor = new PDFCIDFontDescriptor(desc.getEmbedFontName(),
+ descriptor = new PDFCIDFontDescriptor(fontPrefix + desc.getEmbedFontName(),
desc.getFontBBox(),
desc.getCapHeight(),
desc.getFlags(),
diff --git a/src/java/org/apache/fop/pdf/PDFObject.java b/src/java/org/apache/fop/pdf/PDFObject.java
index 09b8673a8..6dd0c800f 100644
--- a/src/java/org/apache/fop/pdf/PDFObject.java
+++ b/src/java/org/apache/fop/pdf/PDFObject.java
@@ -112,7 +112,7 @@ public abstract class PDFObject implements PDFWritable {
}
/**
- * Returns the object's generation.
+ * Returns this object's generation.
* @return the PDF Object generation
*/
public int getGeneration() {
diff --git a/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java b/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java
index 8cc381c18..fc8d10508 100644
--- a/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java
@@ -27,7 +27,6 @@ import java.util.List;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
-
import org.apache.fop.afp.AFPResourceLevel;
import org.apache.fop.afp.AFPResourceLevelDefaults;
import org.apache.fop.afp.fonts.AFPFont;
@@ -258,9 +257,11 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator
}
String name = afpFontCfg.getAttribute("name", characterset);
CharacterSet characterSet = null;
+ boolean ebcdicDBCS = afpFontCfg.getAttributeAsBoolean("ebcdic-dbcs", false);
+
try {
- characterSet = CharacterSetBuilder.getDoubleByteInstance()
- .build(characterset, codepage, encoding, accessor);
+ characterSet = CharacterSetBuilder.getDoubleByteInstance().buildDBCS(characterset,
+ codepage, encoding, ebcdicDBCS, accessor);
} catch (IOException ioe) {
toConfigurationException(codepage, characterset, ioe);
}
diff --git a/src/java/org/apache/fop/render/bitmap/BitmapRendererConfigurator.java b/src/java/org/apache/fop/render/bitmap/BitmapRendererConfigurator.java
index 1b6c43700..2d78b53c6 100644
--- a/src/java/org/apache/fop/render/bitmap/BitmapRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/bitmap/BitmapRendererConfigurator.java
@@ -115,13 +115,13 @@ public class BitmapRendererConfigurator extends Java2DRendererConfigurator
/** {@inheritDoc} */
public void setupFontInfo(IFDocumentHandler documentHandler, FontInfo fontInfo)
throws FOPException {
- FontManager fontManager = userAgent.getFactory().getFontManager();
+ final FontManager fontManager = userAgent.getFactory().getFontManager();
- Graphics2D graphics2D = Java2DFontMetrics.createFontMetricsGraphics2D();
+ final Java2DFontMetrics java2DFontMetrics = new Java2DFontMetrics();
- List fontCollections = new java.util.ArrayList();
- fontCollections.add(new Base14FontCollection(graphics2D));
- fontCollections.add(new InstalledFontCollection(graphics2D));
+ final List fontCollections = new java.util.ArrayList();
+ fontCollections.add(new Base14FontCollection(java2DFontMetrics));
+ fontCollections.add(new InstalledFontCollection(java2DFontMetrics));
Configuration cfg = super.getRendererConfig(documentHandler.getMimeType());
if (cfg != null) {
diff --git a/src/java/org/apache/fop/render/java2d/Base14FontCollection.java b/src/java/org/apache/fop/render/java2d/Base14FontCollection.java
index 29e80dc2f..578745281 100644
--- a/src/java/org/apache/fop/render/java2d/Base14FontCollection.java
+++ b/src/java/org/apache/fop/render/java2d/Base14FontCollection.java
@@ -30,14 +30,15 @@ import org.apache.fop.fonts.FontInfo;
*/
public class Base14FontCollection implements FontCollection {
- private Graphics2D graphics2d = null;
+ /** required when creating new instances of SystemFontMetricsMapper */
+ private final Java2DFontMetrics java2DFontMetrics;
/**
* Main constructor
- * @param graphics2d a graphics 2D
+ * @param java2DFontMetrics required when creating new instances of SystemFontMetricsMapper
*/
- public Base14FontCollection(Graphics2D graphics2d) {
- this.graphics2d = graphics2d;
+ public Base14FontCollection(Java2DFontMetrics java2DFontMetrics) {
+ this.java2DFontMetrics = java2DFontMetrics;
}
/**
@@ -56,47 +57,47 @@ public class Base14FontCollection implements FontCollection {
final int bolditalic = java.awt.Font.BOLD + java.awt.Font.ITALIC;
FontMetricsMapper metric;
- metric = new SystemFontMetricsMapper("SansSerif", normal, graphics2d);
+ metric = new SystemFontMetricsMapper("SansSerif", normal, java2DFontMetrics);
// --> goes to F1
fontInfo.addMetrics("F1", metric);
- metric = new SystemFontMetricsMapper("SansSerif", italic, graphics2d);
+ metric = new SystemFontMetricsMapper("SansSerif", italic, java2DFontMetrics);
// --> goes to F2
fontInfo.addMetrics("F2", metric);
- metric = new SystemFontMetricsMapper("SansSerif", bold, graphics2d);
+ metric = new SystemFontMetricsMapper("SansSerif", bold, java2DFontMetrics);
// --> goes to F3
fontInfo.addMetrics("F3", metric);
- metric = new SystemFontMetricsMapper("SansSerif", bolditalic, graphics2d);
+ metric = new SystemFontMetricsMapper("SansSerif", bolditalic, java2DFontMetrics);
// --> goes to F4
fontInfo.addMetrics("F4", metric);
- metric = new SystemFontMetricsMapper("Serif", normal, graphics2d);
+ metric = new SystemFontMetricsMapper("Serif", normal, java2DFontMetrics);
// --> goes to F5
fontInfo.addMetrics("F5", metric);
- metric = new SystemFontMetricsMapper("Serif", italic, graphics2d);
+ metric = new SystemFontMetricsMapper("Serif", italic, java2DFontMetrics);
// --> goes to F6
fontInfo.addMetrics("F6", metric);
- metric = new SystemFontMetricsMapper("Serif", bold, graphics2d);
+ metric = new SystemFontMetricsMapper("Serif", bold, java2DFontMetrics);
// --> goes to F7
fontInfo.addMetrics("F7", metric);
- metric = new SystemFontMetricsMapper("Serif", bolditalic, graphics2d);
+ metric = new SystemFontMetricsMapper("Serif", bolditalic, java2DFontMetrics);
// --> goes to F8
fontInfo.addMetrics("F8", metric);
- metric = new SystemFontMetricsMapper("MonoSpaced", normal, graphics2d);
+ metric = new SystemFontMetricsMapper("MonoSpaced", normal, java2DFontMetrics);
// --> goes to F9
fontInfo.addMetrics("F9", metric);
- metric = new SystemFontMetricsMapper("MonoSpaced", italic, graphics2d);
+ metric = new SystemFontMetricsMapper("MonoSpaced", italic, java2DFontMetrics);
// --> goes to F10
fontInfo.addMetrics("F10", metric);
- metric = new SystemFontMetricsMapper("MonoSpaced", bold, graphics2d);
+ metric = new SystemFontMetricsMapper("MonoSpaced", bold, java2DFontMetrics);
// --> goes to F11
fontInfo.addMetrics("F11", metric);
- metric = new SystemFontMetricsMapper("MonoSpaced", bolditalic, graphics2d);
+ metric = new SystemFontMetricsMapper("MonoSpaced", bolditalic, java2DFontMetrics);
// --> goes to F12
fontInfo.addMetrics("F12", metric);
- metric = new SystemFontMetricsMapper("Serif", normal, graphics2d);
+ metric = new SystemFontMetricsMapper("Serif", normal, java2DFontMetrics);
//"Symbol" doesn't seem to work here, but "Serif" does the job just fine. *shrug*
// --> goes to F13 and F14
fontInfo.addMetrics("F13", metric);
diff --git a/src/java/org/apache/fop/render/java2d/InstalledFontCollection.java b/src/java/org/apache/fop/render/java2d/InstalledFontCollection.java
index fe4e04766..f41dd4d92 100644
--- a/src/java/org/apache/fop/render/java2d/InstalledFontCollection.java
+++ b/src/java/org/apache/fop/render/java2d/InstalledFontCollection.java
@@ -38,10 +38,10 @@ public class InstalledFontCollection implements FontCollection {
private static Log log = LogFactory.getLog(InstalledFontCollection.class);
- private static final Set HARDCODED_FONT_NAMES;
+ private static final Set<String> HARDCODED_FONT_NAMES;
static {
- HARDCODED_FONT_NAMES = new java.util.HashSet();
+ HARDCODED_FONT_NAMES = new java.util.HashSet<String>();
HARDCODED_FONT_NAMES.add("any");
HARDCODED_FONT_NAMES.add("sans-serif");
HARDCODED_FONT_NAMES.add("serif");
@@ -57,15 +57,16 @@ public class InstalledFontCollection implements FontCollection {
HARDCODED_FONT_NAMES.add("Computer-Modern-Typewriter");
}
- private Graphics2D graphics2D = null;
+ /** Required by new instances of FontMetricsMapper */
+ private final Java2DFontMetrics java2DFontMetrics;
/**
* Main constructor
*
- * @param graphics2D a graphics 2D
+ * @param java2DFontMetrics required by new instances of FontMetricsMapper
*/
- public InstalledFontCollection(Graphics2D graphics2D) {
- this.graphics2D = graphics2D;
+ public InstalledFontCollection(Java2DFontMetrics java2DFontMetrics) {
+ this.java2DFontMetrics = java2DFontMetrics;
}
/**
@@ -98,7 +99,7 @@ public class InstalledFontCollection implements FontCollection {
num++;
String fontKey = "F" + num;
int style = convertToAWTFontStyle(guessedStyle, guessedWeight);
- addFontMetricsMapper(fontInfo, f.getName(), fontKey, graphics2D, style);
+ addFontMetricsMapper(fontInfo, f.getName(), fontKey, java2DFontMetrics, style);
//Register appropriate font triplets matching the font. Two different strategies:
//Example: "Arial Bold", normal, normal
@@ -120,8 +121,8 @@ public class InstalledFontCollection implements FontCollection {
}
private static void addFontMetricsMapper(FontInfo fontInfo, String family, String fontKey,
- Graphics2D graphics, int style) {
- FontMetricsMapper metric = new SystemFontMetricsMapper(family, style, graphics);
+ Java2DFontMetrics java2DFontMetrics, int style) {
+ FontMetricsMapper metric = new SystemFontMetricsMapper(family, style, java2DFontMetrics);
fontInfo.addMetrics(fontKey, metric);
}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java b/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java
index 544f9c589..fc4af3574 100644
--- a/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java
+++ b/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java
@@ -109,13 +109,13 @@ public class Java2DFontMetrics {
/**
* Temp graphics object needed to get the font metrics
*/
- private Graphics2D graphics;
+ private final Graphics2D graphics;
/**
* Creates a Graphics2D object for the sole purpose of getting font metrics.
* @return a Graphics2D object
*/
- public static Graphics2D createFontMetricsGraphics2D() {
+ private static Graphics2D createFontMetricsGraphics2D() {
BufferedImage fontImage = new BufferedImage(100, 100,
BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = fontImage.createGraphics();
@@ -127,11 +127,9 @@ public class Java2DFontMetrics {
/**
* Constructs a new Font-metrics.
- * @param graphics a temp graphics object - this is needed so
- * that we can get an instance of java.awt.FontMetrics
*/
- public Java2DFontMetrics(Graphics2D graphics) {
- this.graphics = graphics;
+ public Java2DFontMetrics() {
+ this.graphics = createFontMetricsGraphics2D();
}
/**
@@ -142,7 +140,7 @@ public class Java2DFontMetrics {
* @param size font size
* @return ascent in milliponts
*/
- public synchronized int getMaxAscent(String family, int style, int size) {
+ public int getMaxAscent(String family, int style, int size) {
setFont(family, style, size);
return Math.round(lineMetrics.getAscent() * FONT_FACTOR);
}
@@ -155,7 +153,7 @@ public class Java2DFontMetrics {
* @param size font size
* @return ascent in milliponts
*/
- public synchronized int getAscender(String family, int style, int size) {
+ public int getAscender(String family, int style, int size) {
setFont(family, style, size);
return ascender * 1000;
@@ -193,7 +191,7 @@ public class Java2DFontMetrics {
* @param size font size
* @return capital height in millipoints
*/
- public synchronized int getCapHeight(String family, int style, int size) {
+ public int getCapHeight(String family, int style, int size) {
// currently just gets Ascent value but maybe should use
// getMaxAcent() at some stage
return getAscender(family, style, size);
@@ -207,7 +205,7 @@ public class Java2DFontMetrics {
* @param size font size
* @return descent in milliponts
*/
- public synchronized int getDescender(String family, int style, int size) {
+ public int getDescender(String family, int style, int size) {
setFont(family, style, size);
return descender * 1000;
}
@@ -220,7 +218,7 @@ public class Java2DFontMetrics {
* @param size font size
* @return font height in milliponts
*/
- public synchronized int getXHeight(String family, int style, int size) {
+ public int getXHeight(String family, int style, int size) {
setFont(family, style, size);
return xHeight * 1000;
}
@@ -234,7 +232,7 @@ public class Java2DFontMetrics {
* @param size font size
* @return character width in millipoints
*/
- public synchronized int width(int i, String family, int style, int size) {
+ public int width(int i, String family, int style, int size) {
int w;
setFont(family, style, size);
w = internalCharWidth(i) * 1000;
@@ -256,7 +254,7 @@ public class Java2DFontMetrics {
* @param size font size
* @return array of character widths in millipoints
*/
- public synchronized int[] getWidths(String family, int style, int size) {
+ public int[] getWidths(String family, int style, int size) {
int i;
if (width == null) {
@@ -351,7 +349,7 @@ public class Java2DFontMetrics {
* @param size font size
* @return font with the desired characeristics.
*/
- public synchronized java.awt.Font getFont(String family, int style, int size) {
+ public java.awt.Font getFont(String family, int style, int size) {
setFont(family, style, size);
return f1;
/*
@@ -372,7 +370,7 @@ public class Java2DFontMetrics {
* @param c the glyph to check
* @return true if the character is supported
*/
- public synchronized boolean hasChar(String family, int style, int size, char c) {
+ public boolean hasChar(String family, int style, int size, char c) {
setFont(family, style, size);
return f1.canDisplay(c);
}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java
index 5e3073ea6..6eaa66d88 100644
--- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java
+++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java
@@ -171,11 +171,11 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem
//Don't call super.setupFontInfo() here! Java2D needs a special font setup
// create a temp Image to test font metrics on
this.fontInfo = inFontInfo;
- Graphics2D graphics2D = Java2DFontMetrics.createFontMetricsGraphics2D();
+ final Java2DFontMetrics java2DFontMetrics = new Java2DFontMetrics();
FontCollection[] fontCollections = new FontCollection[] {
- new Base14FontCollection(graphics2D),
- new InstalledFontCollection(graphics2D),
+ new Base14FontCollection(java2DFontMetrics),
+ new InstalledFontCollection(java2DFontMetrics),
new ConfiguredFontCollection(getFontResolver(), getFontList())
};
userAgent.getFactory().getFontManager().setup(
diff --git a/src/java/org/apache/fop/render/java2d/Java2DUtil.java b/src/java/org/apache/fop/render/java2d/Java2DUtil.java
index 7e11ab263..961d64ad2 100644
--- a/src/java/org/apache/fop/render/java2d/Java2DUtil.java
+++ b/src/java/org/apache/fop/render/java2d/Java2DUtil.java
@@ -44,12 +44,12 @@ public final class Java2DUtil {
*/
public static FontInfo buildDefaultJava2DBasedFontInfo(
FontInfo fontInfo, FOUserAgent userAgent) {
- Graphics2D graphics2D = Java2DFontMetrics.createFontMetricsGraphics2D();
+ Java2DFontMetrics java2DFontMetrics = new Java2DFontMetrics();
FontManager fontManager = userAgent.getFactory().getFontManager();
FontCollection[] fontCollections = new FontCollection[] {
- new org.apache.fop.render.java2d.Base14FontCollection(graphics2D),
- new InstalledFontCollection(graphics2D)
+ new org.apache.fop.render.java2d.Base14FontCollection(java2DFontMetrics),
+ new InstalledFontCollection(java2DFontMetrics)
};
FontInfo fi = (fontInfo != null ? fontInfo : new FontInfo());
diff --git a/src/java/org/apache/fop/render/java2d/SystemFontMetricsMapper.java b/src/java/org/apache/fop/render/java2d/SystemFontMetricsMapper.java
index afcf088cc..fac436909 100644
--- a/src/java/org/apache/fop/render/java2d/SystemFontMetricsMapper.java
+++ b/src/java/org/apache/fop/render/java2d/SystemFontMetricsMapper.java
@@ -42,7 +42,7 @@ public class SystemFontMetricsMapper extends Typeface implements FontMetricsMapp
* This is a Java2DFontMetrics that does the real calculation.
* It is only one class that dynamically determines the font-size.
*/
- private static Java2DFontMetrics metric = null;
+ private final Java2DFontMetrics java2DFontMetrics;
/**
* The java name of the font.
@@ -60,15 +60,14 @@ public class SystemFontMetricsMapper extends Typeface implements FontMetricsMapp
* Constructs a new Font-metrics.
* @param family the family name of the font (java value)
* @param style the java type style value of the font
- * @param graphics a Graphics2D object - this is needed so
- * that we can get an instance of java.awt.FontMetrics
+ * @param java2DFontMetrics metric calculations delegated to this
*/
- public SystemFontMetricsMapper(String family, int style, Graphics2D graphics) {
+ public SystemFontMetricsMapper(String family, int style, Java2DFontMetrics java2DFontMetrics) {
this.family = family;
+
this.style = style;
- if (metric == null) {
- metric = new Java2DFontMetrics(graphics);
- }
+
+ this.java2DFontMetrics = java2DFontMetrics;
}
/** {@inheritDoc} */
@@ -104,42 +103,42 @@ public class SystemFontMetricsMapper extends Typeface implements FontMetricsMapp
* {@inheritDoc}
*/
public int getMaxAscent(int size) {
- return metric.getMaxAscent(family, style, size);
+ return java2DFontMetrics.getMaxAscent(family, style, size);
}
/**
* {@inheritDoc}
*/
public int getAscender(int size) {
- return metric.getAscender(family, style, size);
+ return java2DFontMetrics.getAscender(family, style, size);
}
/**
* {@inheritDoc}
*/
public int getCapHeight(int size) {
- return metric.getCapHeight(family, style, size);
+ return java2DFontMetrics.getCapHeight(family, style, size);
}
/**
* {@inheritDoc}
*/
public int getDescender(int size) {
- return metric.getDescender(family, style, size);
+ return java2DFontMetrics.getDescender(family, style, size);
}
/**
* {@inheritDoc}
*/
public int getXHeight(int size) {
- return metric.getXHeight(family, style, size);
+ return java2DFontMetrics.getXHeight(family, style, size);
}
/**
* {@inheritDoc}
*/
public int getWidth(int i, int size) {
- return metric.width(i, family, style, size);
+ return java2DFontMetrics.width(i, family, style, size);
}
@@ -147,14 +146,14 @@ public class SystemFontMetricsMapper extends Typeface implements FontMetricsMapp
* {@inheritDoc}
*/
public int[] getWidths() {
- return metric.getWidths(family, style, Java2DFontMetrics.FONT_SIZE);
+ return java2DFontMetrics.getWidths(family, style, Java2DFontMetrics.FONT_SIZE);
}
/**
* {@inheritDoc}
*/
public java.awt.Font getFont(int size) {
- return metric.getFont(family, style, size);
+ return java2DFontMetrics.getFont(family, style, size);
}
/**
@@ -183,7 +182,7 @@ public class SystemFontMetricsMapper extends Typeface implements FontMetricsMapp
/** {@inheritDoc} */
public boolean hasChar(char c) {
- return metric.hasChar(family, style, Java2DFontMetrics.FONT_SIZE, c);
+ return java2DFontMetrics.hasChar(family, style, Java2DFontMetrics.FONT_SIZE, c);
}
}
diff --git a/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java b/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java
index 339880b64..605220ee2 100644
--- a/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java
@@ -108,11 +108,10 @@ public class PCLRendererConfigurator extends PrintRendererConfigurator
throws FOPException {
FontManager fontManager = userAgent.getFactory().getFontManager();
- Graphics2D graphics2D = Java2DFontMetrics.createFontMetricsGraphics2D();
-
- List fontCollections = new java.util.ArrayList();
- fontCollections.add(new Base14FontCollection(graphics2D));
- fontCollections.add(new InstalledFontCollection(graphics2D));
+ final Java2DFontMetrics java2DFontMetrics = new Java2DFontMetrics();
+ final List fontCollections = new java.util.ArrayList();
+ fontCollections.add(new Base14FontCollection(java2DFontMetrics));
+ fontCollections.add(new InstalledFontCollection(java2DFontMetrics));
Configuration cfg = super.getRendererConfig(documentHandler.getMimeType());
if (cfg != null) {
diff --git a/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java
index 841dd7e01..994cbc0de 100644
--- a/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java
+++ b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java
@@ -38,6 +38,36 @@ public interface PDFConfigurationConstants {
String NO_EDIT_CONTENT = "noedit";
/** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */
String NO_ANNOTATIONS = "noannotations";
+
+ /**
+ * PDF encryption parameter: Forbids filling in existing interactive forms, datatype:
+ * Boolean or "true"/"false"
+ */
+ String NO_FILLINFORMS = "nofillinforms";
+
+ /**
+ * PDF encryption parameter: Forbids extracting text and graphics, datatype: Boolean
+ * or "true"/"false"
+ */
+ String NO_ACCESSCONTENT = "noaccesscontent";
+
+ /**
+ * PDF encryption parameter: Forbids assembling document, datatype: Boolean or
+ * "true"/"false"
+ */
+ String NO_ASSEMBLEDOC = "noassembledoc";
+
+ /**
+ * PDF encryption parameter: Forbids printing to high quality, datatype: Boolean or
+ * "true"/"false"
+ */
+ String NO_PRINTHQ = "noprinthq";
+
+ /**
+ * PDF encryption length parameter: must be a multiple of 8 between 40 and 128,
+ * default value 40, datatype: int.
+ */
+ String ENCRYPTION_LENGTH = "encryption-length";
/** Rendering Options key for the PDF/A mode. */
String PDF_A_MODE = "pdf-a-mode";
/** Rendering Options key for the PDF/X mode. */
diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.java b/src/java/org/apache/fop/render/pdf/PDFEventProducer.java
index 1e1ddf98a..40062f73f 100644
--- a/src/java/org/apache/fop/render/pdf/PDFEventProducer.java
+++ b/src/java/org/apache/fop/render/pdf/PDFEventProducer.java
@@ -65,4 +65,14 @@ public interface PDFEventProducer extends EventProducer {
* @event.severity WARN
*/
void nonStandardStructureType(Object source, String fo, String type, String fallback);
+
+ /**
+ * The encryption length must be a multiple of 8 between 40 and 128.
+ *
+ * @param source the event source
+ * @param originalValue requested encryption length
+ * @param correctedValue corrected encryption length
+ * @event.severity WARN
+ */
+ void incorrectEncryptionLength(Object source, int originalValue, int correctedValue);
}
diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml
index 7f3c9d609..bf930ea34 100644
--- a/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml
+++ b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml
@@ -2,4 +2,5 @@
<catalogue xml:lang="en">
<message key="nonFullyResolvedLinkTargets">{count} link target{count,equals,1,,s} could not be fully resolved and now point{count,equals,1,,s} to the top of the page or {count,equals,1,is,are} dysfunctional.</message>
<message key="nonStandardStructureType">‘{type}’ is not a standard structure type defined by the PDF Reference. Falling back to ‘{fallback}’.</message>
+ <message key="incorrectEncryptionLength">Encryption length must be a multiple of 8 between 40 and 128. Setting encryption length to {correctedValue} instead of {originalValue}.</message>
</catalogue>
diff --git a/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java b/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java
index 9ebb1d5a1..dcc7dd32e 100644
--- a/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java
@@ -81,7 +81,7 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
Configuration encryptionParamsConfig
= cfg.getChild(PDFConfigurationConstants.ENCRYPTION_PARAMS, false);
if (encryptionParamsConfig != null) {
- PDFEncryptionParams encryptionParams = new PDFEncryptionParams();
+ PDFEncryptionParams encryptionParams = pdfUtil.getEncryptionParams();
Configuration ownerPasswordConfig = encryptionParamsConfig.getChild(
PDFConfigurationConstants.OWNER_PASSWORD, false);
if (ownerPasswordConfig != null) {
@@ -118,8 +118,35 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
if (noAnnotationsConfig != null) {
encryptionParams.setAllowEditAnnotations(false);
}
- pdfUtil.setEncryptionParams(encryptionParams);
+ Configuration noFillInForms = encryptionParamsConfig.getChild(
+ PDFConfigurationConstants.NO_FILLINFORMS, false);
+ if (noFillInForms != null) {
+ encryptionParams.setAllowFillInForms(false);
+ }
+ Configuration noAccessContentConfig = encryptionParamsConfig.getChild(
+ PDFConfigurationConstants.NO_ACCESSCONTENT, false);
+ if (noAccessContentConfig != null) {
+ encryptionParams.setAllowAccessContent(false);
+ }
+ Configuration noAssembleDocConfig = encryptionParamsConfig.getChild(
+ PDFConfigurationConstants.NO_ASSEMBLEDOC, false);
+ if (noAssembleDocConfig != null) {
+ encryptionParams.setAllowAssembleDocument(false);
+ }
+ Configuration noPrintHqConfig = encryptionParamsConfig.getChild(
+ PDFConfigurationConstants.NO_PRINTHQ, false);
+ if (noPrintHqConfig != null) {
+ encryptionParams.setAllowPrintHq(false);
+ }
+ Configuration encryptionLengthConfig = encryptionParamsConfig.getChild(
+ PDFConfigurationConstants.ENCRYPTION_LENGTH, false);
+ if (encryptionLengthConfig != null) {
+ int encryptionLength = checkEncryptionLength(
+ Integer.parseInt(encryptionLengthConfig.getValue(null)));
+ encryptionParams.setEncryptionLengthInBits(encryptionLength);
+ }
}
+
s = cfg.getChild(PDFConfigurationConstants.KEY_OUTPUT_PROFILE, true).getValue(null);
if (s != null) {
pdfUtil.setOutputProfileURI(s);
@@ -132,6 +159,22 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
}
}
+ private int checkEncryptionLength(int encryptionLength) {
+ int correctEncryptionLength = encryptionLength;
+ if (encryptionLength < 40) {
+ correctEncryptionLength = 40;
+ } else if (encryptionLength > 128) {
+ correctEncryptionLength = 128;
+ } else if (encryptionLength % 8 != 0) {
+ correctEncryptionLength = ((int) Math.round(encryptionLength / 8.0f)) * 8;
+ }
+ if (correctEncryptionLength != encryptionLength) {
+ PDFEventProducer.Provider.get(userAgent.getEventBroadcaster())
+ .incorrectEncryptionLength(this, encryptionLength, correctEncryptionLength);
+ }
+ return correctEncryptionLength;
+ }
+
/**
* Builds a filter map from an Avalon Configuration object.
*
diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
index e63059472..c662b0345 100644
--- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
+++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
@@ -124,49 +124,45 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
if (params != null) {
this.encryptionParams = params; //overwrite if available
}
- String pwd;
- pwd = (String)userAgent.getRendererOptions().get(USER_PASSWORD);
- if (pwd != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setUserPassword(pwd);
+ String userPassword = (String)userAgent.getRendererOptions().get(USER_PASSWORD);
+ if (userPassword != null) {
+ getEncryptionParams().setUserPassword(userPassword);
}
- pwd = (String)userAgent.getRendererOptions().get(OWNER_PASSWORD);
- if (pwd != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setOwnerPassword(pwd);
+ String ownerPassword = (String)userAgent.getRendererOptions().get(OWNER_PASSWORD);
+ if (ownerPassword != null) {
+ getEncryptionParams().setOwnerPassword(ownerPassword);
}
- Object setting;
- setting = userAgent.getRendererOptions().get(NO_PRINT);
- if (setting != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setAllowPrint(!booleanValueOf(setting));
+ Object noPrint = userAgent.getRendererOptions().get(NO_PRINT);
+ if (noPrint != null) {
+ getEncryptionParams().setAllowPrint(!booleanValueOf(noPrint));
}
- setting = userAgent.getRendererOptions().get(NO_COPY_CONTENT);
- if (setting != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setAllowCopyContent(!booleanValueOf(setting));
+ Object noCopyContent = userAgent.getRendererOptions().get(NO_COPY_CONTENT);
+ if (noCopyContent != null) {
+ getEncryptionParams().setAllowCopyContent(!booleanValueOf(noCopyContent));
}
- setting = userAgent.getRendererOptions().get(NO_EDIT_CONTENT);
- if (setting != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setAllowEditContent(!booleanValueOf(setting));
+ Object noEditContent = userAgent.getRendererOptions().get(NO_EDIT_CONTENT);
+ if (noEditContent != null) {
+ getEncryptionParams().setAllowEditContent(!booleanValueOf(noEditContent));
}
- setting = userAgent.getRendererOptions().get(NO_ANNOTATIONS);
- if (setting != null) {
- if (encryptionParams == null) {
- this.encryptionParams = new PDFEncryptionParams();
- }
- this.encryptionParams.setAllowEditAnnotations(!booleanValueOf(setting));
+ Object noAnnotations = userAgent.getRendererOptions().get(NO_ANNOTATIONS);
+ if (noAnnotations != null) {
+ getEncryptionParams().setAllowEditAnnotations(!booleanValueOf(noAnnotations));
+ }
+ Object noFillInForms = userAgent.getRendererOptions().get(NO_FILLINFORMS);
+ if (noFillInForms != null) {
+ getEncryptionParams().setAllowFillInForms(!booleanValueOf(noFillInForms));
+ }
+ Object noAccessContent = userAgent.getRendererOptions().get(NO_ACCESSCONTENT);
+ if (noAccessContent != null) {
+ getEncryptionParams().setAllowAccessContent(!booleanValueOf(noAccessContent));
+ }
+ Object noAssembleDoc = userAgent.getRendererOptions().get(NO_ASSEMBLEDOC);
+ if (noAssembleDoc != null) {
+ getEncryptionParams().setAllowAssembleDocument(!booleanValueOf(noAssembleDoc));
+ }
+ Object noPrintHQ = userAgent.getRendererOptions().get(NO_PRINTHQ);
+ if (noPrintHQ != null) {
+ getEncryptionParams().setAllowPrintHq(!booleanValueOf(noPrintHQ));
}
String s = (String)userAgent.getRendererOptions().get(PDF_A_MODE);
if (s != null) {
@@ -184,9 +180,10 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
if (s != null) {
this.outputProfileURI = s;
}
- setting = userAgent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE);
- if (setting != null) {
- this.disableSRGBColorSpace = booleanValueOf(setting);
+ Object disableSRGBColorSpace = userAgent.getRendererOptions().get(
+ KEY_DISABLE_SRGB_COLORSPACE);
+ if (disableSRGBColorSpace != null) {
+ this.disableSRGBColorSpace = booleanValueOf(disableSRGBColorSpace);
}
}
@@ -236,11 +233,14 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
}
/**
- * Sets the encryption parameters used by the PDF renderer.
- * @param encryptionParams the encryption parameters
+ * Gets the encryption parameters used by the PDF renderer.
+ * @return encryptionParams the encryption parameters
*/
- public void setEncryptionParams(PDFEncryptionParams encryptionParams) {
- this.encryptionParams = encryptionParams;
+ PDFEncryptionParams getEncryptionParams() {
+ if (this.encryptionParams == null) {
+ this.encryptionParams = new PDFEncryptionParams();
+ }
+ return this.encryptionParams;
}
private void updateInfo() {
diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/ps/PSImageHandlerGraphics2D.java
index b2934d4dd..9bbd3e62b 100644
--- a/src/java/org/apache/fop/render/ps/PSImageHandlerGraphics2D.java
+++ b/src/java/org/apache/fop/render/ps/PSImageHandlerGraphics2D.java
@@ -26,6 +26,7 @@ import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
+import org.apache.fop.render.RenderingContext;
import org.apache.xmlgraphics.image.loader.Image;
import org.apache.xmlgraphics.image.loader.ImageFlavor;
import org.apache.xmlgraphics.image.loader.ImageInfo;
@@ -36,8 +37,6 @@ import org.apache.xmlgraphics.ps.FormGenerator;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSProcSets;
-import org.apache.fop.render.RenderingContext;
-
/**
* Image handler implementation which handles vector graphics (Java2D) for PostScript output.
*/
@@ -97,34 +96,14 @@ public class PSImageHandlerGraphics2D implements PSImageHandler {
}
/** {@inheritDoc} */
- public void generateForm(RenderingContext context, Image image, PSImageFormResource form)
+ public void generateForm(RenderingContext context, Image image, final PSImageFormResource form)
throws IOException {
PSRenderingContext psContext = (PSRenderingContext)context;
PSGenerator gen = psContext.getGenerator();
final ImageGraphics2D imageG2D = (ImageGraphics2D)image;
ImageInfo info = image.getInfo();
- String imageDescription = info.getMimeType() + " " + info.getOriginalURI();
- final Dimension2D dimensionsPt = info.getSize().getDimensionPt();
- final Dimension2D dimensionsMpt = info.getSize().getDimensionMpt();
-
- FormGenerator formGen = new FormGenerator(
- form.getName(), imageDescription, dimensionsPt) {
- protected void generatePaintProc(PSGenerator gen)
- throws IOException {
- gen.getResourceTracker().notifyResourceUsageOnPage(
- PSProcSets.EPS_PROCSET);
- gen.writeln("BeginEPSF");
- PSGraphics2DAdapter adapter = new PSGraphics2DAdapter(gen, false);
- adapter.paintImage(imageG2D.getGraphics2DImagePainter(),
- null,
- 0, 0,
- (int)Math.round(dimensionsMpt.getWidth()),
- (int)Math.round(dimensionsMpt.getHeight()));
- gen.writeln("EndEPSF");
- }
-
- };
+ FormGenerator formGen = buildFormGenerator(gen.getPSLevel(), form, info, imageG2D);
formGen.generate(gen);
}
/** {@inheritDoc} */
@@ -150,4 +129,70 @@ public class PSImageHandlerGraphics2D implements PSImageHandler {
return false;
}
+ private FormGenerator buildFormGenerator(int psLanguageLevel, final PSImageFormResource form,
+ final ImageInfo info, final ImageGraphics2D imageG2D) {
+ String imageDescription = info.getMimeType() + " " + info.getOriginalURI();
+ final Dimension2D dimensionsPt = info.getSize().getDimensionPt();
+ final Dimension2D dimensionsMpt = info.getSize().getDimensionMpt();
+ FormGenerator formGen;
+
+ if (psLanguageLevel <= 2) {
+ formGen = new EPSFormGenerator(form.getName(), imageDescription, dimensionsPt) {
+
+ @Override
+ void doGeneratePaintProc(PSGenerator gen) throws IOException {
+ paintImageG2D(imageG2D, dimensionsMpt, gen);
+ }
+ };
+ } else {
+ formGen = new EPSFormGenerator(form.getName(), imageDescription, dimensionsPt) {
+
+ @Override
+ protected void generateAdditionalDataStream(PSGenerator gen) throws IOException {
+ gen.writeln("/" + form.getName() + ":Data currentfile <<");
+ gen.writeln(" /Filter /SubFileDecode");
+ gen.writeln(" /DecodeParms << /EODCount 0 /EODString (%FOPEndOfData) >>");
+ gen.writeln(">> /ReusableStreamDecode filter");
+ paintImageG2D(imageG2D, dimensionsMpt, gen);
+ gen.writeln("%FOPEndOfData");
+ gen.writeln("def");
+ }
+
+ @Override
+ void doGeneratePaintProc(PSGenerator gen) throws IOException {
+ gen.writeln(form.getName() + ":Data 0 setfileposition");
+ gen.writeln(form.getName() + ":Data cvx exec");
+ }
+ };
+ }
+ return formGen;
+ }
+
+ private abstract static class EPSFormGenerator extends FormGenerator {
+
+ EPSFormGenerator(String formName, String title, Dimension2D dimensions) {
+ super(formName, title, dimensions);
+ }
+
+ protected void paintImageG2D(final ImageGraphics2D imageG2D, Dimension2D dimensionsMpt,
+ PSGenerator gen) throws IOException {
+ PSGraphics2DAdapter adapter = new PSGraphics2DAdapter(gen, false);
+ adapter.paintImage(imageG2D.getGraphics2DImagePainter(),
+ null,
+ 0, 0,
+ (int) Math.round(dimensionsMpt.getWidth()),
+ (int) Math.round(dimensionsMpt.getHeight()));
+ }
+
+ @Override
+ protected final void generatePaintProc(PSGenerator gen) throws IOException {
+ gen.getResourceTracker().notifyResourceUsageOnPage(
+ PSProcSets.EPS_PROCSET);
+ gen.writeln("BeginEPSF");
+ doGeneratePaintProc(gen);
+ gen.writeln("EndEPSF");
+ }
+
+ abstract void doGeneratePaintProc(PSGenerator gen) throws IOException;
+ }
}
diff --git a/src/java/org/apache/fop/render/rtf/RTFHandler.java b/src/java/org/apache/fop/render/rtf/RTFHandler.java
index ac060caff..56bc0287f 100644
--- a/src/java/org/apache/fop/render/rtf/RTFHandler.java
+++ b/src/java/org/apache/fop/render/rtf/RTFHandler.java
@@ -132,12 +132,6 @@ import org.apache.fop.render.rtf.rtflib.tools.TableContext;
/**
* RTF Handler: generates RTF output using the structure events from
* the FO Tree sent to this structure handler.
- *
- * @author Bertrand Delacretaz <bdelacretaz@codeconsult.ch>
- * @author Trembicki-Guy, Ed <GuyE@DNB.com>
- * @author Boris Poudérous <boris.pouderous@eads-telecom.com>
- * @author Peter Herweg <pherweg@web.de>
- * @author Andreas Putz <a.putz@skynamics.com>
*/
public class RTFHandler extends FOEventHandler {
diff --git a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
index 01ef22efa..c079a80e3 100644
--- a/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
+++ b/src/java/org/apache/fop/svg/AbstractFOPTranscoder.java
@@ -269,7 +269,6 @@ public abstract class AbstractFOPTranscoder extends SVGAbstractTranscoder implem
}
public Source resolveURI(String uri) {
- System.out.println("resolve " + uri);
try {
ParsedURL url = new ParsedURL(baseURI, uri);
InputStream in = url.openStream();
diff --git a/status.xml b/status.xml
index f234edfb5..24a7d3fce 100644
--- a/status.xml
+++ b/status.xml
@@ -60,6 +60,43 @@
documents. Example: the fix of marks layering will be such a case when it's done.
-->
<release version="FOP Trunk" date="TBD">
+ <action context="Fonts" dev="PH" type="fix" fixes-bug="48696">
+ Bugfix for color model in IOCA IDE structure parameter for 4- and 8-bit grayscale images.
+ Revision 4.
+ </action>
+ <action context="Fonts" dev="PH" type="fix" fixes-bug="51760" due-to="Mehdi Houshmand">
+ Changes the way PostScript handles Graphics2D images such that if the language is set to
+ level 3, the image is stored as an embedded file which has no length limit. Previously it
+ was stored as an array which has a implementation limit of 65535 elements.
+ </action>
+ <action context="Fonts" dev="PH" type="fix" fixes-bug="51759" due-to="Mehdi Houshmand">
+ PDFFactory responsible for asdigning name to a subset font.
+ </action>
+ <action context="Fonts" dev="PH" type="fix" fixes-bug="51530" due-to="Mehdi Houshmand">
+ Improved support for EBCDIC encoded double byte fonts fo AFP.
+ </action>
+ <action context="Fonts" dev="PH" type="fix" fixes-bug="51205" due-to="Mehdi Houshmand">
+ Corrected typographical errors in AFPBase12FontCollection.
+ </action>
+ <action context="Renderers" dev="PH" type="fix" fixes-bug="48062">
+ Improved fix of a bug relating to PCL painter thread safetly. Previous fix in rev 895012
+ worked by synchronizing methods of a static instance of Java2DFontMetrics. This fix uses a
+ unique instance for per thread.
+ </action>
+ <action context="Renderers" dev="PH" type="fix">
+ Fixed a bug in AFP where an ArrayOutofBoundsException is throwqn when embedding a Page
+ Segment.
+ </action>
+ <action context="Renderers" dev="VH" type="add">
+ Added support for 128bit encryption in PDF output. Based on work by Michael Rubin.
+ </action>
+ <action context="Renderers" dev="PH" type="fix">
+ Fixed a bug in AFP where the object area axes of an Include Object was incorrectly set when
+ rotated by 180. </action>
+ <action context="Fonts" dev="JM" type="fix" fixes-bug="51596" due-to="Mehdi Houshmand">
+ Fixed a bug in TTF subsetting where a composite glyph could get
+ remapped more than once resulting in garbled character.
+ </action>
<action context="Fonts" dev="JM" type="fix" fixes-bug="50605">
Fixed a number of bugs concerning Type 1 and other single-byte fonts
(glyph width mismatches and overlapping characters).
diff --git a/test/java/org/apache/fop/StandardTestSuite.java b/test/java/org/apache/fop/StandardTestSuite.java
index d6a6f8367..a49cc7f2e 100644
--- a/test/java/org/apache/fop/StandardTestSuite.java
+++ b/test/java/org/apache/fop/StandardTestSuite.java
@@ -22,13 +22,14 @@ package org.apache.fop;
import junit.framework.Test;
import junit.framework.TestSuite;
-import org.apache.fop.area.ViewportTestSuite;
+import org.apache.fop.afp.fonts.CharactersetEncoderTest;
import org.apache.fop.afp.parser.MODCAParserTestCase;
+import org.apache.fop.area.ViewportTestSuite;
import org.apache.fop.fonts.DejaVuLGCSerifTest;
+import org.apache.fop.fonts.truetype.GlyfTableTestCase;
import org.apache.fop.image.loader.batik.ImageLoaderTestCase;
import org.apache.fop.image.loader.batik.ImagePreloaderTestCase;
import org.apache.fop.intermediate.IFMimickingTestCase;
-import org.apache.fop.render.afp.AFPTestSuite;
import org.apache.fop.render.extensions.prepress.PageBoundariesTest;
import org.apache.fop.render.extensions.prepress.PageScaleTest;
import org.apache.fop.render.pdf.PDFAConformanceTestCase;
@@ -53,14 +54,17 @@ public class StandardTestSuite {
//$JUnit-BEGIN$
suite.addTest(BasicDriverTestSuite.suite());
suite.addTest(UtilityCodeTestSuite.suite());
+ suite.addTest(org.apache.fop.afp.AFPTestSuite.suite());
suite.addTest(new TestSuite(PDFAConformanceTestCase.class));
suite.addTest(new TestSuite(PDFEncodingTestCase.class));
suite.addTest(new TestSuite(PDFCMapTestCase.class));
suite.addTest(new TestSuite(PDFsRGBSettingsTestCase.class));
suite.addTest(new TestSuite(DejaVuLGCSerifTest.class));
suite.addTest(new TestSuite(MODCAParserTestCase.class));
- suite.addTest(AFPTestSuite.suite());
+ suite.addTest(new TestSuite(CharactersetEncoderTest.class));
+ suite.addTest(org.apache.fop.render.afp.AFPTestSuite.suite());
suite.addTest(PSTestSuite.suite());
+ suite.addTest(new TestSuite(GlyfTableTestCase.class));
suite.addTest(RichTextFormatTestSuite.suite());
suite.addTest(new TestSuite(ImageLoaderTestCase.class));
suite.addTest(new TestSuite(ImagePreloaderTestCase.class));
diff --git a/test/java/org/apache/fop/UtilityCodeTestSuite.java b/test/java/org/apache/fop/UtilityCodeTestSuite.java
index 004b8f3c3..ac0ea00ad 100644
--- a/test/java/org/apache/fop/UtilityCodeTestSuite.java
+++ b/test/java/org/apache/fop/UtilityCodeTestSuite.java
@@ -23,6 +23,9 @@ import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.fop.events.BasicEventTestCase;
+import org.apache.fop.pdf.FileIDGeneratorTestCase;
+import org.apache.fop.pdf.PDFEncryptionJCETestCase;
+import org.apache.fop.pdf.PDFFactoryTestCase;
import org.apache.fop.pdf.PDFObjectTestCase;
import org.apache.fop.traits.BorderPropsTestCase;
import org.apache.fop.util.BitmapImageUtilTestCase;
@@ -46,6 +49,8 @@ public class UtilityCodeTestSuite {
//$JUnit-BEGIN$
suite.addTest(new TestSuite(PDFNumberTestCase.class));
suite.addTest(new TestSuite(PDFObjectTestCase.class));
+ suite.addTest(FileIDGeneratorTestCase.suite());
+ suite.addTest(new TestSuite(PDFFactoryTestCase.class));
suite.addTest(new TestSuite(ColorUtilTestCase.class));
suite.addTest(new TestSuite(BorderPropsTestCase.class));
suite.addTest(new TestSuite(ElementListUtilsTestCase.class));
@@ -53,6 +58,7 @@ public class UtilityCodeTestSuite {
suite.addTest(new TestSuite(XMLResourceBundleTestCase.class));
suite.addTest(new TestSuite(URIResolutionTestCase.class));
suite.addTest(new TestSuite(BitmapImageUtilTestCase.class));
+ suite.addTest(new TestSuite(PDFEncryptionJCETestCase.class));
//$JUnit-END$
return suite;
}
diff --git a/test/java/org/apache/fop/afp/AFPResourceUtilTestCase.java b/test/java/org/apache/fop/afp/AFPResourceUtilTestCase.java
new file mode 100644
index 000000000..da3e023a4
--- /dev/null
+++ b/test/java/org/apache/fop/afp/AFPResourceUtilTestCase.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.afp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.fop.afp.util.AFPResourceUtil;
+
+/**
+ * Tests the {@link AFPResourceUtil} class.
+ */
+public class AFPResourceUtilTestCase extends TestCase {
+
+ private static final String RESOURCE_FILENAME = "expected_resource.afp";
+
+ private static final String NAMED_RESOURCE_FILENAME = "expected_named_resource.afp";
+
+ private static final String PSEG = "XFEATHER";
+
+ /**
+ * Tests copyResourceFile()
+ * @throws Exception -
+ */
+ public void testCopyResourceFile() throws Exception {
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ InputStream in = null;
+
+ try {
+ in = getClass().getResourceAsStream(RESOURCE_FILENAME);
+ AFPResourceUtil.copyResourceFile(in, baos);
+ } finally {
+ in.close();
+ }
+
+ byte[] expectedBytes = null;
+
+ try {
+ in = getClass().getResourceAsStream(RESOURCE_FILENAME);
+ expectedBytes = IOUtils.toByteArray(in);
+ } finally {
+ in.close();
+ }
+
+ assertTrue(Arrays.equals(expectedBytes, baos.toByteArray()));
+
+ }
+
+ /**
+ * Tests copyNamedResource()
+ * @throws Exception -
+ */
+ public void testCopyNamedResource() throws Exception {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ InputStream in = null;
+
+ try {
+ in = getClass().getResourceAsStream(RESOURCE_FILENAME);
+ AFPResourceUtil.copyNamedResource(PSEG, in, baos);
+ } finally {
+ in.close();
+ }
+
+ byte[] expectedBytes = null;
+
+ try {
+ in = getClass().getResourceAsStream(NAMED_RESOURCE_FILENAME);
+ expectedBytes = IOUtils.toByteArray(in);
+ } finally {
+ in.close();
+ }
+
+ assertTrue(Arrays.equals(expectedBytes, baos.toByteArray()));
+ }
+}
diff --git a/test/java/org/apache/fop/afp/AFPTestSuite.java b/test/java/org/apache/fop/afp/AFPTestSuite.java
new file mode 100644
index 000000000..cb57a92ed
--- /dev/null
+++ b/test/java/org/apache/fop/afp/AFPTestSuite.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.afp;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.apache.fop.afp.modca.AbstractStructuredObjectTestCase;
+import org.apache.fop.afp.modca.AbstractTripletStructuredObjectTestCase;
+import org.apache.fop.afp.modca.IncludeObjectTestCase;
+
+/**
+ * Test suite for FOP's AFP classes.
+ */
+public class AFPTestSuite {
+ /**
+ * Builds the test suite
+ * @return the test suite
+ */
+ public static Test suite() {
+ TestSuite suite = new TestSuite("Test suite for FOP's AFP classes");
+ //$JUnit-BEGIN$
+ suite.addTest(new TestSuite(IncludeObjectTestCase.class));
+ suite.addTest(new TestSuite(AFPResourceUtilTestCase.class));
+ //$JUnit-END$
+ return suite;
+ }
+}
diff --git a/test/java/org/apache/fop/afp/expected_named_resource.afp b/test/java/org/apache/fop/afp/expected_named_resource.afp
new file mode 100644
index 000000000..9fe45c388
--- /dev/null
+++ b/test/java/org/apache/fop/afp/expected_named_resource.afp
Binary files differ
diff --git a/test/java/org/apache/fop/afp/expected_resource.afp b/test/java/org/apache/fop/afp/expected_resource.afp
new file mode 100644
index 000000000..a98ac0e5e
--- /dev/null
+++ b/test/java/org/apache/fop/afp/expected_resource.afp
Binary files differ
diff --git a/test/java/org/apache/fop/afp/fonts/CharactersetEncoderTest.java b/test/java/org/apache/fop/afp/fonts/CharactersetEncoderTest.java
new file mode 100644
index 000000000..5999a04d6
--- /dev/null
+++ b/test/java/org/apache/fop/afp/fonts/CharactersetEncoderTest.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.afp.fonts;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.CharacterCodingException;
+
+import junit.framework.TestCase;
+
+/**
+ * Test {@link CharactersetEncoder}
+ */
+public class CharactersetEncoderTest extends TestCase {
+ private CharactersetEncoder singlebyteEncoder;
+ private CharactersetEncoder doublebyteEncoder;
+
+ public void setUp() {
+ singlebyteEncoder = CharactersetEncoder.newInstance("cp500", false);
+ doublebyteEncoder = CharactersetEncoder.newInstance("cp937", true);
+ }
+
+ // This is just an arbitrary CJK string
+ private final String testCJKText = "\u8ACB\u65BC\u627F\u505A\u65E5\u4E03\u65E5\u5167\u672A\u9054"
+ + "\u4E03\u65E5\u4E4B\u5B9A\u5B58\u8005\u4EE5\u5BE6\u969B\u5230\u671F\u65E5\u5167\u78BA"
+ + "\u8A8D\u672C\u4EA4\u6613\u5167\u5BB9\u3002\u5982\u672A\u65BC\u4E0A\u8FF0\u671F\u9593"
+ + "\u5167\u63D0\u51FA\u7570\u8B70\uFF0C\u8996\u540C\u610F\u627F\u8A8D\u672C\u4EA4\u6613"
+ + "\u3002";
+
+ private final byte[] test6CJKChars = {
+ (byte) 0x61, (byte) 0x99,
+ (byte) 0x50, (byte) 0xf4,
+ (byte) 0x50, (byte) 0xd4,
+ (byte) 0x56, (byte) 0x99,
+ (byte) 0x4c, (byte) 0xc9,
+ (byte) 0x4c, (byte) 0x44 };
+
+ private final String testEngText = "Hello World!";
+ private final byte[] testEngChars = {
+ (byte) 0xc8, // H
+ (byte) 0x85, // e
+ (byte) 0x93, // l
+ (byte) 0x93, // l
+ (byte) 0x96, // o
+ (byte) 0x40, // " "
+ (byte) 0xe6, // W
+ (byte) 0x96, // o
+ (byte) 0x99, // r
+ (byte) 0x93, // l
+ (byte) 0x84, // d
+ (byte) 0x4f // !
+ };
+
+ /**
+ * Tests canEncode() - tests that canEncode() responds properly to various input characters.
+ */
+ public void testCanEncode() {
+ // Both SBCS and DBCS should support Latin characters
+ for (char c = '!'; c < '~'; c++) {
+ assertTrue(singlebyteEncoder.canEncode(c));
+ assertTrue(doublebyteEncoder.canEncode(c));
+ }
+ // ONLY the double byte characters can handle CJK text
+ for (char c : testCJKText.toCharArray()) {
+ assertFalse(singlebyteEncoder.canEncode(c));
+ assertTrue(doublebyteEncoder.canEncode(c));
+ }
+ // Ensure that double byte encoder doesn't just return true all the time...
+ assertFalse(doublebyteEncoder.canEncode('\u00BB'));
+ }
+
+ public void testEncode() throws CharacterCodingException, IOException {
+ CharactersetEncoder.EncodedChars encChars;// = doublebyteEncoder.encode(testCJKText);
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ // JAVA 1.5 has a bug in the JVM in which these err for some reason... JAVA 1.6 no issues
+ /*encChars.writeTo(bOut, 0, encChars.getLength());
+ byte[] bytes = bOut.toByteArray();
+ for (int i = 0; i < 12; i++) {
+ assertEquals(test6CJKChars[i], bytes[i]);
+ }
+ bOut.reset();*/
+
+ encChars = singlebyteEncoder.encode(testEngText);
+ encChars.writeTo(bOut, 0, encChars.getLength());
+ byte[] engBytes = bOut.toByteArray();
+ for (int i = 0; i < testEngChars.length; i++) {
+ assertEquals(testEngChars[i], engBytes[i]);
+ }
+ assertEquals(testEngChars.length, engBytes.length);
+ }
+}
diff --git a/test/java/org/apache/fop/afp/modca/AbstractAFPObjectTestCase.java b/test/java/org/apache/fop/afp/modca/AbstractAFPObjectTestCase.java
new file mode 100644
index 000000000..815939da4
--- /dev/null
+++ b/test/java/org/apache/fop/afp/modca/AbstractAFPObjectTestCase.java
@@ -0,0 +1,245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.afp.modca;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.fop.afp.Streamable;
+
+/**
+ * Tests the {@link AbstractAFPObject} class.
+ */
+public abstract class AbstractAFPObjectTestCase<S extends AbstractAFPObject>
+ extends TestCase {
+
+ private S sut;
+
+ protected final S getSut() {
+ return sut;
+ }
+
+ protected final void setSut(S sut) {
+ if ( this.sut == null) {
+ this.sut = sut;
+ }
+ }
+
+
+ private byte[] header = new byte[] {
+ 0x5A, // Structured field identifier
+ 0x00, // Length byte 1
+ 0x10, // Length byte 2
+ 0x00, // Structured field id byte 1
+ 0x00, // Structured field id byte 2
+ 0x00, // Structured field id byte 3
+ 0x00, // Flags
+ 0x00, // Reserved
+ 0x00 // Reserved
+ };
+
+
+ public void testCopySFStatic() {
+ byte[] actual = new byte[9];
+ Arrays.fill(actual, (byte)-1);
+
+ S.copySF(actual, (byte)0, (byte)0, (byte)0);
+
+ assertTrue(Arrays.equals(actual, header));
+
+ byte[] expected2 = new byte[9];
+ System.arraycopy(header, 0, expected2, 0, header.length);
+
+ final byte clazz = (byte) 0x01;
+ final byte type = (byte) 0x02;
+ final byte catagory = (byte) 0x03;
+ expected2[3] = clazz;
+ expected2[4] = type;
+ expected2[5] = catagory;
+
+ AbstractAFPObject.copySF(actual, clazz, type, catagory);
+
+ assertTrue(Arrays.equals(actual, expected2));
+ }
+
+ public void testCopySF() {
+ byte[] expected = new byte[9];
+ S.copySF(expected, (byte) 0xD3, (byte)0, (byte)0);
+
+ byte[] actual = new byte[9];
+ Arrays.fill(actual, (byte)-1);
+
+ getSut().copySF(actual, (byte)0, (byte)0);
+
+ assertTrue(Arrays.equals(actual, expected));
+
+ byte[] expected2 = new byte[9];
+ System.arraycopy(expected, 0, expected2, 0, expected.length);
+
+ final byte type = (byte)1;
+ final byte catagory = (byte)2;
+ expected2[4] = type;
+ expected2[5] = catagory;
+
+ getSut().copySF(actual, type, catagory);
+
+ assertTrue(Arrays.equals(actual, expected2));
+ }
+
+ /**
+ *
+ */
+ public void testwriteObjects() {
+ final byte[][] expected = {{(byte)0, (byte)1}, {(byte)2, (byte)3}, {(byte)4, (byte)5}};
+
+ List<Streamable> objects = new ArrayList<Streamable>() {
+ {
+ add(StreamableObject.instance(expected[0]));
+ add(StreamableObject.instance(expected[1]));
+ add(StreamableObject.instance(expected[2]));
+ } };
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ try {
+ getSut().writeObjects(objects, baos);
+ } catch (IOException e) {
+ fail();
+ }
+
+ byte[] actual = baos.toByteArray();
+
+ int index = 0;
+ for (int i = 0; i < expected.length; i++) {
+ for (int j = 0; j < expected[i].length; j++) {
+ assertTrue("" + index, actual[index] == expected[i][j]);
+ index++;
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ public void testTruncate() {
+ String expected = "abc";
+ assertTrue(AbstractAFPObject.truncate(expected, 4) == expected);
+ assertTrue(AbstractAFPObject.truncate(expected, 3) == expected);
+ assertEquals(AbstractAFPObject.truncate(expected + "d", 3), expected);
+ assertEquals(AbstractAFPObject.truncate(expected, 0), "");
+ try {
+ assertTrue(AbstractAFPObject.truncate(null, 4) == null);
+ fail();
+ } catch (NullPointerException e) {
+ // PASS
+ }
+ }
+
+ /**
+ *
+ */
+ public void testWriteChunksToStream() throws IOException {
+ final byte[] data = new byte[256];
+ int counter = 0;
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (byte) counter++;
+ }
+
+ byte[] header = new byte[9];
+ // Test when chunk size % data.length == 0
+ testWithGivenChunkSize(data, header, 16);
+
+ // test when chunk size % data.length != 0
+ testWithGivenChunkSize(data, header, 10);
+
+ // test with an odd number...
+ testWithGivenChunkSize(data, header, 13);
+ }
+
+ private void testWithGivenChunkSize(byte[] data, byte[] header, int chunkSize)
+ throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ S.writeChunksToStream(data, header, 0, chunkSize, baos);
+ byte[] testData = baos.toByteArray();
+
+ int numberOfFullDataChunks = data.length / chunkSize;
+ int lastChunkSize = data.length % chunkSize;
+ int lengthOfTestData = numberOfFullDataChunks * (chunkSize + header.length);
+ lengthOfTestData += lastChunkSize == 0 ? 0 : header.length + lastChunkSize;
+
+ putLengthInHeader(header, chunkSize);
+
+ assertEquals(lengthOfTestData, testData.length);
+ int testIndex = 0;
+ int expectedIndex = 0;
+ for (int i = 0; i < numberOfFullDataChunks; i++) {
+ checkHeaderAndData(header, data, testData, expectedIndex, testIndex, chunkSize);
+ expectedIndex += chunkSize + header.length;
+ testIndex += chunkSize;
+ }
+
+ putLengthInHeader(header, lastChunkSize);
+ // check last chunk
+ if (lastChunkSize != 0) {
+ checkHeaderAndData(header, data, testData, expectedIndex, testIndex, lastChunkSize);
+ }
+ }
+
+ private void putLengthInHeader(byte[] header, int chunkSize) {
+ header[0] = 0;
+ header[1] = (byte) (chunkSize + header.length);
+ }
+
+ private void checkHeaderAndData(byte[] header, byte[] data, byte[] testData, int expectedIndex,
+ int testIndex, int chunkSize) {
+ for (int i = 0; i < header.length; i++) {
+ assertEquals(testData[expectedIndex++], header[i]);
+ }
+ for (int i = 0; i < chunkSize; i++) {
+ assertEquals(testData[expectedIndex++], data[i + testIndex]);
+ }
+ }
+
+ /**
+ *
+ */
+ private static class StreamableObject implements Streamable {
+ private byte[] bytes;
+
+ StreamableObject(byte[] bytes) {
+ this.bytes = new byte[bytes.length];
+ System.arraycopy(bytes, 0, this.bytes, 0, bytes.length);
+ }
+
+ private static Streamable instance(byte[] bytes) {
+ return new StreamableObject(bytes);
+ }
+
+ public void writeToStream(OutputStream os) throws IOException {
+ os.write(bytes);
+ }
+ }
+}
diff --git a/test/java/org/apache/fop/afp/modca/AbstractNamedAFPObjectTestCase.java b/test/java/org/apache/fop/afp/modca/AbstractNamedAFPObjectTestCase.java
new file mode 100644
index 000000000..975c26994
--- /dev/null
+++ b/test/java/org/apache/fop/afp/modca/AbstractNamedAFPObjectTestCase.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.afp.modca;
+
+import java.util.Arrays;
+
+/**
+ * Tests the {@linkplain AbstractAFPObject} class.
+ */
+public abstract class AbstractNamedAFPObjectTestCase<S extends AbstractNamedAFPObject>
+ extends AbstractAFPObjectTestCase<S> {
+
+ public void testCopySF() {
+
+ final S sut = getSut();
+
+ byte[] expected = new byte[17];
+ S.copySF(expected, (byte) 0xD3, (byte)0, (byte)0);
+
+ byte[] nameData = sut.getNameBytes();
+ System.arraycopy(nameData, 0, expected, 9, nameData.length);
+
+ byte[] actual = new byte[17];
+ Arrays.fill(actual, (byte)-1);
+
+ getSut().copySF(actual, (byte)0, (byte)0);
+
+ assertTrue(Arrays.equals(actual, expected));
+
+ byte[] expected2 = new byte[17];
+ System.arraycopy(expected, 0, expected2, 0, expected.length);
+ System.arraycopy(nameData, 0, expected, 9, nameData.length);
+
+ final byte type = (byte)1;
+ final byte catagory = (byte)2;
+ expected2[4] = type;
+ expected2[5] = catagory;
+
+ getSut().copySF(actual, type, catagory);
+
+ assertTrue(Arrays.equals(actual, expected2));
+ }
+}
diff --git a/test/java/org/apache/fop/afp/modca/AbstractStructuredObjectTestCase.java b/test/java/org/apache/fop/afp/modca/AbstractStructuredObjectTestCase.java
new file mode 100644
index 000000000..e5bf70c7a
--- /dev/null
+++ b/test/java/org/apache/fop/afp/modca/AbstractStructuredObjectTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.afp.modca;
+
+import java.io.IOException;
+
+public abstract class AbstractStructuredObjectTestCase<S extends AbstractStructuredObject> extends AbstractAFPObjectTestCase<S> {
+
+ /**
+ * Test writeStart() - test that the contract is maintained with
+ * {@link AbstractStructuredObject}.
+ *
+ * @throws IOException
+ */
+ public void testwriteStart() throws IOException {
+ }
+
+ /**
+ * Test writeEnd() - test that the contract is maintained with {@link AbstractStructuredObject}.
+ *
+ * @throws IOException
+ */
+ public void testWriteEnd() throws IOException {
+ }
+
+ /**
+ * Test writeContent() - test that the contract is maintained with
+ * {@link AbstractStructuredObject}.
+ *
+ * @throws IOException
+ */
+ public void testWriteContent() throws IOException {
+ }
+
+ /**
+ * Test writeToStream() - test that the contract is maintained with
+ * {@link AbstractStructuredObject}.
+ *
+ * @throws IOException
+ */
+ public void testWriteToStream() throws IOException {
+ testwriteStart();
+ testWriteEnd();
+ testWriteContent();
+ }
+}
diff --git a/test/java/org/apache/fop/afp/modca/AbstractTripletStructuredObjectTestCase.java b/test/java/org/apache/fop/afp/modca/AbstractTripletStructuredObjectTestCase.java
new file mode 100644
index 000000000..ebb0f42e9
--- /dev/null
+++ b/test/java/org/apache/fop/afp/modca/AbstractTripletStructuredObjectTestCase.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.afp.modca;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.fop.afp.modca.triplets.AbstractTriplet;
+import org.apache.fop.afp.modca.triplets.AttributeQualifierTriplet;
+import org.apache.fop.afp.modca.triplets.CommentTriplet;
+import org.apache.fop.afp.modca.triplets.ObjectAreaSizeTriplet;
+import org.apache.fop.afp.modca.triplets.Triplet;
+
+/**
+ * Test {@link AbstractTripletStructuredObject}
+ */
+public abstract class AbstractTripletStructuredObjectTestCase<S extends AbstractTripletStructuredObject>
+ extends AbstractStructuredObjectTestCase<AbstractTripletStructuredObject> {
+
+ private static final List<AbstractTriplet> TRIPLETS;
+
+ static {
+ List<AbstractTriplet> triplets = new ArrayList<AbstractTriplet>();
+
+ triplets.add(new CommentTriplet((byte) 0x01, "test comment"));
+
+ triplets.add(new AttributeQualifierTriplet(1, 1));
+
+ triplets.add(new ObjectAreaSizeTriplet(10, 20));
+
+ TRIPLETS = Collections.unmodifiableList(triplets);
+ }
+
+ private AbstractTripletStructuredObject emptyStructuredObject
+ = new AbstractTripletStructuredObject() { };
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ AbstractTripletStructuredObject sut = getSut();
+
+ for (AbstractTriplet triplet : TRIPLETS) {
+ sut.addTriplet(triplet);
+ }
+ }
+
+
+ /**
+ * Test getTripletLength() - ensure a sum of all enclosing object lengths is returned.
+ */
+ public void testGetTripletLength() {
+
+ int dataLength = 0;
+ for (Triplet t : TRIPLETS) {
+ dataLength += t.getDataLength();
+ }
+ assertEquals(dataLength, getSut().getTripletDataLength());
+ assertEquals(0, emptyStructuredObject.getTripletDataLength());
+ }
+
+ /**
+ * Test hasTriplets()
+ */
+ public void testHasTriplets() {
+ assertTrue(getSut().hasTriplets());
+ assertFalse(emptyStructuredObject.hasTriplets());
+ }
+
+ /**
+ * Test writeTriplets() - Ensure the triplets are written properly.
+ *
+ * @throws IOException -
+ */
+ public void testWriteObjects() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ for (AbstractTriplet triplet : TRIPLETS) {
+ triplet.writeToStream(baos);
+ }
+ byte[] expected = baos.toByteArray();
+ baos.reset();
+ getSut().writeTriplets(baos);
+ assertTrue(Arrays.equals(expected, baos.toByteArray()));
+
+ baos.reset();
+ // Ensure it doesn't die if no data has been added
+ emptyStructuredObject.writeTriplets(baos);
+ byte[] emptyArray = baos.toByteArray();
+ assertTrue(Arrays.equals(emptyArray, new byte[0]));
+ }
+
+ /**
+ * Test hasTriplet() - ensure both positive and negative values are returned.
+ */
+ public void testHasTriplet() {
+ for (AbstractTriplet triplet : TRIPLETS) {
+ assertTrue(getSut().hasTriplet(triplet.getId()));
+ assertFalse(emptyStructuredObject.hasTriplet(triplet.getId()));
+ }
+ CommentTriplet notInSystem = new CommentTriplet((byte) 0x30, "This should return false");
+ assertFalse(getSut().hasTriplet(notInSystem.getId()));
+ }
+
+ /**
+ * Test addTriplet() - mostly tested above, but check boundary cases
+ */
+ public void testAddTriplet() {
+ // ensure null doesn't kill it... not sure what else to test
+ getSut().addTriplet(null);
+ }
+
+ /**
+ * Test addTriplets() - ensure all triplets are added.
+ */
+ public void testAddTriplets() {
+ // Tested on empty object
+ List<AbstractTriplet> expectedList = TRIPLETS;
+ emptyStructuredObject.addTriplets(expectedList);
+ // checks equals() on each member of both lists
+ assertEquals(expectedList, emptyStructuredObject.getTriplets());
+
+ // Add a list to an already populated list
+ getSut().addTriplets(expectedList);
+
+ List<AbstractTriplet> newExpected = new ArrayList<AbstractTriplet>(expectedList);
+ newExpected.addAll(expectedList);
+ assertEquals(newExpected, getSut().getTriplets());
+
+ // Ensure null doesn't throw exception
+ emptyStructuredObject.addTriplets(null);
+ }
+
+} \ No newline at end of file
diff --git a/test/java/org/apache/fop/afp/modca/IncludeObjectTestCase.java b/test/java/org/apache/fop/afp/modca/IncludeObjectTestCase.java
new file mode 100644
index 000000000..5e28f00bd
--- /dev/null
+++ b/test/java/org/apache/fop/afp/modca/IncludeObjectTestCase.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id:$ */
+
+package org.apache.fop.afp.modca;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.fop.afp.util.BinaryUtils;
+
+/**
+ * Test {@link IncludeObject}
+ */
+public class IncludeObjectTestCase extends AbstractNamedAFPObjectTestCase<IncludeObject> {
+
+ @Override
+ public void setUp() throws Exception {
+ setSut(new IncludeObject("8__chars"));
+ super.setUp();
+ }
+
+ /**
+ * Test writeToStream()
+ * @throws IOException -
+ */
+ public void testWriteToStream() throws IOException {
+ final IncludeObject sut = getSut();
+
+ byte[] expected = defaultIncludeObjectBytes(sut.getTripletDataLength(), sut.getNameBytes());
+
+ testWriteToStreamHelper(sut, expected);
+ }
+
+ /**
+ * Test writeToStream() - the orientation of the referenced object is a right-
+ * handed with a 180 x-axis
+ * @throws IOException -
+ */
+ public void testWriteToStreamForOrientation() throws IOException {
+ final IncludeObject sut = getSut();
+
+ byte[] expected = defaultIncludeObjectBytes(sut.getTripletDataLength(), sut.getNameBytes());
+
+ expected[25] = (byte)0x5A;
+ expected[26] = (byte)0x00;
+ expected[27] = (byte)0x87;
+ expected[28] = (byte)0x00;
+
+ sut.setObjectAreaOrientation(180);
+
+ testWriteToStreamHelper(sut, expected);
+ }
+
+ private void testWriteToStreamHelper(IncludeObject sut, byte[] expected) throws IOException {
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ sut.writeToStream(baos);
+
+ byte[] actual = baos.toByteArray();
+
+ assertTrue(Arrays.equals(actual, expected));
+ }
+
+ private byte[] defaultIncludeObjectBytes(int tripletDataLength, byte[] nameData) {
+
+ byte[] expected = new byte[36];
+
+ byte[] header = new byte[] {
+ 0x5A, // Structured field identifier
+ 0x00, // Length byte 1
+ 0x10, // Length byte 2
+ (byte)0xD3, // Structured field id byte 1
+ (byte)0xAF, // Structured field id byte 2 - type 'input'
+ (byte)0xC3, // Structured field id byte 3 - category 'data resource'
+ 0x00, // Flags
+ 0x00, // Reserved
+ 0x00, // Reserved
+ };
+
+ System.arraycopy(header, 0, expected, 0, header.length);
+
+ byte[] lengthBytes = BinaryUtils.convert(35 + tripletDataLength, 2); //Ignore first byte
+ expected[1] = lengthBytes[0];
+ expected[2] = lengthBytes[1];
+
+ System.arraycopy(nameData, 0, expected, 9, nameData.length);
+
+ expected[18] = (byte)0x92; // object type 'other'
+
+ expected[27] = (byte)0x2D; // orientation of the reference object
+ writeOsetTo(expected, 29, -1); // the X-axis origin defined in the object
+ writeOsetTo(expected, 32, -1); // the Y-axis origin defined in the object
+
+ expected[35] = 0x01; // Page or overlay coordinate system
+
+ return expected;
+ }
+
+ private static void writeOsetTo(byte[] out, int offset, int oset) {
+ if (oset > -1) {
+ byte[] y = BinaryUtils.convert(oset, 3);
+ out[offset] = y[0];
+ out[offset + 1] = y[1];
+ out[offset + 2] = y[2];
+ } else {
+ out[offset] = (byte)0xFF;
+ out[offset + 1] = (byte)0xFF;
+ out[offset + 2] = (byte)0xFF;
+ }
+ }
+} \ No newline at end of file
diff --git a/test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java b/test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java
new file mode 100644
index 000000000..89527a775
--- /dev/null
+++ b/test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.fonts.truetype;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests {@link GlyfTable}.
+ */
+public class GlyfTableTestCase extends TestCase {
+
+ private final static class DirData {
+
+ final long offset;
+ final long length;
+
+ DirData(long offset, long length) {
+ this.offset = offset;
+ this.length = length;
+ }
+ }
+
+ private FontFileReader subsetReader;
+
+ private long[] glyphOffsets;
+
+ private FontFileReader originalFontReader;
+
+ @Override
+ public void setUp() throws IOException {
+ originalFontReader = new FontFileReader("test/resources/fonts/DejaVuLGCSerif.ttf");
+ }
+
+ /**
+ * Tests that composed glyphs are included in the glyph subset if a composite glyph is used.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ public void testPopulateGlyphsWithComposites() throws IOException {
+ // Glyph 408 -> U+01D8 "uni01D8" this is a composite glyph.
+ int[] composedIndices = setupTest(408);
+
+ int[] expected = new int[composedIndices.length];
+ expected[1] = 6;
+ expected[5] = 2;
+ expected[6] = 4;
+
+ assertArrayEquals(expected, composedIndices);
+ }
+
+ /**
+ * Tests that no glyphs are added if there are no composite glyphs the subset.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ public void testPopulateNoCompositeGlyphs() throws IOException {
+ int[] composedIndices = setupTest(36, 37, 38); // "A", "B", "C"
+ int[] expected = new int[composedIndices.length];
+
+ // There should be NO composite glyphs
+ assertArrayEquals(expected, composedIndices);
+ }
+
+ /**
+ * Tests that glyphs aren't remapped twice if the glyph before a composite glyph has 0-length.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ public void testGlyphsNotRemappedTwice() throws IOException {
+ int composedGlyph = 12;
+ // The order of these glyph indices, must NOT be changed! (see javadoc above)
+ int[] composedIndices = setupTest(1, 2, 3, 16, 2014, 4, 7, 8, 13, 2015, composedGlyph);
+
+ // There are 2 composed glyphs within the subset
+ int[] expected = new int[composedIndices.length];
+ expected[10] = composedGlyph;
+
+ assertArrayEquals(expected, composedIndices);
+ }
+
+ /**
+ * Tests that the correct glyph is included in the subset, when a composite glyph composed of a
+ * composite glyph is used.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ public void testSingleRecursionStep() throws IOException {
+ // Glyph 2077 -> U+283F "uni283F" this is composed of a composite glyph (recursive).
+ int[] composedIndices = setupTest(2077);
+
+ int[] expected = new int[composedIndices.length];
+ expected[1] = 2;
+
+ assertArrayEquals(expected, composedIndices);
+ }
+
+ private int[] setupTest(int... glyphIndices) throws IOException {
+ Map<Integer, Integer> glyphs = new HashMap<Integer, Integer>();
+ int index = 0;
+ glyphs.put(0, index++); // Glyph 0 (.notdef) must ALWAYS be in the subset
+
+ for (int glyphIndex : glyphIndices) {
+ glyphs.put(glyphIndex, index++);
+ }
+ setupSubsetReader(glyphs);
+ readLoca();
+
+ return retrieveIndicesOfComposedGlyphs();
+ }
+
+ private void setupSubsetReader(Map<Integer, Integer> glyphs) throws IOException {
+ TTFSubSetFile fontFile = new TTFSubSetFile();
+ byte[] subsetFont = fontFile.readFont(originalFontReader, "Deja", glyphs);
+ InputStream intputStream = new ByteArrayInputStream(subsetFont);
+ subsetReader = new FontFileReader(intputStream);
+ }
+
+ private void readLoca() throws IOException {
+ DirData loca = getTableData("loca");
+ int numberOfGlyphs = (int) (loca.length - 4) / 4;
+ glyphOffsets = new long[numberOfGlyphs];
+ subsetReader.seekSet(loca.offset);
+
+ for (int i = 0; i < numberOfGlyphs; i++) {
+ glyphOffsets[i] = subsetReader.readTTFULong();
+ }
+ }
+
+ private int[] retrieveIndicesOfComposedGlyphs() throws IOException {
+ DirData glyf = getTableData("glyf");
+ int[] composedGlyphIndices = new int[glyphOffsets.length];
+
+ for (int i = 0; i < glyphOffsets.length; i++) {
+ long glyphOffset = glyphOffsets[i];
+ if (i != glyphOffsets.length - 1 && glyphOffset == glyphOffsets[i + 1]) {
+ continue;
+ }
+ subsetReader.seekSet(glyf.offset + glyphOffset);
+ short numberOfContours = subsetReader.readTTFShort();
+ if (numberOfContours < 0) {
+ subsetReader.skip(8);
+ subsetReader.readTTFUShort(); // flags
+ int glyphIndex = subsetReader.readTTFUShort();
+ composedGlyphIndices[i] = glyphIndex;
+ }
+ }
+ return composedGlyphIndices;
+ }
+
+ private DirData getTableData(String tableName) throws IOException {
+ subsetReader.seekSet(0);
+ subsetReader.skip(12);
+ String name;
+ do {
+ name = subsetReader.readTTFString(4);
+ subsetReader.skip(4 * 3);
+ } while (!name.equals(tableName));
+
+ subsetReader.skip(-8); // We've found the table, go back to get the data we skipped over
+ return new DirData(subsetReader.readTTFLong(), subsetReader.readTTFLong());
+ }
+
+ private void assertArrayEquals(int[] expected, int[] actual) {
+ assertTrue(Arrays.equals(expected, actual));
+ }
+}
diff --git a/test/java/org/apache/fop/pdf/FileIDGeneratorTestCase.java b/test/java/org/apache/fop/pdf/FileIDGeneratorTestCase.java
new file mode 100644
index 000000000..3e9617743
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/FileIDGeneratorTestCase.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import java.util.Arrays;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests the {@link FileIDGenerator} class.
+ */
+public abstract class FileIDGeneratorTestCase extends TestCase {
+
+ /**
+ * Returns a suite containing all the {@link FileIDGenerator} test cases.
+ *
+ * @return the test suite
+ */
+ public static final Test suite() {
+ TestSuite suite = new TestSuite(new Class[] {
+ RandomFileIDGeneratorTestCase.class,
+ DigestFileIDGeneratorTestCase.class },
+ FileIDGeneratorTestCase.class.getName());
+ return suite;
+ }
+
+ /** The generator under test. */
+ protected FileIDGenerator fileIDGenerator;
+
+
+ /** Tests that the getOriginalFileID method generates valid output. */
+ public void testOriginal() {
+ byte[] fileID = fileIDGenerator.getOriginalFileID();
+ fileIDMustBeValid(fileID);
+ }
+
+ /** Tests that the getUpdatedFileID method generates valid output. */
+ public void testUpdated() {
+ byte[] fileID = fileIDGenerator.getUpdatedFileID();
+ fileIDMustBeValid(fileID);
+ }
+
+ private void fileIDMustBeValid(byte[] fileID) {
+ assertNotNull(fileID);
+ assertEquals(16, fileID.length);
+ }
+
+ /** Tests that multiple calls to getOriginalFileID method always return the same value. */
+ public void testOriginalMultipleCalls() {
+ byte[] fileID1 = fileIDGenerator.getUpdatedFileID();
+ byte[] fileID2 = fileIDGenerator.getUpdatedFileID();
+ assertTrue(Arrays.equals(fileID1, fileID2));
+ }
+
+ /** Tests that getUpdatedFileID returns the same value as getOriginalFileID. */
+ public void testUpdateEqualsOriginal() {
+ byte[] originalFileID = fileIDGenerator.getOriginalFileID();
+ byte[] updatedFileID = fileIDGenerator.getUpdatedFileID();
+ assertTrue(Arrays.equals(originalFileID, updatedFileID));
+ }
+
+ /**
+ * Tests the random file ID generator.
+ */
+ public static class RandomFileIDGeneratorTestCase extends FileIDGeneratorTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ fileIDGenerator = FileIDGenerator.getRandomFileIDGenerator();
+ }
+
+ }
+
+ /**
+ * Tests the file ID generator based on an MD5 digest.
+ */
+ public static class DigestFileIDGeneratorTestCase extends FileIDGeneratorTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ fileIDGenerator = FileIDGenerator.getDigestFileIDGenerator(
+ new PDFDocument("Apache FOP"));
+ }
+
+ }
+
+}
diff --git a/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java b/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java
new file mode 100644
index 000000000..c85018166
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java
@@ -0,0 +1,492 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the {@link PDFEncryptionJCE} class.
+ */
+public class PDFEncryptionJCETestCase extends TestCase {
+
+ private EncryptionTest test;
+
+ private PDFEncryptionJCE encryptionObject;
+
+ private static final class EncryptionTest {
+
+ private int objectNumber = 1;
+
+ private final PDFEncryptionParams encryptionParameters = new PDFEncryptionParams();
+
+ private byte[] data;
+
+ private byte[] encryptedData;
+
+ private final EncryptionDictionaryTester encryptionDictionaryTester;
+
+ EncryptionTest() {
+ this(new EncryptionDictionaryTester());
+ }
+
+ EncryptionTest(EncryptionDictionaryTester encryptionDictionaryTester) {
+ encryptionParameters.setUserPassword("TestUserPassword");
+ encryptionParameters.setOwnerPassword("TestOwnerPassword");
+ setData(0x00, 0xAA, 0xFF, 0x55, 0xCC, 0x33, 0xF0);
+ this.encryptionDictionaryTester = encryptionDictionaryTester;
+ this.encryptionDictionaryTester.setLength(
+ encryptionParameters.getEncryptionLengthInBits());
+ }
+
+ int getObjectNumber() {
+ return objectNumber;
+ }
+
+ EncryptionTest setObjectNumber(int objectNumber) {
+ this.objectNumber = objectNumber;
+ return this;
+ }
+
+ byte[] getData() {
+ return data;
+ }
+
+ EncryptionTest setData(int... data) {
+ /*
+ * Use an array of int to avoid having to cast some elements to byte in the
+ * method call.
+ */
+ this.data = convertIntArrayToByteArray(data);
+ return this;
+ }
+
+ byte[] getEncryptedData() {
+ return encryptedData;
+ }
+
+ EncryptionTest setEncryptedData(int... encryptedData) {
+ this.encryptedData = convertIntArrayToByteArray(encryptedData);
+ return this;
+ }
+
+ private byte[] convertIntArrayToByteArray(int[] intArray) {
+ byte[] byteArray = new byte[intArray.length];
+ for (int i = 0; i < intArray.length; i++) {
+ byteArray[i] = (byte) intArray[i];
+ }
+ return byteArray;
+ }
+
+ PDFEncryptionParams getEncryptionParameters() {
+ return encryptionParameters;
+ }
+
+ EncryptionTest setUserPassword(String userPassword) {
+ encryptionParameters.setUserPassword(userPassword);
+ return this;
+ }
+
+ EncryptionTest setOwnerPassword(String ownerPassword) {
+ encryptionParameters.setOwnerPassword(ownerPassword);
+ return this;
+ }
+
+ EncryptionTest setEncryptionLength(int encryptionLength) {
+ encryptionParameters.setEncryptionLengthInBits(encryptionLength);
+ encryptionDictionaryTester.setLength(encryptionLength);
+ return this;
+ }
+
+ EncryptionTest disablePrint() {
+ encryptionParameters.setAllowPrint(false);
+ return this;
+ }
+
+ EncryptionTest disableEditContent() {
+ encryptionParameters.setAllowEditContent(false);
+ return this;
+ }
+
+ EncryptionTest disableCopyContent() {
+ encryptionParameters.setAllowCopyContent(false);
+ return this;
+ }
+
+ EncryptionTest disableEditAnnotations() {
+ encryptionParameters.setAllowEditAnnotations(false);
+ return this;
+ }
+
+ EncryptionTest disableFillInForms() {
+ encryptionParameters.setAllowFillInForms(false);
+ return this;
+ }
+
+ EncryptionTest disableAccessContent() {
+ encryptionParameters.setAllowAccessContent(false);
+ return this;
+ }
+
+ EncryptionTest disableAssembleDocument() {
+ encryptionParameters.setAllowAssembleDocument(false);
+ return this;
+ }
+
+ EncryptionTest disablePrintHq() {
+ encryptionParameters.setAllowPrintHq(false);
+ return this;
+ }
+
+ void testEncryptionDictionary(PDFEncryptionJCE encryptionObject) {
+ encryptionDictionaryTester.test(encryptionObject);
+ }
+ }
+
+ private static final class EncryptionDictionaryTester {
+
+ private int version = 1;
+
+ private int revision = 2;
+
+ private int length = 40;
+
+ private int permissions = -4;
+
+ private String ownerEntry
+ = "3EE8C4000CA44B2645EED029C9EA7D4FC63C6D9B89349E8FA5A40C7691AB96B5";
+
+ private String userEntry
+ = "D1810D9E6E488BA5D2DDCBB3F974F7472D0D5389F554DB55574A787DC5C59884";
+
+ EncryptionDictionaryTester setVersion(int version) {
+ this.version = version;
+ return this;
+ }
+
+ EncryptionDictionaryTester setRevision(int revision) {
+ this.revision = revision;
+ return this;
+ }
+
+ EncryptionDictionaryTester setLength(int length) {
+ this.length = length;
+ return this;
+ }
+
+ EncryptionDictionaryTester setPermissions(int permissions) {
+ this.permissions = permissions;
+ return this;
+ }
+
+ EncryptionDictionaryTester setOwnerEntry(String ownerEntry) {
+ this.ownerEntry = ownerEntry;
+ return this;
+ }
+
+ EncryptionDictionaryTester setUserEntry(String userEntry) {
+ this.userEntry = userEntry;
+ return this;
+ }
+
+ void test(PDFEncryptionJCE encryptionObject) {
+ byte[] encryptionDictionary = encryptionObject.toPDF();
+ RegexTestedCharSequence dictionary = new RegexTestedCharSequence(encryptionDictionary);
+
+ final String whitespace = "\\s+";
+ final String digits = "\\d+";
+ final String hexDigits = "\\p{XDigit}+";
+
+ dictionary.mustContain("1" + whitespace + "0" + whitespace + "obj");
+
+ dictionary.mustContain("/Filter" + whitespace + "/Standard\\b");
+
+ dictionary.mustContain("/V" + whitespace + "(" + digits + ")")
+ .withGroup1EqualTo(Integer.toString(version));
+
+ dictionary.mustContain("/R" + whitespace + "(" + digits + ")")
+ .withGroup1EqualTo(Integer.toString(revision));
+
+ dictionary.mustContain("/Length" + whitespace + "(" + digits + ")")
+ .withGroup1EqualTo(Integer.toString(length));
+
+ dictionary.mustContain("/P" + whitespace + "(-?" + digits + ")")
+ .withGroup1EqualTo(Integer.toString(permissions));
+
+ dictionary.mustContain("/O" + whitespace + "<(" + hexDigits + ")>")
+ .withGroup1EqualTo(ownerEntry);
+
+ dictionary.mustContain("/U" + whitespace + "<(" + hexDigits + ")>")
+ .withGroup1EqualTo(userEntry);
+ }
+ }
+
+ private static final class RegexTestedCharSequence {
+
+ private final String string;
+
+ private Matcher matcher;
+
+ RegexTestedCharSequence(byte[] bytes) {
+ try {
+ string = new String(bytes, "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ RegexTestedCharSequence mustContain(String regex) {
+ Pattern pattern = Pattern.compile(regex);
+ matcher = pattern.matcher(string);
+ assertTrue(matcher.find());
+ return this;
+ }
+
+ RegexTestedCharSequence withGroup1EqualTo(String expected) {
+ assertEquals(expected, matcher.group(1));
+ return this;
+ }
+ }
+
+ public final void testMake() {
+ PDFEncryption testEncryptionObj = createEncryptionObject(new PDFEncryptionParams());
+ assertTrue(testEncryptionObj instanceof PDFEncryptionJCE);
+ assertEquals(1, ((PDFEncryptionJCE) testEncryptionObj).getObjectNumber());
+ }
+
+ public void testBasic() throws IOException {
+ test = new EncryptionTest();
+ test.setData(0x00).setEncryptedData(0x56);
+ runEncryptionTests();
+
+ test.setData(0xAA).setEncryptedData(0xFC);
+ runEncryptionTests();
+
+ test.setData(0xFF).setEncryptedData(0xA9);
+ runEncryptionTests();
+
+ test = new EncryptionTest().setEncryptedData(0x56, 0x0C, 0xFC, 0xA5, 0xAB, 0x61, 0x73);
+ runEncryptionTests();
+ }
+
+ public void test128bit() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setVersion(2)
+ .setRevision(3)
+ .setPermissions(-4)
+ .setOwnerEntry("D9A98017F0500EF9B69738641C9B4CBA1229EDC3F2151BC6C9C4FB07B1CB315E")
+ .setUserEntry("D3EF424BFEA2E434000E1A74941CC87300000000000000000000000000000000");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(2)
+ .setEncryptionLength(128)
+ .setEncryptedData(0xE3, 0xCB, 0xB2, 0x55, 0xD9, 0x26, 0x55);
+ runEncryptionTests();
+ }
+
+ public void testDisableRev2Permissions() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setPermissions(-64)
+ .setUserEntry("3E65D0090746C4C37C5EF23C1BDB6323E00C24C4B2D744DD3BFB654CD58591A1");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(3)
+ .disablePrint()
+ .disableEditContent()
+ .disableCopyContent()
+ .disableEditAnnotations()
+ .setEncryptedData(0x66, 0xEE, 0xA7, 0x93, 0xC4, 0xB1, 0xB4);
+ runEncryptionTests();
+ }
+
+ public void testDisableRev3Permissions() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setVersion(2)
+ .setRevision(3)
+ .setPermissions(-3844)
+ .setOwnerEntry("8D4BCA4F4AB2BAB4E38F161D61F937EC50BE5EB30C2DC05EA409D252CD695E55")
+ .setUserEntry("0F01171E22C7FB27B079C132BA4277DE00000000000000000000000000000000");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(4)
+ .disableFillInForms()
+ .disableAccessContent()
+ .disableAssembleDocument()
+ .disablePrintHq()
+ .setEncryptedData(0x8E, 0x3C, 0xD2, 0x05, 0x50, 0x48, 0x82);
+ runEncryptionTests();
+ }
+
+ public void test128bitDisableSomePermissions() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setVersion(2)
+ .setRevision(3)
+ .setPermissions(-1304)
+ .setOwnerEntry("D9A98017F0500EF9B69738641C9B4CBA1229EDC3F2151BC6C9C4FB07B1CB315E")
+ .setUserEntry("62F0E4D8641D482E0F8E71A89270045A00000000000000000000000000000000");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(5)
+ .setEncryptionLength(128)
+ .disablePrint()
+ .disableCopyContent()
+ .disableFillInForms()
+ .disableAssembleDocument()
+ .setEncryptedData(0xF7, 0x85, 0x4F, 0xB0, 0x50, 0x5C, 0xDF);
+ runEncryptionTests();
+ }
+
+ public void testDifferentPasswords() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setOwnerEntry("D11C233C65E9DC872E858ABBD8B62198771167ADCE7AB8DC7AE0A1A7E21A1E25")
+ .setUserEntry("6F449167DB8DDF0D2DF4602DDBBA97ABF9A9101F632CC16AB0BE74EB9500B469");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(6)
+ .setUserPassword("ADifferentUserPassword")
+ .setOwnerPassword("ADifferentOwnerPassword")
+ .setEncryptedData(0x27, 0xAC, 0xB1, 0x6C, 0x42, 0xE0, 0xA8);
+ runEncryptionTests();
+ }
+
+ public void testNoOwnerPassword() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setOwnerEntry("5163AAF3EE74C76D7C223593A84C8702FEA8AA4493E4933FF5B5A5BBB20AE4BB")
+ .setUserEntry("42DDF1C1BF3AB04786D5038E7B0A723AE614D944E1DE91A922FC54F5F2345E00");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(7)
+ .setUserPassword("ADifferentUserPassword")
+ .setOwnerPassword("")
+ .setEncryptedData(0xEC, 0x2E, 0x5D, 0xC2, 0x7F, 0xAD, 0x58);
+ runEncryptionTests();
+ }
+
+ public void test128bitDisableSomePermissionsDifferentPasswords() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setVersion(2)
+ .setRevision(3)
+ .setPermissions(-2604)
+ .setOwnerEntry("F83CA049FAA2F774F8541F25E746A92EE2A7F060C46C91C693E673BF18FF7B36")
+ .setUserEntry("88A4C58F5385B5F08FACA0636D790EDF00000000000000000000000000000000");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(8)
+ .setUserPassword("ADifferentUserPassword")
+ .setOwnerPassword("ADifferentOwnerPassword")
+ .setEncryptionLength(128)
+ .disableEditContent()
+ .disableEditAnnotations()
+ .disableAccessContent()
+ .disablePrintHq()
+ .setEncryptedData(0x77, 0x54, 0x67, 0xA5, 0xCC, 0x73, 0xDE);
+ runEncryptionTests();
+ }
+
+ public void test128bitNoPermissionNoOwnerPassword() throws IOException {
+ EncryptionDictionaryTester encryptionDictionaryTester = new EncryptionDictionaryTester()
+ .setVersion(2)
+ .setRevision(3)
+ .setPermissions(-3904)
+ .setOwnerEntry("3EEB3FA5594CBD935BFB2F83FB184DD41FBCD7C36A04F1FFD0899B0DFFCFF96B")
+ .setUserEntry("D972B72DD2633F613B0DDB7511C719C500000000000000000000000000000000");
+ test = new EncryptionTest(encryptionDictionaryTester)
+ .setObjectNumber(9)
+ .setUserPassword("ADifferentUserPassword")
+ .setOwnerPassword("")
+ .setEncryptionLength(128)
+ .disablePrint()
+ .disableEditContent()
+ .disableCopyContent()
+ .disableEditAnnotations()
+ .disableFillInForms()
+ .disableAccessContent()
+ .disableAssembleDocument()
+ .disablePrintHq()
+ .setEncryptedData(0x0C, 0xAD, 0x49, 0xC7, 0xE5, 0x05, 0xB8);
+ runEncryptionTests();
+ }
+
+ /**
+ * Creates an encryption object using a fixed file ID generator for test reproducibility.
+ *
+ * @param params the encryption parameters
+ * @return PDFEncryptionJCE the encryption object
+ */
+ private PDFEncryptionJCE createEncryptionObject(PDFEncryptionParams params) {
+ PDFDocument doc = new PDFDocument("Apache FOP") {
+
+ @Override
+ FileIDGenerator getFileIDGenerator() {
+ return new FileIDGenerator() {
+
+ private final byte[] fixedFileID = new byte[] {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
+
+ @Override
+ byte[] getOriginalFileID() {
+ return fixedFileID;
+ }
+
+ @Override
+ byte[] getUpdatedFileID() {
+ return fixedFileID;
+ }
+
+ };
+ }
+ };
+ return (PDFEncryptionJCE) PDFEncryptionJCE.make(1, params, doc);
+ }
+
+ private void runEncryptionTests() throws IOException {
+ encryptionObject = createEncryptionObject(test.getEncryptionParameters());
+ runEncryptTest();
+ runFilterTest();
+ runEncryptionDictionaryTest();
+ }
+
+ private void runEncryptTest() {
+ PDFText text = new PDFText();
+ text.setObjectNumber(test.getObjectNumber());
+ byte[] byteResult = encryptionObject.encrypt(test.getData(), text);
+
+ assertTrue(Arrays.equals(test.getEncryptedData(), byteResult));
+ }
+
+ private void runFilterTest() throws IOException {
+ PDFStream stream = new PDFStream();
+ stream.setDocument(encryptionObject.getDocumentSafely());
+ stream.setObjectNumber(test.getObjectNumber());
+ stream.setData(test.getData());
+ encryptionObject.applyFilter(stream);
+
+ StreamCache streamCache = stream.encodeStream();
+ ByteArrayOutputStream testOutputStream = new ByteArrayOutputStream();
+ streamCache.outputContents(testOutputStream);
+
+ assertTrue(Arrays.equals(test.getEncryptedData(), testOutputStream.toByteArray()));
+ }
+
+ private void runEncryptionDictionaryTest() {
+ test.testEncryptionDictionary(encryptionObject);
+ }
+
+}
diff --git a/test/java/org/apache/fop/pdf/PDFFactoryTestCase.java b/test/java/org/apache/fop/pdf/PDFFactoryTestCase.java
new file mode 100644
index 000000000..d81eb228a
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/PDFFactoryTestCase.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import junit.framework.TestCase;
+
+import org.apache.fop.fonts.CIDSubset;
+import org.apache.fop.fonts.MultiByteFont;
+
+/**
+ * Test case for {@link PDFFactory}.
+ */
+public class PDFFactoryTestCase extends TestCase {
+
+ /**
+ * This tests that when a font is subset embedded in a PDF, the font name is prefixed with a
+ * pseudo-random tag as per the PDF spec.
+ */
+ public void testSubsetFontNamePrefix() {
+ class MockedFont extends MultiByteFont {
+ @Override
+ public int[] getWidths() {
+ return new int[] { 0 };
+ }
+
+ @Override
+ public CIDSubset getCIDSubset() {
+ return new CIDSubset();
+ }
+ }
+ PDFDocument doc = new PDFDocument("Test");
+ PDFFactory pdfFactory = new PDFFactory(doc);
+ MockedFont font = new MockedFont();
+
+ PDFFont pdfDejaVu = pdfFactory.makeFont("DejaVu", "DejaVu", "TTF", font, font);
+ assertEquals("/EAAAAA+DejaVu", pdfDejaVu.getBaseFont().toString());
+
+ PDFFont pdfArial = pdfFactory.makeFont("Arial", "Arial", "TTF", font, font);
+ assertEquals("/EAAAAB+Arial", pdfArial.getBaseFont().toString());
+ }
+}
diff --git a/test/java/org/apache/fop/render/pdf/PDFRendererConfiguratorTestCase.java b/test/java/org/apache/fop/render/pdf/PDFRendererConfiguratorTestCase.java
new file mode 100644
index 000000000..01889e437
--- /dev/null
+++ b/test/java/org/apache/fop/render/pdf/PDFRendererConfiguratorTestCase.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf;
+
+import java.io.File;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.FopFactory;
+import org.apache.fop.events.Event;
+import org.apache.fop.events.EventListener;
+import org.apache.fop.pdf.PDFEncryptionParams;
+
+/**
+ * Tests that encryption length is properly set up.
+ */
+public class PDFRendererConfiguratorTestCase extends TestCase {
+
+ private FOUserAgent foUserAgent;
+
+ private PDFDocumentHandler documentHandler;
+
+ private boolean eventTriggered;
+
+ private class EncryptionEventFilter implements EventListener {
+
+ private final int specifiedEncryptionLength;
+
+ private final int correctedEncryptionLength;
+
+ EncryptionEventFilter(int specifiedEncryptionLength, int correctedEncryptionLength) {
+ this.specifiedEncryptionLength = specifiedEncryptionLength;
+ this.correctedEncryptionLength = correctedEncryptionLength;
+ }
+
+ public void processEvent(Event event) {
+ assertEquals(PDFEventProducer.class.getName() + ".incorrectEncryptionLength",
+ event.getEventID());
+ assertEquals(specifiedEncryptionLength, event.getParam("originalValue"));
+ assertEquals(correctedEncryptionLength, event.getParam("correctedValue"));
+ eventTriggered = true;
+ }
+ }
+
+ /**
+ * Non-multiple of 8 should be rounded.
+ *
+ * @throws Exception if an error occurs
+ */
+ public void testRoundUp() throws Exception {
+ runTest("roundUp", 55, 56);
+ }
+
+ /**
+ * Non-multiple of 8 should be rounded.
+ *
+ * @throws Exception if an error occurs
+ */
+ public void testRoundDown() throws Exception {
+ runTest("roundDown", 67, 64);
+ }
+
+ /**
+ * Encryption length must be at least 40.
+ *
+ * @throws Exception if an error occurs
+ */
+ public void testBelow40() throws Exception {
+ runTest("below40", 32, 40);
+ }
+
+ /**
+ * Encryption length must be at most 128.
+ *
+ * @throws Exception if an error occurs
+ */
+ public void testAbove128() throws Exception {
+ runTest("above128", 233, 128);
+ }
+
+ /**
+ * A correct value must be properly set up.
+ *
+ * @throws Exception if an error occurs
+ */
+ public void testCorrectValue() throws Exception {
+ givenAConfigurationFile("correct", new EventListener() {
+
+ public void processEvent(Event event) {
+ throw new AssertionFailedError("No event was expected");
+ }
+ });
+ whenCreatingAndConfiguringDocumentHandler();
+ thenEncryptionLengthShouldBe(128);
+
+ }
+
+ private void runTest(String configFilename,
+ final int specifiedEncryptionLength,
+ final int correctedEncryptionLength) throws Exception {
+ givenAConfigurationFile(configFilename,
+ new EncryptionEventFilter(specifiedEncryptionLength, correctedEncryptionLength));
+ whenCreatingAndConfiguringDocumentHandler();
+ assertTrue(eventTriggered);
+ }
+
+ private void givenAConfigurationFile(String filename, EventListener eventListener)
+ throws Exception {
+ FopFactory fopFactory = FopFactory.newInstance();
+ fopFactory.setUserConfig(new File("test/resources/org/apache/fop/render/pdf/"
+ + filename + ".xconf"));
+ foUserAgent = fopFactory.newFOUserAgent();
+ foUserAgent.getEventBroadcaster().addEventListener(eventListener);
+ }
+
+ private void whenCreatingAndConfiguringDocumentHandler() throws FOPException {
+ PDFDocumentHandlerMaker maker = new PDFDocumentHandlerMaker();
+ documentHandler = (PDFDocumentHandler) maker.makeIFDocumentHandler(foUserAgent);
+ new PDFRendererConfigurator(foUserAgent).configure(documentHandler);
+ }
+
+ private void thenEncryptionLengthShouldBe(int expectedEncryptionLength) {
+ PDFEncryptionParams encryptionParams = documentHandler.getPDFUtil().getEncryptionParams();
+ assertEquals(expectedEncryptionLength, encryptionParams.getEncryptionLengthInBits());
+ }
+}
diff --git a/test/java/org/apache/fop/render/pdf/RenderPDFTestSuite.java b/test/java/org/apache/fop/render/pdf/RenderPDFTestSuite.java
new file mode 100644
index 000000000..c9a17da0b
--- /dev/null
+++ b/test/java/org/apache/fop/render/pdf/RenderPDFTestSuite.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.render.pdf;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+
+/**
+ * A test suite for org.apache.fop.render.pdf.*
+ */
+public final class RenderPDFTestSuite {
+
+ private RenderPDFTestSuite() { }
+
+ /**
+ * Creates the test suite.
+ *
+ * @return the test suite
+ */
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ //$JUnit-BEGIN$
+ suite.addTest(new TestSuite(PDFRendererConfiguratorTestCase.class));
+ //$JUnit-END$
+ return suite;
+ }
+}
diff --git a/test/resources/org/apache/fop/render/pdf/above128.xconf b/test/resources/org/apache/fop/render/pdf/above128.xconf
new file mode 100644
index 000000000..2bdab04f4
--- /dev/null
+++ b/test/resources/org/apache/fop/render/pdf/above128.xconf
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+ <renderers>
+ <renderer mime="application/pdf">
+ <encryption-params>
+ <encryption-length>233</encryption-length>
+ </encryption-params>
+ </renderer>
+ </renderers>
+</fop>
diff --git a/test/resources/org/apache/fop/render/pdf/below40.xconf b/test/resources/org/apache/fop/render/pdf/below40.xconf
new file mode 100644
index 000000000..19086f763
--- /dev/null
+++ b/test/resources/org/apache/fop/render/pdf/below40.xconf
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+ <renderers>
+ <renderer mime="application/pdf">
+ <encryption-params>
+ <encryption-length>32</encryption-length>
+ </encryption-params>
+ </renderer>
+ </renderers>
+</fop>
diff --git a/test/resources/org/apache/fop/render/pdf/correct.xconf b/test/resources/org/apache/fop/render/pdf/correct.xconf
new file mode 100644
index 000000000..246c17e99
--- /dev/null
+++ b/test/resources/org/apache/fop/render/pdf/correct.xconf
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+ <renderers>
+ <renderer mime="application/pdf">
+ <encryption-params>
+ <encryption-length>128</encryption-length>
+ </encryption-params>
+ </renderer>
+ </renderers>
+</fop>
diff --git a/test/resources/org/apache/fop/render/pdf/roundDown.xconf b/test/resources/org/apache/fop/render/pdf/roundDown.xconf
new file mode 100644
index 000000000..722808c03
--- /dev/null
+++ b/test/resources/org/apache/fop/render/pdf/roundDown.xconf
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+ <renderers>
+ <renderer mime="application/pdf">
+ <encryption-params>
+ <encryption-length>67</encryption-length>
+ </encryption-params>
+ </renderer>
+ </renderers>
+</fop>
diff --git a/test/resources/org/apache/fop/render/pdf/roundUp.xconf b/test/resources/org/apache/fop/render/pdf/roundUp.xconf
new file mode 100644
index 000000000..ffe06905d
--- /dev/null
+++ b/test/resources/org/apache/fop/render/pdf/roundUp.xconf
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+ <renderers>
+ <renderer mime="application/pdf">
+ <encryption-params>
+ <encryption-length>55</encryption-length>
+ </encryption-params>
+ </renderer>
+ </renderers>
+</fop>