git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/fop-1_1rc1@1353186 13f79535-47bb-0310-9956-ffa450edef68tags/fop-1_1rc1old
@@ -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! * |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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&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"> |
@@ -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 |
@@ -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 |
@@ -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; | |||
} |
@@ -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); | |||
/** |
@@ -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 ( ',' ); |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); |
@@ -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); | |||
} | |||
} |
@@ -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; |
@@ -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)) { |
@@ -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(); | |||
} |
@@ -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); | |||
} |
@@ -130,4 +130,9 @@ public class FontType { | |||
return value; | |||
} | |||
@Override | |||
public String toString() { | |||
return name; | |||
} | |||
} |
@@ -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; |
@@ -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 |
@@ -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 |
@@ -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; | |||
} | |||
} | |||
@@ -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()); |
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 |
@@ -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); |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
} | |||
} | |||
@@ -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); |
@@ -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); |
@@ -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 |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
/** |
@@ -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,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> |
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 +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} */ |
@@ -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()); | |||
} | |||
} |
@@ -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()) { |
@@ -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 |
@@ -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 '<' 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> "); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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> |
@@ -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 { | |||
} |
@@ -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); | |||
} | |||
/** |
@@ -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"); | |||
} | |||
} |
@@ -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 { | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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 | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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 { | |||
} |
@@ -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> ")); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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. |