Browse Source

Merge from origin/trunk.

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/fop-1_1rc1@1353186 13f79535-47bb-0310-9956-ffa450edef68
tags/fop-1_1rc1old
Glenn Adams 12 years ago
parent
commit
381ddddbcf
81 changed files with 4969 additions and 1016 deletions
  1. 14
    2
      build.xml
  2. 46
    0
      findbugs-exclude.xml
  3. 18
    0
      src/documentation/content/xdocs/trunk/configuration.xml
  4. 5
    5
      src/documentation/content/xdocs/trunk/fonts.xml
  5. 8
    5
      src/documentation/content/xdocs/trunk/graphics.xml
  6. 2
    0
      src/java/META-INF/services/org.apache.fop.render.ImageHandler
  7. 21
    20
      src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java
  8. 3
    2
      src/java/org/apache/fop/fo/properties/CondLengthProperty.java
  9. 1
    1
      src/java/org/apache/fop/fonts/CIDFontType.java
  10. 31
    8
      src/java/org/apache/fop/fonts/CMapSegment.java
  11. 40
    0
      src/java/org/apache/fop/fonts/CustomFont.java
  12. 1
    1
      src/java/org/apache/fop/fonts/CustomFontCollection.java
  13. 23
    0
      src/java/org/apache/fop/fonts/EmbedFontInfo.java
  14. 58
    0
      src/java/org/apache/fop/fonts/EmbeddingMode.java
  15. 1
    1
      src/java/org/apache/fop/fonts/EncodingMode.java
  16. 5
    1
      src/java/org/apache/fop/fonts/FontInfoConfigurator.java
  17. 15
    7
      src/java/org/apache/fop/fonts/FontLoader.java
  18. 9
    4
      src/java/org/apache/fop/fonts/FontReader.java
  19. 5
    0
      src/java/org/apache/fop/fonts/FontType.java
  20. 6
    4
      src/java/org/apache/fop/fonts/LazyFont.java
  21. 27
    35
      src/java/org/apache/fop/fonts/MultiByteFont.java
  22. 6
    0
      src/java/org/apache/fop/fonts/MutableFont.java
  23. 24
    0
      src/java/org/apache/fop/fonts/SingleByteFont.java
  24. 12
    18
      src/java/org/apache/fop/fonts/apps/TTFReader.java
  25. 4
    2
      src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
  26. 12
    17
      src/java/org/apache/fop/fonts/truetype/FontFileReader.java
  27. 0
    118
      src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java
  28. 8
    0
      src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java
  29. 452
    257
      src/java/org/apache/fop/fonts/truetype/TTFFile.java
  30. 33
    32
      src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
  31. 48
    0
      src/java/org/apache/fop/fonts/truetype/TTFGlyphOutputStream.java
  32. 49
    0
      src/java/org/apache/fop/fonts/truetype/TTFOutputStream.java
  33. 246
    279
      src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
  34. 163
    0
      src/java/org/apache/fop/fonts/truetype/TTFTableName.java
  35. 37
    0
      src/java/org/apache/fop/fonts/truetype/TTFTableOutputStream.java
  36. 5
    1
      src/java/org/apache/fop/pdf/BitmapImage.java
  37. 3
    2
      src/java/org/apache/fop/pdf/PDFFactory.java
  38. 2
    1
      src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
  39. 68
    1
      src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java
  40. 258
    0
      src/java/org/apache/fop/render/pdf/ImageRawPNGAdapter.java
  41. 2
    71
      src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java
  42. 65
    0
      src/java/org/apache/fop/render/pdf/PDFImageHandlerRawPNG.java
  43. 9
    8
      src/java/org/apache/fop/render/ps/FontResourceCache.java
  44. 113
    0
      src/java/org/apache/fop/render/ps/ImageEncoderPNG.java
  45. 12
    7
      src/java/org/apache/fop/render/ps/PSDocumentHandler.java
  46. 7
    0
      src/java/org/apache/fop/render/ps/PSEventProducer.java
  47. 1
    0
      src/java/org/apache/fop/render/ps/PSEventProducer.xml
  48. 77
    0
      src/java/org/apache/fop/render/ps/PSFontResource.java
  49. 393
    47
      src/java/org/apache/fop/render/ps/PSFontUtils.java
  50. 111
    0
      src/java/org/apache/fop/render/ps/PSImageHandlerRawPNG.java
  51. 19
    7
      src/java/org/apache/fop/render/ps/PSImageHandlerRenderedImage.java
  52. 48
    18
      src/java/org/apache/fop/render/ps/PSPainter.java
  53. 33
    12
      src/java/org/apache/fop/render/ps/PSTextPainter.java
  54. 7
    3
      src/java/org/apache/fop/render/ps/ResourceHandler.java
  55. 101
    0
      src/java/org/apache/fop/render/ps/fonts/PSTTFGenerator.java
  56. 75
    0
      src/java/org/apache/fop/render/ps/fonts/PSTTFGlyphOutputStream.java
  57. 62
    0
      src/java/org/apache/fop/render/ps/fonts/PSTTFOutputStream.java
  58. 59
    0
      src/java/org/apache/fop/render/ps/fonts/PSTTFTableOutputStream.java
  59. 57
    0
      src/java/org/apache/fop/util/HexEncoder.java
  60. 19
    3
      status.xml
  61. 7
    1
      test/java/org/apache/fop/UtilityCodeTestSuite.java
  62. 2
    1
      test/java/org/apache/fop/fonts/DejaVuLGCSerifTestCase.java
  63. 13
    5
      test/java/org/apache/fop/fonts/EncodingModeTestCase.java
  64. 42
    0
      test/java/org/apache/fop/fonts/FOPFontsTestSuite.java
  65. 304
    0
      test/java/org/apache/fop/fonts/truetype/FontFileReaderTestCase.java
  66. 4
    3
      test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java
  67. 427
    0
      test/java/org/apache/fop/fonts/truetype/TTFFileTestCase.java
  68. 7
    6
      test/java/org/apache/fop/fonts/truetype/TTFFontLoaderTestCase.java
  69. 76
    0
      test/java/org/apache/fop/fonts/truetype/TTFSubSetFileTestCase.java
  70. 153
    0
      test/java/org/apache/fop/fonts/truetype/TTFTableNameTestCase.java
  71. 92
    0
      test/java/org/apache/fop/render/RawPNGTestUtil.java
  72. 142
    0
      test/java/org/apache/fop/render/pdf/ImageRawPNGAdapterTestCase.java
  73. 133
    0
      test/java/org/apache/fop/render/ps/ImageEncoderPNGTestCase.java
  74. 43
    0
      test/java/org/apache/fop/render/ps/RenderPSTestSuite.java
  75. 120
    0
      test/java/org/apache/fop/render/ps/fonts/PSTTFGeneratorTestCase.java
  76. 109
    0
      test/java/org/apache/fop/render/ps/fonts/PSTTFGlyphOutputStreamTestCase.java
  77. 90
    0
      test/java/org/apache/fop/render/ps/fonts/PSTTFOutputStreamTestCase.java
  78. 87
    0
      test/java/org/apache/fop/render/ps/fonts/PSTTFTableOutputStreamTestCase.java
  79. 61
    0
      test/java/org/apache/fop/util/HexEncoderTestCase.java
  80. 18
    0
      test/resources/fonts/ttf/DroidSansMono.LICENSE
  81. BIN
      test/resources/fonts/ttf/DroidSansMono.ttf

+ 14
- 2
build.xml View File

@@ -864,16 +864,28 @@ list of possible build targets.
<target name="junit-text-linebreak" depends="junit-compile" description="Runs FOP's JUnit unicode linebreak tests" if="junit.present">
<junit-run title="Unicode UAX#14 support" testsuite="org.apache.fop.text.linebreak.LineBreakStatusTestCase" outfile="TEST-linebreak"/>
</target>
<target name="junit-fonts" depends="junit-compile">
<echo message="Running tests for the fonts package"/>
<junit-run title="fonts" testsuite="org.apache.fop.fonts.FOPFontsTestSuite" outfile="TEST-fonts"/>
</target>
<target name="junit-render-ps" depends="junit-compile">
<echo message="Running tests for the render ps package"/>
<junit-run title="render-ps" testsuite="org.apache.fop.render.ps.RenderPSTestSuite" outfile="TEST-render-ps"/>
</target>
<target name="junit-render-pdf" depends="junit-compile">
<junit-run title="render-pdf" testsuite="org.apache.fop.render.pdf.RenderPDFTestSuite" outfile="TEST-render-pdf"/>
</target>
<target name="junit-complexscripts" depends="junit-compile">
<junit-run title="complexscripts" testsuite="org.apache.fop.complexscripts.ComplexScriptsTestSuite" outfile="TEST-complexscripts"/>
</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-reduced" depends="junit-userconfig, junit-basic, junit-transcoder,
junit-text-linebreak, junit-fotree, junit-fonts, junit-render-pdf, junit-render-ps,
junit-complexscripts"/>
<target name="junit" depends="junit-all" 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>
<fail><condition><or><isset property="fop.junit.error"/><isset
property="fop.junit.failure"/><not><isset
property="hyphenation.present"/></not></or></condition>
NOTE:
**************************************************************************
* One or more of the Junit tests had Failures or Errors or were skipped! *

+ 46
- 0
findbugs-exclude.xml View File

@@ -1,5 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FindBugsFilter>
<Match>
<Class name="org.apache.fop.fonts.truetype.TTFFile$1"/>
<Bug pattern="SIC_INNER_SHOULD_BE_STATIC_ANON"/>
</Match>
<Match>
<Class name="org.apache.fop.fonts.truetype.FontFileReader"/>
<Method name="getAllBytes"/>
<Bug pattern="EI_EXPOSE_REP"/>
</Match>
<Match>
<Class name="org.apache.fop.fo.properties.FontFamilyProperty"/>
<Bug pattern="EQ_DOESNT_OVERRIDE_EQUALS"/>
@@ -5162,4 +5171,41 @@
<Method name="getNonEmptyLevels"/>
<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
</Match>
<Match>
<Class name="org.apache.fop.render.pdf.AbstractImageAdapter"/>
<Method name="populateXObjectDictionaryForIndexColorModel"/>
<Bug pattern="OS_OPEN_STREAM"/>
</Match>
<Match>
<Class name="org.apache.fop.render.pdf.ImageRawPNGAdapter"/>
<Or>
<Method name="outputContents"/>
<Method name="setup"/>
</Or>
<Or>
<Bug pattern="OS_OPEN_STREAM"/>
<Bug pattern="OS_OPEN_STREAM_EXCEPTION_PATH"/>
</Or>
</Match>
<Match>
<Class name="org.apache.fop.render.ps.ImageEncoderPNG"/>
<Method name="writeTo"/>
<Bug pattern="OS_OPEN_STREAM"/>
</Match>
<Match>
<Or>
<Class name="org.apache.fop.render.pdf.PDFImageHandlerRawPNG"/>
<Class name="org.apache.fop.render.ps.PSImageHandlerRawPNG"/>
</Or>
<Method name="getSupportedImageFlavors"/>
<Bug pattern="EI_EXPOSE_REP"/>
</Match>
<Match>
<Class name="org.apache.fop.render.ps.PSImageHandlerRawPNG"/>
<Or>
<Method name="handleImage"/>
<Method name="generateForm"/>
</Or>
<Bug pattern="BC_UNCONFIRMED_CAST"/>
</Match>
</FindBugsFilter>

+ 18
- 0
src/documentation/content/xdocs/trunk/configuration.xml View File

@@ -281,6 +281,24 @@
treated as zero penalty in most cases. For more details on the image loading framework,
please consult the documentation there.
</p>
<p>
The ImageLoaderPNG and ImageLoaderRawPNG have a hard-coded penalty of 1000 and as such the
ImageLoaderImageIO image loader will be selected by default when loading PNGs unless
the latter is disabled by awarding a INFINITE penalty to it, or one of the former two is
promoted by awarding a strong negative penalty (say, -10000) to it.
</p>
<source><![CDATA[<fop version="1.0">
[..]
<image-loading>
<penalty value="-10000"
class="org.apache.xmlgraphics.image.loader.impl.ImageLoaderRawPNG"/>
<penalty value="INFINITE"
class="org.apache.xmlgraphics.image.loader.impl.ImageLoaderPNG"/>
<penalty value="INFINITE"
class="org.apache.xmlgraphics.image.loader.impl.imageio.ImageLoaderImageIO"/>
</image-loading>
<renderers....
</fop>]]></source>
</section>
<section id="renderers">
<title>Renderer configuration</title>

+ 5
- 5
src/documentation/content/xdocs/trunk/fonts.xml View File

@@ -493,10 +493,10 @@
Various notes related to embedded fonts:
</p>
<ul>
<li>The PostScript renderer does not yet support TrueType fonts, but can embed Type 1 fonts.</li>
<li>The font is simply embedded into the PDF file, it is not converted.</li>
<li>When FOP embeds a font, it adds a prefix to the fontname to ensure that the name will not match the fontname of an installed font.
This is helpful with older versions of Acrobat Reader that preferred installed fonts over embedded fonts.</li>
<li>The font is simply embedded into the output file, it is not converted.</li>
<li>When FOP embeds a font in a PDF file, it adds a prefix to the fontname to ensure that
the name will not match the fontname of an installed font. This is helpful with older
versions of Acrobat Reader that preferred installed fonts over embedded fonts.</li>
<li>When embedding PostScript fonts, the entire font is always embedded.</li>
<li>When embedding TrueType fonts (ttf) or TrueType Collections (ttc), a subset of the
original font, containing only the glyphs used, is embedded in the output document.
@@ -576,4 +576,4 @@
</p>
</section>
</body>
</document>
</document>

+ 8
- 5
src/documentation/content/xdocs/trunk/graphics.xml View File

@@ -108,7 +108,7 @@
<tr>
<td><a href="#png">PNG</a> (Portable Network Graphic)</td>
<td>bitmap</td>
<td/>
<td>(X)</td>
<td/>
<td>X</td>
</tr>
@@ -217,8 +217,8 @@
</tr>
<tr>
<td><a href="#png">PNG</a> (Portable Network Graphic)</td>
<td>X</td>
<td>X</td>
<td>X [2]</td>
<td>X [2]</td>
<td>X</td>
<td>X</td>
<td>X</td>
@@ -383,8 +383,11 @@
<section id="png">
<title>PNG</title>
<p>
PNG images are supported through an Image&amp;nbsp;I/O codec. Transparency is supported but
not guaranteed to work with every output format.
FOP native support of PNG only includes the variants with 8 bits per channel and without
interlacing. Native support requires using the ImageLoaderRawPNG image loader.
Support through a Image I/O codec can use either the internal XGC PNG codec or the JRE PNG
codec. The associated image loaders are, respectively, ImageLoaderPNG and ImageLoaderImageIO.
Transparency is supported but not guaranteed to work with every output format.
</p>
</section>
<section id="svg">

+ 2
- 0
src/java/META-INF/services/org.apache.fop.render.ImageHandler View File

@@ -1,6 +1,7 @@
org.apache.fop.render.pdf.PDFImageHandlerGraphics2D
org.apache.fop.render.pdf.PDFImageHandlerRenderedImage
org.apache.fop.render.pdf.PDFImageHandlerRawJPEG
org.apache.fop.render.pdf.PDFImageHandlerRawPNG
org.apache.fop.render.pdf.PDFImageHandlerRawCCITTFax
org.apache.fop.render.pdf.PDFImageHandlerSVG
org.apache.fop.render.java2d.Java2DImageHandlerRenderedImage
@@ -11,6 +12,7 @@ org.apache.fop.render.ps.PSImageHandlerRenderedImage
org.apache.fop.render.ps.PSImageHandlerEPS
org.apache.fop.render.ps.PSImageHandlerRawCCITTFax
org.apache.fop.render.ps.PSImageHandlerRawJPEG
org.apache.fop.render.ps.PSImageHandlerRawPNG
org.apache.fop.render.ps.PSImageHandlerGraphics2D
org.apache.fop.render.ps.PSImageHandlerSVG
org.apache.fop.render.afp.AFPImageHandlerRenderedImage

+ 21
- 20
src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java View File

@@ -31,6 +31,7 @@ import org.apache.commons.logging.LogFactory;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFDirTabEntry;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.fonts.truetype.TTFTableName;

// CSOFF: AvoidNestedBlocksCheck
// CSOFF: NoWhitespaceAfterCheck
@@ -126,7 +127,7 @@ public final class OTFAdvancedTypographicTableReader {
return gpos;
}

private void readLangSysTable(String tableTag, long langSysTable, String langSysTag) throws IOException {
private void readLangSysTable(TTFTableName tableTag, long langSysTable, String langSysTag) throws IOException {
in.seekSet(langSysTable);
if (log.isDebugEnabled()) {
log.debug(tableTag + " lang sys table: " + langSysTag );
@@ -168,7 +169,7 @@ public final class OTFAdvancedTypographicTableReader {

private static String defaultTag = "dflt";

private void readScriptTable(String tableTag, long scriptTable, String scriptTag) throws IOException {
private void readScriptTable(TTFTableName tableTag, long scriptTable, String scriptTag) throws IOException {
in.seekSet(scriptTable);
if (log.isDebugEnabled()) {
log.debug(tableTag + " script table: " + scriptTag );
@@ -221,7 +222,7 @@ public final class OTFAdvancedTypographicTableReader {
seLanguages = null;
}

private void readScriptList(String tableTag, long scriptList) throws IOException {
private void readScriptList(TTFTableName tableTag, long scriptList) throws IOException {
in.seekSet(scriptList);
// read script record count
int ns = in.readTTFUShort();
@@ -250,7 +251,7 @@ public final class OTFAdvancedTypographicTableReader {
}
}

private void readFeatureTable(String tableTag, long featureTable, String featureTag, int featureIndex) throws IOException {
private void readFeatureTable(TTFTableName tableTag, long featureTable, String featureTag, int featureIndex) throws IOException {
in.seekSet(featureTable);
if (log.isDebugEnabled()) {
log.debug(tableTag + " feature table: " + featureTag );
@@ -278,7 +279,7 @@ public final class OTFAdvancedTypographicTableReader {
seFeatures.put ( "f" + featureIndex, new Object[] { featureTag, lul } );
}

private void readFeatureList(String tableTag, long featureList) throws IOException {
private void readFeatureList(TTFTableName tableTag, long featureList) throws IOException {
in.seekSet(featureList);
// read feature record count
int nf = in.readTTFUShort();
@@ -3144,9 +3145,9 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}

private void readLookupTable(String tableTag, int lookupSequence, long lookupTable) throws IOException {
boolean isGSUB = tableTag.equals ( "GSUB" );
boolean isGPOS = tableTag.equals ( "GPOS" );
private void readLookupTable(TTFTableName tableTag, int lookupSequence, long lookupTable) throws IOException {
boolean isGSUB = tableTag.equals ( TTFTableName.GSUB );
boolean isGPOS = tableTag.equals ( TTFTableName.GPOS );
in.seekSet(lookupTable);
// read lookup type
int lt = in.readTTFUShort();
@@ -3197,7 +3198,7 @@ public final class OTFAdvancedTypographicTableReader {
}
}

private void readLookupList(String tableTag, long lookupList) throws IOException {
private void readLookupList(TTFTableName tableTag, long lookupList) throws IOException {
in.seekSet(lookupList);
// read lookup record count
int nl = in.readTTFUShort();
@@ -3232,7 +3233,7 @@ public final class OTFAdvancedTypographicTableReader {
* @param lookupList offset to lookup list from beginning of font file
* @throws IOException In case of a I/O problem
*/
private void readCommonLayoutTables(String tableTag, long scriptList, long featureList, long lookupList) throws IOException {
private void readCommonLayoutTables(TTFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException {
if ( scriptList > 0 ) {
readScriptList ( tableTag, scriptList );
}
@@ -3244,7 +3245,7 @@ public final class OTFAdvancedTypographicTableReader {
}
}

private void readGDEFClassDefTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
private void readGDEFClassDefTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// subtable is a bare class definition table
@@ -3256,7 +3257,7 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}

private void readGDEFAttachmentTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
private void readGDEFAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// read coverage offset
@@ -3274,7 +3275,7 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}

private void readGDEFLigatureCaretTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
private void readGDEFLigatureCaretTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// read coverage offset
@@ -3304,7 +3305,7 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}

private void readGDEFMarkAttachmentTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
private void readGDEFMarkAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// subtable is a bare class definition table
@@ -3316,7 +3317,7 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}

private void readGDEFMarkGlyphsTableFormat1(String tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException {
private void readGDEFMarkGlyphsTableFormat1(TTFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// skip over format (already known)
@@ -3350,7 +3351,7 @@ public final class OTFAdvancedTypographicTableReader {
resetATSubState();
}

private void readGDEFMarkGlyphsTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
private void readGDEFMarkGlyphsTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read mark set subtable format
int sf = in.readTTFUShort();
@@ -3366,11 +3367,11 @@ public final class OTFAdvancedTypographicTableReader {
* @throws IOException In case of a I/O problem
*/
private void readGDEF() throws IOException {
String tableTag = "GDEF";
TTFTableName tableTag = TTFTableName.GDEF;
// Initialize temporary state
initATState();
// Read glyph definition (GDEF) table
TTFDirTabEntry dirTab = ttf.getDirectoryEntry ( tableTag );
TTFDirTabEntry dirTab = ttf.getDirectoryEntry( tableTag );
if ( gdef != null ) {
if (log.isDebugEnabled()) {
log.debug(tableTag + ": ignoring duplicate table");
@@ -3439,7 +3440,7 @@ public final class OTFAdvancedTypographicTableReader {
* @throws IOException In case of a I/O problem
*/
private void readGSUB() throws IOException {
String tableTag = "GSUB";
TTFTableName tableTag = TTFTableName.GSUB;
// Initialize temporary state
initATState();
// Read glyph substitution (GSUB) table
@@ -3476,7 +3477,7 @@ public final class OTFAdvancedTypographicTableReader {
* @throws IOException In case of a I/O problem
*/
private void readGPOS() throws IOException {
String tableTag = "GPOS";
TTFTableName tableTag = TTFTableName.GPOS;
// Initialize temporary state
initATState();
// Read glyph positioning (GPOS) table

+ 3
- 2
src/java/org/apache/fop/fo/properties/CondLengthProperty.java View File

@@ -26,6 +26,7 @@ import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.expr.PropertyException;
import org.apache.fop.util.CompareUtil;

/**
* Superclass for properties that have conditional lengths
@@ -192,8 +193,8 @@ public class CondLengthProperty extends Property implements CompoundDatatype {

if (obj instanceof CondLengthProperty) {
CondLengthProperty clp = (CondLengthProperty)obj;
return (this.length == clp.length
&& this.conditionality == clp.conditionality);
return (CompareUtil.equal(this.length, clp.length)
&& CompareUtil.equal(this.conditionality, clp.conditionality));
}
return false;
}

+ 1
- 1
src/java/org/apache/fop/fonts/CIDFontType.java View File

@@ -34,7 +34,7 @@ public class CIDFontType extends ValuedEnum {
/**
* CID Font Type 2 (based on TrueType format)
*/
public static final CIDFontType CIDTYPE2 = new CIDFontType("CIDFontType2", 1);
public static final CIDFontType CIDTYPE2 = new CIDFontType("CIDFontType2", 2);


/**

src/java/org/apache/fop/fonts/BFEntry.java → src/java/org/apache/fop/fonts/CMapSegment.java View File

@@ -20,26 +20,49 @@
package org.apache.fop.fonts;

/**
* This is just a holder class for bfentries, groups of characters of a base font (bf).
* A segment in a cmap table of format 4. Unicode code points between
* {@link #getUnicodeStart()} and {@link #getUnicodeEnd()} map to contiguous glyph indices
* starting from {@link #getGlyphStartIndex()}.
*/
public class BFEntry {
public final class CMapSegment {

private int unicodeStart;
private int unicodeEnd;
private int glyphStartIndex;
private final int unicodeStart;
private final int unicodeEnd;
private final int glyphStartIndex;

/**
* Main constructor.
* Creates a new segment.
*
* @param unicodeStart Unicode start index
* @param unicodeEnd Unicode end index
* @param glyphStartIndex glyph start index
*/
public BFEntry(int unicodeStart, int unicodeEnd, int glyphStartIndex) {
public CMapSegment(int unicodeStart, int unicodeEnd, int glyphStartIndex) {
this.unicodeStart = unicodeStart;
this.unicodeEnd = unicodeEnd;
this.glyphStartIndex = glyphStartIndex;
}

@Override
public int hashCode() {
int hc = 17;
hc = 31 * hc + unicodeStart;
hc = 31 * hc + unicodeEnd;
hc = 31 * hc + glyphStartIndex;
return hc;
}

@Override
public boolean equals(Object o) {
if (o instanceof CMapSegment) {
CMapSegment ce = (CMapSegment) o;
return ce.unicodeStart == this.unicodeStart
&& ce.unicodeEnd == this.unicodeEnd
&& ce.glyphStartIndex == this.glyphStartIndex;
}
return false;
}

/**
* Returns the unicodeStart.
* @return the Unicode start index
@@ -67,7 +90,7 @@ public class BFEntry {
/** {@inheritDoc} */
@Override
public String toString() {
StringBuilder sb = new StringBuilder("BFEntry: ");
StringBuilder sb = new StringBuilder("CMapSegment: ");
sb.append ( "{ UC[" );
sb.append ( unicodeStart );
sb.append ( ',' );

+ 40
- 0
src/java/org/apache/fop/fonts/CustomFont.java View File

@@ -42,6 +42,7 @@ public abstract class CustomFont extends Typeface
private String embedFileName = null;
private String embedResourceName = null;
private FontResolver resolver = null;
private EmbeddingMode embeddingMode = EmbeddingMode.AUTO;

private int capHeight = 0;
private int xHeight = 0;
@@ -62,6 +63,9 @@ public abstract class CustomFont extends Typeface
private boolean useKerning = true;
private boolean useAdvanced = true;

/** the character map, mapping Unicode ranges to glyph indices. */
protected CMapSegment[] cmap;

/** {@inheritDoc} */
public String getFontName() {
return fontName;
@@ -111,6 +115,14 @@ public abstract class CustomFont extends Typeface
return embedFileName;
}

/**
* Returns the embedding mode for this font.
* @return embedding mode
*/
public EmbeddingMode getEmbeddingMode() {
return embeddingMode;
}

/**
* Returns a Source representing an embeddable font file.
* @return Source for an embeddable font file
@@ -334,6 +346,13 @@ public abstract class CustomFont extends Typeface
this.embedResourceName = name;
}

/**
* {@inheritDoc}
*/
public void setEmbeddingMode(EmbeddingMode embeddingMode) {
this.embeddingMode = embeddingMode;
}

/**
* {@inheritDoc}
*/
@@ -473,4 +492,25 @@ public abstract class CustomFont extends Typeface
}
}

/**
* Sets the character map for this font. It maps all available Unicode characters
* to their glyph indices inside the font.
* @param cmap the character map
*/
public void setCMap(CMapSegment[] cmap) {
this.cmap = new CMapSegment[cmap.length];
System.arraycopy(cmap, 0, this.cmap, 0, cmap.length);
}

/**
* Returns the character map for this font. It maps all available Unicode characters
* to their glyph indices inside the font.
* @return the character map
*/
public CMapSegment[] getCMap() {
CMapSegment[] copy = new CMapSegment[cmap.length];
System.arraycopy(this.cmap, 0, copy, 0, this.cmap.length);
return copy;
}

}

+ 1
- 1
src/java/org/apache/fop/fonts/CustomFontCollection.java View File

@@ -72,7 +72,7 @@ public class CustomFontCollection implements FontCollection {

List<FontTriplet> triplets = embedFontInfo.getFontTriplets();
for (int tripletIndex = 0; tripletIndex < triplets.size(); tripletIndex++) {
FontTriplet triplet = (FontTriplet) triplets.get(tripletIndex);
FontTriplet triplet = triplets.get(tripletIndex);
fontInfo.addFontProperties(internalName, triplet);
}
}

+ 23
- 0
src/java/org/apache/fop/fonts/EmbedFontInfo.java View File

@@ -25,6 +25,8 @@ import java.util.List;

/**
* FontInfo contains meta information on fonts (where is the metrics file etc.)
* TODO: We need to remove this class and think about more intelligent design patterns
* (Data classes => Procedural code)
*/
public class EmbedFontInfo implements Serializable {

@@ -41,6 +43,8 @@ public class EmbedFontInfo implements Serializable {
protected boolean advanced;
/** the requested encoding mode for the font */
protected EncodingMode encodingMode = EncodingMode.AUTO;
/** the requested embedding mode for this font */
protected EmbeddingMode embeddingMode = EmbeddingMode.AUTO;

/** the PostScript name of the font */
protected String postScriptName = null;
@@ -148,6 +152,14 @@ public class EmbedFontInfo implements Serializable {
}
}

/**
* Returns the embedding mode for this font.
* @return the embedding mode.
*/
public EmbeddingMode getEmbeddingMode() {
return embeddingMode;
}

/**
* Defines whether the font is embedded or not.
* @param value true to embed the font, false to reference it
@@ -175,6 +187,17 @@ public class EmbedFontInfo implements Serializable {
this.encodingMode = mode;
}

/**
* Sets the embedding mode for this font, currently not supported for Type 1 fonts.
* @param embeddingMode the new embedding mode.
*/
public void setEmbeddingMode(EmbeddingMode embeddingMode) {
if (embeddingMode == null) {
throw new NullPointerException("embeddingMode must not be null");
}
this.embeddingMode = embeddingMode;
}

private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();

+ 58
- 0
src/java/org/apache/fop/fonts/EmbeddingMode.java View File

@@ -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.fonts;

import java.util.Locale;

/**
* This enumerates the embedding mode of fonts; full; subset; auto (auto defaults to full for
* Type 1 fonts and subset for TrueType fonts.
*/
public enum EmbeddingMode {
/** Default option: assumes FULL for Type 1 fonts and SUBSET for TrueType fonts. */
AUTO,
/** Full font embedding: This means the whole of the font is written to file. */
FULL,
/** Subset font embedding: Only the mandatory tables and a subset of glyphs are written
* to file.*/
SUBSET;

/**
* Returns the name of this embedding mode.
* @return the name of this embedding mode in lower case.
*/
public String getName() {
return this.toString().toLowerCase(Locale.ENGLISH);
}

/**
* Returns the embedding mode corresponding to the given name.
* @param value the name of an embedding mode (not case sensitive)
* @return the corresponding embedding mode
*/
public static EmbeddingMode getValue(String value) {
for (EmbeddingMode mode : EmbeddingMode.values()) {
if (mode.toString().equalsIgnoreCase(value)) {
return mode;
}
}
throw new IllegalArgumentException("Invalid embedding-mode: " + value);
}
}

+ 1
- 1
src/java/org/apache/fop/fonts/EncodingMode.java View File

@@ -52,7 +52,7 @@ public enum EncodingMode {
* @param name the name of the encoding mode to look up
* @return the encoding mode constant
*/
public static EncodingMode getEncodingMode(String name) {
public static EncodingMode getValue(String name) {
for (EncodingMode em : EncodingMode.values()) {
if (name.equalsIgnoreCase(em.getName())) {
return em;

+ 5
- 1
src/java/org/apache/fop/fonts/FontInfoConfigurator.java View File

@@ -254,12 +254,16 @@ public class FontInfoConfigurator {

boolean useKerning = fontCfg.getAttributeAsBoolean("kerning", true);
boolean useAdvanced = fontCfg.getAttributeAsBoolean("advanced", true);
EncodingMode encodingMode = EncodingMode.getEncodingMode(
EncodingMode encodingMode = EncodingMode.getValue(
fontCfg.getAttribute("encoding-mode", EncodingMode.AUTO.getName()));
EmbeddingMode embeddingMode = EmbeddingMode.getValue(
fontCfg.getAttribute("embedding-mode", EmbeddingMode.AUTO.toString()));
EmbedFontInfo embedFontInfo
= new EmbedFontInfo(metricsUrl, useKerning, useAdvanced, tripletList, embedUrl,
subFont);
embedFontInfo.setEncodingMode(encodingMode);
embedFontInfo.setEmbeddingMode(embeddingMode);

boolean skipCachedFont = false;
if (fontCache != null) {
if (!fontCache.containsFont(embedFontInfo)) {

+ 15
- 7
src/java/org/apache/fop/fonts/FontLoader.java View File

@@ -85,15 +85,17 @@ public abstract class FontLoader {
* @param fontFile the File representation of the font
* @param subFontName the sub-fontname of a font (for TrueType Collections, null otherwise)
* @param embedded indicates whether the font is embedded or referenced
* @param embeddingMode the embedding mode
* @param encodingMode the requested encoding mode
* @param resolver the font resolver to use when resolving URIs
* @return the newly loaded font
* @throws IOException In case of an I/O error
*/
public static CustomFont loadFont(File fontFile, String subFontName,
boolean embedded, EncodingMode encodingMode, FontResolver resolver) throws IOException {
boolean embedded, EmbeddingMode embeddingMode, EncodingMode encodingMode,
FontResolver resolver) throws IOException {
return loadFont(fontFile.toURI().toURL(), subFontName,
embedded, encodingMode, resolver);
embedded, embeddingMode, encodingMode, resolver);
}

/**
@@ -101,16 +103,17 @@ public abstract class FontLoader {
* @param fontUrl the URL representation of the font
* @param subFontName the sub-fontname of a font (for TrueType Collections, null otherwise)
* @param embedded indicates whether the font is embedded or referenced
* @param embeddingMode the embedding mode of the font
* @param encodingMode the requested encoding mode
* @param resolver the font resolver to use when resolving URIs
* @return the newly loaded font
* @throws IOException In case of an I/O error
*/
public static CustomFont loadFont(URL fontUrl, String subFontName,
boolean embedded, EncodingMode encodingMode,
boolean embedded, EmbeddingMode embeddingMode, EncodingMode encodingMode,
FontResolver resolver) throws IOException {
return loadFont(fontUrl.toExternalForm(), subFontName,
embedded, encodingMode, true, true,
embedded, embeddingMode, encodingMode, true, true,
resolver);
}

@@ -119,6 +122,7 @@ public abstract class FontLoader {
* @param fontFileURI the URI to the font
* @param subFontName the sub-fontname of a font (for TrueType Collections, null otherwise)
* @param embedded indicates whether the font is embedded or referenced
* @param embeddingMode the embedding mode of the font
* @param encodingMode the requested encoding mode
* @param useKerning indicates whether kerning information should be loaded if available
* @param useAdvanced indicates whether advanced typographic information shall be loaded if
@@ -128,8 +132,8 @@ public abstract class FontLoader {
* @throws IOException In case of an I/O error
*/
public static CustomFont loadFont(String fontFileURI, String subFontName,
boolean embedded, EncodingMode encodingMode, boolean useKerning,
boolean useAdvanced, FontResolver resolver) throws IOException {
boolean embedded, EmbeddingMode embeddingMode, EncodingMode encodingMode,
boolean useKerning, boolean useAdvanced, FontResolver resolver) throws IOException {
fontFileURI = fontFileURI.trim();
boolean type1 = isType1(fontFileURI);
FontLoader loader;
@@ -138,10 +142,14 @@ public abstract class FontLoader {
throw new IllegalArgumentException(
"CID encoding mode not supported for Type 1 fonts");
}
if (embeddingMode == EmbeddingMode.SUBSET) {
throw new IllegalArgumentException(
"Subset embedding for Type 1 fonts is not supported");
}
loader = new Type1FontLoader(fontFileURI, embedded, useKerning, resolver);
} else {
loader = new TTFFontLoader(fontFileURI, subFontName,
embedded, encodingMode, useKerning, useAdvanced, resolver);
embedded, embeddingMode, encodingMode, useKerning, useAdvanced, resolver);
}
return loader.getFont();
}

+ 9
- 4
src/java/org/apache/fop/fonts/FontReader.java View File

@@ -64,7 +64,7 @@ public class FontReader extends DefaultHandler {

private Map<Integer, Integer> currentKerning = null;

private List<BFEntry> bfranges = null;
private List<CMapSegment> bfranges = null;

private void createFont(InputSource source) throws FOPException {
XMLReader parser = null;
@@ -154,12 +154,14 @@ public class FontReader extends DefaultHandler {
/**
* {@inheritDoc}
*/
@Override
public void startDocument() {
}

/**
* {@inheritDoc}
*/
@Override
public void setDocumentLocator(Locator locator) {
// this.locator = locator; // not used at present
}
@@ -167,6 +169,7 @@ public class FontReader extends DefaultHandler {
/**
* {@inheritDoc}
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if (localName.equals("font-metrics")) {
@@ -198,9 +201,9 @@ public class FontReader extends DefaultHandler {
returnFont.putKerningEntry(new Integer(attributes.getValue("kpx1")),
currentKerning);
} else if ("bfranges".equals(localName)) {
bfranges = new ArrayList<BFEntry>();
bfranges = new ArrayList<CMapSegment>();
} else if ("bf".equals(localName)) {
BFEntry entry = new BFEntry(getInt(attributes.getValue("us")),
CMapSegment entry = new CMapSegment(getInt(attributes.getValue("us")),
getInt(attributes.getValue("ue")),
getInt(attributes.getValue("gi")));
bfranges.add(entry);
@@ -236,6 +239,7 @@ public class FontReader extends DefaultHandler {
/**
* {@inheritDoc}
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
String content = text.toString().trim();
if ("font-name".equals(localName)) {
@@ -303,7 +307,7 @@ public class FontReader extends DefaultHandler {
multiFont.setWidthArray(wds);

} else if ("bfranges".equals(localName)) {
multiFont.setBFEntries(bfranges.toArray(new BFEntry[0]));
multiFont.setCMap(bfranges.toArray(new CMapSegment[0]));
}
text.setLength(0); //Reset text buffer (see characters())
}
@@ -311,6 +315,7 @@ public class FontReader extends DefaultHandler {
/**
* {@inheritDoc}
*/
@Override
public void characters(char[] ch, int start, int length) {
text.append(ch, start, length);
}

+ 5
- 0
src/java/org/apache/fop/fonts/FontType.java View File

@@ -130,4 +130,9 @@ public class FontType {
return value;
}

@Override
public String toString() {
return name;
}

}

+ 6
- 4
src/java/org/apache/fop/fonts/LazyFont.java View File

@@ -36,7 +36,6 @@ import org.apache.fop.apps.FOPException;
import org.apache.fop.complexscripts.fonts.Positionable;
import org.apache.fop.complexscripts.fonts.Substitutable;


/**
* This class is used to defer the loading of a font until it is really used.
*/
@@ -49,7 +48,8 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable,
private boolean useKerning;
private boolean useAdvanced;
private EncodingMode encodingMode = EncodingMode.AUTO;
private boolean embedded;
private EmbeddingMode embeddingMode = EmbeddingMode.AUTO;
private boolean embedded = true;
private String subFontName;

private boolean isMetricsLoaded;
@@ -74,6 +74,7 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable,
this.useAdvanced = fontInfo.getAdvanced();
}
this.encodingMode = fontInfo.getEncodingMode();
this.embeddingMode = fontInfo.getEmbeddingMode();
this.subFontName = fontInfo.getSubFontName();
this.embedded = fontInfo.isEmbedded();
this.resolver = resolver;
@@ -147,8 +148,9 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable,
if (fontEmbedPath == null) {
throw new RuntimeException("Cannot load font. No font URIs available.");
}
realFont = FontLoader.loadFont(fontEmbedPath, this.subFontName,
this.embedded, this.encodingMode, useKerning, useAdvanced, resolver);
realFont = FontLoader.loadFont(fontEmbedPath, subFontName,
embedded, embeddingMode, encodingMode,
useKerning, useAdvanced, resolver);
}
if (realFont instanceof FontDescriptor) {
realFontDescriptor = (FontDescriptor) realFont;

+ 27
- 35
src/java/org/apache/fop/fonts/MultiByteFont.java View File

@@ -51,14 +51,6 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl

private CIDSubset subset = new CIDSubset();

/**
* A map from Unicode indices to glyph indices. No assumption
* about ordering is made below. If lookup is changed to a binary
* search (from the current linear search), then addPrivateUseMapping()
* needs to be changed to perform ordered inserts.
*/
private BFEntry[] bfentries = null;

/* advanced typographic support */
private GlyphDefinitionTable gdef;
private GlyphSubstitutionTable gsub;
@@ -82,26 +74,31 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
}

/** {@inheritDoc} */
@Override
public int getDefaultWidth() {
return defaultWidth;
}

/** {@inheritDoc} */
@Override
public String getRegistry() {
return "Adobe";
}

/** {@inheritDoc} */
@Override
public String getOrdering() {
return "UCS";
}

/** {@inheritDoc} */
@Override
public int getSupplement() {
return 0;
}

/** {@inheritDoc} */
@Override
public CIDFontType getCIDType() {
return cidType;
}
@@ -115,6 +112,7 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
}

/** {@inheritDoc} */
@Override
public String getEmbedFontName() {
if (isEmbeddable()) {
return FontUtil.stripWhiteSpace(super.getFontName());
@@ -128,17 +126,18 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
return !(getEmbedFileName() == null && getEmbedResourceName() == null);
}

/** {@inheritDoc} */
public boolean isSubsetEmbedded() {
return true;
}

/** {@inheritDoc} */
@Override
public CIDSubset getCIDSubset() {
return this.subset;
}

/** {@inheritDoc} */
@Override
public String getEncodingName() {
return encoding;
}
@@ -171,30 +170,30 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
int idx = c;
int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT;

for (int i = 0; (i < bfentries.length) && retIdx == 0; i++) {
if (bfentries[i].getUnicodeStart() <= idx
&& bfentries[i].getUnicodeEnd() >= idx) {
for (int i = 0; (i < cmap.length) && retIdx == 0; i++) {
if (cmap[i].getUnicodeStart() <= idx
&& cmap[i].getUnicodeEnd() >= idx) {

retIdx = bfentries[i].getGlyphStartIndex()
retIdx = cmap[i].getGlyphStartIndex()
+ idx
- bfentries[i].getUnicodeStart();
- cmap[i].getUnicodeStart();
}
}
return retIdx;
}

/**
* Add a private use mapping {PU,GI} to the existing BFENTRIES map.
* Add a private use mapping {PU,GI} to the existing character map.
* N.B. Does not insert in order, merely appends to end of existing map.
*/
private synchronized void addPrivateUseMapping ( int pu, int gi ) {
assert findGlyphIndex ( pu ) == SingleByteEncoding.NOT_FOUND_CODE_POINT;
BFEntry[] bfeOld = bfentries;
int bfeCnt = bfeOld.length;
BFEntry[] bfeNew = new BFEntry [ bfeCnt + 1 ];
System.arraycopy ( bfeOld, 0, bfeNew, 0, bfeCnt );
bfeNew [ bfeCnt ] = new BFEntry ( pu, pu, gi );
bfentries = bfeNew;
CMapSegment[] oldCmap = cmap;
int cmapLength = oldCmap.length;
CMapSegment[] newCmap = new CMapSegment [ cmapLength + 1 ];
System.arraycopy ( oldCmap, 0, newCmap, 0, cmapLength );
newCmap [ cmapLength ] = new CMapSegment ( pu, pu, gi );
cmap = newCmap;
}

/**
@@ -252,12 +251,12 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
// [TBD] - needs optimization, i.e., change from linear search to binary search
private int findCharacterFromGlyphIndex ( int gi, boolean augment ) {
int cc = 0;
for ( int i = 0, n = bfentries.length; i < n; i++ ) {
BFEntry be = bfentries [ i ];
int s = be.getGlyphStartIndex();
int e = s + ( be.getUnicodeEnd() - be.getUnicodeStart() );
for ( int i = 0, n = cmap.length; i < n; i++ ) {
CMapSegment segment = cmap [ i ];
int s = segment.getGlyphStartIndex();
int e = s + ( segment.getUnicodeEnd() - segment.getUnicodeStart() );
if ( ( gi >= s ) && ( gi <= e ) ) {
cc = be.getUnicodeStart() + ( gi - s );
cc = segment.getUnicodeStart() + ( gi - s );
break;
}
}
@@ -273,6 +272,7 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl


/** {@inheritDoc} */
@Override
public char mapChar(char c) {
notifyMapOperation();
int glyphIndex = findGlyphIndex(c);
@@ -287,19 +287,11 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl
}

/** {@inheritDoc} */
@Override
public boolean hasChar(char c) {
return (findGlyphIndex(c) != SingleByteEncoding.NOT_FOUND_CODE_POINT);
}

/**
* Sets the array of BFEntry instances which constitutes the Unicode to glyph index map for
* a font. ("BF" means "base font")
* @param entries the Unicode to glyph index map
*/
public void setBFEntries(BFEntry[] entries) {
this.bfentries = entries;
}

/**
* Sets the defaultWidth.
* @param defaultWidth The defaultWidth to set

+ 6
- 0
src/java/org/apache/fop/fonts/MutableFont.java View File

@@ -60,6 +60,12 @@ public interface MutableFont {
*/
void setEmbedResourceName(String name);

/**
* Sets the embedding mode.
* @param embeddingMode the embedding mode
*/
void setEmbeddingMode(EmbeddingMode embeddingMode);

/**
* Sets the capital height value.
* @param capHeight capital height

+ 24
- 0
src/java/org/apache/fop/fonts/SingleByteFont.java View File

@@ -31,6 +31,8 @@ import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.fonts.Glyphs;

import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion;

/**
* Generic SingleByte font
*/
@@ -48,6 +50,7 @@ public class SingleByteFont extends CustomFont {
private List<SimpleSingleByteEncoding> additionalEncodings;
private Map<Character, Character> alternativeCodes;

private PostScriptVersion ttPostScriptVersion;

/**
* Main constructor.
@@ -397,5 +400,26 @@ public class SingleByteFont extends CustomFont {
}
}

/**
* Sets the version of the PostScript table stored in the TrueType font represented by
* this instance.
*
* @param version version of the <q>post</q> table
*/
public void setTrueTypePostScriptVersion(PostScriptVersion version) {
ttPostScriptVersion = version;
}

/**
* Returns the version of the PostScript table stored in the TrueType font represented by
* this instance.
*
* @return the version of the <q>post</q> table
*/
public PostScriptVersion getTrueTypePostScriptVersion() {
assert getFontType() == FontType.TRUETYPE;
return ttPostScriptVersion;
}

}


+ 12
- 18
src/java/org/apache/fop/fonts/apps/TTFReader.java View File

@@ -20,7 +20,6 @@
package org.apache.fop.fonts.apps;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

@@ -34,9 +33,9 @@ import org.xml.sax.SAXException;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.Version;
import org.apache.fop.fonts.CMapSegment;
import org.apache.fop.fonts.FontUtil;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFCmapEntry;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.util.CommandLineLogger;

@@ -288,9 +287,9 @@ public class TTFReader extends AbstractFontReader {
root.appendChild(el);
el.appendChild(doc.createTextNode(ttf.getFullName()));
}
Set familyNames = ttf.getFamilyNames();
Set<String> familyNames = ttf.getFamilyNames();
if (familyNames.size() > 0) {
String familyName = (String)familyNames.iterator().next();
String familyName = familyNames.iterator().next();
el = doc.createElement("family-name");
root.appendChild(el);
el.appendChild(doc.createTextNode(familyName));
@@ -386,9 +385,7 @@ public class TTFReader extends AbstractFontReader {

el = doc.createElement("bfranges");
mel.appendChild(el);
Iterator iter = ttf.getCMaps().listIterator();
while (iter.hasNext()) {
TTFCmapEntry ce = (TTFCmapEntry)iter.next();
for (CMapSegment ce : ttf.getCMaps()) {
Element el2 = doc.createElement("bf");
el.appendChild(el2);
el2.setAttribute("us", String.valueOf(ce.getUnicodeStart()));
@@ -443,31 +440,28 @@ public class TTFReader extends AbstractFontReader {
Document doc = parent.getOwnerDocument();

// Get kerning
Iterator iter;
Set<Integer> kerningKeys;
if (isCid) {
iter = ttf.getKerning().keySet().iterator();
kerningKeys = ttf.getKerning().keySet();
} else {
iter = ttf.getAnsiKerning().keySet().iterator();
kerningKeys = ttf.getAnsiKerning().keySet();
}

while (iter.hasNext()) {
Integer kpx1 = (Integer)iter.next();
for (Integer kpx1 : kerningKeys) {

el = doc.createElement("kerning");
el.setAttribute("kpx1", kpx1.toString());
parent.appendChild(el);
Element el2 = null;

Map h2;
Map<Integer, Integer> h2;
if (isCid) {
h2 = (Map)ttf.getKerning().get(kpx1);
h2 = ttf.getKerning().get(kpx1);
} else {
h2 = (Map)ttf.getAnsiKerning().get(kpx1);
h2 = ttf.getAnsiKerning().get(kpx1);
}

Iterator iter2 = h2.keySet().iterator();
while (iter2.hasNext()) {
Integer kpx2 = (Integer)iter2.next();
for (Integer kpx2 : h2.keySet()) {
if (isCid || kpx2.intValue() < 256) {
el2 = doc.createElement("pair");
el2.setAttribute("kpx2", kpx2.toString());

+ 4
- 2
src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java View File

@@ -32,6 +32,7 @@ import org.apache.commons.logging.LogFactory;

import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.EmbedFontInfo;
import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.EncodingMode;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontCache;
@@ -222,7 +223,7 @@ public class FontInfoFinder {
}
try {
TTFFontLoader ttfLoader = new TTFFontLoader(
fontFileURL, fontName, true, EncodingMode.AUTO,
fontFileURL, fontName, true, EmbeddingMode.AUTO, EncodingMode.AUTO,
useKerning, useAdvanced, resolver);
customFont = ttfLoader.getFont();
if (this.eventListener != null) {
@@ -247,7 +248,8 @@ public class FontInfoFinder {
} else {
// The normal case
try {
customFont = FontLoader.loadFont(fontURL, null, true, EncodingMode.AUTO, resolver);
customFont = FontLoader.loadFont(fontURL, null, true, EmbeddingMode.AUTO,
EncodingMode.AUTO, resolver);
if (this.eventListener != null) {
customFont.setEventListener(this.eventListener);
}

+ 12
- 17
src/java/org/apache/fop/fonts/truetype/FontFileReader.java View File

@@ -89,16 +89,6 @@ public class FontFileReader {
current = (int)offset;
}

/**
* Set current file position to offset
*
* @param add The number of bytes to advance
* @throws IOException In case of an I/O problem
*/
public void seekAdd(long add) throws IOException {
seekSet(current + add);
}

/**
* Skip a given number of bytes.
*
@@ -106,7 +96,7 @@ public class FontFileReader {
* @throws IOException In case of an I/O problem
*/
public void skip(long add) throws IOException {
seekAdd(add);
seekSet(current + add);
}

/**
@@ -133,7 +123,7 @@ public class FontFileReader {
* @return One byte
* @throws IOException If EOF is reached
*/
public byte read() throws IOException {
private byte read() throws IOException {
if (current >= fsize) {
throw new java.io.EOFException("Reached EOF, file size=" + fsize);
}
@@ -278,14 +268,14 @@ public class FontFileReader {
public final String readTTFString() throws IOException {
int i = current;
while (file[i++] != 0) {
if (i > fsize) {
if (i >= fsize) {
throw new java.io.EOFException("Reached EOF, file size="
+ fsize);
}
}

byte[] tmp = new byte[i - current];
System.arraycopy(file, current, tmp, 0, i - current);
byte[] tmp = new byte[i - current - 1];
System.arraycopy(file, current, tmp, 0, i - current - 1);
return new String(tmp, "ISO-8859-1");
}

@@ -353,6 +343,11 @@ public class FontFileReader {
System.arraycopy(file, offset, ret, 0, length);
return ret;
}


/**
* Returns the full byte array representation of the file.
* @return byte array.
*/
public byte[] getAllBytes() {
return file;
}
}

+ 0
- 118
src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java View File

@@ -1,118 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.fonts.truetype;

/**
* The CMap entry contains information of a Unicode range and the
* the glyph indexes related to the range
*/
public class TTFCmapEntry {

private int unicodeStart;
private int unicodeEnd;
private int glyphStartIndex;

TTFCmapEntry() {
unicodeStart = 0;
unicodeEnd = 0;
glyphStartIndex = 0;
}

TTFCmapEntry(int unicodeStart, int unicodeEnd, int glyphStartIndex) {
this.unicodeStart = unicodeStart;
this.unicodeEnd = unicodeEnd;
this.glyphStartIndex = glyphStartIndex;
}

/**
* {@inheritDoc}
*/
public int hashCode() {
int hc = super.hashCode();
hc ^= ( hc * 11 ) + unicodeStart;
hc ^= ( hc * 19 ) + unicodeEnd;
hc ^= ( hc * 23 ) + glyphStartIndex;
return hc;
}

/**
* {@inheritDoc}
*/
public boolean equals(Object o) {
if (o instanceof TTFCmapEntry) {
TTFCmapEntry ce = (TTFCmapEntry)o;
if (ce.unicodeStart == this.unicodeStart
&& ce.unicodeEnd == this.unicodeEnd
&& ce.glyphStartIndex == this.glyphStartIndex) {
return true;
}
}
return false;
}

/**
* Returns the glyphStartIndex.
* @return int
*/
public int getGlyphStartIndex() {
return glyphStartIndex;
}

/**
* Returns the unicodeEnd.
* @return int
*/
public int getUnicodeEnd() {
return unicodeEnd;
}

/**
* Returns the unicodeStart.
* @return int
*/
public int getUnicodeStart() {
return unicodeStart;
}

/**
* Sets the glyphStartIndex.
* @param glyphStartIndex The glyphStartIndex to set
*/
public void setGlyphStartIndex(int glyphStartIndex) {
this.glyphStartIndex = glyphStartIndex;
}

/**
* Sets the unicodeEnd.
* @param unicodeEnd The unicodeEnd to set
*/
public void setUnicodeEnd(int unicodeEnd) {
this.unicodeEnd = unicodeEnd;
}

/**
* Sets the unicodeStart.
* @param unicodeStart The unicodeStart to set
*/
public void setUnicodeStart(int unicodeStart) {
this.unicodeStart = unicodeStart;
}

}

+ 8
- 0
src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java View File

@@ -33,6 +33,14 @@ public class TTFDirTabEntry {
private long offset;
private long length;

public TTFDirTabEntry() {
}

public TTFDirTabEntry(long offset, long length) {
this.offset = offset;
this.length = length;
}

/**
* Read Dir Tab.
* @param in font file reader

+ 452
- 257
src/java/org/apache/fop/fonts/truetype/TTFFile.java
File diff suppressed because it is too large
View File


+ 33
- 32
src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java View File

@@ -21,17 +21,14 @@ package org.apache.fop.fonts.truetype;

import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.IOUtils;

import org.apache.xmlgraphics.fonts.Glyphs;

import org.apache.fop.fonts.BFEntry;
import org.apache.fop.fonts.CIDFontType;
import org.apache.fop.fonts.CMapSegment;
import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.EncodingMode;
import org.apache.fop.fonts.FontLoader;
import org.apache.fop.fonts.FontResolver;
@@ -39,6 +36,8 @@ import org.apache.fop.fonts.FontType;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.NamedCharacter;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion;
import org.apache.fop.util.HexEncoder;

/**
* Loads a TrueType font into memory directly from the original font file.
@@ -49,6 +48,7 @@ public class TTFFontLoader extends FontLoader {
private SingleByteFont singleFont;
private final String subFontName;
private EncodingMode encodingMode;
private EmbeddingMode embeddingMode;

/**
* Default constructor
@@ -56,7 +56,7 @@ public class TTFFontLoader extends FontLoader {
* @param resolver the FontResolver for font URI resolution
*/
public TTFFontLoader(String fontFileURI, FontResolver resolver) {
this(fontFileURI, null, true, EncodingMode.AUTO, true, true, resolver);
this(fontFileURI, null, true, EmbeddingMode.AUTO, EncodingMode.AUTO, true, true, resolver);
}

/**
@@ -65,24 +65,28 @@ public class TTFFontLoader extends FontLoader {
* @param subFontName the sub-fontname of a font in a TrueType Collection (or null for normal
* TrueType fonts)
* @param embedded indicates whether the font is embedded or referenced
* @param embeddingMode the embedding mode of the font
* @param encodingMode the requested encoding mode
* @param useKerning true to enable loading kerning info if available, false to disable
* @param useAdvanced true to enable loading advanced info if available, false to disable
* @param resolver the FontResolver for font URI resolution
*/
public TTFFontLoader(String fontFileURI, String subFontName,
boolean embedded, EncodingMode encodingMode, boolean useKerning,
boolean useAdvanced, FontResolver resolver) {
boolean embedded, EmbeddingMode embeddingMode, EncodingMode encodingMode,
boolean useKerning, boolean useAdvanced, FontResolver resolver) {
super(fontFileURI, embedded, useKerning, useAdvanced, resolver);
this.subFontName = subFontName;
this.encodingMode = encodingMode;
this.embeddingMode = embeddingMode;
if (this.encodingMode == EncodingMode.AUTO) {
this.encodingMode = EncodingMode.CID; //Default to CID mode for TrueType
}
if (this.embeddingMode == EmbeddingMode.AUTO) {
this.embeddingMode = EmbeddingMode.SUBSET;
}
}

/** {@inheritDoc} */
@Override
protected void read() throws IOException {
read(this.subFontName);
}
@@ -145,29 +149,20 @@ public class TTFFontLoader extends FontLoader {
returnFont.setItalicAngle(Integer.parseInt(ttf.getItalicAngle()));
returnFont.setMissingWidth(0);
returnFont.setWeight(ttf.getWeightClass());
returnFont.setEmbeddingMode(this.embeddingMode);
if (isCid) {
multiFont.setCIDType(CIDFontType.CIDTYPE2);
int[] wx = ttf.getWidths();
multiFont.setWidthArray(wx);
List entries = ttf.getCMaps();
BFEntry[] bfentries = new BFEntry[entries.size()];
int pos = 0;
Iterator iter = ttf.getCMaps().listIterator();
while (iter.hasNext()) {
TTFCmapEntry ce = (TTFCmapEntry)iter.next();
bfentries[pos] = new BFEntry(ce.getUnicodeStart(), ce.getUnicodeEnd(),
ce.getGlyphStartIndex());
pos++;
}
multiFont.setBFEntries(bfentries);
} else {
singleFont.setFontType(FontType.TRUETYPE);
singleFont.setEncoding(ttf.getCharSetName());
returnFont.setFirstChar(ttf.getFirstChar());
returnFont.setLastChar(ttf.getLastChar());
singleFont.setTrueTypePostScriptVersion(ttf.getPostScriptVersion());
copyWidthsSingleByte(ttf);
}
returnFont.setCMap(getCMap(ttf));

if (useKerning) {
copyKerning(ttf, isCid);
@@ -186,23 +181,30 @@ public class TTFFontLoader extends FontLoader {
}
}

private CMapSegment[] getCMap(TTFFile ttf) {
CMapSegment[] array = new CMapSegment[ttf.getCMaps().size()];
return ttf.getCMaps().toArray(array);
}

private void copyWidthsSingleByte(TTFFile ttf) {
int[] wx = ttf.getWidths();
for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) {
singleFont.setWidth(i, ttf.getCharWidth(i));
}
Iterator iter = ttf.getCMaps().listIterator();
while (iter.hasNext()) {
TTFCmapEntry ce = (TTFCmapEntry)iter.next();
if (ce.getUnicodeStart() < 0xFFFE) {
for (char u = (char)ce.getUnicodeStart(); u <= ce.getUnicodeEnd(); u++) {

for (CMapSegment segment : ttf.getCMaps()) {
if (segment.getUnicodeStart() < 0xFFFE) {
for (char u = (char)segment.getUnicodeStart(); u <= segment.getUnicodeEnd(); u++) {
int codePoint = singleFont.getEncoding().mapChar(u);
if (codePoint <= 0) {
String unicode = Character.toString(u);
String charName = Glyphs.stringToGlyph(unicode);
if (charName.length() > 0) {
NamedCharacter nc = new NamedCharacter(charName, unicode);
int glyphIndex = ce.getGlyphStartIndex() + u - ce.getUnicodeStart();
int glyphIndex = segment.getGlyphStartIndex() + u - segment.getUnicodeStart();
String glyphName = ttf.getGlyphName(glyphIndex);
if (glyphName.length() == 0 && ttf.getPostScriptVersion() != PostScriptVersion.V2) {
glyphName = "u" + HexEncoder.encode(u);
}
if (glyphName.length() > 0) {
String unicode = Character.toString(u);
NamedCharacter nc = new NamedCharacter(glyphName, unicode);
singleFont.addUnencodedCharacter(nc, wx[glyphIndex]);
}
}
@@ -225,7 +227,6 @@ public class TTFFontLoader extends FontLoader {
}

for (Integer kpx1 : kerningSet) {

Map<Integer, Integer> h2;
if (isCid) {
h2 = ttf.getKerning().get(kpx1);

+ 48
- 0
src/java/org/apache/fop/fonts/truetype/TTFGlyphOutputStream.java View File

@@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.fonts.truetype;

import java.io.IOException;

/**
* An interface for writing individual glyphs from the glyf table of a TrueType font to an output stream.
*/
public interface TTFGlyphOutputStream {

/**
* Begins the streaming of glyphs.
*/
void startGlyphStream() throws IOException;

/**
* Streams an individual glyph from the given byte array.
*
* @param glyphData the source of the glyph data to stream from
* @param offset the position in the glyph data where the glyph starts
* @param size the size of the glyph data in bytes
*/
void streamGlyph(byte[] glyphData, int offset, int size) throws IOException;

/**
* Ends the streaming of glyphs.
*/
void endGlyphStream() throws IOException;

}

+ 49
- 0
src/java/org/apache/fop/fonts/truetype/TTFOutputStream.java View File

@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.fonts.truetype;

import java.io.IOException;

/**
* An interface for writing a TrueType font to an output stream.
*/
public interface TTFOutputStream {

/**
* Starts writing the font.
*/
void startFontStream() throws IOException;

/**
* Returns an object for streaming TrueType tables.
*/
TTFTableOutputStream getTableOutputStream();

/**
* Returns an object for streaming TrueType glyphs in the glyf table.
*/
TTFGlyphOutputStream getGlyphOutputStream();

/**
* Ends writing the font.
*/
void endFontStream() throws IOException;

}

+ 246
- 279
src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java View File

@@ -20,8 +20,9 @@
package org.apache.fop.fonts.truetype;

import java.io.IOException;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;


/**
@@ -42,24 +43,18 @@ public class TTFSubSetFile extends TTFFile {
* Offsets in name table to be filled out by table.
* The offsets are to the checkSum field
*/
private int cvtDirOffset = 0;
private int fpgmDirOffset = 0;
private int glyfDirOffset = 0;
private int headDirOffset = 0;
private int hheaDirOffset = 0;
private int hmtxDirOffset = 0;
private int locaDirOffset = 0;
private int maxpDirOffset = 0;
private int prepDirOffset = 0;
private Map<TTFTableName, Integer> offsets = new HashMap<TTFTableName, Integer>();

private int checkSumAdjustmentOffset = 0;
private int locaOffset = 0;

/** Stores the glyph offsets so that we can end strings at glyph boundaries */
private int[] glyphOffsets;

/**
* Default Constructor
*/
public TTFSubSetFile() {
this(false, false);
}

/**
@@ -71,16 +66,9 @@ public class TTFSubSetFile extends TTFFile {
super(useKerning, useAdvanced);
}

/**
* Initalize the output array
*/
private void init(int size) {
output = new byte[size];
realSize = 0;
currentPos = 0;

// createDirectory()
}
/** The dir tab entries in the new subset font. */
private Map<TTFTableName, TTFDirTabEntry> newDirTabs
= new HashMap<TTFTableName, TTFDirTabEntry>();

private int determineTableCount() {
int numTables = 4; //4 req'd tables: head,hhea,hmtx,maxp
@@ -88,7 +76,7 @@ public class TTFSubSetFile extends TTFFile {
throw new UnsupportedOperationException(
"OpenType fonts with CFF glyphs are not supported");
} else {
numTables += 2; //1 req'd table: glyf,loca
numTables += 5; //5 req'd tables: glyf,loca,post,name,OS/2
if (hasCvt()) {
numTables++;
}
@@ -119,7 +107,7 @@ public class TTFSubSetFile extends TTFFile {

// Create searchRange, entrySelector and rangeShift
int maxPow = maxPow2(numTables);
int searchRange = maxPow * 16;
int searchRange = (int) Math.pow(2, maxPow) * 16;
writeUShort(searchRange);
realSize += 2;

@@ -128,151 +116,122 @@ public class TTFSubSetFile extends TTFFile {

writeUShort((numTables * 16) - searchRange);
realSize += 2;
// Create space for the table entries (these must be in ASCII alphabetical order[A-Z] then[a-z])
writeTableName(TTFTableName.OS2);

// Create space for the table entries
if (hasCvt()) {
writeString("cvt ");
cvtDirOffset = currentPos;
currentPos += 12;
realSize += 16;
writeTableName(TTFTableName.CVT);
}

if (hasFpgm()) {
writeString("fpgm");
fpgmDirOffset = currentPos;
currentPos += 12;
realSize += 16;
writeTableName(TTFTableName.FPGM);
}
writeTableName(TTFTableName.GLYF);
writeTableName(TTFTableName.HEAD);
writeTableName(TTFTableName.HHEA);
writeTableName(TTFTableName.HMTX);
writeTableName(TTFTableName.LOCA);
writeTableName(TTFTableName.MAXP);
writeTableName(TTFTableName.NAME);
writeTableName(TTFTableName.POST);
if (hasPrep()) {
writeTableName(TTFTableName.PREP);
}
newDirTabs.put(TTFTableName.TABLE_DIRECTORY, new TTFDirTabEntry(0, currentPos));
}

writeString("glyf");
glyfDirOffset = currentPos;
currentPos += 12;
realSize += 16;

writeString("head");
headDirOffset = currentPos;
currentPos += 12;
realSize += 16;

writeString("hhea");
hheaDirOffset = currentPos;
private void writeTableName(TTFTableName tableName) {
writeString(tableName.getName());
offsets.put(tableName, currentPos);
currentPos += 12;
realSize += 16;
}

writeString("hmtx");
hmtxDirOffset = currentPos;
currentPos += 12;
realSize += 16;

writeString("loca");
locaDirOffset = currentPos;
currentPos += 12;
realSize += 16;

writeString("maxp");
maxpDirOffset = currentPos;
currentPos += 12;
realSize += 16;
private boolean hasCvt() {
return dirTabs.containsKey(TTFTableName.CVT);
}

if (hasPrep()) {
writeString("prep");
prepDirOffset = currentPos;
currentPos += 12;
realSize += 16;
}
private boolean hasFpgm() {
return dirTabs.containsKey(TTFTableName.FPGM);
}

private boolean hasPrep() {
return dirTabs.containsKey(TTFTableName.PREP);
}

/**
* Copy the cvt table as is from original font to subset font
* Create an empty loca table without updating checksum
*/
private boolean createCvt(FontFileReader in) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("cvt ");
private void createLoca(int size) throws IOException {
pad4();
locaOffset = currentPos;
int dirTableOffset = offsets.get(TTFTableName.LOCA);
writeULong(dirTableOffset + 4, currentPos);
writeULong(dirTableOffset + 8, size * 4 + 4);
currentPos += size * 4 + 4;
realSize += size * 4 + 4;
}

private boolean copyTable(FontFileReader in, TTFTableName tableName) throws IOException {
TTFDirTabEntry entry = dirTabs.get(tableName);
if (entry != null) {
pad4();
seekTab(in, "cvt ", 0);
seekTab(in, tableName, 0);
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
0, output, currentPos, (int)entry.getLength());

int checksum = getCheckSum(currentPos, (int)entry.getLength());
writeULong(cvtDirOffset, checksum);
writeULong(cvtDirOffset + 4, currentPos);
writeULong(cvtDirOffset + 8, (int)entry.getLength());
currentPos += (int)entry.getLength();
realSize += (int)entry.getLength();
updateCheckSum(currentPos, (int) entry.getLength(), tableName);
currentPos += (int) entry.getLength();
realSize += (int) entry.getLength();
return true;
} else {
return false;
//throw new IOException("Can't find cvt table");
}
}

private boolean hasCvt() {
return dirTabs.containsKey("cvt ");
}

private boolean hasFpgm() {
return dirTabs.containsKey("fpgm");
}

private boolean hasPrep() {
return dirTabs.containsKey("prep");
/**
* Copy the cvt table as is from original font to subset font
*/
private boolean createCvt(FontFileReader in) throws IOException {
return copyTable(in, TTFTableName.CVT);
}

/**
* Copy the fpgm table as is from original font to subset font
*/
private boolean createFpgm(FontFileReader in) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("fpgm");
if (entry != null) {
pad4();
seekTab(in, "fpgm", 0);
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
0, output, currentPos, (int)entry.getLength());
int checksum = getCheckSum(currentPos, (int)entry.getLength());
writeULong(fpgmDirOffset, checksum);
writeULong(fpgmDirOffset + 4, currentPos);
writeULong(fpgmDirOffset + 8, (int)entry.getLength());
currentPos += (int)entry.getLength();
realSize += (int)entry.getLength();
return true;
} else {
return false;
}
return copyTable(in, TTFTableName.FPGM);
}



/**
* Create an empty loca table without updating checksum
* Copy the name table as is from the original.
*/
private void createLoca(int size) throws IOException {
pad4();
locaOffset = currentPos;
writeULong(locaDirOffset + 4, currentPos);
writeULong(locaDirOffset + 8, size * 4 + 4);
currentPos += size * 4 + 4;
realSize += size * 4 + 4;
private boolean createName(FontFileReader in) throws IOException {
return copyTable(in, TTFTableName.NAME);
}

/**
* Copy the OS/2 table as is from the original.
*/
private boolean createOS2(FontFileReader in) throws IOException {
return copyTable(in, TTFTableName.OS2);
}

/**
* Copy the maxp table as is from original font to subset font
* and set num glyphs to size
*/
private void createMaxp(FontFileReader in, int size) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("maxp");
TTFTableName maxp = TTFTableName.MAXP;
TTFDirTabEntry entry = dirTabs.get(maxp);
if (entry != null) {
pad4();
seekTab(in, "maxp", 0);
seekTab(in, maxp, 0);
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
0, output, currentPos, (int)entry.getLength());
writeUShort(currentPos + 4, size);

int checksum = getCheckSum(currentPos, (int)entry.getLength());
writeULong(maxpDirOffset, checksum);
writeULong(maxpDirOffset + 4, currentPos);
writeULong(maxpDirOffset + 8, (int)entry.getLength());
updateCheckSum(currentPos, (int)entry.getLength(), maxp);
currentPos += (int)entry.getLength();
realSize += (int)entry.getLength();
} else {
@@ -280,28 +239,34 @@ public class TTFSubSetFile extends TTFFile {
}
}

private void createPost(FontFileReader in) throws IOException {
TTFTableName post = TTFTableName.POST;
TTFDirTabEntry entry = dirTabs.get(post);
if (entry != null) {
pad4();
seekTab(in, post, 0);
int newTableSize = 32; // This is the post table size with glyphs truncated
byte[] newPostTable = new byte[newTableSize];
// We only want the first 28 bytes (truncate the glyph names);
System.arraycopy(in.getBytes((int) entry.getOffset(), newTableSize),
0, newPostTable, 0, newTableSize);
// set the post table to Format 3.0
newPostTable[1] = 0x03;
System.arraycopy(newPostTable, 0, output, currentPos, newTableSize);
updateCheckSum(currentPos, newTableSize, post);
currentPos += newTableSize;
realSize += newTableSize;
} else {
throw new IOException("Can't find post table");
}
}


/**
* Copy the prep table as is from original font to subset font
*/
private boolean createPrep(FontFileReader in) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("prep");
if (entry != null) {
pad4();
seekTab(in, "prep", 0);
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
0, output, currentPos, (int)entry.getLength());

int checksum = getCheckSum(currentPos, (int)entry.getLength());
writeULong(prepDirOffset, checksum);
writeULong(prepDirOffset + 4, currentPos);
writeULong(prepDirOffset + 8, (int)entry.getLength());
currentPos += (int)entry.getLength();
realSize += (int)entry.getLength();
return true;
} else {
return false;
}
return copyTable(in, TTFTableName.PREP);
}


@@ -310,20 +275,17 @@ public class TTFSubSetFile extends TTFFile {
* and fill in size of hmtx table
*/
private void createHhea(FontFileReader in, int size) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hhea");
TTFDirTabEntry entry = dirTabs.get(TTFTableName.HHEA);
if (entry != null) {
pad4();
seekTab(in, "hhea", 0);
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
0, output, currentPos, (int)entry.getLength());
writeUShort((int)entry.getLength() + currentPos - 2, size);

int checksum = getCheckSum(currentPos, (int)entry.getLength());
writeULong(hheaDirOffset, checksum);
writeULong(hheaDirOffset + 4, currentPos);
writeULong(hheaDirOffset + 8, (int)entry.getLength());
currentPos += (int)entry.getLength();
realSize += (int)entry.getLength();
seekTab(in, TTFTableName.HHEA, 0);
System.arraycopy(in.getBytes((int) entry.getOffset(), (int) entry.getLength()), 0,
output, currentPos, (int) entry.getLength());
writeUShort((int) entry.getLength() + currentPos - 2, size);

updateCheckSum(currentPos, (int) entry.getLength(), TTFTableName.HHEA);
currentPos += (int) entry.getLength();
realSize += (int) entry.getLength();
} else {
throw new IOException("Can't find hhea table");
}
@@ -337,10 +299,11 @@ public class TTFSubSetFile extends TTFFile {
* in checkSumAdjustmentOffset
*/
private void createHead(FontFileReader in) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("head");
TTFTableName head = TTFTableName.HEAD;
TTFDirTabEntry entry = dirTabs.get(head);
if (entry != null) {
pad4();
seekTab(in, "head", 0);
seekTab(in, head, 0);
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
0, output, currentPos, (int)entry.getLength());

@@ -352,11 +315,7 @@ public class TTFSubSetFile extends TTFFile {
output[currentPos + 50] = 0; // long locaformat
output[currentPos + 51] = 1; // long locaformat

int checksum = getCheckSum(currentPos, (int)entry.getLength());
writeULong(headDirOffset, checksum);
writeULong(headDirOffset + 4, currentPos);
writeULong(headDirOffset + 8, (int)entry.getLength());

updateCheckSum(currentPos, (int)entry.getLength(), head);
currentPos += (int)entry.getLength();
realSize += (int)entry.getLength();
} else {
@@ -369,30 +328,24 @@ public class TTFSubSetFile extends TTFFile {
* Create the glyf table and fill in loca table
*/
private void createGlyf(FontFileReader in,
Map glyphs) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf");
Map<Integer, Integer> glyphs) throws IOException {
TTFTableName glyf = TTFTableName.GLYF;
TTFDirTabEntry entry = dirTabs.get(glyf);
int size = 0;
int start = 0;
int startPos = 0;
int endOffset = 0; // Store this as the last loca
if (entry != null) {
pad4();
start = currentPos;
startPos = currentPos;

/* Loca table must be in order by glyph index, so build
* an array first and then write the glyph info and
* location offset.
*/
int[] origIndexes = new int[glyphs.size()];

Iterator e = glyphs.keySet().iterator();
while (e.hasNext()) {
Integer origIndex = (Integer)e.next();
Integer subsetIndex = (Integer)glyphs.get(origIndex);
origIndexes[subsetIndex.intValue()] = origIndex.intValue();
}
int[] origIndexes = buildSubsetIndexToOrigIndexMap(glyphs);
glyphOffsets = new int[origIndexes.length];

for (int i = 0; i < origIndexes.length; i++) {
int glyphLength = 0;
int nextOffset = 0;
int origGlyphIndex = origIndexes[i];
if (origGlyphIndex >= (mtxTab.length - 1)) {
@@ -400,46 +353,64 @@ public class TTFSubSetFile extends TTFFile {
} else {
nextOffset = (int)mtxTab[origGlyphIndex + 1].getOffset();
}
glyphLength = nextOffset - (int)mtxTab[origGlyphIndex].getOffset();
int glyphOffset = (int)mtxTab[origGlyphIndex].getOffset();
int glyphLength = nextOffset - glyphOffset;

byte[] glyphData = in.getBytes(
(int)entry.getOffset() + glyphOffset,
glyphLength);
int endOffset1 = endOffset;
// Copy glyph
System.arraycopy(
in.getBytes((int)entry.getOffset() + (int)mtxTab[origGlyphIndex].getOffset(),
glyphLength), 0,
glyphData, 0,
output, currentPos,
glyphLength);


// Update loca table
writeULong(locaOffset + i * 4, currentPos - start);
if ((currentPos - start + glyphLength) > endOffset) {
endOffset = (currentPos - start + glyphLength);
writeULong(locaOffset + i * 4, currentPos - startPos);
if ((currentPos - startPos + glyphLength) > endOffset1) {
endOffset1 = (currentPos - startPos + glyphLength);
}

// Store the glyph boundary positions relative to the start of the font
glyphOffsets[i] = currentPos;
currentPos += glyphLength;
realSize += glyphLength;


endOffset = endOffset1;
}

size = currentPos - start;

int checksum = getCheckSum(start, size);
writeULong(glyfDirOffset, checksum);
writeULong(glyfDirOffset + 4, start);
writeULong(glyfDirOffset + 8, size);
size = currentPos - startPos;

currentPos += 12;
realSize += 12;
updateCheckSum(startPos, size + 12, glyf);

// Update loca checksum and last loca index
writeULong(locaOffset + glyphs.size() * 4, endOffset);

checksum = getCheckSum(locaOffset, glyphs.size() * 4 + 4);
writeULong(locaDirOffset, checksum);
int locaSize = glyphs.size() * 4 + 4;
int checksum = getCheckSum(output, locaOffset, locaSize);
writeULong(offsets.get(TTFTableName.LOCA), checksum);
int padSize = (locaOffset + locaSize) % 4;
newDirTabs.put(TTFTableName.LOCA,
new TTFDirTabEntry(locaOffset, locaSize + padSize));
} else {
throw new IOException("Can't find glyf table");
}
}

private int[] buildSubsetIndexToOrigIndexMap(Map<Integer, Integer> glyphs) {
int[] origIndexes = new int[glyphs.size()];
for (Map.Entry<Integer, Integer> glyph : glyphs.entrySet()) {
int origIndex = glyph.getKey();
int subsetIndex = glyph.getValue();
origIndexes[subsetIndex] = origIndex;
}
return origIndexes;
}

/**
* Create the hmtx table by copying metrics from original
@@ -448,8 +419,9 @@ public class TTFSubSetFile extends TTFFile {
* metric (key) to the subset metric (value)
*/
private void createHmtx(FontFileReader in,
Map glyphs) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hmtx");
Map<Integer, Integer> glyphs) throws IOException {
TTFTableName hmtx = TTFTableName.HMTX;
TTFDirTabEntry entry = dirTabs.get(hmtx);

int longHorMetricSize = glyphs.size() * 2;
int leftSideBearingSize = glyphs.size() * 2;
@@ -458,10 +430,9 @@ public class TTFSubSetFile extends TTFFile {
if (entry != null) {
pad4();
//int offset = (int)entry.offset;
Iterator e = glyphs.keySet().iterator();
while (e.hasNext()) {
Integer origIndex = (Integer)e.next();
Integer subsetIndex = (Integer)glyphs.get(origIndex);
for (Map.Entry<Integer, Integer> glyph : glyphs.entrySet()) {
Integer origIndex = glyph.getKey();
Integer subsetIndex = glyph.getValue();

writeUShort(currentPos + subsetIndex.intValue() * 4,
mtxTab[origIndex.intValue()].getWx());
@@ -469,10 +440,7 @@ public class TTFSubSetFile extends TTFFile {
mtxTab[origIndex.intValue()].getLsb());
}

int checksum = getCheckSum(currentPos, hmtxSize);
writeULong(hmtxDirOffset, checksum);
writeULong(hmtxDirOffset + 4, currentPos);
writeULong(hmtxDirOffset + 8, hmtxSize);
updateCheckSum(currentPos, hmtxSize, hmtx);
currentPos += hmtxSize;
realSize += hmtxSize;
} else {
@@ -481,43 +449,37 @@ public class TTFSubSetFile extends TTFFile {
}

/**
* Returns a subset of the original font.
* Reads a font and creates a subset of the font.
*
* @param in FontFileReader to read from
* @param name Name to be checked for in the font file
* @param glyphs Map of glyphs (glyphs has old index as (Integer) key and
* new index as (Integer) value)
* @return A subset of the original font
* @throws IOException in case of an I/O problem
*/
public byte[] readFont(FontFileReader in, String name,
public void readFont(FontFileReader in, String name,
Map<Integer, Integer> glyphs) throws IOException {
fontFile = in;
//Check if TrueType collection, and that the name exists in the collection
if (!checkTTC(in, name)) {
if (!checkTTC(name)) {
throw new IOException("Failed to read font");
}

//Copy the Map as we're going to modify it
Map<Integer, Integer> subsetGlyphs = new java.util.HashMap<Integer, Integer>(glyphs);
Map<Integer, Integer> subsetGlyphs = new HashMap<Integer, Integer>(glyphs);

output = new byte[in.getFileSize()];

readDirTabs(in);
readFontHeader(in);
getNumGlyphs(in);
readHorizontalHeader(in);
readHorizontalMetrics(in);
readIndexToLocation(in);
readDirTabs();
readFontHeader();
getNumGlyphs();
readHorizontalHeader();
readHorizontalMetrics();
readIndexToLocation();

scanGlyphs(in, subsetGlyphs);

createDirectory(); // Create the TrueType header and directory

createHead(in);
createHhea(in, subsetGlyphs.size()); // Create the hhea table
createHmtx(in, subsetGlyphs); // Create hmtx table
createMaxp(in, subsetGlyphs.size()); // copy the maxp table
createDirectory(); // Create the TrueType header and directory

boolean optionalTableFound;
optionalTableFound = createCvt(in); // copy the cvt table
@@ -531,6 +493,16 @@ public class TTFSubSetFile extends TTFFile {
// fpgm is optional (used in TrueType fonts only)
log.debug("TrueType: fpgm table not present. Skipped.");
}
createLoca(subsetGlyphs.size()); // create empty loca table
createGlyf(in, subsetGlyphs); //create glyf table and update loca table

createOS2(in); // copy the OS/2 table
createHead(in);
createHhea(in, subsetGlyphs.size()); // Create the hhea table
createHmtx(in, subsetGlyphs); // Create hmtx table
createMaxp(in, subsetGlyphs.size()); // copy the maxp table
createName(in); // copy the name table
createPost(in); // copy the post table

optionalTableFound = createPrep(in); // copy prep table
if (!optionalTableFound) {
@@ -538,21 +510,59 @@ public class TTFSubSetFile extends TTFFile {
log.debug("TrueType: prep table not present. Skipped.");
}

createLoca(subsetGlyphs.size()); // create empty loca table
createGlyf(in, subsetGlyphs); //create glyf table and update loca table

pad4();
createCheckSumAdjustment();
}

/**
* Returns a subset of the fonts (readFont() MUST be called first in order to create the
* subset).
* @return byte array
*/
public byte[] getFontSubset() {
byte[] ret = new byte[realSize];
System.arraycopy(output, 0, ret, 0, realSize);

return ret;
}

private void handleGlyphSubset(TTFGlyphOutputStream glyphOut) throws IOException {
glyphOut.startGlyphStream();
// Stream all but the last glyph
for (int i = 0; i < glyphOffsets.length - 1; i++) {
glyphOut.streamGlyph(output, glyphOffsets[i],
glyphOffsets[i + 1] - glyphOffsets[i]);
}
// Stream the last glyph
TTFDirTabEntry glyf = newDirTabs.get(TTFTableName.GLYF);
long lastGlyphLength = glyf.getLength()
- (glyphOffsets[glyphOffsets.length - 1] - glyf.getOffset());
glyphOut.streamGlyph(output, glyphOffsets[glyphOffsets.length - 1],
(int) lastGlyphLength);
glyphOut.endGlyphStream();
}

@Override
public void stream(TTFOutputStream ttfOut) throws IOException {
SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> sortedDirTabs
= sortDirTabMap(newDirTabs);
TTFTableOutputStream tableOut = ttfOut.getTableOutputStream();
TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream();

ttfOut.startFontStream();
for (Map.Entry<TTFTableName, TTFDirTabEntry> entry : sortedDirTabs) {
if (entry.getKey().equals(TTFTableName.GLYF)) {
handleGlyphSubset(glyphOut);
} else {
tableOut.streamTable(output, (int) entry.getValue().getOffset(),
(int) entry.getValue().getLength());
}
}
ttfOut.endFontStream();
}

private void scanGlyphs(FontFileReader in, Map<Integer, Integer> subsetGlyphs)
throws IOException {
TTFDirTabEntry glyfTableInfo = (TTFDirTabEntry) dirTabs.get("glyf");
TTFDirTabEntry glyfTableInfo = dirTabs.get(TTFTableName.GLYF);
if (glyfTableInfo == null) {
throw new IOException("Glyf table could not be found");
}
@@ -610,20 +620,6 @@ public class TTFSubSetFile extends TTFFile {
output[pos + 1] = b2;
}

/**
* Appends a ULONG to the output array,
* updates currentPos but not realSize
*/
private void writeULong(int s) {
byte b1 = (byte)((s >> 24) & 0xff);
byte b2 = (byte)((s >> 16) & 0xff);
byte b3 = (byte)((s >> 8) & 0xff);
byte b4 = (byte)(s & 0xff);
writeByte(b1);
writeByte(b2);
writeByte(b3);
writeByte(b4);
}

/**
* Appends a ULONG to the output array,
@@ -640,41 +636,17 @@ public class TTFSubSetFile extends TTFFile {
output[pos + 3] = b4;
}

/**
* Read a signed short value at given position
*/
private short readShort(int pos) {
int ret = readUShort(pos);
return (short)ret;
}

/**
* Read a unsigned short value at given position
*/
private int readUShort(int pos) {
int ret = output[pos];
if (ret < 0) {
ret += 256;
}
ret = ret << 8;
if (output[pos + 1] < 0) {
ret |= output[pos + 1] + 256;
} else {
ret |= output[pos + 1];
}

return ret;
}

/**
* Create a padding in the fontfile to align
* on a 4-byte boundary
*/
private void pad4() {
int padSize = currentPos % 4;
for (int i = 0; i < padSize; i++) {
output[currentPos++] = 0;
realSize++;
int padSize = getPadSize(currentPos);
if (padSize < 4) {
for (int i = 0; i < padSize; i++) {
output[currentPos++] = 0;
realSize++;
}
}
}

@@ -683,23 +655,25 @@ public class TTFSubSetFile extends TTFFile {
*/
private int maxPow2(int max) {
int i = 0;
while (Math.pow(2, i) < max) {
while (Math.pow(2, i) <= max) {
i++;
}

return (i - 1);
}

private int log2(int num) {
return (int)(Math.log(num) / Math.log(2));
}


private int getCheckSum(int start, int size) {
return (int)getLongCheckSum(start, size);
private void updateCheckSum(int tableStart, int tableSize, TTFTableName tableName) {
int checksum = getCheckSum(output, tableStart, tableSize);
int offset = offsets.get(tableName);
int padSize = getPadSize(tableStart + tableSize);
newDirTabs.put(tableName, new TTFDirTabEntry(tableStart, tableSize + padSize));
writeULong(offset, checksum);
writeULong(offset + 4, tableStart);
writeULong(offset + 8, tableSize);
}

private long getLongCheckSum(int start, int size) {
private static int getCheckSum(byte[] data, int start, int size) {
// All the tables here are aligned on four byte boundaries
// Add remainder to size if it's not a multiple of 4
int remainder = size % 4;
@@ -710,26 +684,19 @@ public class TTFSubSetFile extends TTFFile {
long sum = 0;

for (int i = 0; i < size; i += 4) {
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;
long l = 0;
for (int j = 0; j < 4; j++) {
l <<= 8;
l |= data[start + i + j] & 0xff;
}
sum += l;
}

return sum;
return (int) sum;
}

private void createCheckSumAdjustment() {
long sum = getLongCheckSum(0, realSize);
long sum = getCheckSum(output, 0, realSize);
int checksum = (int)(0xb1b0afba - sum);
writeULong(checkSumAdjustmentOffset, checksum);
}

}




+ 163
- 0
src/java/org/apache/fop/fonts/truetype/TTFTableName.java View File

@@ -0,0 +1,163 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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;


/**
* Represents table names as found in a TrueType font's Table Directory.
* TrueType fonts may have custom tables so we cannot use an enum.
*/
public final class TTFTableName {

/** The first table in a TrueType font file containing metadata about other tables. */
public static final TTFTableName TABLE_DIRECTORY = new TTFTableName("tableDirectory");

/** Embedded bitmap data. */
public static final TTFTableName EBDT = new TTFTableName("EBDT");

/** Embedded bitmap location data. */
public static final TTFTableName EBLC = new TTFTableName("EBLC");

/** Embedded bitmap scaling data. */
public static final TTFTableName EBSC = new TTFTableName("EBSC");

/** A FontForge specific table. */
public static final TTFTableName FFTM = new TTFTableName("FFTM");

/** Divides glyphs into various classes that make using the GPOS/GSUB tables easier. */
public static final TTFTableName GDEF = new TTFTableName("GDEF");

/** Provides kerning information, mark-to-base, etc. for opentype fonts. */
public static final TTFTableName GPOS = new TTFTableName("GPOS");

/** Provides ligature information, swash, etc. for opentype fonts. */
public static final TTFTableName GSUB = new TTFTableName("GSUB");

/** Linear threshold table. */
public static final TTFTableName LTSH = new TTFTableName("LTSH");

/** OS/2 and Windows specific metrics. */
public static final TTFTableName OS2 = new TTFTableName("OS/2");

/** PCL 5 data. */
public static final TTFTableName PCLT = new TTFTableName("PCLT");

/** Vertical Device Metrics table. */
public static final TTFTableName VDMX = new TTFTableName("VDMX");

/** Character to glyph mapping. */
public static final TTFTableName CMAP = new TTFTableName("cmap");

/** Control Value Table. */
public static final TTFTableName CVT = new TTFTableName("cvt ");

/** Font program. */
public static final TTFTableName FPGM = new TTFTableName("fpgm");

/** Grid-fitting and scan conversion procedure (grayscale). */
public static final TTFTableName GASP = new TTFTableName("gasp");

/** Glyph data. */
public static final TTFTableName GLYF = new TTFTableName("glyf");

/** Horizontal device metrics. */
public static final TTFTableName HDMX = new TTFTableName("hdmx");

/** Font header. */
public static final TTFTableName HEAD = new TTFTableName("head");

/** Horizontal header. */
public static final TTFTableName HHEA = new TTFTableName("hhea");

/** Horizontal metrics. */
public static final TTFTableName HMTX = new TTFTableName("hmtx");

/** Kerning. */
public static final TTFTableName KERN = new TTFTableName("kern");

/** Index to location. */
public static final TTFTableName LOCA = new TTFTableName("loca");

/** Maximum profile. */
public static final TTFTableName MAXP = new TTFTableName("maxp");

/** Naming table. */
public static final TTFTableName NAME = new TTFTableName("name");

/** PostScript information. */
public static final TTFTableName POST = new TTFTableName("post");

/** CVT Program. */
public static final TTFTableName PREP = new TTFTableName("prep");

/** Vertical Metrics header. */
public static final TTFTableName VHEA = new TTFTableName("vhea");

/** Vertical Metrics. */
public static final TTFTableName VMTX = new TTFTableName("vmtx");

private final String name;

private TTFTableName(String name) {
this.name = name;
}

/**
* Returns the name of the table as it should be in the Directory Table.
*/
public String getName() {
return name;
}

/**
* Returns an instance of this class corresponding to the given string representation.
* @param tableName table name as in the Table Directory
* @return TTFTableName
*/
public static TTFTableName getValue(String tableName) {
if (tableName != null) {
return new TTFTableName(tableName);
}
throw new IllegalArgumentException("A TrueType font table name must not be null");
}

@Override
public int hashCode() {
return name.hashCode();
}

@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof TTFTableName)) {
return false;
}
TTFTableName to = (TTFTableName) o;
return this.name.equals(to.getName());
}

@Override
public String toString() {
return name;
}

}

+ 37
- 0
src/java/org/apache/fop/fonts/truetype/TTFTableOutputStream.java View File

@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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;

/**
* An interface for writing a TrueType table to an output stream.
*/
public interface TTFTableOutputStream {

/**
* Streams a table from the given byte array.
*
* @param ttfData the source of the table to stream from
* @param offset the position in the byte array where the table starts
* @param size the size of the table in bytes
*/
void streamTable(byte[] ttfData, int offset, int size) throws IOException;
}

+ 5
- 1
src/java/org/apache/fop/pdf/BitmapImage.java View File

@@ -37,6 +37,7 @@ public class BitmapImage implements PDFImage {
private PDFColor transparent = null;
private String key;
private PDFDocument pdfDoc;
private PDFFilter pdfFilter;

/**
* Create a bitmap image.
@@ -208,9 +209,12 @@ public class BitmapImage implements PDFImage {
* {@inheritDoc}
*/
public PDFFilter getPDFFilter() {
return null;
return pdfFilter;
}

public void setPDFFilter(PDFFilter pdfFilter) {
this.pdfFilter = pdfFilter;
}
}



+ 3
- 2
src/java/org/apache/fop/pdf/PDFFactory.java View File

@@ -45,6 +45,7 @@ import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.java2d.color.ColorUtil;
import org.apache.xmlgraphics.java2d.color.NamedColorSpace;

import org.apache.xmlgraphics.xmp.Metadata;

import org.apache.fop.fonts.CIDFont;
@@ -1674,8 +1675,8 @@ public class PDFFactory {
FontFileReader reader = new FontFileReader(in);

TTFSubSetFile subset = new TTFSubSetFile();
byte[] subsetFont = subset.readFont(reader,
mbfont.getTTCName(), mbfont.getUsedGlyphs());
subset.readFont(reader, mbfont.getTTCName(), mbfont.getUsedGlyphs());
byte[] subsetFont = subset.getFontSubset();
// Only TrueType CID fonts are supported now

embeddedFont = new PDFTTFStream(subsetFont.length);

+ 2
- 1
src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java View File

@@ -89,7 +89,8 @@ public class ConfiguredFontCollection implements FontCollection {
font = new CustomFontMetricsMapper(fontMetrics, fontSource);
} else {
CustomFont fontMetrics = FontLoader.loadFont(
fontFile, null, true, EncodingMode.AUTO,
fontFile, null, true, configFontInfo.getEmbeddingMode(),
EncodingMode.AUTO,
configFontInfo.getKerning(),
configFontInfo.getAdvanced(), fontResolver);
font = new CustomFontMetricsMapper(fontMetrics);

+ 68
- 1
src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java View File

@@ -20,13 +20,16 @@
package org.apache.fop.render.pdf;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.IndexColorModel;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.image.loader.Image;
import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil;

import org.apache.fop.pdf.PDFArray;
import org.apache.fop.pdf.PDFColor;
import org.apache.fop.pdf.PDFConformanceException;
import org.apache.fop.pdf.PDFDeviceColorSpace;
@@ -50,7 +53,9 @@ public abstract class AbstractImageAdapter implements PDFImage {
/** the image */
protected Image image;

private PDFICCStream pdfICCStream = null;
private PDFICCStream pdfICCStream;

private static final int MAX_HIVAL = 255;

/**
* Creates a new PDFImage from an Image instance.
@@ -202,6 +207,68 @@ public abstract class AbstractImageAdapter implements PDFImage {
//nop
}

/**
* This is to be used by populateXObjectDictionary() when the image is palette based.
* @param dict the dictionary to fill in
* @param icm the image color model
*/
protected void populateXObjectDictionaryForIndexColorModel(PDFDictionary dict, IndexColorModel icm) {
PDFArray indexed = new PDFArray(dict);
indexed.add(new PDFName("Indexed"));
if (icm.getColorSpace().getType() != ColorSpace.TYPE_RGB) {
log.warn("Indexed color space is not using RGB as base color space."
+ " The image may not be handled correctly." + " Base color space: "
+ icm.getColorSpace() + " Image: " + image.getInfo());
}
indexed.add(new PDFName(toPDFColorSpace(icm.getColorSpace()).getName()));
int c = icm.getMapSize();
int hival = c - 1;
if (hival > MAX_HIVAL) {
throw new UnsupportedOperationException("hival must not go beyond " + MAX_HIVAL);
}
indexed.add(Integer.valueOf(hival));
int[] palette = new int[c];
icm.getRGBs(palette);
ByteArrayOutputStream baout = new ByteArrayOutputStream();
for (int i = 0; i < c; i++) {
// TODO Probably doesn't work for non RGB based color spaces
// See log warning above
int entry = palette[i];
baout.write((entry & 0xFF0000) >> 16);
baout.write((entry & 0xFF00) >> 8);
baout.write(entry & 0xFF);
}
indexed.add(baout.toByteArray());

dict.put("ColorSpace", indexed);
dict.put("BitsPerComponent", icm.getPixelSize());

Integer index = getIndexOfFirstTransparentColorInPalette(icm);
if (index != null) {
PDFArray mask = new PDFArray(dict);
mask.add(index);
mask.add(index);
dict.put("Mask", mask);
}
}

private static Integer getIndexOfFirstTransparentColorInPalette(IndexColorModel icm) {
byte[] alphas = new byte[icm.getMapSize()];
byte[] reds = new byte[icm.getMapSize()];
byte[] greens = new byte[icm.getMapSize()];
byte[] blues = new byte[icm.getMapSize()];
icm.getAlphas(alphas);
icm.getReds(reds);
icm.getGreens(greens);
icm.getBlues(blues);
for (int i = 0; i < icm.getMapSize(); i++) {
if ((alphas[i] & 0xFF) == 0) {
return Integer.valueOf(i);
}
}
return null;
}

/**
* Converts a ColorSpace object to a PDFColorSpace object.
* @param cs ColorSpace instance

+ 258
- 0
src/java/org/apache/fop/render/pdf/ImageRawPNGAdapter.java View File

@@ -0,0 +1,258 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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$ */
// Original author: Matthias Reichenbacher
package org.apache.fop.render.pdf;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;
import org.apache.xmlgraphics.image.loader.impl.ImageRawStream;
import org.apache.fop.pdf.BitmapImage;
import org.apache.fop.pdf.FlateFilter;
import org.apache.fop.pdf.PDFColor;
import org.apache.fop.pdf.PDFDeviceColorSpace;
import org.apache.fop.pdf.PDFDictionary;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFFilter;
import org.apache.fop.pdf.PDFFilterException;
import org.apache.fop.pdf.PDFFilterList;
import org.apache.fop.pdf.PDFICCStream;
import org.apache.fop.pdf.PDFReference;
public class ImageRawPNGAdapter extends AbstractImageAdapter {
/** logging instance */
private static Log log = LogFactory.getLog(ImageRawPNGAdapter.class);
private PDFICCStream pdfICCStream;
private PDFFilter pdfFilter;
private String maskRef;
private PDFReference softMask;
private int numberOfInterleavedComponents;
/**
* Creates a new PDFImage from an Image instance.
* @param image the image
* @param key XObject key
*/
public ImageRawPNGAdapter(ImageRawPNG image, String key) {
super(image, key);
}
/** {@inheritDoc} */
public void setup(PDFDocument doc) {
super.setup(doc);
ColorModel cm = ((ImageRawPNG) this.image).getColorModel();
if (cm instanceof IndexColorModel) {
numberOfInterleavedComponents = 1;
} else {
// this can be 1 (gray), 2 (gray + alpha), 3 (rgb) or 4 (rgb + alpha)
// numberOfInterleavedComponents = (cm.hasAlpha() ? 1 : 0) + cm.getNumColorComponents();
numberOfInterleavedComponents = cm.getNumComponents();
}
// set up image compression for non-alpha channel
FlateFilter flate;
try {
flate = new FlateFilter();
flate.setApplied(true);
flate.setPredictor(FlateFilter.PREDICTION_PNG_OPT);
if (numberOfInterleavedComponents < 3) {
// means palette (1) or gray (1) or gray + alpha (2)
flate.setColors(1);
} else {
// means rgb (3) or rgb + alpha (4)
flate.setColors(3);
}
flate.setColumns(image.getSize().getWidthPx());
flate.setBitsPerComponent(this.getBitsPerComponent());
} catch (PDFFilterException e) {
throw new RuntimeException("FlateFilter configuration error", e);
}
this.pdfFilter = flate;
// Handle transparency channel if applicable; note that for palette images the transparency is
// not TRANSLUCENT
if (cm.hasAlpha() && cm.getTransparency() == ColorModel.TRANSLUCENT) {
doc.getProfile().verifyTransparencyAllowed(image.getInfo().getOriginalURI());
// TODO: Implement code to combine image with background color if transparency is not allowed
// here we need to inflate the PNG pixel data, which includes alpha, separate the alpha channel
// and then deflate it back again
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DeflaterOutputStream dos = new DeflaterOutputStream(baos, new Deflater());
InputStream in = ((ImageRawStream) image).createInputStream();
try {
InflaterInputStream infStream = new InflaterInputStream(in, new Inflater());
DataInputStream dataStream = new DataInputStream(infStream);
// offset is the byte offset of the alpha component
int offset = numberOfInterleavedComponents - 1; // 1 for GA, 3 for RGBA
int numColumns = image.getSize().getWidthPx();
int bytesPerRow = numberOfInterleavedComponents * numColumns;
int filter;
// read line by line; the first byte holds the filter
while ((filter = dataStream.read()) != -1) {
byte[] bytes = new byte[bytesPerRow];
dataStream.readFully(bytes, 0, bytesPerRow);
dos.write((byte) filter);
for (int j = 0; j < numColumns; j++) {
dos.write(bytes, offset, 1);
offset += numberOfInterleavedComponents;
}
offset = numberOfInterleavedComponents - 1;
}
dos.close();
} catch (IOException e) {
throw new RuntimeException("Error processing transparency channel:", e);
} finally {
IOUtils.closeQuietly(in);
}
// set up alpha channel compression
FlateFilter transFlate;
try {
transFlate = new FlateFilter();
transFlate.setApplied(true);
transFlate.setPredictor(FlateFilter.PREDICTION_PNG_OPT);
transFlate.setColors(1);
transFlate.setColumns(image.getSize().getWidthPx());
transFlate.setBitsPerComponent(this.getBitsPerComponent());
} catch (PDFFilterException e) {
throw new RuntimeException("FlateFilter configuration error", e);
}
BitmapImage alphaMask = new BitmapImage("Mask:" + this.getKey(), image.getSize().getWidthPx(),
image.getSize().getHeightPx(), baos.toByteArray(), null);
alphaMask.setPDFFilter(transFlate);
alphaMask.setColorSpace(new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_GRAY));
softMask = doc.addImage(null, alphaMask).makeReference();
}
}
/** {@inheritDoc} */
public PDFDeviceColorSpace getColorSpace() {
// DeviceGray, DeviceRGB, or DeviceCMYK
return toPDFColorSpace(image.getColorSpace());
}
/** {@inheritDoc} */
public int getBitsPerComponent() {
return ((ImageRawPNG) this.image).getBitDepth();
}
/** {@inheritDoc} */
public boolean isTransparent() {
return ((ImageRawPNG) this.image).isTransparent();
}
/** {@inheritDoc} */
public PDFColor getTransparentColor() {
return new PDFColor(((ImageRawPNG) this.image).getTransparentColor());
}
/** {@inheritDoc} */
public String getMask() {
return maskRef;
}
/** {@inheritDoc} */
public String getSoftMask() {
return softMask.toString();
}
/** {@inheritDoc} */
public PDFReference getSoftMaskReference() {
return softMask;
}
/** {@inheritDoc} */
public PDFFilter getPDFFilter() {
return pdfFilter;
}
/** {@inheritDoc} */
public void outputContents(OutputStream out) throws IOException {
InputStream in = ((ImageRawStream) image).createInputStream();
try {
if (numberOfInterleavedComponents == 1 || numberOfInterleavedComponents == 3) {
// means we have Gray, RGB, or Palette
IOUtils.copy(in, out);
} else {
// means we have Gray + alpha or RGB + alpha
// TODO: since we have alpha here do this when the alpha channel is extracted
int numBytes = numberOfInterleavedComponents - 1; // 1 for Gray, 3 for RGB
int numColumns = image.getSize().getWidthPx();
InflaterInputStream infStream = new InflaterInputStream(in, new Inflater());
DataInputStream dataStream = new DataInputStream(infStream);
int offset = 0;
int bytesPerRow = numberOfInterleavedComponents * numColumns;
int filter;
// here we need to inflate the PNG pixel data, which includes alpha, separate the alpha
// channel and then deflate the RGB channels back again
DeflaterOutputStream dos = new DeflaterOutputStream(out, new Deflater());
while ((filter = dataStream.read()) != -1) {
byte[] bytes = new byte[bytesPerRow];
dataStream.readFully(bytes, 0, bytesPerRow);
dos.write((byte) filter);
for (int j = 0; j < numColumns; j++) {
dos.write(bytes, offset, numBytes);
offset += numberOfInterleavedComponents;
}
offset = 0;
}
dos.close();
}
} finally {
IOUtils.closeQuietly(in);
}
}
/** {@inheritDoc} */
public PDFICCStream getICCStream() {
return pdfICCStream;
}
/** {@inheritDoc} */
public String getFilterHint() {
return PDFFilterList.PRECOMPRESSED_FILTER;
}
public void populateXObjectDictionary(PDFDictionary dict) {
ColorModel cm = ((ImageRawPNG) image).getColorModel();
if (cm instanceof IndexColorModel) {
IndexColorModel icm = (IndexColorModel) cm;
super.populateXObjectDictionaryForIndexColorModel(dict, icm);
}
}
}

+ 2
- 71
src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java View File

@@ -27,8 +27,6 @@ import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

@@ -36,14 +34,12 @@ import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
import org.apache.xmlgraphics.ps.ImageEncodingHelper;

import org.apache.fop.pdf.AlphaRasterImage;
import org.apache.fop.pdf.PDFArray;
import org.apache.fop.pdf.PDFColor;
import org.apache.fop.pdf.PDFDeviceColorSpace;
import org.apache.fop.pdf.PDFDictionary;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFFilter;
import org.apache.fop.pdf.PDFFilterList;
import org.apache.fop.pdf.PDFName;
import org.apache.fop.pdf.PDFReference;

/**
@@ -162,30 +158,6 @@ public class ImageRenderedAdapter extends AbstractImageAdapter {
return (getImage().getTransparentColor() != null);
}

private static Integer getIndexOfFirstTransparentColorInPalette(RenderedImage image) {
ColorModel cm = image.getColorModel();
if (cm instanceof IndexColorModel) {
IndexColorModel icm = (IndexColorModel)cm;
//Identify the transparent color in the palette
byte[] alphas = new byte[icm.getMapSize()];
byte[] reds = new byte[icm.getMapSize()];
byte[] greens = new byte[icm.getMapSize()];
byte[] blues = new byte[icm.getMapSize()];
icm.getAlphas(alphas);
icm.getReds(reds);
icm.getGreens(greens);
icm.getBlues(blues);
for (int i = 0;
i < ((IndexColorModel) cm).getMapSize();
i++) {
if ((alphas[i] & 0xFF) == 0) {
return Integer.valueOf(i);
}
}
}
return null;
}

/** {@inheritDoc} */
@Override
public PDFColor getTransparentColor() {
@@ -230,54 +202,13 @@ public class ImageRenderedAdapter extends AbstractImageAdapter {
}
}

private static final int MAX_HIVAL = 255;

/** {@inheritDoc} */
@Override
public void populateXObjectDictionary(PDFDictionary dict) {
ColorModel cm = getEffectiveColorModel();
if (cm instanceof IndexColorModel) {
IndexColorModel icm = (IndexColorModel)cm;
PDFArray indexed = new PDFArray(dict);
indexed.add(new PDFName("Indexed"));

if (icm.getColorSpace().getType() != ColorSpace.TYPE_RGB) {
log.warn("Indexed color space is not using RGB as base color space."
+ " The image may not be handled correctly."
+ " Base color space: " + icm.getColorSpace()
+ " Image: " + image.getInfo());
}
indexed.add(new PDFName(toPDFColorSpace(icm.getColorSpace()).getName()));
int c = icm.getMapSize();
int hival = c - 1;
if (hival > MAX_HIVAL) {
throw new UnsupportedOperationException("hival must not go beyond " + MAX_HIVAL);
}
indexed.add(Integer.valueOf(hival));
int[] palette = new int[c];
icm.getRGBs(palette);
ByteArrayOutputStream baout = new ByteArrayOutputStream();
for (int i = 0; i < c; i++) {
//TODO Probably doesn't work for non RGB based color spaces
//See log warning above
int entry = palette[i];
baout.write((entry & 0xFF0000) >> 16);
baout.write((entry & 0xFF00) >> 8);
baout.write(entry & 0xFF);
}
indexed.add(baout.toByteArray());
IOUtils.closeQuietly(baout);

dict.put("ColorSpace", indexed);
dict.put("BitsPerComponent", icm.getPixelSize());

Integer index = getIndexOfFirstTransparentColorInPalette(getImage().getRenderedImage());
if (index != null) {
PDFArray mask = new PDFArray(dict);
mask.add(index);
mask.add(index);
dict.put("Mask", mask);
}
IndexColorModel icm = (IndexColorModel) cm;
super.populateXObjectDictionaryForIndexColorModel(dict, icm);
}
}


+ 65
- 0
src/java/org/apache/fop/render/pdf/PDFImageHandlerRawPNG.java View File

@@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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$ */

// Original author: Matthias Reichenbacher

package org.apache.fop.render.pdf;

import org.apache.xmlgraphics.image.loader.Image;
import org.apache.xmlgraphics.image.loader.ImageFlavor;
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;

import org.apache.fop.pdf.PDFImage;
import org.apache.fop.render.RenderingContext;

/**
* Image handler implementation which handles CCITT encoded images (CCITT fax group 3/4)
* for PDF output.
*/
public class PDFImageHandlerRawPNG extends AbstractPDFImageHandler {

private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {ImageFlavor.RAW_PNG};

@Override
PDFImage createPDFImage(Image image, String xobjectKey) {
return new ImageRawPNGAdapter((ImageRawPNG) image, xobjectKey);
}

/** {@inheritDoc} */
public int getPriority() {
return 100;
}

/** {@inheritDoc} */
public Class<ImageRawPNG> getSupportedImageClass() {
return ImageRawPNG.class;
}

/** {@inheritDoc} */
public ImageFlavor[] getSupportedImageFlavors() {
return FLAVORS;
}

/** {@inheritDoc} */
public boolean isCompatible(RenderingContext targetContext, Image image) {
return (image == null || image instanceof ImageRawPNG)
&& targetContext instanceof PDFRenderingContext;
}

}

+ 9
- 8
src/java/org/apache/fop/render/ps/FontResourceCache.java View File

@@ -42,19 +42,20 @@ class FontResourceCache {
}

/**
* Returns the PSResource for the given font key.
* Returns the PSFontResource for the given font key.
* @param key the font key ("F*")
* @return the matching PSResource
* @return the matching PSFontResource instance
*/
public PSResource getPSResourceForFontKey(String key) {
PSResource res = null;
public PSFontResource getFontResourceForFontKey(String key) {
PSFontResource res = null;
if (this.fontResources != null) {
res = (PSResource)this.fontResources.get(key);
res = (PSFontResource)this.fontResources.get(key);
} else {
this.fontResources = new java.util.HashMap();
}
if (res == null) {
res = new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key));
res = PSFontResource.createFontResource(
new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key)));
this.fontResources.put(key, res);
}
return res;
@@ -76,9 +77,9 @@ class FontResourceCache {
throw new IllegalStateException("Font not available: " + key);
}
if (postFix == null) {
return tf.getFontName();
return tf.getEmbedFontName();
} else {
return tf.getFontName() + postFix;
return tf.getEmbedFontName() + postFix;
}
}


+ 113
- 0
src/java/org/apache/fop/render/ps/ImageEncoderPNG.java View File

@@ -0,0 +1,113 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.render.ps;

import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

import org.apache.commons.io.IOUtils;

import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;
import org.apache.xmlgraphics.image.loader.impl.ImageRawStream;
import org.apache.xmlgraphics.ps.ImageEncoder;

/**
* ImageEncoder implementation for PNG images.
*/
public class ImageEncoderPNG implements ImageEncoder {
private final ImageRawPNG image;
private int numberOfInterleavedComponents;

/**
* Main constructor
* @param image the PNG image
*/
public ImageEncoderPNG(ImageRawPNG image) {
this.image = image;
ColorModel cm = ((ImageRawPNG) this.image).getColorModel();
if (cm instanceof IndexColorModel) {
numberOfInterleavedComponents = 1;
} else {
// this can be 1 (gray), 2 (gray + alpha), 3 (rgb) or 4 (rgb + alpha)
// numberOfInterleavedComponents = (cm.hasAlpha() ? 1 : 0) + cm.getNumColorComponents();
numberOfInterleavedComponents = cm.getNumComponents();
}
}

/** {@inheritDoc} */
public void writeTo(OutputStream out) throws IOException {
// TODO: refactor this code with equivalent PDF code
InputStream in = ((ImageRawStream) image).createInputStream();
try {
if (numberOfInterleavedComponents == 1 || numberOfInterleavedComponents == 3) {
// means we have Gray, RGB, or Palette
IOUtils.copy(in, out);
} else {
// means we have Gray + alpha or RGB + alpha
int numBytes = numberOfInterleavedComponents - 1; // 1 for Gray, 3 for RGB
int numColumns = image.getSize().getWidthPx();
InflaterInputStream infStream = new InflaterInputStream(in, new Inflater());
DataInputStream dataStream = new DataInputStream(infStream);
int offset = 0;
int bytesPerRow = numberOfInterleavedComponents * numColumns;
int filter;
// here we need to inflate the PNG pixel data, which includes alpha, separate the alpha
// channel and then deflate the RGB channels back again
// TODO: not using the baos below and using the original out instead (as happens in PDF)
// would be preferable but that does not work with the rest of the postscript code; this
// needs to be revisited
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DeflaterOutputStream dos = new DeflaterOutputStream(/* out */baos, new Deflater());
while ((filter = dataStream.read()) != -1) {
byte[] bytes = new byte[bytesPerRow];
dataStream.readFully(bytes, 0, bytesPerRow);
dos.write((byte) filter);
for (int j = 0; j < numColumns; j++) {
dos.write(bytes, offset, numBytes);
offset += numberOfInterleavedComponents;
}
offset = 0;
}
dos.close();
IOUtils.copy(new ByteArrayInputStream(baos.toByteArray()), out);
}
} finally {
IOUtils.closeQuietly(in);
}
}

/** {@inheritDoc} */
public String getImplicitFilter() {
String filter = "<< /Predictor 15 /Columns " + image.getSize().getWidthPx();
filter += " /Colors " + (numberOfInterleavedComponents > 2 ? 3 : 1);
filter += " /BitsPerComponent " + image.getBitDepth() + " >> /FlateDecode";
return filter;
}
}

+ 12
- 7
src/java/org/apache/fop/render/ps/PSDocumentHandler.java View File

@@ -50,6 +50,7 @@ import org.apache.xmlgraphics.ps.dsc.ResourceTracker;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBoundingBox;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox;

import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler;
import org.apache.fop.render.intermediate.IFContext;
@@ -107,6 +108,8 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
private static final int COMMENT_PAGE_TRAILER = 2;
private static final int PAGE_TRAILER_CODE_BEFORE = 3;

private PSEventProducer eventProducer;

/**
* Default constructor.
*/
@@ -126,7 +129,9 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
/** {@inheritDoc} */
public void setContext(IFContext context) {
super.setContext(context);
this.psUtil = new PSRenderingUtil(context.getUserAgent());
FOUserAgent userAgent = context.getUserAgent();
this.psUtil = new PSRenderingUtil(userAgent);
eventProducer = PSEventProducer.Provider.get(userAgent.getEventBroadcaster());
}

/** {@inheritDoc} */
@@ -145,7 +150,7 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
try {
OutputStream out;
if (psUtil.isOptimizeResources()) {
this.tempFile = File.createTempFile("fop", null);
this.tempFile = File.createTempFile("fop", ".ps");
out = new java.io.FileOutputStream(this.tempFile);
out = new java.io.BufferedOutputStream(out);
} else {
@@ -203,7 +208,7 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
gen.writeDSCComment(DSCConstants.BEGIN_SETUP);
PSRenderingUtil.writeSetupCodeList(gen, setupCodeList, "SetupCode");
if (!psUtil.isOptimizeResources()) {
this.fontResources.addAll(PSFontUtils.writeFontDict(gen, fontInfo));
this.fontResources.addAll(PSFontUtils.writeFontDict(gen, fontInfo, eventProducer));
} else {
gen.commentln("%FOPFontSetup"); //Place-holder, will be replaced in the second pass
}
@@ -258,8 +263,8 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
in = new java.io.BufferedInputStream(in);
try {
try {
ResourceHandler handler = new ResourceHandler(getUserAgent(), this.fontInfo,
resTracker, this.formResources);
ResourceHandler handler = new ResourceHandler(getUserAgent(), eventProducer,
this.fontInfo, resTracker, this.formResources);
handler.process(in, this.outputStream,
this.currentPageNumber, this.documentBoundingBox);
this.outputStream.flush();
@@ -547,8 +552,8 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
* @param key the font key ("F*")
* @return the matching PSResource
*/
protected PSResource getPSResourceForFontKey(String key) {
return this.fontResources.getPSResourceForFontKey(key);
protected PSFontResource getPSResourceForFontKey(String key) {
return this.fontResources.getFontResourceForFontKey(key);
}

/**

+ 7
- 0
src/java/org/apache/fop/render/ps/PSEventProducer.java View File

@@ -53,4 +53,11 @@ public interface PSEventProducer extends EventProducer {
*/
void postscriptDictionaryParseError(Object source, String content, Exception e);

/**
* PostScript Level 3 features are necessary.
*
* @param source the event source
* @event.severity FATAL
*/
void postscriptLevel3Needed(Object source);
}

+ 1
- 0
src/java/org/apache/fop/render/ps/PSEventProducer.xml View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<catalogue xml:lang="en">
<message key="postscriptDictionaryParseError">Failed to parse dictionary string. Reason: {e}, content = "{content}"</message>
<message key="postscriptLevel3Needed">PostScript Level 3 features are needed to handle this document.</message>
</catalogue>

+ 77
- 0
src/java/org/apache/fop/render/ps/PSFontResource.java View File

@@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.render.ps;

import org.apache.xmlgraphics.ps.PSResource;
import org.apache.xmlgraphics.ps.dsc.ResourceTracker;

/**
* A DSC resource corresponding to a font. This class handles the possible other resources
* that a font may depend on. For example, a CID-keyed font depends on a CIDFont resource, a
* CMap resource, and the ProcSet CIDInit resource.
*/
abstract class PSFontResource {

static PSFontResource createFontResource(final PSResource fontResource) {
return new PSFontResource() {

String getName() {
return fontResource.getName();
}

void notifyResourceUsageOnPage(ResourceTracker resourceTracker) {
resourceTracker.notifyResourceUsageOnPage(fontResource);
}
};
}

static PSFontResource createFontResource(final PSResource fontResource,
final PSResource procsetCIDInitResource, final PSResource cmapResource,
final PSResource cidFontResource) {
return new PSFontResource() {

String getName() {
return fontResource.getName();
}

void notifyResourceUsageOnPage(ResourceTracker resourceTracker) {
resourceTracker.notifyResourceUsageOnPage(fontResource);
resourceTracker.notifyResourceUsageOnPage(procsetCIDInitResource);
resourceTracker.notifyResourceUsageOnPage(cmapResource);
resourceTracker.notifyResourceUsageOnPage(cidFontResource);
}
};
}

/**
* Returns the name of the font resource.
*
* @return the name of the font
*/
abstract String getName();

/**
* Notifies the given resource tracker of all the resources needed by this font.
*
* @param resourceTracker
*/
abstract void notifyResourceUsageOnPage(ResourceTracker resourceTracker);

}

+ 393
- 47
src/java/org/apache/fop/render/ps/PSFontUtils.java View File

@@ -23,7 +23,11 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
@@ -38,14 +42,26 @@ import org.apache.xmlgraphics.ps.PSResource;
import org.apache.xmlgraphics.ps.dsc.ResourceTracker;

import org.apache.fop.fonts.Base14Font;
import org.apache.fop.fonts.CIDFontType;
import org.apache.fop.fonts.CIDSubset;
import org.apache.fop.fonts.CMapSegment;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontType;
import org.apache.fop.fonts.LazyFont;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.SingleByteEncoding;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion;
import org.apache.fop.fonts.truetype.TTFOutputStream;
import org.apache.fop.fonts.truetype.TTFSubSetFile;
import org.apache.fop.render.ps.fonts.PSTTFOutputStream;
import org.apache.fop.util.HexEncoder;

/**
* Utility code for font handling in PostScript.
@@ -54,7 +70,6 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {

/** logging instance */
protected static final Log log = LogFactory.getLog(PSFontUtils.class);

/**
* Generates the PostScript code for the font dictionary. This method should only be
* used if no "resource optimization" is performed, i.e. when the fonts are not embedded
@@ -66,7 +81,22 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
*/
public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo)
throws IOException {
return writeFontDict(gen, fontInfo, fontInfo.getFonts(), true);
return writeFontDict(gen, fontInfo, null);
}

/**
* Generates the PostScript code for the font dictionary. This method should only be
* used if no "resource optimization" is performed, i.e. when the fonts are not embedded
* in a second pass.
* @param gen PostScript generator to use for output
* @param fontInfo available fonts
* @param eventProducer to report events
* @return a Map of PSResource instances representing all defined fonts (key: font key)
* @throws IOException in case of an I/O problem
*/
public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo,
PSEventProducer eventProducer) throws IOException {
return writeFontDict(gen, fontInfo, fontInfo.getFonts(), true, eventProducer);
}

/**
@@ -76,13 +106,13 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
* @param gen PostScript generator to use for output
* @param fontInfo available fonts
* @param fonts the set of fonts to work with
* @param eventProducer the event producer
* @return a Map of PSResource instances representing all defined fonts (key: font key)
* @throws IOException in case of an I/O problem
*/
public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo,
Map<String, Typeface> fonts)
throws IOException {
return writeFontDict(gen, fontInfo, fonts, false);
public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo, Map<String, Typeface> fonts,
PSEventProducer eventProducer) throws IOException {
return writeFontDict(gen, fontInfo, fonts, false, eventProducer);
}

/**
@@ -96,15 +126,16 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
* @throws IOException in case of an I/O problem
*/
private static Map writeFontDict(PSGenerator gen, FontInfo fontInfo,
Map<String, Typeface> fonts, boolean encodeAllCharacters) throws IOException {
Map<String, Typeface> fonts, boolean encodeAllCharacters, PSEventProducer eventProducer)
throws IOException {
gen.commentln("%FOPBeginFontDict");

Map fontResources = new java.util.HashMap();
Map fontResources = new HashMap();
for (String key : fonts.keySet()) {
Typeface tf = getTypeFace(fontInfo, fonts, key);
PSResource fontRes = new PSResource(PSResource.TYPE_FONT, tf.getFontName());
fontResources.put(key, fontRes);
embedFont(gen, tf, fontRes);
PSResource fontRes = new PSResource(PSResource.TYPE_FONT, tf.getEmbedFontName());
PSFontResource fontResource = embedFont(gen, tf, fontRes, eventProducer);
fontResources.put(key, fontResource);

if (tf instanceof SingleByteFont) {
SingleByteFont sbf = (SingleByteFont)tf;
@@ -117,9 +148,18 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
SingleByteEncoding encoding = sbf.getAdditionalEncoding(i);
defineEncoding(gen, encoding);
String postFix = "_" + (i + 1);
PSResource derivedFontRes = defineDerivedFont(gen, tf.getFontName(),
tf.getFontName() + postFix, encoding.getName());
fontResources.put(key + postFix, derivedFontRes);
PSResource derivedFontRes;
if (tf.getFontType() == FontType.TRUETYPE
&& sbf.getTrueTypePostScriptVersion() != PostScriptVersion.V2) {
derivedFontRes = defineDerivedTrueTypeFont(gen, eventProducer,
tf.getEmbedFontName(), tf.getEmbedFontName() + postFix, encoding,
sbf.getCMap());
} else {
derivedFontRes = defineDerivedFont(gen, tf.getEmbedFontName(),
tf.getEmbedFontName() + postFix, encoding.getName());
}
fontResources.put(key + postFix,
PSFontResource.createFontResource(derivedFontRes));
}
}
}
@@ -156,12 +196,12 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
} else {
if (tf instanceof Base14Font) {
//Our Base 14 fonts don't use the default encoding
redefineFontEncoding(gen, tf.getFontName(), tf.getEncodingName());
redefineFontEncoding(gen, tf.getEmbedFontName(), tf.getEncodingName());
} else if (tf instanceof SingleByteFont) {
SingleByteFont sbf = (SingleByteFont)tf;
if (!sbf.isUsingNativeEncoding()) {
//Font has been configured to use an encoding other than the default one
redefineFontEncoding(gen, tf.getFontName(), tf.getEncodingName());
redefineFontEncoding(gen, tf.getEmbedFontName(), tf.getEncodingName());
}
}
}
@@ -184,39 +224,299 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
return tf;
}

/**
* Embeds a font in the PostScript file.
* @param gen the PostScript generator
* @param tf the font
* @param fontRes the PSResource associated with the font
* @throws IOException In case of an I/O error
*/
public static void embedFont(PSGenerator gen, Typeface tf, PSResource fontRes)
throws IOException {
boolean embeddedFont = false;
if (FontType.TYPE1 == tf.getFontType()) {
if (tf instanceof CustomFont) {
CustomFont cf = (CustomFont)tf;
if (isEmbeddable(cf)) {
InputStream in = getInputStreamOnFont(gen, cf);
if (in != null) {
gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE,
fontRes);
embedType1Font(gen, in);
gen.writeDSCComment(DSCConstants.END_RESOURCE);
gen.getResourceTracker().registerSuppliedResource(fontRes);
embeddedFont = true;
} else {
gen.commentln("%WARNING: Could not embed font: " + cf.getFontName());
log.warn("Font " + cf.getFontName() + " is marked as supplied in the"
+ " PostScript file but could not be embedded!");
private static PSFontResource embedFont(PSGenerator gen, Typeface tf, PSResource fontRes,
PSEventProducer eventProducer) throws IOException {
FontType fontType = tf.getFontType();
PSFontResource fontResource = null;
if (!(fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE
|| fontType == FontType.TYPE0) || !(tf instanceof CustomFont)) {
gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes);
fontResource = PSFontResource.createFontResource(fontRes);
return fontResource;
}
CustomFont cf = (CustomFont)tf;
if (isEmbeddable(cf)) {
InputStream in = getInputStreamOnFont(gen, cf);
if (in == null) {
gen.commentln("%WARNING: Could not embed font: " + cf.getEmbedFontName());
log.warn("Font " + cf.getEmbedFontName() + " is marked as supplied in the"
+ " PostScript file but could not be embedded!");
gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes);
fontResource = PSFontResource.createFontResource(fontRes);
return fontResource;
}
if (fontType == FontType.TYPE0) {
if (gen.embedIdentityH()) {
checkPostScriptLevel3(gen, eventProducer);
/*
* First CID-keyed font to be embedded; add
* %%IncludeResource: comment for ProcSet CIDInit.
*/
gen.includeProcsetCIDInitResource();
}
PSResource cidFontResource = embedType2CIDFont(gen,
(MultiByteFont) tf, in);
fontResource = PSFontResource.createFontResource(fontRes,
gen.getProcsetCIDInitResource(), gen.getIdentityHCMapResource(),
cidFontResource);
}
gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, fontRes);
if (fontType == FontType.TYPE1) {
embedType1Font(gen, in);
fontResource = PSFontResource.createFontResource(fontRes);
} else if (fontType == FontType.TRUETYPE) {
embedTrueTypeFont(gen, (SingleByteFont) tf, in);
fontResource = PSFontResource.createFontResource(fontRes);
} else {
composeType0Font(gen, (MultiByteFont) tf, in);
}
gen.writeDSCComment(DSCConstants.END_RESOURCE);
gen.getResourceTracker().registerSuppliedResource(fontRes);
}
return fontResource;
}

private static void checkPostScriptLevel3(PSGenerator gen, PSEventProducer eventProducer) {
if (gen.getPSLevel() < 3) {
if (eventProducer != null) {
eventProducer.postscriptLevel3Needed(gen);
} else {
throw new IllegalStateException("PostScript Level 3 is"
+ " required to use TrueType fonts,"
+ " configured level is "
+ gen.getPSLevel());
}
}
}

private static void embedTrueTypeFont(PSGenerator gen,
SingleByteFont font, InputStream fontStream) throws IOException {
/* See Adobe Technical Note #5012, "The Type 42 Font Format Specification" */
gen.commentln("%!PS-TrueTypeFont-65536-65536-1"); // TODO TrueType & font versions
gen.writeln("11 dict begin");
if (font.getEmbeddingMode() == EmbeddingMode.AUTO) {
font.setEmbeddingMode(EmbeddingMode.SUBSET);
}
FontFileReader reader = new FontFileReader(fontStream);
TTFFile ttfFile = new TTFFile();
ttfFile.readFont(reader, font.getFullName());
createType42DictionaryEntries(gen, font, font.getCMap(), ttfFile);
gen.writeln("FontName currentdict end definefont pop");
}

private static void createType42DictionaryEntries(PSGenerator gen, CustomFont font,
CMapSegment[] cmap, TTFFile ttfFile) throws IOException {
gen.write("/FontName /");
gen.write(font.getEmbedFontName());
gen.writeln(" def");
gen.writeln("/PaintType 0 def");
gen.writeln("/FontMatrix [1 0 0 1 0 0] def");
writeFontBBox(gen, font);
gen.writeln("/FontType 42 def");
gen.writeln("/Encoding 256 array");
gen.writeln("0 1 255{1 index exch/.notdef put}for");
boolean buildCharStrings;
Set<String> glyphNames = new HashSet<String>();
if (font.getFontType() == FontType.TYPE0 && font.getEmbeddingMode() != EmbeddingMode.FULL) {
//"/Encoding" is required but ignored for CID fonts
//so we keep it minimal to save space
buildCharStrings = false;
} else {
buildCharStrings = true;
for (int i = 0; i < Glyphs.WINANSI_ENCODING.length; i++) {
gen.write("dup ");
gen.write(i);
gen.write(" /");
String glyphName = Glyphs.charToGlyphName(Glyphs.WINANSI_ENCODING[i]);
if (glyphName.equals("")) {
gen.write(Glyphs.NOTDEF);
} else {
gen.write(glyphName);
glyphNames.add(glyphName);
}
gen.writeln(" put");
}
}
gen.writeln("readonly def");
TTFOutputStream ttfOut = new PSTTFOutputStream(gen);
ttfFile.stream(ttfOut);

buildCharStrings(gen, buildCharStrings, cmap, glyphNames, font);
}

private static void buildCharStrings(PSGenerator gen, boolean buildCharStrings,
CMapSegment[] cmap, Set<String> glyphNames, CustomFont font) throws IOException {
gen.write("/CharStrings ");
if (!buildCharStrings) {
gen.write(1);
} else if (font.getEmbeddingMode() != EmbeddingMode.FULL) {
int charCount = 1; //1 for .notdef
for (CMapSegment segment : cmap) {
charCount += segment.getUnicodeEnd() - segment.getUnicodeStart() + 1;
}
gen.write(charCount);
} else {
gen.write(font.getCMap().length);
}
gen.writeln(" dict dup begin");
gen.write("/");
gen.write(Glyphs.NOTDEF);
gen.writeln(" 0 def"); // .notdef always has to be at index 0
if (!buildCharStrings) {
// If we're not building the full CharStrings we can end here
gen.writeln("end readonly def");
return;
}
if (font.getEmbeddingMode() != EmbeddingMode.FULL) {
//Only performed in singly-byte mode, ignored for CID fonts
for (CMapSegment segment : cmap) {
int glyphIndex = segment.getGlyphStartIndex();
for (int ch = segment.getUnicodeStart(); ch <= segment.getUnicodeEnd(); ch++) {
char ch16 = (char)ch; //TODO Handle Unicode characters beyond 16bit
String glyphName = Glyphs.charToGlyphName(ch16);
if ("".equals(glyphName)) {
glyphName = "u" + Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
}
writeGlyphDefs(gen, glyphName, glyphIndex);

glyphIndex++;
}
}
} else {
for (String name : glyphNames) {
writeGlyphDefs(gen, name,
getGlyphIndex(Glyphs.getUnicodeSequenceForGlyphName(name).charAt(0),
font.getCMap()));
}
}
if (!embeddedFont) {
gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes);
gen.writeln("end readonly def");
}

private static void writeGlyphDefs(PSGenerator gen, String glyphName, int glyphIndex)
throws IOException {
gen.write("/");
gen.write(glyphName);
gen.write(" ");
gen.write(glyphIndex);
gen.writeln(" def");
}

private static int getGlyphIndex(char c, CMapSegment[] cmap) {
for (CMapSegment segment : cmap) {
if (segment.getUnicodeStart() <= c && c <= segment.getUnicodeEnd()) {
return segment.getGlyphStartIndex() + c - segment.getUnicodeStart();
}
}
return 0;
}

private static void composeType0Font(PSGenerator gen, MultiByteFont font,
InputStream fontStream) throws IOException {
String psName = font.getEmbedFontName();
gen.write("/");
gen.write(psName);
gen.write(" /Identity-H [/");
gen.write(psName);
gen.writeln("] composefont pop");
}

private static PSResource embedType2CIDFont(PSGenerator gen,
MultiByteFont font, InputStream fontStream) throws IOException {
assert font.getCIDType() == CIDFontType.CIDTYPE2;

String psName = font.getEmbedFontName();
gen.write("%%BeginResource: CIDFont ");
gen.writeln(psName);

gen.write("%%Title: (");
gen.write(psName);
gen.writeln(" Adobe Identity 0)");

gen.writeln("%%Version: 1"); // TODO use font revision?
gen.writeln("/CIDInit /ProcSet findresource begin");
gen.writeln("20 dict begin");

gen.write("/CIDFontName /");
gen.write(psName);
gen.writeln(" def");

gen.writeln("/CIDFontVersion 1 def"); // TODO same as %%Version above

gen.write("/CIDFontType ");
gen.write(font.getCIDType().getValue());
gen.writeln(" def");

gen.writeln("/CIDSystemInfo 3 dict dup begin");
gen.writeln(" /Registry (Adobe) def");
gen.writeln(" /Ordering (Identity) def");
gen.writeln(" /Supplement 0 def");
gen.writeln("end def");

// TODO UIDBase (and UIDOffset in CMap) necessary if PostScript Level 1 & 2
// interpreters are to be supported
// (Level 1: with composite font extensions; Level 2: those that do not offer
// native mode support for CID-keyed fonts)

// TODO XUID (optional but strongly recommended)

// TODO /FontInfo

gen.write("/CIDCount ");
CIDSubset cidSubset = font.getCIDSubset();
int subsetSize = cidSubset.getSubsetSize();
gen.write(subsetSize);
gen.writeln(" def");
gen.writeln("/GDBytes 2 def"); // TODO always 2?
gen.writeln("/CIDMap [<");
int colCount = 0;
int lineCount = 1;
for (int cid = 0; cid < subsetSize; cid++) {
if (colCount++ == 20) {
gen.newLine();
colCount = 1;
if (lineCount++ == 800) {
gen.writeln("> <");
lineCount = 1;
}
}
String gid;
if (font.getEmbeddingMode() != EmbeddingMode.FULL) {
gid = HexEncoder.encode(cid, 4);
} else {
gid = HexEncoder.encode(cidSubset.getGlyphIndexForSubsetIndex(cid), 4);
}
gen.write(gid);
}
gen.writeln(">] def");
FontFileReader reader = new FontFileReader(fontStream);

TTFFile ttfFile;
if (font.getEmbeddingMode() != EmbeddingMode.FULL) {
ttfFile = new TTFSubSetFile();
ttfFile.readFont(reader, font.getTTCName(), font.getUsedGlyphs());
} else {
ttfFile = new TTFFile();
ttfFile.readFont(reader, font.getTTCName());
}


createType42DictionaryEntries(gen, font, new CMapSegment[0], ttfFile);
gen.writeln("CIDFontName currentdict end /CIDFont defineresource pop");
gen.writeln("end");
gen.writeln("%%EndResource");
PSResource cidFontResource = new PSResource(PSResource.TYPE_CIDFONT, psName);
gen.getResourceTracker().registerSuppliedResource(cidFontResource);
return cidFontResource;
}

private static void writeFontBBox(PSGenerator gen, CustomFont font) throws IOException {
int[] bbox = font.getFontBBox();
gen.write("/FontBBox[");
for (int i = 0; i < 4; i++) {
gen.write(" ");
gen.write(bbox[i]);
}
gen.writeln(" ] def");
}

private static boolean isEmbeddable(CustomFont font) {
@@ -273,12 +573,20 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
Map fontResources = new java.util.HashMap();
for (String key : fonts.keySet()) {
Typeface tf = getTypeFace(fontInfo, fonts, key);
PSResource fontRes = new PSResource("font", tf.getFontName());
PSResource fontRes = new PSResource("font", tf.getEmbedFontName());
fontResources.put(key, fontRes);
if (FontType.TYPE1 == tf.getFontType()) {
FontType fontType = tf.getFontType();
if (fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE
|| fontType == FontType.TYPE0) {
if (tf instanceof CustomFont) {
CustomFont cf = (CustomFont)tf;
if (isEmbeddable(cf)) {
if (fontType == FontType.TYPE0) {
resTracker.registerSuppliedResource(
new PSResource(PSResource.TYPE_CIDFONT, tf.getEmbedFontName()));
resTracker.registerSuppliedResource(
new PSResource(PSResource.TYPE_CMAP, "Identity-H"));
}
resTracker.registerSuppliedResource(fontRes);
}
if (tf instanceof SingleByteFont) {
@@ -289,7 +597,7 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
PSResource.TYPE_ENCODING, encoding.getName());
resTracker.registerSuppliedResource(encodingRes);
PSResource derivedFontRes = new PSResource(
PSResource.TYPE_FONT, tf.getFontName() + "_" + (i + 1));
PSResource.TYPE_FONT, tf.getEmbedFontName() + "_" + (i + 1));
resTracker.registerSuppliedResource(derivedFontRes);
}
}
@@ -366,4 +674,42 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
return res;
}

private static PSResource defineDerivedTrueTypeFont(PSGenerator gen,
PSEventProducer eventProducer, String baseFontName, String fontName,
SingleByteEncoding encoding, CMapSegment[] cmap) throws IOException {
checkPostScriptLevel3(gen, eventProducer);
PSResource res = new PSResource(PSResource.TYPE_FONT, fontName);
gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, res);
gen.commentln("%XGCDependencies: font " + baseFontName);
gen.commentln("%XGC+ encoding " + encoding.getName());
gen.writeln("/" + baseFontName + " findfont");
gen.writeln("dup length dict begin");
gen.writeln(" {1 index /FID ne {def} {pop pop} ifelse} forall");
gen.writeln(" /Encoding " + encoding.getName() + " def");

gen.writeln(" /CharStrings 256 dict dup begin");
String[] charNameMap = encoding.getCharNameMap();
char[] unicodeCharMap = encoding.getUnicodeCharMap();
assert charNameMap.length == unicodeCharMap.length;
for (int i = 0; i < charNameMap.length; i++) {
String glyphName = charNameMap[i];
gen.write(" /");
gen.write(glyphName);
gen.write(" ");
if (glyphName.equals(".notdef")) {
gen.write(0);
} else {
gen.write(getGlyphIndex(unicodeCharMap[i], cmap));
}
gen.writeln(" def");
}
gen.writeln(" end readonly def");

gen.writeln(" currentdict");
gen.writeln("end");
gen.writeln("/" + fontName + " exch definefont pop");
gen.writeDSCComment(DSCConstants.END_RESOURCE);
gen.getResourceTracker().registerSuppliedResource(res);
return res;
}
}

+ 111
- 0
src/java/org/apache/fop/render/ps/PSImageHandlerRawPNG.java View File

@@ -0,0 +1,111 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.ps;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.io.IOException;

import org.apache.xmlgraphics.image.loader.Image;
import org.apache.xmlgraphics.image.loader.ImageFlavor;
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;
import org.apache.xmlgraphics.ps.FormGenerator;
import org.apache.xmlgraphics.ps.ImageEncoder;
import org.apache.xmlgraphics.ps.ImageFormGenerator;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSImageUtils;

import org.apache.fop.render.RenderingContext;

/**
* Image handler implementation which handles raw (not decoded) PNG images for PostScript output.
*/
public class PSImageHandlerRawPNG implements PSImageHandler {

private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {ImageFlavor.RAW_PNG};

/** {@inheritDoc} */
public void handleImage(RenderingContext context, Image image, Rectangle pos) throws IOException {
PSRenderingContext psContext = (PSRenderingContext) context;
PSGenerator gen = psContext.getGenerator();
ImageRawPNG png = (ImageRawPNG) image;

float x = (float) pos.getX() / 1000f;
float y = (float) pos.getY() / 1000f;
float w = (float) pos.getWidth() / 1000f;
float h = (float) pos.getHeight() / 1000f;
Rectangle2D targetRect = new Rectangle2D.Float(x, y, w, h);

ImageEncoder encoder = new ImageEncoderPNG(png);
ImageInfo info = image.getInfo();
Dimension imgDim = info.getSize().getDimensionPx();
String imgDescription = image.getClass().getName();
ColorModel cm = png.getColorModel();

PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen);
}

/** {@inheritDoc} */
public void generateForm(RenderingContext context, Image image, PSImageFormResource form)
throws IOException {
PSRenderingContext psContext = (PSRenderingContext) context;
PSGenerator gen = psContext.getGenerator();
ImageRawPNG png = (ImageRawPNG) image;
ImageInfo info = image.getInfo();
String imageDescription = info.getMimeType() + " " + info.getOriginalURI();

ImageEncoder encoder = new ImageEncoderPNG(png);
FormGenerator formGen = new ImageFormGenerator(form.getName(), imageDescription, info.getSize()
.getDimensionPt(), info.getSize().getDimensionPx(), encoder, png.getColorSpace(),
false);
formGen.generate(gen);
}

/** {@inheritDoc} */
public int getPriority() {
return 200;
}

/** {@inheritDoc} */
public Class<ImageRawPNG> getSupportedImageClass() {
return ImageRawPNG.class;
}

/** {@inheritDoc} */
public ImageFlavor[] getSupportedImageFlavors() {
return FLAVORS;
}

/** {@inheritDoc} */
public boolean isCompatible(RenderingContext targetContext, Image image) {
if (targetContext instanceof PSRenderingContext) {
PSRenderingContext psContext = (PSRenderingContext) targetContext;
// The filters required for this implementation need PS level 2 or higher
if (psContext.getGenerator().getPSLevel() >= 2) {
return (image == null || image instanceof ImageRawPNG);
}
}
return false;
}

}

+ 19
- 7
src/java/org/apache/fop/render/ps/PSImageHandlerRenderedImage.java View File

@@ -19,7 +19,10 @@

package org.apache.fop.render.ps;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.RenderedImage;
import java.io.IOException;

@@ -28,6 +31,8 @@ import org.apache.xmlgraphics.image.loader.ImageFlavor;
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
import org.apache.xmlgraphics.ps.FormGenerator;
import org.apache.xmlgraphics.ps.ImageEncoder;
import org.apache.xmlgraphics.ps.ImageEncodingHelper;
import org.apache.xmlgraphics.ps.ImageFormGenerator;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSImageUtils;
@@ -47,17 +52,24 @@ public class PSImageHandlerRenderedImage implements PSImageHandler {
/** {@inheritDoc} */
public void handleImage(RenderingContext context, Image image, Rectangle pos)
throws IOException {
PSRenderingContext psContext = (PSRenderingContext)context;
PSRenderingContext psContext = (PSRenderingContext) context;
PSGenerator gen = psContext.getGenerator();
ImageRendered imageRend = (ImageRendered)image;
ImageRendered imageRend = (ImageRendered) image;

float x = (float)pos.getX() / 1000f;
float y = (float)pos.getY() / 1000f;
float w = (float)pos.getWidth() / 1000f;
float h = (float)pos.getHeight() / 1000f;
float x = (float) pos.getX() / 1000f;
float y = (float) pos.getY() / 1000f;
float w = (float) pos.getWidth() / 1000f;
float h = (float) pos.getHeight() / 1000f;
Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h);

RenderedImage ri = imageRend.getRenderedImage();
PSImageUtils.renderBitmapImage(ri, x, y, w, h, gen);
ImageEncoder encoder = ImageEncodingHelper.createRenderedImageEncoder(ri);
Dimension imgDim = new Dimension(ri.getWidth(), ri.getHeight());
String imgDescription = ri.getClass().getName();
ImageEncodingHelper helper = new ImageEncodingHelper(ri);
ColorModel cm = helper.getEncodedColorModel();

PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen);
}

/** {@inheritDoc} */

+ 48
- 18
src/java/org/apache/fop/render/ps/PSPainter.java View File

@@ -44,6 +44,7 @@ import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.fonts.LazyFont;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.render.RenderingContext;
@@ -55,6 +56,7 @@ import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
import org.apache.fop.util.HexEncoder;

/**
* IFPainter implementation that produces PostScript.
@@ -392,7 +394,7 @@ public class PSPainter extends AbstractIFPainter {
if (currentEncoding != encoding) {
if (i > 0) {
writeText(text, start, i - start,
letterSpacing, wordSpacing, dp, font, tf);
letterSpacing, wordSpacing, dp, font, tf, false);
}
if (encoding == 0) {
useFont(fontKey, sizeMillipoints);
@@ -404,19 +406,18 @@ public class PSPainter extends AbstractIFPainter {
}
}
} else {
//Simple single-font painting
useFont(fontKey, sizeMillipoints);
}
writeText(text, start, textLen - start, letterSpacing, wordSpacing, dp, font, tf);
writeText(text, start, textLen - start, letterSpacing, wordSpacing, dp, font, tf,
tf instanceof MultiByteFont);
} catch (IOException ioe) {
throw new IFException("I/O error in drawText()", ioe);
}
}

private void writeText( // CSOK: ParameterNumber
String text, int start, int len,
private void writeText(String text, int start, int len,
int letterSpacing, int wordSpacing, int[][] dp,
Font font, Typeface tf) throws IOException {
Font font, Typeface tf, boolean multiByte) throws IOException {
PSGenerator generator = getGenerator();
int end = start + len;
int initialSize = len;
@@ -451,8 +452,12 @@ public class PSPainter extends AbstractIFPainter {
if (dx != null && i < dxl - 1) {
glyphAdjust -= dx[i + 1];
}
char codepoint = (char)(ch % 256);
PSGenerator.escapeChar(codepoint, accText); //add character to accumulated text
if (multiByte) {
accText.append(HexEncoder.encode(ch));
} else {
char codepoint = (char)(ch % 256);
PSGenerator.escapeChar(codepoint, accText); //add character to accumulated text
}
if (glyphAdjust != 0) {
needTJ = true;
if (sb.length() == 0) {
@@ -463,9 +468,8 @@ public class PSPainter extends AbstractIFPainter {
sb.append(PSGenerator.LF);
lineStart = sb.length();
}
sb.append('(');
sb.append(accText);
sb.append(") ");
lineStart = writePostScriptString(sb, accText, multiByte, lineStart);
sb.append(' ');
accText.setLength(0); //reset accumulated text
}
sb.append(Integer.toString(glyphAdjust)).append(' ');
@@ -473,9 +477,10 @@ public class PSPainter extends AbstractIFPainter {
}
if (needTJ) {
if (accText.length() > 0) {
sb.append('(');
sb.append(accText);
sb.append(')');
if ((sb.length() - lineStart + accText.length()) > 200) {
sb.append(PSGenerator.LF);
}
writePostScriptString(sb, accText, multiByte);
}
if (hasLetterSpacing) {
sb.append("] " + formatMptAsPt(generator, letterSpacing) + " ATJ");
@@ -483,7 +488,7 @@ public class PSPainter extends AbstractIFPainter {
sb.append("] TJ");
}
} else {
sb.append('(').append(accText).append(")");
writePostScriptString(sb, accText, multiByte);
if (hasLetterSpacing) {
StringBuffer spb = new StringBuffer();
spb.append(formatMptAsPt(generator, letterSpacing))
@@ -497,12 +502,37 @@ public class PSPainter extends AbstractIFPainter {
generator.writeln(sb.toString());
}

private void writePostScriptString(StringBuffer buffer, StringBuffer string,
boolean multiByte) {
writePostScriptString(buffer, string, multiByte, 0);
}

private int writePostScriptString(StringBuffer buffer, StringBuffer string, boolean multiByte,
int lineStart) {
buffer.append(multiByte ? '<' : '(');
int l = string.length();
int index = 0;
int maxCol = 200;
buffer.append(string.substring(index, Math.min(index + maxCol, l)));
index += maxCol;
while (index < l) {
if (!multiByte) {
buffer.append('\\');
}
buffer.append(PSGenerator.LF);
lineStart = buffer.length();
buffer.append(string.substring(index, Math.min(index + maxCol, l)));
index += maxCol;
}
buffer.append(multiByte ? '>' : ')');
return lineStart;
}

private void useFont(String key, int size) throws IOException {
PSResource res = this.documentHandler.getPSResourceForFontKey(key);
PSFontResource res = this.documentHandler.getPSResourceForFontKey(key);
PSGenerator generator = getGenerator();
generator.useFont("/" + res.getName(), size / 1000f);
generator.getResourceTracker().notifyResourceUsageOnPage(res);
res.notifyResourceUsageOnPage(generator.getResourceTracker());
}


}

+ 33
- 12
src/java/org/apache/fop/render/ps/PSTextPainter.java View File

@@ -41,12 +41,15 @@ import org.apache.batik.gvt.text.TextSpanLayout;

import org.apache.xmlgraphics.java2d.ps.PSGraphics2D;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSResource;

import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontMetrics;
import org.apache.fop.fonts.LazyFont;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.svg.NativeTextPainter;
import org.apache.fop.util.CharUtilities;
import org.apache.fop.util.HexEncoder;

/**
* Renders the attributed character iterator of a text node.
@@ -240,9 +243,9 @@ public class PSTextPainter extends NativeTextPainter {
}
}

private PSResource getResourceForFont(Font f, String postfix) {
private PSFontResource getResourceForFont(Font f, String postfix) {
String key = (postfix != null ? f.getFontName() + '_' + postfix : f.getFontName());
return this.fontResources.getPSResourceForFontKey(key);
return this.fontResources.getFontResourceForFontKey(key);
}

private void clip(PSGraphics2D ps, Shape shape) throws IOException {
@@ -299,9 +302,9 @@ public class PSTextPainter extends NativeTextPainter {
public void selectFont(Font f, char mapped) throws IOException {
int encoding = mapped / 256;
String postfix = (encoding == 0 ? null : Integer.toString(encoding));
PSResource res = getResourceForFont(f, postfix);
PSFontResource res = getResourceForFont(f, postfix);
gen.useFont("/" + res.getName(), f.getFontSize() / 1000f);
gen.getResourceTracker().notifyResourceUsageOnPage(res);
res.notifyResourceUsageOnPage(gen.getResourceTracker());
}

public Font getCurrentFont() {
@@ -427,15 +430,23 @@ public class PSTextPainter extends NativeTextPainter {
textUtil.setCurrentFont(f, mapped);
applyColor(paint, gen);

FontMetrics metrics = f.getFontMetrics();
boolean multiByte = metrics instanceof MultiByteFont
|| metrics instanceof LazyFont
&& ((LazyFont) metrics).getRealFont() instanceof MultiByteFont;
StringBuffer sb = new StringBuffer();
sb.append('(');
sb.append(multiByte ? '<' : '(');
for (int i = 0, c = this.currentChars.length(); i < c; i++) {
char ch = this.currentChars.charAt(i);
mapped = f.mapChar(ch);
char codepoint = (char) (mapped % 256);
PSGenerator.escapeChar(codepoint, sb);
if (multiByte) {
sb.append(HexEncoder.encode(mapped));
} else {
char codepoint = (char) (mapped % 256);
PSGenerator.escapeChar(codepoint, sb);
}
}
sb.append(')');
sb.append(multiByte ? '>' : ')');
if (x || y) {
sb.append("\n[");
int idx = 0;
@@ -513,10 +524,20 @@ public class PSTextPainter extends NativeTextPainter {
textUtil.selectFont(f, mapped);
textUtil.setCurrentFont(f, mapped);
}
mapped = f.mapChar(this.currentChars.charAt(i));
//add glyph outlines to current path
char codepoint = (char)(mapped % 256);
gen.write("(" + codepoint + ")");
mapped = f.mapChar(this.currentChars.charAt(i));
FontMetrics metrics = f.getFontMetrics();
boolean multiByte = metrics instanceof MultiByteFont
|| metrics instanceof LazyFont
&& ((LazyFont) metrics).getRealFont() instanceof MultiByteFont;
if (multiByte) {
gen.write('<');
gen.write(HexEncoder.encode(mapped));
gen.write('>');
} else {
char codepoint = (char)(mapped % 256);
gen.write("(" + codepoint + ")");
}
gen.writeln(" false charpath");

if (iter.hasNext()) {

+ 7
- 3
src/java/org/apache/fop/render/ps/ResourceHandler.java View File

@@ -83,6 +83,8 @@ public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors {
private FOUserAgent userAgent;
private FontInfo fontInfo;

private PSEventProducer eventProducer;

private ResourceTracker resTracker;

//key: URI, values PSImageFormResource
@@ -93,13 +95,15 @@ public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors {
/**
* Main constructor.
* @param userAgent the FO user agent
* @param eventProducer the event producer
* @param fontInfo the font information
* @param resTracker the resource tracker to use
* @param formResources Contains all forms used by this document (maintained by PSRenderer)
*/
public ResourceHandler(FOUserAgent userAgent, FontInfo fontInfo,
ResourceTracker resTracker, Map formResources) {
public ResourceHandler(FOUserAgent userAgent, PSEventProducer eventProducer,
FontInfo fontInfo, ResourceTracker resTracker, Map formResources) {
this.userAgent = userAgent;
this.eventProducer = eventProducer;
this.fontInfo = fontInfo;
this.resTracker = resTracker;
determineInlineForms(formResources);
@@ -222,7 +226,7 @@ public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors {
if (fontSetupPlaceholder == null) {
throw new DSCException("Didn't find %FOPFontSetup comment in stream");
}
PSFontUtils.writeFontDict(gen, fontInfo, fontInfo.getUsedFonts());
PSFontUtils.writeFontDict(gen, fontInfo, fontInfo.getUsedFonts(), eventProducer);
generateForms(globalFormResources, gen);

//Skip the prolog and to the first page

+ 101
- 0
src/java/org/apache/fop/render/ps/fonts/PSTTFGenerator.java View File

@@ -0,0 +1,101 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.render.ps.fonts;

import java.io.IOException;

import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.util.io.ASCIIHexOutputStream;

/**
* This is a wrapper for {@link PSGenerator} that contains some members specific for streaming
* TrueType fonts to a PostScript document.
*/
public class PSTTFGenerator {
private PSGenerator gen;
private ASCIIHexOutputStream hexOut;

/**
* The buffer is used to store the font file in an array of hex-encoded strings. Strings are
* limited to 65535 characters, string will start with a newline, 2 characters are needed to
* hex-encode each byte.
*/
public static final int MAX_BUFFER_SIZE = 32764;

/**
* Creates a new instance wrapping the given generator.
* @param gen the PSGenerator to wrap
*/
public PSTTFGenerator(PSGenerator gen) {
this.gen = gen;
hexOut = new ASCIIHexOutputStream(gen.getOutputStream());
}

/**
* Writes the '&lt;' character that starts a string.
*/
public void startString() throws IOException {
// We need to reset the streamer so that it starts a new line in the PS document
hexOut = new ASCIIHexOutputStream(gen.getOutputStream());
gen.writeln("<");
}

/**
* Writes the given string to the output.
* @param cmd a string
*/
public void write(String cmd) throws IOException {
gen.write(cmd);
}

/**
* Writes the given string to the output, followed by a newline.
* @param cmd a string
*/
public void writeln(String cmd) throws IOException {
gen.writeln(cmd);
}

/**
* Writes bytes from the given byte array to the output.
*
* @param byteArray byte[] a byte array
* @param offset the position in the byte array where the streaming must start
* @param length the number of bytes to stream. This MUST be less than
* {@link #MAX_BUFFER_SIZE} - 1 since strings are suffixed by '00' (see Section 4.2 of
* Adobe Technical Note #5012, <em>The Type 42 Font Format Specification</em>.).
*/
public void streamBytes(byte[] byteArray, int offset, int length) throws IOException {
if (length > MAX_BUFFER_SIZE) {
throw new UnsupportedOperationException("Attempting to write a string to a PostScript"
+ " file that is greater than the buffer size.");
}
hexOut.write(byteArray, offset, length);
}

/**
* Finishes writing a string by appending '00' and '>' to the end.
*/
public void endString() throws IOException {
/* Appends a '00' to the end of the string as specified in the spec */
gen.write("00\n> ");
}

}

+ 75
- 0
src/java/org/apache/fop/render/ps/fonts/PSTTFGlyphOutputStream.java View File

@@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.render.ps.fonts;

import java.io.IOException;

import org.apache.fop.fonts.truetype.TTFGlyphOutputStream;

/**
* Streams glyphs in accordance with the constraints of the PostScript file format.
* Mainly, PostScript strings have a limited capacity and the font data may have to be
* broken down into several strings; however, this must occur at well-defined places like
* table or glyph boundaries. See also Adobe Technical Note #5012, <em>The Type 42 Font
* Format Specification</em>.
*/
public class PSTTFGlyphOutputStream implements TTFGlyphOutputStream {

/** Total number of bytes written so far. */
private int byteCounter;

private int lastStringBoundary;

private PSTTFGenerator ttfGen;

/**
* Constructor
* @param ttfGen PSTTFGenerator
*/
public PSTTFGlyphOutputStream(PSTTFGenerator ttfGen) {
this.ttfGen = ttfGen;
}

public void startGlyphStream() throws IOException {
ttfGen.startString();
}

public void streamGlyph(byte[] glyphData, int offset, int size) throws IOException {
if (size > PSTTFGenerator.MAX_BUFFER_SIZE) {
throw new UnsupportedOperationException("The glyph is " + size
+ " bytes. There may be an error in the font file.");
}

if (size + (byteCounter - lastStringBoundary) < PSTTFGenerator.MAX_BUFFER_SIZE) {
ttfGen.streamBytes(glyphData, offset, size);
} else {
ttfGen.endString();
lastStringBoundary = byteCounter;
ttfGen.startString();
ttfGen.streamBytes(glyphData, offset, size);
}
byteCounter += size;
}

public void endGlyphStream() throws IOException {
ttfGen.endString();
}

}

+ 62
- 0
src/java/org/apache/fop/render/ps/fonts/PSTTFOutputStream.java View File

@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.ps.fonts;

import java.io.IOException;

import org.apache.xmlgraphics.ps.PSGenerator;

import org.apache.fop.fonts.truetype.TTFGlyphOutputStream;
import org.apache.fop.fonts.truetype.TTFOutputStream;
import org.apache.fop.fonts.truetype.TTFTableOutputStream;

/**
* Streams a TrueType font according to the PostScript format.
*/
public class PSTTFOutputStream implements TTFOutputStream {

private final PSTTFGenerator ttfGen;

/**
* Creates a new instance wrapping the given generator.
*
* @param gen the generator to wrap
*/
public PSTTFOutputStream(PSGenerator gen) {
this.ttfGen = new PSTTFGenerator(gen);
}

public void startFontStream() throws IOException {
ttfGen.write("/sfnts[");
}

public TTFTableOutputStream getTableOutputStream() {
return new PSTTFTableOutputStream(ttfGen);
}

public TTFGlyphOutputStream getGlyphOutputStream() {
return new PSTTFGlyphOutputStream(ttfGen);
}

public void endFontStream() throws IOException {
ttfGen.writeln("] def");
}

}

+ 59
- 0
src/java/org/apache/fop/render/ps/fonts/PSTTFTableOutputStream.java View File

@@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.ps.fonts;

import java.io.IOException;

import org.apache.fop.fonts.truetype.TTFTableOutputStream;

/**
* Streams a TrueType table according to the PostScript format.
*/
public class PSTTFTableOutputStream implements TTFTableOutputStream {

private PSTTFGenerator ttfGen;

/**
* Constructor.
* @param ttfGen the helper object to stream TrueType data
*/
public PSTTFTableOutputStream(PSTTFGenerator ttfGen) {
this.ttfGen = ttfGen;
}

public void streamTable(byte[] ttfData, int offset, int size) throws IOException {
int offsetPosition = offset;
// Need to split the table into MAX_BUFFER_SIZE chunks
for (int i = 0; i < size / PSTTFGenerator.MAX_BUFFER_SIZE; i++) {
streamString(ttfData, offsetPosition, PSTTFGenerator.MAX_BUFFER_SIZE);
offsetPosition += PSTTFGenerator.MAX_BUFFER_SIZE;
}
if (size % PSTTFGenerator.MAX_BUFFER_SIZE > 0) {
streamString(ttfData, offsetPosition, size % PSTTFGenerator.MAX_BUFFER_SIZE);
}
}

private void streamString(byte[] byteArray, int offset, int length) throws IOException {
ttfGen.startString();
ttfGen.streamBytes(byteArray, offset, length);
ttfGen.endString();
}

}

+ 57
- 0
src/java/org/apache/fop/util/HexEncoder.java View File

@@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.util;

/**
* A helper class to create hex-encoded representations of numbers.
*/
public final class HexEncoder {

private HexEncoder() { }

/**
* Returns an hex encoding of the given number as a string of the given length,
* left-padded with zeros if necessary.
*
* @param n a number
* @param width required length of the string
* @return an hex-encoded representation of the number
*/
public static String encode(int n, int width) {
char[] digits = new char[width];
for (int i = width - 1; i >= 0; i--) {
int digit = n & 0xF;
digits[i] = (char) (digit < 10 ? '0' + digit : 'A' + digit - 10);
n >>= 4;
}
return new String(digits);
}

/**
* Returns an hex encoding of the given character as a four-character string.
*
* @param c a character
* @return an hex-encoded representation of the character
*/
public static String encode(char c) {
return encode(c, 4);
}

}

+ 19
- 3
status.xml View File

@@ -62,6 +62,25 @@
documents. Example: the fix of marks layering will be such a case when it's done.
-->
<release version="1.1rc1" date="3 June 2012">
<release version="FOP Trunk" date="TBD">
<action context="Code" dev="GA" type="fix">
Eliminate javadocs warnings.
</action>
<action context="Renderers" dev="VH" type="add" fixes-bug="52338" importance="high">
Added possibility to embed TrueType fonts in PostScript.
</action>
<action context="Images" dev="GA" type="update" fixes-bug="40676" due-to="Luis Bernardo">
Update site documentation about PNG image loading configuration and support.
</action>
<action context="Images" dev="GA" type="update" fixes-bug="40676" due-to="Luis Bernardo">
Fix newly introduced findbugs warnings.
</action>
<action context="Images" dev="GA" type="fix" fixes-bug="40676" due-to="Luis Bernardo, Matthias Reischenbacher" importance="high">
Support use of ImageLoaderRawPNG decoder in order to prevent re-encoding of PNG images (and unnecssary output file bloat).
</action>
<action context="Code" dev="GA" type="fix" fixes-bug="53412" due-to="Alexios Giotis">
Eliminate incorrect use of object identity which produces excessive property cache collisions.
</action>
<action context="Code" dev="GA" type="fix">
Eliminate javadocs warnings.
</action>
@@ -74,9 +93,6 @@
<action context="Renderers" dev="GA" type="fix" fixes-bug="53304,53306">
Add version attribute to AT and IF intermediate formats. Also eliminate redundant use of reversed attribute in AT format.
</action>
<action context="Renderers" dev="GA" type="fix" fixes-bug="53304,53306">
Add version attribute to AT and IF intermediate formats. Also eliminate redundant use of reversed attribute in AT format.
</action>
<action context="Renderers" dev="GA" type="fix" fixes-bug="53295" due-to="Luis Bernardo" importance="high">
Add extension to place code just before PostScript %PageTrailer.
</action>

+ 7
- 1
test/java/org/apache/fop/UtilityCodeTestSuite.java View File

@@ -28,10 +28,13 @@ import org.apache.fop.pdf.FileIDGeneratorTestCase;
import org.apache.fop.pdf.PDFDocumentGraphics2DTestCase;
import org.apache.fop.pdf.PDFEncryptionJCETestCase;
import org.apache.fop.pdf.PDFFactoryTestCase;
import org.apache.fop.pdf.PDFNumberTestCase;
import org.apache.fop.pdf.PDFObjectTestCase;
import org.apache.fop.traits.BorderPropsTestCase;
import org.apache.fop.util.BitmapImageUtilTestCase;
import org.apache.fop.util.ColorUtilTestCase;
import org.apache.fop.util.ElementListUtilsTestCase;
import org.apache.fop.util.HexEncoderTestCase;
import org.apache.fop.util.XMLResourceBundleTestCase;

/**
@@ -49,7 +52,10 @@ import org.apache.fop.util.XMLResourceBundleTestCase;
PDFFactoryTestCase.class,
PDFEncryptionJCETestCase.class,
BitmapImageUtilTestCase.class,
PDFDocumentGraphics2DTestCase.class
PDFDocumentGraphics2DTestCase.class,
PDFNumberTestCase.class,
PDFObjectTestCase.class,
HexEncoderTestCase.class
})
public class UtilityCodeTestSuite {
}

+ 2
- 1
test/java/org/apache/fop/fonts/DejaVuLGCSerifTestCase.java View File

@@ -43,7 +43,8 @@ public class DejaVuLGCSerifTestCase {
@Before
public void setUp() throws Exception {
File file = new File("test/resources/fonts/ttf/DejaVuLGCSerif.ttf");
font = FontLoader.loadFont(file, "", true, EncodingMode.AUTO, fontResolver);
font = FontLoader.loadFont(file, "", true, EmbeddingMode.AUTO, EncodingMode.AUTO,
fontResolver);
}

/**

+ 13
- 5
test/java/org/apache/fop/fonts/EncodingModeTestCase.java View File

@@ -19,10 +19,13 @@

package org.apache.fop.fonts;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
* Tests {@link EncodingMode}.
*/
public class EncodingModeTestCase {

@Test
@@ -34,8 +37,13 @@ public class EncodingModeTestCase {

@Test
public void testGetValue() {
assertEquals(EncodingMode.AUTO, EncodingMode.getEncodingMode("auto"));
assertEquals(EncodingMode.SINGLE_BYTE, EncodingMode.getEncodingMode("single-byte"));
assertEquals(EncodingMode.CID, EncodingMode.getEncodingMode("cid"));
assertEquals(EncodingMode.AUTO, EncodingMode.getValue("auto"));
assertEquals(EncodingMode.SINGLE_BYTE, EncodingMode.getValue("single-byte"));
assertEquals(EncodingMode.CID, EncodingMode.getValue("cid"));
}

@Test(expected = IllegalArgumentException.class)
public void getValueMustCheckForIllegalArguments() {
EncodingMode.getValue("fail");
}
}

+ 42
- 0
test/java/org/apache/fop/fonts/FOPFontsTestSuite.java View File

@@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

import org.apache.fop.fonts.truetype.FontFileReaderTestCase;
import org.apache.fop.fonts.truetype.TTFFileTestCase;
import org.apache.fop.fonts.truetype.TTFSubSetFileTestCase;
import org.apache.fop.fonts.truetype.TTFTableNameTestCase;

/**
* A test suite designed for org.apache.fop.fonts.*
*/
@RunWith(Suite.class)
@SuiteClasses({
EncodingModeTestCase.class,
FontFileReaderTestCase.class,
TTFFileTestCase.class,
TTFSubSetFileTestCase.class,
TTFTableNameTestCase.class })
public final class FOPFontsTestSuite {
}

+ 304
- 0
test/java/org/apache/fop/fonts/truetype/FontFileReaderTestCase.java View File

@@ -0,0 +1,304 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
* A test class for org.apache.fop.truetype.FontFileReader
*/
public class FontFileReaderTestCase {
private FontFileReader fontReader;
private final InputStream in;
private final byte[] byteArray;

/**
* Constructor - initialises an array that only needs to be created once. It creates a byte[]
* of form { 0x00, 0x01, 0x02, 0x03..., 0xff};
*/
public FontFileReaderTestCase() {
byteArray = new byte[256];
for (int i = 0; i < 256; i++) {
byteArray[i] = (byte) i;
}
in = new ByteArrayInputStream(byteArray);
}

/**
* sets up the test subject object for testing.
*/
@Before
public void setUp() {
try {
fontReader = new FontFileReader(in);
} catch (Exception e) {
fail("Error: " + e.getMessage());
}
}

/**
* the "destructor" method.
*
*/
public void tearDown() {
fontReader = null;
}

/**
* Test readTTFByte()
* @throws IOException exception
*/
@Test
public void testReadTTFByte() throws IOException {
for (int i = 0; i < 256; i++) {
assertEquals((byte) i, fontReader.readTTFByte());
}
}

/**
* Test seekSet() - check that it moves to the correct position and enforce a failure case.
* @throws IOException exception
*/
@Test
public void testSeekSet() throws IOException {
fontReader.seekSet(10);
assertEquals(10, fontReader.readTTFByte());
try {
fontReader.seekSet(257);
fail("FileFontReaderTest Failed testSeekSet");
} catch (IOException e) {
// Passed
}
}

/**
* Test skip() - check that it moves to the correct position and enforce a failure case.
* @throws IOException exception
*/
@Test
public void testSkip() throws IOException {
fontReader.skip(100);
assertEquals(100, fontReader.readTTFByte());
try {
// 100 (seekAdd) + 1 (read() = 1 byte) + 156 = 257
fontReader.skip(156);
fail("FileFontReaderTest Failed testSkip");
} catch (IOException e) {
// Passed
}
}

/**
* Test getCurrentPos() - 3 checks:
* 1) test with seekSet(int)
* 2) test with skip(int)
* 3) test with a readTTFByte() (this moves the position by the size of the data being read)
* @throws IOException exception
*/
@Test
public void testGetCurrentPos() throws IOException {
fontReader.seekSet(10);
fontReader.skip(100);
assertEquals(110, fontReader.getCurrentPos());
fontReader.readTTFByte();
assertEquals(111, fontReader.getCurrentPos());
}

/**
* Test getFileSize()
*/
@Test
public void testGetFileSize() {
assertEquals(256, fontReader.getFileSize());
}

/**
* Test readTTFUByte()
* @throws IOException exception
*/
@Test
public void testReadTTFUByte() throws IOException {
for (int i = 0; i < 256; i++) {
assertEquals(i, fontReader.readTTFUByte());
}
}

/**
* Test readTTFShort() - Test positive and negative numbers (two's compliment).
* @throws IOException exception
*/
@Test
public void testReadTTFShort() throws IOException {
// 0x0001 = 1
assertEquals("Should have been 1 (0x0001)", 1, fontReader.readTTFShort());
// 0x0203 = 515
assertEquals(515, fontReader.readTTFShort());
// now test negative numbers
fontReader.seekSet(250);
// 0xfafb
assertEquals(-1285, fontReader.readTTFShort());
}

/**
* Test readTTFUShort() - Test positive and potentially negative numbers (two's compliment).
* @throws IOException exception
*/
@Test
public void testReadTTFUShort() throws IOException {
// 0x0001
assertEquals(1, fontReader.readTTFUShort());
// 0x0203
assertEquals(515, fontReader.readTTFUShort());
// test potential negatives
fontReader.seekSet(250);
// 0xfafb
assertEquals((250 << 8) + 251, fontReader.readTTFUShort());
}

/**
* Test readTTFShort(int) - test reading ahead of current position and behind current position
* and in both cases ensure that our current position isn't changed.
* @throws IOException exception
*/
@Test
public void testReadTTFShortWithArg() throws IOException {
// 0x6465
assertEquals(25701, fontReader.readTTFShort(100));
assertEquals(0, fontReader.getCurrentPos());
// read behind current position (and negative)
fontReader.seekSet(255);
// 0xfafb
assertEquals(-1285, fontReader.readTTFShort(250));
assertEquals(255, fontReader.getCurrentPos());
}

/**
* Test readTTFUShort(int arg) - test reading ahead of current position and behind current
* position and in both cases ensure that our current position isn't changed.
* @throws IOException exception
*/
@Test
public void testReadTTFUShortWithArg() throws IOException {
// 0x6465
assertEquals(25701, fontReader.readTTFUShort(100));
assertEquals(0, fontReader.getCurrentPos());
// read behind current position (and potential negative)
fontReader.seekSet(255);
// 0xfafb
assertEquals(64251, fontReader.readTTFUShort(250));
assertEquals(255, fontReader.getCurrentPos());
}

/**
* Test readTTFLong()
* @throws IOException exception
*/
@Test
public void testReadTTFLong() throws IOException {
// 0x00010203
assertEquals(66051, fontReader.readTTFLong());
// test negative numbers
fontReader.seekSet(250);
// 0xf0f1f2f3
assertEquals(-84148995, fontReader.readTTFLong());
}

/**
* Test readTTFULong()
* @throws IOException exception
*/
@Test
public void testReadTTFULong() throws IOException {
// 0x00010203
assertEquals(66051, fontReader.readTTFULong());
// test negative numbers
fontReader.seekSet(250);
// 0xfafbfcfd
assertEquals(4210818301L, fontReader.readTTFULong());
}

/**
* Test readTTFString() - there are two paths to test here:
* 1) A null terminated string
* 2) A string not terminated with a null (we expect this to throw an EOFException)
* @throws IOException exception
*/
@Test
public void testReadTTFString() throws IOException {
byte[] strByte = {(byte)'t', (byte)'e', (byte)'s', (byte)'t', 0x00};
fontReader = new FontFileReader(new ByteArrayInputStream(strByte));
assertEquals("test", fontReader.readTTFString());
try {
// not NUL terminated
byte[] strByteNoNull = {(byte)'t', (byte)'e', (byte)'s', (byte)'t'};
fontReader = new FontFileReader(new ByteArrayInputStream(strByteNoNull));
assertEquals("test", fontReader.readTTFString());
fail("FontFileReaderTest testReadTTFString Fails.");
} catch (EOFException e) {
// Pass
}
}

/**
* Test readTTFString(int arg)
* @throws IOException exception
*/
@Test
public void testReadTTFStringIntArg() throws IOException {
byte[] strByte = {(byte)'t', (byte)'e', (byte)'s', (byte)'t'};
fontReader = new FontFileReader(new ByteArrayInputStream(strByte));
assertEquals("test", fontReader.readTTFString(4));
try {
fontReader = new FontFileReader(new ByteArrayInputStream(strByte));
assertEquals("test", fontReader.readTTFString(5));
fail("FontFileReaderTest testReadTTFStringIntArg Fails.");
} catch (EOFException e) {
// Pass
}
}

/**
* Test readTTFString(int arg1, int arg2)
*/
public void testReadTTFString2IntArgs() {
// currently the same as above
}

/**
* Test getBytes()
* @throws IOException exception
*/
@Test
public void testGetBytes() throws IOException {
byte[] retrievedBytes = fontReader.getBytes(0, 256);
assertTrue(Arrays.equals(byteArray, retrievedBytes));
}
}

+ 4
- 3
test/java/org/apache/fop/fonts/truetype/GlyfTableTestCase.java View File

@@ -19,8 +19,6 @@

package org.apache.fop.fonts.truetype;

import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -31,6 +29,8 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertTrue;

/**
* Tests {@link GlyfTable}.
*/
@@ -141,7 +141,8 @@ public class GlyfTableTestCase {

private void setupSubsetReader(Map<Integer, Integer> glyphs) throws IOException {
TTFSubSetFile fontFile = new TTFSubSetFile();
byte[] subsetFont = fontFile.readFont(originalFontReader, "Deja", glyphs);
fontFile.readFont(originalFontReader, "Deja", glyphs);
byte[] subsetFont = fontFile.getFontSubset();
InputStream intputStream = new ByteArrayInputStream(subsetFont);
subsetReader = new FontFileReader(intputStream);
}

+ 427
- 0
test/java/org/apache/fop/fonts/truetype/TTFFileTestCase.java View File

@@ -0,0 +1,427 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.Map;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion;

/**
* Class for testing org.apache.fop.fonts.truetype.TTFFile
*/
public class TTFFileTestCase {
// We only want to initialize the FontFileReader once (for performance reasons)
/** The truetype font file (DejaVuLGCSerif) */
protected final TTFFile dejavuTTFFile;
/** The FontFileReader for ttfFile (DejaVuLGCSerif) */
protected final FontFileReader dejavuReader;
/** The truetype font file (DroidSansMono) */
protected final TTFFile droidmonoTTFFile;
/** The FontFileReader for ttfFile (DroidSansMono) */
protected final FontFileReader droidmonoReader;


/**
* Constructor initialises FileFontReader to
* @throws IOException exception
*/
public TTFFileTestCase() throws IOException {
dejavuTTFFile = new TTFFile();
dejavuReader = new FontFileReader("test/resources/fonts/ttf/DejaVuLGCSerif.ttf");
dejavuTTFFile.readFont(dejavuReader);
droidmonoTTFFile = new TTFFile();
droidmonoReader = new FontFileReader("test/resources/fonts/ttf/DroidSansMono.ttf");
droidmonoTTFFile.readFont(droidmonoReader);
}

/**
* Test convertTTFUnit2PDFUnit() - The units per em retrieved reading the HEAD table from
* the font file. (DroidSansMono has the same units per em as DejaVu so no point testing it)
*/
@Test
public void testConvertTTFUnit2PDFUnit() {
// DejaVu has 2048 units per em (PDF works in millipts, thus the 1000)
// test rational number
assertEquals(1000, dejavuTTFFile.convertTTFUnit2PDFUnit(2048));
// test smallest case, this should = 0.488 (round down to 0)
assertEquals(0, dejavuTTFFile.convertTTFUnit2PDFUnit(1));
// this should round up, but since it's millipts...
assertEquals(0, dejavuTTFFile.convertTTFUnit2PDFUnit(2));
// ensure behaviour is the same for negative numbers
assertEquals(0, dejavuTTFFile.convertTTFUnit2PDFUnit(-0));
assertEquals(-1000, dejavuTTFFile.convertTTFUnit2PDFUnit(-2048));
assertEquals(0, dejavuTTFFile.convertTTFUnit2PDFUnit(-1));
assertEquals(0, dejavuTTFFile.convertTTFUnit2PDFUnit(-2));
}

/**
* Test checkTTC()
* @throws IOException exception
*/
@Test
public void testCheckTTC() throws IOException {
// DejaVu is not a TTC, thus this returns true
assertTrue(dejavuTTFFile.checkTTC(""));
assertTrue(droidmonoTTFFile.checkTTC(""));
/*
* Cannot reasonably test the rest of this method without an actual truetype collection
* because all methods in FontFileReader are "final" and thus mocking isn't possible.
*/
}

/**
* Test getAnsiKerning() - Tests values retrieved from the kern table in the font file.
*/
@Test
public void testGetAnsiKerning() {
Map<Integer, Map<Integer, Integer>> ansiKerning = dejavuTTFFile.getKerning();
if (ansiKerning.isEmpty()) {
fail();
}
Integer k1 = ansiKerning.get(Integer.valueOf('A')).get(
Integer.valueOf('T'));
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-112), k1.intValue());
Integer k2 = ansiKerning.get(Integer.valueOf('Y')).get(Integer.valueOf('u'));
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-178), k2.intValue());

// DroidSansMono doens't have kerning (it's mono-spaced)
ansiKerning = droidmonoTTFFile.getAnsiKerning();
if (!ansiKerning.isEmpty()) {
fail("DroidSansMono shouldn't have any kerning data.");
}
}

/**
* Test getCapHeight - there are several paths to test:
* 1) The PCLT table (if present)
* 2) The yMax (3rd) value, for the bounding box, for 'H' in the glyf table.
* if not the above:
* 3) The caps height in the OS/2 table
* Tests values retrieved from analysing the font file.
*/
@Test
public void testGetCapHeight() {
// DejaVu doesn't have the PCLT table and so these have to be guessed
// The height is approximated to be the height of the "H" which for
// Deja = 1493 TTFunits
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(1493), dejavuTTFFile.getCapHeight());
// DroidSansMono doesn't have a PCLT table either
// height of "H" = 1462
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(1462),
droidmonoTTFFile.getCapHeight());
}

/**
* Test getCharSetName() - check that it returns "WinAnsiEncoding".
*/
@Test
public void testGetCharSetName() {
assertTrue("WinAnsiEncoding".equals(dejavuTTFFile.getCharSetName()));
assertTrue("WinAnsiEncoding".equals(droidmonoTTFFile.getCharSetName()));
}

/**
* Test getCharWidth() - Test values retrieved from the metrics in the glyf table in
* the font file.
*/
@Test
public void testGetCharWidth() {
// Arbitrarily test a few values:
// The width of "H" (Unicode index 0x0048) is 1786
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(1786), dejavuTTFFile.getCharWidth(0x48));
// The width of "i" (unicode index 0x0069) is 655 TTFunits
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(655), dejavuTTFFile.getCharWidth(0x69));
// final check, "!" (unicode index 0x0021) is 823 TTFunits
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(823), dejavuTTFFile.getCharWidth(0x21));

// All the glyphs should be the same width in DroidSansMono (mono-spaced)
int charWidth = droidmonoTTFFile.convertTTFUnit2PDFUnit(1229);
for (int i = 0; i < 255; i++) {
assertEquals(charWidth, droidmonoTTFFile.getCharWidth(i));
}
}

/**
* TODO: add implementation to this test
*/
public void testGetCMaps() {
}

/**
* Test getFamilyNames() - Test value retrieved from the name table in the font file.
*/
@Test
public void testGetFamilyNames() {
assertEquals(1, dejavuTTFFile.getFamilyNames().size());
for (String name : dejavuTTFFile.getFamilyNames()) {
assertEquals("DejaVu LGC Serif", name);
}
assertEquals(1, droidmonoTTFFile.getFamilyNames().size());
for (String name : droidmonoTTFFile.getFamilyNames()) {
assertEquals("Droid Sans Mono", name);
}
}

/**
* Test getFirstChar() - TODO: implement a more intelligent test here.
*/
@Test
public void testGetFirstChar() {
// Not really sure how to test this intelligently
assertEquals(0, dejavuTTFFile.getFirstChar());
assertEquals(0, droidmonoTTFFile.getFirstChar());
}

/**
* Test getFlags() - Test values retrieved from the POST table in the font file.
*/
@Test
public void testGetFlags() {
/* DejaVu flags are:
* italic angle = 0
* fixed pitch = 0
* has serifs = true (default value; this font doesn't have a PCLT table)
*/
int flags = dejavuTTFFile.getFlags();
assertEquals(0, flags & 64); // Italics angle = 0
assertEquals(32, flags & 32); // Adobe standard charset
assertEquals(0, flags & 2); // fixed pitch = 0
assertEquals(1, flags & 1); // has serifs = 1 (true)
/*
* Droid flags are:
* italic angle = 0
* fixed pitch = 1
* has serifs = true (default value; this font doesn't have a PCLT table)
*/
flags = droidmonoTTFFile.getFlags();
assertEquals(0, flags & 64);
assertEquals(32, flags & 32);
assertEquals(2, flags & 2);
assertEquals(1, flags & 1);
}

/**
* Test getFontBBox() - Test values retrieved from values in the HEAD table in the font file.
*/
@Test
public void testGetFontBBox() {
int[] bBox = dejavuTTFFile.getFontBBox();
/*
* The head table has the following values(DejaVu):
* xmin = -1576, ymin = -710, xmax = 3439, ymax = 2544
*/
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-1576), bBox[0]);
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-710), bBox[1]);
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(3439), bBox[2]);
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(2544), bBox[3]);
/*
* The head table has the following values (DroidSansMono):
* xmin = -312, ymin= -555, xmax = 1315, ymax = 2163
*/
bBox = droidmonoTTFFile.getFontBBox();
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(-312), bBox[0]);
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(-555), bBox[1]);
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(1315), bBox[2]);
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(2163), bBox[3]);
}

/**
* Test getFullName() - Test value retrieved from the name table in the font file.
*/
@Test
public void testGetFullName() {
assertEquals("DejaVu LGC Serif", dejavuTTFFile.getFullName());
assertEquals("Droid Sans Mono", droidmonoTTFFile.getFullName());
}

/**
* Test getGlyphName - Test value retrieved from the POST table in the font file.
*/
@Test
public void testGetGlyphName() {
assertEquals("H", dejavuTTFFile.getGlyphName(43));
assertEquals("H", droidmonoTTFFile.getGlyphName(43));
}

/**
* Test getItalicAngle() - Test value retrieved from the POST table in the font file.
*/
@Test
public void testGetItalicAngle() {
assertEquals("0", dejavuTTFFile.getItalicAngle());
assertEquals("0", droidmonoTTFFile.getItalicAngle());
}

/**
* Test getKerning() - Test values retrieved from the kern table in the font file.
*/
@Test
public void testGetKerning() {
Map<Integer, Map<Integer, Integer>> kerning = dejavuTTFFile.getKerning();
if (kerning.isEmpty()) {
fail();
}
Integer k1 = kerning.get(Integer.valueOf('A')).get(Integer.valueOf('T'));
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-112), k1.intValue());
Integer k2 = kerning.get(Integer.valueOf('K')).get(Integer.valueOf('u'));
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-45), k2.intValue());

// DroidSansMono has no kerning data (mono-spaced)
kerning = droidmonoTTFFile.getKerning();
if (!kerning.isEmpty()) {
fail("DroidSansMono shouldn't have any kerning data");
}
}

/**
* Test lastChar() - TODO: implement a more intelligent test
*/
@Test
public void testLastChar() {
assertEquals(0xff, dejavuTTFFile.getLastChar());
assertEquals(0xff, droidmonoTTFFile.getLastChar());
}

/**
* Test getLowerCaseAscent() - There are several paths to test:
* 1) The values in the HHEA table (see code)
* 2) Fall back to values from the OS/2 table
* Test values retrieved from the font file.
*/
@Test
public void testGetLowerCaseAscent() {
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(1556),
dejavuTTFFile.getLowerCaseAscent());
// Curiously the same value
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(1556),
droidmonoTTFFile.getLowerCaseAscent());
}

/**
* Test getPostScriptName() - Test values retrieved from the post table in the font file.
*/
@Test
public void testGetPostScriptName() {
assertEquals(PostScriptVersion.V2, dejavuTTFFile.getPostScriptVersion());
assertEquals(PostScriptVersion.V2, droidmonoTTFFile.getPostScriptVersion());
}

/**
* Test getStemV() - Undefined.
*/
@Test
public void testGetStemV() {
// Undefined
assertEquals("0", dejavuTTFFile.getStemV());
assertEquals("0", droidmonoTTFFile.getStemV());
}

/**
* Test getSubFamilyName() - Test values retrieved from the name table in the font file.
*/
@Test
public void testGetSubFamilyName() {
assertEquals("Book", dejavuTTFFile.getSubFamilyName());
assertEquals("Regular", droidmonoTTFFile.getSubFamilyName());
}

/**
* Test getTTCnames() - TODO: add implementation with TTC font.
*/
public void testGetTTCnames() {
// Can't test with with DejaVu since it's not a TrueType Collection
}

/**
* Test getWeightClass() - Test value retrieved from the OS/2 table in the font file.
*/
@Test
public void testGetWeightClass() {
// Retrieved from OS/2 table
assertEquals(400, dejavuTTFFile.getWeightClass());
assertEquals(400, droidmonoTTFFile.getWeightClass());
}

/**
* Test getWidths() - Test values retrieved from the hmtx table in the font file.
*/
@Test
public void testGetWidths() {
int[] widths = dejavuTTFFile.getWidths();
// using the width of 'A' index = 36
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(1479), widths[36]);
// using the width of '|' index = 95
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(690), widths[95]);
widths = droidmonoTTFFile.getWidths();
// DroidSansMono should have all widths the same size (mono-spaced)
int width = droidmonoTTFFile.convertTTFUnit2PDFUnit(1229);
for (int i = 0; i < 255; i++) {
assertEquals(width, widths[i]);
}
}

/**
* Test getXHeight() - There are several paths to test:
* 1) The PCLT table (if available)
* 2) The yMax for the bounding box for 'x' in the glyf table.
* Fall back:
* 3) The xheight in the OS/2 table.
*/
@Test
public void testGetXHeight() {
// Since there's no PCLT table, the height of 'x' is used for both DejaVu and DroidSansMono
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(1064), dejavuTTFFile.getXHeight());
assertEquals(droidmonoTTFFile.convertTTFUnit2PDFUnit(1098), droidmonoTTFFile.getXHeight());
}

/**
* Test isCFF() - TODO: add test for a CFF font.
*/
@Test
public void testIsCFF() {
// Neither DejaVu nor DroidSansMono are a compact format font
assertEquals(false, dejavuTTFFile.isCFF());
assertEquals(false, droidmonoTTFFile.isCFF());
}

/**
* Test isEmbeddable() - Test value retrieved from the OS/2 table in the font file.
*/
@Test
public void testIsEmbeddable() {
// Dejavu and DroidSansMono are both embeddable
assertEquals(true, dejavuTTFFile.isEmbeddable());
assertEquals(true, droidmonoTTFFile.isEmbeddable());
}

/**
* Test readFont() - Add implementation if necessary.
*/
public void testReadFont() {
// I'm pretty sure we've tested this with all the other tests
}
}

+ 7
- 6
test/java/org/apache/fop/fonts/truetype/TTFFontLoaderTestCase.java View File

@@ -19,18 +19,19 @@

package org.apache.fop.fonts.truetype;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;

import org.junit.Test;

import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.EncodingMode;
import org.apache.fop.fonts.FontManager;
import org.apache.fop.fonts.FontResolver;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
* Test case for {@link TTFFontLoader}.
*/
@@ -47,12 +48,12 @@ public class TTFFontLoaderTestCase {
boolean useKerning = true;

TTFFontLoader fontLoader = new TTFFontLoader(absoluteFilePath, fontName, embedded,
EncodingMode.AUTO, useKerning, useComplexScriptFeatures, resolver);
EmbeddingMode.AUTO, EncodingMode.AUTO, useKerning, useComplexScriptFeatures, resolver);
assertTrue(fontLoader.getFont().hasKerningInfo());
useKerning = false;

fontLoader = new TTFFontLoader(absoluteFilePath, fontName, embedded, EncodingMode.AUTO,
useKerning, useComplexScriptFeatures, resolver);
fontLoader = new TTFFontLoader(absoluteFilePath, fontName, embedded, EmbeddingMode.AUTO,
EncodingMode.AUTO, useKerning, useComplexScriptFeatures, resolver);
assertFalse(fontLoader.getFont().hasKerningInfo());
}
}

+ 76
- 0
test/java/org/apache/fop/fonts/truetype/TTFSubSetFileTestCase.java View File

@@ -0,0 +1,76 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.util.HashMap;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
* This class tests TTFSubSetFile
* TODO: Test with more than just a single font
*/
public class TTFSubSetFileTestCase extends TTFFileTestCase {
private TTFSubSetFile ttfSubset;
private byte[] subset;
/**
* Constructor
* @throws IOException exception
*/
public TTFSubSetFileTestCase() throws IOException {
super();
}

/**
* setUp()
* @exception IOException file read error
*/
@Before
public void setUp() throws IOException {
ttfSubset = new TTFSubSetFile();
Map<Integer, Integer> glyphs = new HashMap<Integer, Integer>();
for (int i = 0; i < 255; i++) {
glyphs.put(i, i);
}
ttfSubset.readFont(dejavuReader, "DejaVu", glyphs);
subset = ttfSubset.getFontSubset();
}
/**
* Test readFont(FontFileReader, String, Map) - Reads the font and tests the output by injecting
* it into a TTFFile object to check the validity of the file as a font. This currently doesn't
* create a cmap table, and so the font doesn't contain ALL of the mandatory tables.
* @throws IOException exception
*/
@Test
public void testReadFont3Args() throws IOException {

ByteArrayInputStream byteArray = new ByteArrayInputStream(subset);
dejavuTTFFile.readFont(new FontFileReader(byteArray));
// Test a couple arbitrary values
assertEquals(dejavuTTFFile.convertTTFUnit2PDFUnit(-1576), dejavuTTFFile.getFontBBox()[0]);
assertEquals(dejavuTTFFile.getFullName(), "DejaVu LGC Serif");
}
}

+ 153
- 0
test/java/org/apache/fop/fonts/truetype/TTFTableNameTestCase.java View File

@@ -0,0 +1,153 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
* This class tests the enum org.apache.fop.fonts.truetype.TTFTableName
*
*/
public class TTFTableNameTestCase {
/**
* Test getName() - tests that the getName() method returns the expected String as expected in
* the Directory Table.
* @exception IllegalAccessException error
*/
@Test
public void testGetName() throws IllegalAccessException {
assertEquals("tableDirectory", TTFTableName.TABLE_DIRECTORY.getName());
assertEquals("EBDT", TTFTableName.EBDT.getName());
assertEquals("EBLC", TTFTableName.EBLC.getName());
assertEquals("EBSC", TTFTableName.EBSC.getName());
assertEquals("FFTM", TTFTableName.FFTM.getName());
assertEquals("GDEF", TTFTableName.GDEF.getName());
assertEquals("GPOS", TTFTableName.GPOS.getName());
assertEquals("GSUB", TTFTableName.GSUB.getName());
assertEquals("LTSH", TTFTableName.LTSH.getName());
assertEquals("OS/2", TTFTableName.OS2.getName());
assertEquals("PCLT", TTFTableName.PCLT.getName());
assertEquals("VDMX", TTFTableName.VDMX.getName());
assertEquals("cmap", TTFTableName.CMAP.getName());
assertEquals("cvt ", TTFTableName.CVT.getName());
assertEquals("fpgm", TTFTableName.FPGM.getName());
assertEquals("gasp", TTFTableName.GASP.getName());
assertEquals("glyf", TTFTableName.GLYF.getName());
assertEquals("hdmx", TTFTableName.HDMX.getName());
assertEquals("head", TTFTableName.HEAD.getName());
assertEquals("hhea", TTFTableName.HHEA.getName());
assertEquals("hmtx", TTFTableName.HMTX.getName());
assertEquals("kern", TTFTableName.KERN.getName());
assertEquals("loca", TTFTableName.LOCA.getName());
assertEquals("maxp", TTFTableName.MAXP.getName());
assertEquals("name", TTFTableName.NAME.getName());
assertEquals("post", TTFTableName.POST.getName());
assertEquals("prep", TTFTableName.PREP.getName());
assertEquals("vhea", TTFTableName.VHEA.getName());
assertEquals("vmtx", TTFTableName.VMTX.getName());
// make sure it works with other table names
TTFTableName test = TTFTableName.getValue("test");
assertEquals("test", test.getName());
}

/**
* Test getValue(String) - tests that the getValue(String) method returns the expected
* TTFTableNames value when it is given a String (name of a table).
* @exception IllegalAccessException error
*/
@Test
public void testGetValue() throws IllegalAccessException {
assertEquals(TTFTableName.EBDT, TTFTableName.getValue("EBDT"));
assertEquals(TTFTableName.EBLC, TTFTableName.getValue("EBLC"));
assertEquals(TTFTableName.EBSC, TTFTableName.getValue("EBSC"));
assertEquals(TTFTableName.FFTM, TTFTableName.getValue("FFTM"));
assertEquals(TTFTableName.LTSH, TTFTableName.getValue("LTSH"));
assertEquals(TTFTableName.OS2, TTFTableName.getValue("OS/2"));
assertEquals(TTFTableName.PCLT, TTFTableName.getValue("PCLT"));
assertEquals(TTFTableName.VDMX, TTFTableName.getValue("VDMX"));
assertEquals(TTFTableName.CMAP, TTFTableName.getValue("cmap"));
assertEquals(TTFTableName.CVT, TTFTableName.getValue("cvt "));
assertEquals(TTFTableName.FPGM, TTFTableName.getValue("fpgm"));
assertEquals(TTFTableName.GASP, TTFTableName.getValue("gasp"));
assertEquals(TTFTableName.GLYF, TTFTableName.getValue("glyf"));
assertEquals(TTFTableName.HDMX, TTFTableName.getValue("hdmx"));
assertEquals(TTFTableName.HEAD, TTFTableName.getValue("head"));
assertEquals(TTFTableName.HHEA, TTFTableName.getValue("hhea"));
assertEquals(TTFTableName.HMTX, TTFTableName.getValue("hmtx"));
assertEquals(TTFTableName.KERN, TTFTableName.getValue("kern"));
assertEquals(TTFTableName.LOCA, TTFTableName.getValue("loca"));
assertEquals(TTFTableName.MAXP, TTFTableName.getValue("maxp"));
assertEquals(TTFTableName.NAME, TTFTableName.getValue("name"));
assertEquals(TTFTableName.POST, TTFTableName.getValue("post"));
assertEquals(TTFTableName.PREP, TTFTableName.getValue("prep"));
assertEquals(TTFTableName.VHEA, TTFTableName.getValue("vhea"));
assertEquals(TTFTableName.VMTX, TTFTableName.getValue("vmtx"));
// Test that we can store a random table name and it will not fail or throw an error.
TTFTableName test = TTFTableName.getValue("random");
assertTrue(test instanceof TTFTableName);
}

/**
* This class overrides hashCode() - we need to ensure it works properly by instantiating two
* objects and comparing their hash-codes.
* @exception IllegalAccessException error
*/
@Test
public void testHashCode() throws IllegalAccessException {
TTFTableName a = TTFTableName.getValue("testObject");
TTFTableName b = TTFTableName.getValue("testObject");
assertTrue(a.hashCode() == b.hashCode());
TTFTableName c = TTFTableName.getValue("fail");
assertFalse(a.hashCode() == c.hashCode());
}

/**
* This class overrides equals(object) - we need to test:
* 1) Reflexivity
* 2) Symmetry
* 3) Transitivity
* 4) Consistency
* 5) check it fails if you put in a null value
* @throws IllegalAccessException error
*/
@Test
public void testEquals() throws IllegalAccessException {
// Reflexivity
TTFTableName a = TTFTableName.getValue("test");
assertTrue(a.equals(a));
// Symmetry
TTFTableName b = TTFTableName.getValue("test");
assertTrue(a.equals(b));
assertTrue(b.equals(a));
// Transitivity (tested with symmetry)
// Consistency (test that a == b is true and that a == c fails)
TTFTableName c = TTFTableName.getValue("fail");
for (int i = 0; i < 100; i++) {
assertTrue(a.equals(b));
assertFalse(a.equals(c));
}
// check with null value
assertFalse(a.equals(null));
}
}

+ 92
- 0
test/java/org/apache/fop/render/RawPNGTestUtil.java View File

@@ -0,0 +1,92 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.render;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

import org.apache.xmlgraphics.image.loader.ImageSize;

public final class RawPNGTestUtil {

private static int NUM_ROWS = 32;
private static int NUM_COLUMNS = 32;
private static int DPI = 72;

private RawPNGTestUtil() {

}

/**
* Builds a PNG IDAT section for a square of a given color and alpha; the filter is fixed.
* @param gray the gray color; set to -1 if using RGB
* @param red the red color; ignored if gray > -1
* @param green the green color; ignored if gray > -1
* @param blue the blue color; ignored if gray > -1
* @param alpha the alpha color; set to -1 if not present
* @return the PNG IDAT byte array
* @throws IOException
*/
public static byte[] buildGRGBAData(int gray, int red, int green, int blue, int alpha) throws IOException {
// build an image, 32x32, Gray or RGB, with or without alpha, and with filter
int filter = 0;
int numRows = NUM_ROWS;
int numColumns = NUM_COLUMNS;
int numComponents = (gray > -1 ? 1 : 3) + (alpha > -1 ? 1 : 0);
int numBytesPerRow = numColumns * numComponents + 1; // 1 for filter
int numBytes = numRows * numBytesPerRow;
byte[] data = new byte[numBytes];
for (int r = 0; r < numRows; r++) {
data[r * numBytesPerRow] = (byte) filter;
for (int c = 0; c < numColumns; c++) {
if (numComponents == 1) {
data[r * numBytesPerRow + numComponents * c + 1] = (byte) gray;
} else if (numComponents == 2) {
data[r * numBytesPerRow + numComponents * c + 1] = (byte) gray;
data[r * numBytesPerRow + numComponents * c + 2] = (byte) alpha;
} else if (numComponents == 3) {
data[r * numBytesPerRow + numComponents * c + 1] = (byte) red;
data[r * numBytesPerRow + numComponents * c + 2] = (byte) green;
data[r * numBytesPerRow + numComponents * c + 3] = (byte) blue;
} else if (numComponents == 4) {
data[r * numBytesPerRow + numComponents * c + 1] = (byte) red;
data[r * numBytesPerRow + numComponents * c + 2] = (byte) green;
data[r * numBytesPerRow + numComponents * c + 3] = (byte) blue;
data[r * numBytesPerRow + numComponents * c + 4] = (byte) alpha;
}
}
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DeflaterOutputStream dos = new DeflaterOutputStream(baos, new Deflater());
dos.write(data);
dos.close();
return baos.toByteArray();
}

/**
*
* @return a default ImageSize
*/
public static ImageSize getImageSize() {
return new ImageSize(NUM_ROWS, NUM_COLUMNS, DPI);
}
}

+ 142
- 0
test/java/org/apache/fop/render/pdf/ImageRawPNGAdapterTestCase.java View File

@@ -0,0 +1,142 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.awt.image.ComponentColorModel;
import java.awt.image.IndexColorModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

import org.junit.Test;

import org.apache.xmlgraphics.image.loader.ImageSize;
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;

import org.apache.fop.pdf.FlateFilter;
import org.apache.fop.pdf.PDFAMode;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFProfile;
import org.apache.fop.render.RawPNGTestUtil;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ImageRawPNGAdapterTestCase {

@Test
public void testSetupWithIndexColorModel() {
IndexColorModel cm = mock(IndexColorModel.class);
ImageRawPNG irpng = mock(ImageRawPNG.class);
PDFDocument doc = mock(PDFDocument.class);
PDFProfile profile = mock(PDFProfile.class);
ImageRawPNGAdapter irpnga = new ImageRawPNGAdapter(irpng, "mock");
ImageSize is = RawPNGTestUtil.getImageSize();

when(irpng.getColorModel()).thenReturn(cm);
// when(cm.hasAlpha()).thenReturn(false);
when(doc.getProfile()).thenReturn(profile);
when(profile.getPDFAMode()).thenReturn(PDFAMode.PDFA_1A);
when(irpng.getSize()).thenReturn(is);
irpnga.setup(doc);
FlateFilter filter = (FlateFilter) irpnga.getPDFFilter();
assertEquals(1, filter.getColors());
}

@Test
public void testSetupWithComponentColorModel() throws IOException {
ComponentColorModel cm = mock(ComponentColorModel.class);
ImageRawPNG irpng = mock(ImageRawPNG.class);
PDFDocument doc = mock(PDFDocument.class);
PDFProfile profile = mock(PDFProfile.class);
ImageRawPNGAdapter irpnga = new ImageRawPNGAdapter(irpng, "mock");
ImageSize is = RawPNGTestUtil.getImageSize();

when(irpng.getColorModel()).thenReturn(cm);
when(cm.getNumComponents()).thenReturn(3);
// when(cm.hasAlpha()).thenReturn(false);
when(doc.getProfile()).thenReturn(profile);
when(profile.getPDFAMode()).thenReturn(PDFAMode.PDFA_1A);
when(irpng.getSize()).thenReturn(is);
irpnga.setup(doc);
FlateFilter filter = (FlateFilter) irpnga.getPDFFilter();
assertEquals(3, filter.getColors());
}

@Test
public void testOutputContentsWithRGBPNG() throws IOException {
testOutputContentsWithGRGBAPNG(-1, 128, 128, 128, -1);
}

@Test
public void testOutputContentsWithRGBAPNG() throws IOException {
testOutputContentsWithGRGBAPNG(-1, 128, 128, 128, 128);
}

@Test
public void testOutputContentsWithGPNG() throws IOException {
testOutputContentsWithGRGBAPNG(128, -1, -1, -1, -1);
}

@Test
public void testOutputContentsWithGAPNG() throws IOException {
testOutputContentsWithGRGBAPNG(128, -1, -1, -1, 128);
}

private void testOutputContentsWithGRGBAPNG(int gray, int red, int green, int blue, int alpha)
throws IOException {
int numColorComponents = gray > -1 ? 1 : 3;
int numComponents = numColorComponents + (alpha > -1 ? 1 : 0);
ComponentColorModel cm = mock(ComponentColorModel.class);
ImageRawPNG irpng = mock(ImageRawPNG.class);
PDFDocument doc = mock(PDFDocument.class);
PDFProfile profile = mock(PDFProfile.class);
ImageRawPNGAdapter irpnga = new ImageRawPNGAdapter(irpng, "mock");
ImageSize is = RawPNGTestUtil.getImageSize();

when(irpng.getColorModel()).thenReturn(cm);
when(cm.getNumComponents()).thenReturn(numComponents);
// when(cm.hasAlpha()).thenReturn(false);
when(doc.getProfile()).thenReturn(profile);
when(profile.getPDFAMode()).thenReturn(PDFAMode.PDFA_1A);
when(irpng.getSize()).thenReturn(is);
irpnga.setup(doc);
FlateFilter filter = (FlateFilter) irpnga.getPDFFilter();
assertEquals(numColorComponents, filter.getColors());

ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] data = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, alpha);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
when(irpng.createInputStream()).thenReturn(bais);
irpnga.outputContents(baos);
if (alpha > -1) {
byte[] expected = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, -1);
assertArrayEquals(expected, baos.toByteArray());
} else {
assertArrayEquals(data, baos.toByteArray());
}
}

}

+ 133
- 0
test/java/org/apache/fop/render/ps/ImageEncoderPNGTestCase.java View File

@@ -0,0 +1,133 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.ps;

import java.awt.image.ComponentColorModel;
import java.awt.image.IndexColorModel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.junit.Test;

import org.apache.xmlgraphics.image.loader.ImageSize;
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG;

import org.apache.fop.render.RawPNGTestUtil;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ImageEncoderPNGTestCase {

@Test
public void testWriteToWithRGBPNG() throws IOException {
testWriteToWithGRGBAPNG(-1, 128, 128, 128, -1);
}

@Test
public void testWriteToWithGPNG() throws IOException {
testWriteToWithGRGBAPNG(128, -1, -1, -1, -1);
}

@Test
public void testWriteToWithRGBAPNG() throws IOException {
testWriteToWithGRGBAPNG(-1, 128, 128, 128, 128);
}

@Test
public void testWriteToWithGAPNG() throws IOException {
testWriteToWithGRGBAPNG(128, -1, -1, -1, 128);
}

private void testWriteToWithGRGBAPNG(int gray, int red, int green, int blue, int alpha)
throws IOException {
int numComponents = (gray > -1 ? 1 : 3) + (alpha > -1 ? 1 : 0);
ImageSize is = RawPNGTestUtil.getImageSize();
ComponentColorModel cm = mock(ComponentColorModel.class);
when(cm.getNumComponents()).thenReturn(numComponents);
ImageRawPNG irpng = mock(ImageRawPNG.class);
when(irpng.getColorModel()).thenReturn(cm);
when(irpng.getSize()).thenReturn(is);
ImageEncoderPNG iepng = new ImageEncoderPNG(irpng);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] data = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, alpha);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
when(irpng.createInputStream()).thenReturn(bais);
iepng.writeTo(baos);
if (alpha > -1) {
byte[] expected = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, -1);
assertArrayEquals(expected, baos.toByteArray());
} else {
assertArrayEquals(data, baos.toByteArray());
}
}

@Test
public void testWriteToWithPalettePNG() throws IOException {
ImageSize is = RawPNGTestUtil.getImageSize();
IndexColorModel cm = mock(IndexColorModel.class);
ImageRawPNG irpng = mock(ImageRawPNG.class);
when(irpng.getColorModel()).thenReturn(cm);
when(irpng.getSize()).thenReturn(is);
ImageEncoderPNG iepng = new ImageEncoderPNG(irpng);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] data = RawPNGTestUtil.buildGRGBAData(128, -1, -1, -1, -1);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
when(irpng.createInputStream()).thenReturn(bais);
iepng.writeTo(baos);
assertArrayEquals(data, baos.toByteArray());
}

@Test
public void testGetImplicitFilterWithIndexColorModel() {
ImageSize is = RawPNGTestUtil.getImageSize();
IndexColorModel cm = mock(IndexColorModel.class);
ImageRawPNG irpng = mock(ImageRawPNG.class);
when(irpng.getColorModel()).thenReturn(cm);
when(irpng.getBitDepth()).thenReturn(8);
when(irpng.getSize()).thenReturn(is);
ImageEncoderPNG iepng = new ImageEncoderPNG(irpng);

String expectedFilter = "<< /Predictor 15 /Columns 32 /Colors 1 /BitsPerComponent 8 >> /FlateDecode";
assertEquals(expectedFilter, iepng.getImplicitFilter());
}

@Test
public void testGetImplicitFilterWithComponentColorModel() {
ImageSize is = RawPNGTestUtil.getImageSize();
ComponentColorModel cm = mock(ComponentColorModel.class);
when(cm.getNumComponents()).thenReturn(3);
ImageRawPNG irpng = mock(ImageRawPNG.class);
when(irpng.getColorModel()).thenReturn(cm);
when(irpng.getBitDepth()).thenReturn(8);
when(irpng.getSize()).thenReturn(is);
ImageEncoderPNG iepng = new ImageEncoderPNG(irpng);

String expectedFilter = "<< /Predictor 15 /Columns 32 /Colors 3 /BitsPerComponent 8 >> /FlateDecode";
assertEquals(expectedFilter, iepng.getImplicitFilter());
}

}

+ 43
- 0
test/java/org/apache/fop/render/ps/RenderPSTestSuite.java View File

@@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.ps;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

import org.apache.fop.render.ps.fonts.PSTTFGeneratorTestCase;
import org.apache.fop.render.ps.fonts.PSTTFGlyphOutputStreamTestCase;
import org.apache.fop.render.ps.fonts.PSTTFOutputStreamTestCase;
import org.apache.fop.render.ps.fonts.PSTTFTableOutputStreamTestCase;


/**
* A test Suite for org.apache.fop.render.ps.*
*/
@RunWith(Suite.class)
@SuiteClasses({
PSTTFGeneratorTestCase.class,
PSTTFOutputStreamTestCase.class,
PSTTFGlyphOutputStreamTestCase.class,
PSTTFTableOutputStreamTestCase.class
})
public final class RenderPSTestSuite {
}

+ 120
- 0
test/java/org/apache/fop/render/ps/fonts/PSTTFGeneratorTestCase.java View File

@@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.render.ps.fonts;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.apache.xmlgraphics.ps.PSGenerator;

/**
* The test class for org.apache.fop.render.ps.fonts.PSGenerator
*/
public class PSTTFGeneratorTestCase {
private PSTTFGenerator ttfGen;
private ByteArrayOutputStream out = new ByteArrayOutputStream();
private PSGenerator gen = new PSGenerator(out);
private byte[] byteArray;

/**
* Constructor
*/
public PSTTFGeneratorTestCase() {
byteArray = new byte[65536];
for (int i = 0; i < 65536; i++) {
byteArray[i] = (byte) i;
}
}

@Before
public void setUp() {
ttfGen = new PSTTFGenerator(gen);
}

/**
* Tests startString() - starts the string in an appropriate way for a PostScript file.
* @exception IOException write error
*/
@Test
public void testStartString() throws IOException {
ttfGen.startString();
assertEquals("<\n", out.toString());
}

/**
* Test streamBytes() - tests that strings are written to file in the proper format.
* @throws IOException write error.
*/
@Test
public void testStreamBytes() throws IOException {
ttfGen.streamBytes(byteArray, 0, 16);
assertEquals("000102030405060708090A0B0C0D0E0F", out.toString());
/*
* 65520 is the closes multiple of 80 to 65535 (max string size in PS document) and since
* one byte takes up two characters, 65520 / 2 - 16 (16 bytes already written)= 32744.
*/
ttfGen.streamBytes(byteArray, 0, 32744);
// Using a regex to ensure that the format is correct
assertTrue(out.toString().matches("([0-9A-F]{80}\n){819}"));
try {
ttfGen.streamBytes(byteArray, 0, PSTTFGenerator.MAX_BUFFER_SIZE + 1);
fail("Shouldn't be able to write more than MAX_BUFFER_SIZE to a PS document");
} catch (UnsupportedOperationException e) {
// PASS
}
}

/**
* Test reset() - reset should reset the line counter such that when reset() is invoked the
* following string streamed to the PS document should be 80 chars long.
* @throws IOException file write error.
*/
@Test
public void testReset() throws IOException {
ttfGen.streamBytes(byteArray, 0, 40);
assertTrue(out.toString().matches("([0-9A-F]{80}\n)"));
ttfGen.streamBytes(byteArray, 0, 40);
assertTrue(out.toString().matches("([0-9A-F]{80}\n){2}"));

}

/**
* Test endString() - ensures strings are ended in the PostScript document in the correct
* format, a "00" needs to be appended to the end of a string.
* @throws IOException file write error
*/
@Test
public void testEndString() throws IOException {
ttfGen.endString();
assertEquals("00\n> ", out.toString());
out.reset();
// we need to check that this doesn't write more than 80 chars per line
ttfGen.streamBytes(byteArray, 0, 40);
ttfGen.endString();
assertTrue(out.toString().matches("([0-9A-F]{80}\n)00\n> "));
}
}

+ 109
- 0
test/java/org/apache/fop/render/ps/fonts/PSTTFGlyphOutputStreamTestCase.java View File

@@ -0,0 +1,109 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.ps.fonts;

import java.io.IOException;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;

import static org.junit.Assert.fail;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
* Test class for PSTTFGlyphOutputStream
*/
public class PSTTFGlyphOutputStreamTestCase {
private PSTTFGenerator mockGen;
private PSTTFGlyphOutputStream glyphOut;

@Before
public void setUp() {
mockGen = mock(PSTTFGenerator.class);
glyphOut = new PSTTFGlyphOutputStream(mockGen);
}

/**
* Test startGlyphStream() - test that startGlyphStream() invokes reset() and startString() in
* PSTTFGenerator.
* @exception IOException file write error
*/
@Test
public void testStartGlyphStream() throws IOException {
glyphOut.startGlyphStream();
verify(mockGen).startString();
}

/**
* Test streamGlyph(byte[],int,int) - tests several paths:
* 1) strings are properly appended
* 2) when total strings size > PSTTFGenerator.MAX_BUFFER_SIZE, the strings is closed and a new
* strings is started.
* 3) if a glyph of size > PSTTFGenerator.MAX_BUFFER_SIZE is attempted, an exception is thrown.
* @throws IOException file write error.
*/
@Test
public void testStreamGlyph() throws IOException {
int byteArraySize = 10;
byte[] byteArray = new byte[byteArraySize];
int runs = 100;
for (int i = 0; i < runs; i++) {
glyphOut.streamGlyph(byteArray, 0, byteArraySize);
}
verify(mockGen, times(runs)).streamBytes(byteArray, 0, byteArraySize);

/*
* We want to run this for MAX_BUFFER_SIZE / byteArraySize so that go over the string
* boundary and enforce the ending and starting of a new string. Using mockito to ensure
* that this behaviour is performed in order (since this is an integral behavioural aspect)
*/
int stringLimit = PSTTFGenerator.MAX_BUFFER_SIZE / byteArraySize;
for (int i = 0; i < stringLimit; i++) {
glyphOut.streamGlyph(byteArray, 0, byteArraySize);
}
InOrder inOrder = inOrder(mockGen);
inOrder.verify(mockGen, times(stringLimit)).streamBytes(byteArray, 0, byteArraySize);
inOrder.verify(mockGen).endString();
inOrder.verify(mockGen).startString();
inOrder.verify(mockGen, times(runs)).streamBytes(byteArray, 0, byteArraySize);

try {
glyphOut.streamGlyph(byteArray, 0, PSTTFGenerator.MAX_BUFFER_SIZE + 1);
fail("Shouldn't allow a length > PSTTFGenerator.MAX_BUFFER_SIZE");
} catch (UnsupportedOperationException e) {
// PASS
}
}

/**
* Test endGlyphStream() - tests that PSTTFGenerator.endString() is invoked when this method
* is called.
* @throws IOException file write exception
*/
@Test
public void testEndGlyphStream() throws IOException {
glyphOut.endGlyphStream();
verify(mockGen).endString();
}
}

+ 90
- 0
test/java/org/apache/fop/render/ps/fonts/PSTTFOutputStreamTestCase.java View File

@@ -0,0 +1,90 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.ps.fonts;

import java.io.IOException;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import org.apache.xmlgraphics.ps.PSGenerator;

import org.apache.fop.fonts.truetype.TTFGlyphOutputStream;
import org.apache.fop.fonts.truetype.TTFTableOutputStream;

/**
* Tests PSTTFOuputStream
*/
public class PSTTFOutputStreamTestCase {
private PSGenerator gen;
private PSTTFOutputStream out;

/**
* Assigns an OutputStream to the PSGenerator.
*/
@Before
public void setUp() {
gen = mock(PSGenerator.class);
out = new PSTTFOutputStream(gen);
}

/**
* Test startFontStream() - Just tests that the font is properly initiated in the PostScript
* document (in this case with "/sfnts[")
* @throws IOException write exception.
*/
@Test
public void testStartFontStream() throws IOException {
out.startFontStream();
verify(gen).write("/sfnts[");
}

/**
* Test getTableOutputStream() - we need to test that the inheritance model is properly obeyed.
*/
@Test
public void testGetTableOutputStream() {
TTFTableOutputStream tableOut = out.getTableOutputStream();
assertTrue(tableOut instanceof PSTTFTableOutputStream);
}

/**
* Test getGlyphOutputStream() - we need to test that the inheritance model is properly obeyed.
*/
@Test
public void testGetGlyphOutputStream() {
TTFGlyphOutputStream glyphOut = out.getGlyphOutputStream();
assertTrue(glyphOut instanceof PSTTFGlyphOutputStream);
}

/**
* Test endFontStream()
* @exception IOException write error.
*/
@Test
public void testEndFontStream() throws IOException {
out.endFontStream();
verify(gen).writeln("] def");
}
}

+ 87
- 0
test/java/org/apache/fop/render/ps/fonts/PSTTFTableOutputStreamTestCase.java View File

@@ -0,0 +1,87 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.ps.fonts;

import java.io.IOException;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;

import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;

/**
* Test class for unit testing PSTTFTableOutputStream
*/
public class PSTTFTableOutputStreamTestCase {
private PSTTFGenerator mockGen;
private PSTTFTableOutputStream tableOut;

@Before
public void setUp() {
mockGen = mock(PSTTFGenerator.class);
tableOut = new PSTTFTableOutputStream(mockGen);
}

/**
* Test streamTable() - several paths to test (2. and 3. test corner cases):
* 1) that a table of length < PSTTFGenerator.MAX_BUFFER_SIZE invokes the correct methods in
* PSTTFGenerator.
* 2) that a table of length > PSTTFGenerator.MAX_BUFFER_SIZE and
* length == n * PSTTFGenerator.MAX_BUFFER_SIZE is split up and the methods in PSTTFGenerator
* are invoked.
* 3) that a table of length > PSTTFGenerator.MAX_BUFFER_SIZE but
* length != n * PSTTFGenerator.MAX_BUFFER_SIZE is split up and the methods in PSTTFGenerator
* are invoked.
* @throws IOException file write error.
*/
@Test
public void testStreamTable() throws IOException {
byte[] byteArray = new byte[PSTTFGenerator.MAX_BUFFER_SIZE * 3];
tableOut.streamTable(byteArray, 0, 10);
InOrder inOrder = inOrder(mockGen);
inOrder.verify(mockGen).startString();
inOrder.verify(mockGen).streamBytes(byteArray, 0, 10);
inOrder.verify(mockGen).endString();

setUp(); // reset all all the method calls
/* We're going to run this 3 times to ensure the proper method calls are invoked and all
* the bytes are streamed */
tableOut.streamTable(byteArray, 0, byteArray.length);
inOrder = inOrder(mockGen);
for (int i = 0; i < 3; i++) {
int offset = PSTTFGenerator.MAX_BUFFER_SIZE * i;
inOrder.verify(mockGen).startString();
inOrder.verify(mockGen).streamBytes(byteArray, offset, PSTTFGenerator.MAX_BUFFER_SIZE);
inOrder.verify(mockGen).endString();
}

setUp(); // reset all the method calls
tableOut.streamTable(byteArray, 0, PSTTFGenerator.MAX_BUFFER_SIZE + 1);
inOrder = inOrder(mockGen);
inOrder.verify(mockGen).startString();
inOrder.verify(mockGen).streamBytes(byteArray, 0, PSTTFGenerator.MAX_BUFFER_SIZE);
inOrder.verify(mockGen).endString();
inOrder.verify(mockGen).startString();
inOrder.verify(mockGen).streamBytes(byteArray, PSTTFGenerator.MAX_BUFFER_SIZE, 1);
inOrder.verify(mockGen).endString();
}
}

+ 61
- 0
test/java/org/apache/fop/util/HexEncoderTestCase.java View File

@@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $Id$ */

package org.apache.fop.util;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
* Test case for the conversion of characters into hex-encoded strings.
*/
public class HexEncoderTestCase {

/**
* Tests that characters are properly encoded into hex strings.
*/
@Test
public void testEncodeChar() {
char[] digits = new char[] {'0', '0', '0', '0'};
for (int c = 0; c <= 0xFFFF; c++) {
assertEquals(new String(digits), HexEncoder.encode((char) c));
increment(digits);
}
}

private static void increment(char[] digits) {
int d = 4;
do {
d--;
digits[d] = successor(digits[d]);
} while (digits[d] == '0' && d > 0);
}

private static char successor(char d) {
if (d == '9') {
return 'A';
} else if (d == 'F') {
return '0';
} else {
return (char) (d + 1);
}
}

}

+ 18
- 0
test/resources/fonts/ttf/DroidSansMono.LICENSE View File

@@ -0,0 +1,18 @@
Copyright (C) 2008 The Android Open Source Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

##########

This directory contains the fonts for the platform. They are licensed
under the Apache 2 license.

BIN
test/resources/fonts/ttf/DroidSansMono.ttf View File


Loading…
Cancel
Save