git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/fop-1_1rc1@1353186 13f79535-47bb-0310-9956-ffa450edef68tags/fop-1_1rc1old
<target name="junit-text-linebreak" depends="junit-compile" description="Runs FOP's JUnit unicode linebreak tests" if="junit.present"> | <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"/> | <junit-run title="Unicode UAX#14 support" testsuite="org.apache.fop.text.linebreak.LineBreakStatusTestCase" outfile="TEST-linebreak"/> | ||||
</target> | </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"> | <target name="junit-render-pdf" depends="junit-compile"> | ||||
<junit-run title="render-pdf" testsuite="org.apache.fop.render.pdf.RenderPDFTestSuite" outfile="TEST-render-pdf"/> | <junit-run title="render-pdf" testsuite="org.apache.fop.render.pdf.RenderPDFTestSuite" outfile="TEST-render-pdf"/> | ||||
</target> | </target> | ||||
<target name="junit-complexscripts" depends="junit-compile"> | <target name="junit-complexscripts" depends="junit-compile"> | ||||
<junit-run title="complexscripts" testsuite="org.apache.fop.complexscripts.ComplexScriptsTestSuite" outfile="TEST-complexscripts"/> | <junit-run title="complexscripts" testsuite="org.apache.fop.complexscripts.ComplexScriptsTestSuite" outfile="TEST-complexscripts"/> | ||||
</target> | </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" | <target name="junit" depends="junit-all" description="Runs all of FOP's JUnit tests" | ||||
if="junit.present"> | 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: | NOTE: | ||||
************************************************************************** | ************************************************************************** | ||||
* One or more of the Junit tests had Failures or Errors or were skipped! * | * One or more of the Junit tests had Failures or Errors or were skipped! * |
<?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||
<FindBugsFilter> | <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> | <Match> | ||||
<Class name="org.apache.fop.fo.properties.FontFamilyProperty"/> | <Class name="org.apache.fop.fo.properties.FontFamilyProperty"/> | ||||
<Bug pattern="EQ_DOESNT_OVERRIDE_EQUALS"/> | <Bug pattern="EQ_DOESNT_OVERRIDE_EQUALS"/> | ||||
<Method name="getNonEmptyLevels"/> | <Method name="getNonEmptyLevels"/> | ||||
<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/> | <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/> | ||||
</Match> | </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> | </FindBugsFilter> |
treated as zero penalty in most cases. For more details on the image loading framework, | treated as zero penalty in most cases. For more details on the image loading framework, | ||||
please consult the documentation there. | please consult the documentation there. | ||||
</p> | </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> | ||||
<section id="renderers"> | <section id="renderers"> | ||||
<title>Renderer configuration</title> | <title>Renderer configuration</title> |
Various notes related to embedded fonts: | Various notes related to embedded fonts: | ||||
</p> | </p> | ||||
<ul> | <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 PostScript fonts, the entire font is always embedded.</li> | ||||
<li>When embedding TrueType fonts (ttf) or TrueType Collections (ttc), a subset of the | <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. | original font, containing only the glyphs used, is embedded in the output document. | ||||
</p> | </p> | ||||
</section> | </section> | ||||
</body> | </body> | ||||
</document> | |||||
</document> |
<tr> | <tr> | ||||
<td><a href="#png">PNG</a> (Portable Network Graphic)</td> | <td><a href="#png">PNG</a> (Portable Network Graphic)</td> | ||||
<td>bitmap</td> | <td>bitmap</td> | ||||
<td/> | |||||
<td>(X)</td> | |||||
<td/> | <td/> | ||||
<td>X</td> | <td>X</td> | ||||
</tr> | </tr> | ||||
</tr> | </tr> | ||||
<tr> | <tr> | ||||
<td><a href="#png">PNG</a> (Portable Network Graphic)</td> | <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> | <td>X</td> | ||||
<td>X</td> | <td>X</td> | ||||
<section id="png"> | <section id="png"> | ||||
<title>PNG</title> | <title>PNG</title> | ||||
<p> | <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> | </p> | ||||
</section> | </section> | ||||
<section id="svg"> | <section id="svg"> |
org.apache.fop.render.pdf.PDFImageHandlerGraphics2D | org.apache.fop.render.pdf.PDFImageHandlerGraphics2D | ||||
org.apache.fop.render.pdf.PDFImageHandlerRenderedImage | org.apache.fop.render.pdf.PDFImageHandlerRenderedImage | ||||
org.apache.fop.render.pdf.PDFImageHandlerRawJPEG | org.apache.fop.render.pdf.PDFImageHandlerRawJPEG | ||||
org.apache.fop.render.pdf.PDFImageHandlerRawPNG | |||||
org.apache.fop.render.pdf.PDFImageHandlerRawCCITTFax | org.apache.fop.render.pdf.PDFImageHandlerRawCCITTFax | ||||
org.apache.fop.render.pdf.PDFImageHandlerSVG | org.apache.fop.render.pdf.PDFImageHandlerSVG | ||||
org.apache.fop.render.java2d.Java2DImageHandlerRenderedImage | org.apache.fop.render.java2d.Java2DImageHandlerRenderedImage | ||||
org.apache.fop.render.ps.PSImageHandlerEPS | org.apache.fop.render.ps.PSImageHandlerEPS | ||||
org.apache.fop.render.ps.PSImageHandlerRawCCITTFax | org.apache.fop.render.ps.PSImageHandlerRawCCITTFax | ||||
org.apache.fop.render.ps.PSImageHandlerRawJPEG | org.apache.fop.render.ps.PSImageHandlerRawJPEG | ||||
org.apache.fop.render.ps.PSImageHandlerRawPNG | |||||
org.apache.fop.render.ps.PSImageHandlerGraphics2D | org.apache.fop.render.ps.PSImageHandlerGraphics2D | ||||
org.apache.fop.render.ps.PSImageHandlerSVG | org.apache.fop.render.ps.PSImageHandlerSVG | ||||
org.apache.fop.render.afp.AFPImageHandlerRenderedImage | org.apache.fop.render.afp.AFPImageHandlerRenderedImage |
import org.apache.fop.fonts.truetype.FontFileReader; | import org.apache.fop.fonts.truetype.FontFileReader; | ||||
import org.apache.fop.fonts.truetype.TTFDirTabEntry; | import org.apache.fop.fonts.truetype.TTFDirTabEntry; | ||||
import org.apache.fop.fonts.truetype.TTFFile; | import org.apache.fop.fonts.truetype.TTFFile; | ||||
import org.apache.fop.fonts.truetype.TTFTableName; | |||||
// CSOFF: AvoidNestedBlocksCheck | // CSOFF: AvoidNestedBlocksCheck | ||||
// CSOFF: NoWhitespaceAfterCheck | // CSOFF: NoWhitespaceAfterCheck | ||||
return gpos; | 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); | in.seekSet(langSysTable); | ||||
if (log.isDebugEnabled()) { | if (log.isDebugEnabled()) { | ||||
log.debug(tableTag + " lang sys table: " + langSysTag ); | log.debug(tableTag + " lang sys table: " + langSysTag ); | ||||
private static String defaultTag = "dflt"; | 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); | in.seekSet(scriptTable); | ||||
if (log.isDebugEnabled()) { | if (log.isDebugEnabled()) { | ||||
log.debug(tableTag + " script table: " + scriptTag ); | log.debug(tableTag + " script table: " + scriptTag ); | ||||
seLanguages = null; | seLanguages = null; | ||||
} | } | ||||
private void readScriptList(String tableTag, long scriptList) throws IOException { | |||||
private void readScriptList(TTFTableName tableTag, long scriptList) throws IOException { | |||||
in.seekSet(scriptList); | in.seekSet(scriptList); | ||||
// read script record count | // read script record count | ||||
int ns = in.readTTFUShort(); | int ns = in.readTTFUShort(); | ||||
} | } | ||||
} | } | ||||
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); | in.seekSet(featureTable); | ||||
if (log.isDebugEnabled()) { | if (log.isDebugEnabled()) { | ||||
log.debug(tableTag + " feature table: " + featureTag ); | log.debug(tableTag + " feature table: " + featureTag ); | ||||
seFeatures.put ( "f" + featureIndex, new Object[] { featureTag, lul } ); | 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); | in.seekSet(featureList); | ||||
// read feature record count | // read feature record count | ||||
int nf = in.readTTFUShort(); | int nf = in.readTTFUShort(); | ||||
resetATSubState(); | 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); | in.seekSet(lookupTable); | ||||
// read lookup type | // read lookup type | ||||
int lt = in.readTTFUShort(); | int lt = in.readTTFUShort(); | ||||
} | } | ||||
} | } | ||||
private void readLookupList(String tableTag, long lookupList) throws IOException { | |||||
private void readLookupList(TTFTableName tableTag, long lookupList) throws IOException { | |||||
in.seekSet(lookupList); | in.seekSet(lookupList); | ||||
// read lookup record count | // read lookup record count | ||||
int nl = in.readTTFUShort(); | int nl = in.readTTFUShort(); | ||||
* @param lookupList offset to lookup list from beginning of font file | * @param lookupList offset to lookup list from beginning of font file | ||||
* @throws IOException In case of a I/O problem | * @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 ) { | if ( scriptList > 0 ) { | ||||
readScriptList ( tableTag, scriptList ); | readScriptList ( tableTag, scriptList ); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private void readGDEFClassDefTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException { | |||||
private void readGDEFClassDefTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { | |||||
initATSubState(); | initATSubState(); | ||||
in.seekSet(subtableOffset); | in.seekSet(subtableOffset); | ||||
// subtable is a bare class definition table | // subtable is a bare class definition table | ||||
resetATSubState(); | resetATSubState(); | ||||
} | } | ||||
private void readGDEFAttachmentTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException { | |||||
private void readGDEFAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { | |||||
initATSubState(); | initATSubState(); | ||||
in.seekSet(subtableOffset); | in.seekSet(subtableOffset); | ||||
// read coverage offset | // read coverage offset | ||||
resetATSubState(); | resetATSubState(); | ||||
} | } | ||||
private void readGDEFLigatureCaretTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException { | |||||
private void readGDEFLigatureCaretTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { | |||||
initATSubState(); | initATSubState(); | ||||
in.seekSet(subtableOffset); | in.seekSet(subtableOffset); | ||||
// read coverage offset | // read coverage offset | ||||
resetATSubState(); | resetATSubState(); | ||||
} | } | ||||
private void readGDEFMarkAttachmentTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException { | |||||
private void readGDEFMarkAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { | |||||
initATSubState(); | initATSubState(); | ||||
in.seekSet(subtableOffset); | in.seekSet(subtableOffset); | ||||
// subtable is a bare class definition table | // subtable is a bare class definition table | ||||
resetATSubState(); | 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(); | initATSubState(); | ||||
in.seekSet(subtableOffset); | in.seekSet(subtableOffset); | ||||
// skip over format (already known) | // skip over format (already known) | ||||
resetATSubState(); | 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); | in.seekSet(subtableOffset); | ||||
// read mark set subtable format | // read mark set subtable format | ||||
int sf = in.readTTFUShort(); | int sf = in.readTTFUShort(); | ||||
* @throws IOException In case of a I/O problem | * @throws IOException In case of a I/O problem | ||||
*/ | */ | ||||
private void readGDEF() throws IOException { | private void readGDEF() throws IOException { | ||||
String tableTag = "GDEF"; | |||||
TTFTableName tableTag = TTFTableName.GDEF; | |||||
// Initialize temporary state | // Initialize temporary state | ||||
initATState(); | initATState(); | ||||
// Read glyph definition (GDEF) table | // Read glyph definition (GDEF) table | ||||
TTFDirTabEntry dirTab = ttf.getDirectoryEntry ( tableTag ); | |||||
TTFDirTabEntry dirTab = ttf.getDirectoryEntry( tableTag ); | |||||
if ( gdef != null ) { | if ( gdef != null ) { | ||||
if (log.isDebugEnabled()) { | if (log.isDebugEnabled()) { | ||||
log.debug(tableTag + ": ignoring duplicate table"); | log.debug(tableTag + ": ignoring duplicate table"); | ||||
* @throws IOException In case of a I/O problem | * @throws IOException In case of a I/O problem | ||||
*/ | */ | ||||
private void readGSUB() throws IOException { | private void readGSUB() throws IOException { | ||||
String tableTag = "GSUB"; | |||||
TTFTableName tableTag = TTFTableName.GSUB; | |||||
// Initialize temporary state | // Initialize temporary state | ||||
initATState(); | initATState(); | ||||
// Read glyph substitution (GSUB) table | // Read glyph substitution (GSUB) table | ||||
* @throws IOException In case of a I/O problem | * @throws IOException In case of a I/O problem | ||||
*/ | */ | ||||
private void readGPOS() throws IOException { | private void readGPOS() throws IOException { | ||||
String tableTag = "GPOS"; | |||||
TTFTableName tableTag = TTFTableName.GPOS; | |||||
// Initialize temporary state | // Initialize temporary state | ||||
initATState(); | initATState(); | ||||
// Read glyph positioning (GPOS) table | // Read glyph positioning (GPOS) table |
import org.apache.fop.fo.FObj; | import org.apache.fop.fo.FObj; | ||||
import org.apache.fop.fo.PropertyList; | import org.apache.fop.fo.PropertyList; | ||||
import org.apache.fop.fo.expr.PropertyException; | import org.apache.fop.fo.expr.PropertyException; | ||||
import org.apache.fop.util.CompareUtil; | |||||
/** | /** | ||||
* Superclass for properties that have conditional lengths | * Superclass for properties that have conditional lengths | ||||
if (obj instanceof CondLengthProperty) { | if (obj instanceof CondLengthProperty) { | ||||
CondLengthProperty clp = (CondLengthProperty)obj; | 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; | return false; | ||||
} | } |
/** | /** | ||||
* CID Font Type 2 (based on TrueType format) | * 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); | |||||
/** | /** |
package org.apache.fop.fonts; | 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 unicodeStart Unicode start index | ||||
* @param unicodeEnd Unicode end index | * @param unicodeEnd Unicode end index | ||||
* @param glyphStartIndex glyph start 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.unicodeStart = unicodeStart; | ||||
this.unicodeEnd = unicodeEnd; | this.unicodeEnd = unicodeEnd; | ||||
this.glyphStartIndex = glyphStartIndex; | 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. | * Returns the unicodeStart. | ||||
* @return the Unicode start index | * @return the Unicode start index | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | @Override | ||||
public String toString() { | public String toString() { | ||||
StringBuilder sb = new StringBuilder("BFEntry: "); | |||||
StringBuilder sb = new StringBuilder("CMapSegment: "); | |||||
sb.append ( "{ UC[" ); | sb.append ( "{ UC[" ); | ||||
sb.append ( unicodeStart ); | sb.append ( unicodeStart ); | ||||
sb.append ( ',' ); | sb.append ( ',' ); |
private String embedFileName = null; | private String embedFileName = null; | ||||
private String embedResourceName = null; | private String embedResourceName = null; | ||||
private FontResolver resolver = null; | private FontResolver resolver = null; | ||||
private EmbeddingMode embeddingMode = EmbeddingMode.AUTO; | |||||
private int capHeight = 0; | private int capHeight = 0; | ||||
private int xHeight = 0; | private int xHeight = 0; | ||||
private boolean useKerning = true; | private boolean useKerning = true; | ||||
private boolean useAdvanced = true; | private boolean useAdvanced = true; | ||||
/** the character map, mapping Unicode ranges to glyph indices. */ | |||||
protected CMapSegment[] cmap; | |||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
public String getFontName() { | public String getFontName() { | ||||
return fontName; | return fontName; | ||||
return embedFileName; | 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. | * Returns a Source representing an embeddable font file. | ||||
* @return Source for an embeddable font file | * @return Source for an embeddable font file | ||||
this.embedResourceName = name; | this.embedResourceName = name; | ||||
} | } | ||||
/** | |||||
* {@inheritDoc} | |||||
*/ | |||||
public void setEmbeddingMode(EmbeddingMode embeddingMode) { | |||||
this.embeddingMode = embeddingMode; | |||||
} | |||||
/** | /** | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
*/ | */ | ||||
} | } | ||||
} | } | ||||
/** | |||||
* 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; | |||||
} | |||||
} | } |
List<FontTriplet> triplets = embedFontInfo.getFontTriplets(); | List<FontTriplet> triplets = embedFontInfo.getFontTriplets(); | ||||
for (int tripletIndex = 0; tripletIndex < triplets.size(); tripletIndex++) { | for (int tripletIndex = 0; tripletIndex < triplets.size(); tripletIndex++) { | ||||
FontTriplet triplet = (FontTriplet) triplets.get(tripletIndex); | |||||
FontTriplet triplet = triplets.get(tripletIndex); | |||||
fontInfo.addFontProperties(internalName, triplet); | fontInfo.addFontProperties(internalName, triplet); | ||||
} | } | ||||
} | } |
/** | /** | ||||
* FontInfo contains meta information on fonts (where is the metrics file etc.) | * 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 { | public class EmbedFontInfo implements Serializable { | ||||
protected boolean advanced; | protected boolean advanced; | ||||
/** the requested encoding mode for the font */ | /** the requested encoding mode for the font */ | ||||
protected EncodingMode encodingMode = EncodingMode.AUTO; | protected EncodingMode encodingMode = EncodingMode.AUTO; | ||||
/** the requested embedding mode for this font */ | |||||
protected EmbeddingMode embeddingMode = EmbeddingMode.AUTO; | |||||
/** the PostScript name of the font */ | /** the PostScript name of the font */ | ||||
protected String postScriptName = null; | protected String postScriptName = null; | ||||
} | } | ||||
} | } | ||||
/** | |||||
* Returns the embedding mode for this font. | |||||
* @return the embedding mode. | |||||
*/ | |||||
public EmbeddingMode getEmbeddingMode() { | |||||
return embeddingMode; | |||||
} | |||||
/** | /** | ||||
* Defines whether the font is embedded or not. | * Defines whether the font is embedded or not. | ||||
* @param value true to embed the font, false to reference it | * @param value true to embed the font, false to reference it | ||||
this.encodingMode = mode; | 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) | private void readObject(java.io.ObjectInputStream in) | ||||
throws IOException, ClassNotFoundException { | throws IOException, ClassNotFoundException { | ||||
in.defaultReadObject(); | in.defaultReadObject(); |
/* | |||||
* 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); | |||||
} | |||||
} |
* @param name the name of the encoding mode to look up | * @param name the name of the encoding mode to look up | ||||
* @return the encoding mode constant | * @return the encoding mode constant | ||||
*/ | */ | ||||
public static EncodingMode getEncodingMode(String name) { | |||||
public static EncodingMode getValue(String name) { | |||||
for (EncodingMode em : EncodingMode.values()) { | for (EncodingMode em : EncodingMode.values()) { | ||||
if (name.equalsIgnoreCase(em.getName())) { | if (name.equalsIgnoreCase(em.getName())) { | ||||
return em; | return em; |
boolean useKerning = fontCfg.getAttributeAsBoolean("kerning", true); | boolean useKerning = fontCfg.getAttributeAsBoolean("kerning", true); | ||||
boolean useAdvanced = fontCfg.getAttributeAsBoolean("advanced", true); | boolean useAdvanced = fontCfg.getAttributeAsBoolean("advanced", true); | ||||
EncodingMode encodingMode = EncodingMode.getEncodingMode( | |||||
EncodingMode encodingMode = EncodingMode.getValue( | |||||
fontCfg.getAttribute("encoding-mode", EncodingMode.AUTO.getName())); | fontCfg.getAttribute("encoding-mode", EncodingMode.AUTO.getName())); | ||||
EmbeddingMode embeddingMode = EmbeddingMode.getValue( | |||||
fontCfg.getAttribute("embedding-mode", EmbeddingMode.AUTO.toString())); | |||||
EmbedFontInfo embedFontInfo | EmbedFontInfo embedFontInfo | ||||
= new EmbedFontInfo(metricsUrl, useKerning, useAdvanced, tripletList, embedUrl, | = new EmbedFontInfo(metricsUrl, useKerning, useAdvanced, tripletList, embedUrl, | ||||
subFont); | subFont); | ||||
embedFontInfo.setEncodingMode(encodingMode); | embedFontInfo.setEncodingMode(encodingMode); | ||||
embedFontInfo.setEmbeddingMode(embeddingMode); | |||||
boolean skipCachedFont = false; | boolean skipCachedFont = false; | ||||
if (fontCache != null) { | if (fontCache != null) { | ||||
if (!fontCache.containsFont(embedFontInfo)) { | if (!fontCache.containsFont(embedFontInfo)) { |
* @param fontFile the File representation of the font | * @param fontFile the File representation of the font | ||||
* @param subFontName the sub-fontname of a font (for TrueType Collections, null otherwise) | * @param subFontName the sub-fontname of a font (for TrueType Collections, null otherwise) | ||||
* @param embedded indicates whether the font is embedded or referenced | * @param embedded indicates whether the font is embedded or referenced | ||||
* @param embeddingMode the embedding mode | |||||
* @param encodingMode the requested encoding mode | * @param encodingMode the requested encoding mode | ||||
* @param resolver the font resolver to use when resolving URIs | * @param resolver the font resolver to use when resolving URIs | ||||
* @return the newly loaded font | * @return the newly loaded font | ||||
* @throws IOException In case of an I/O error | * @throws IOException In case of an I/O error | ||||
*/ | */ | ||||
public static CustomFont loadFont(File fontFile, String subFontName, | 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, | return loadFont(fontFile.toURI().toURL(), subFontName, | ||||
embedded, encodingMode, resolver); | |||||
embedded, embeddingMode, encodingMode, resolver); | |||||
} | } | ||||
/** | /** | ||||
* @param fontUrl the URL representation of the font | * @param fontUrl the URL representation of the font | ||||
* @param subFontName the sub-fontname of a font (for TrueType Collections, null otherwise) | * @param subFontName the sub-fontname of a font (for TrueType Collections, null otherwise) | ||||
* @param embedded indicates whether the font is embedded or referenced | * @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 encodingMode the requested encoding mode | ||||
* @param resolver the font resolver to use when resolving URIs | * @param resolver the font resolver to use when resolving URIs | ||||
* @return the newly loaded font | * @return the newly loaded font | ||||
* @throws IOException In case of an I/O error | * @throws IOException In case of an I/O error | ||||
*/ | */ | ||||
public static CustomFont loadFont(URL fontUrl, String subFontName, | public static CustomFont loadFont(URL fontUrl, String subFontName, | ||||
boolean embedded, EncodingMode encodingMode, | |||||
boolean embedded, EmbeddingMode embeddingMode, EncodingMode encodingMode, | |||||
FontResolver resolver) throws IOException { | FontResolver resolver) throws IOException { | ||||
return loadFont(fontUrl.toExternalForm(), subFontName, | return loadFont(fontUrl.toExternalForm(), subFontName, | ||||
embedded, encodingMode, true, true, | |||||
embedded, embeddingMode, encodingMode, true, true, | |||||
resolver); | resolver); | ||||
} | } | ||||
* @param fontFileURI the URI to the font | * @param fontFileURI the URI to the font | ||||
* @param subFontName the sub-fontname of a font (for TrueType Collections, null otherwise) | * @param subFontName the sub-fontname of a font (for TrueType Collections, null otherwise) | ||||
* @param embedded indicates whether the font is embedded or referenced | * @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 encodingMode the requested encoding mode | ||||
* @param useKerning indicates whether kerning information should be loaded if available | * @param useKerning indicates whether kerning information should be loaded if available | ||||
* @param useAdvanced indicates whether advanced typographic information shall be loaded if | * @param useAdvanced indicates whether advanced typographic information shall be loaded if | ||||
* @throws IOException In case of an I/O error | * @throws IOException In case of an I/O error | ||||
*/ | */ | ||||
public static CustomFont loadFont(String fontFileURI, String subFontName, | 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(); | fontFileURI = fontFileURI.trim(); | ||||
boolean type1 = isType1(fontFileURI); | boolean type1 = isType1(fontFileURI); | ||||
FontLoader loader; | FontLoader loader; | ||||
throw new IllegalArgumentException( | throw new IllegalArgumentException( | ||||
"CID encoding mode not supported for Type 1 fonts"); | "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); | loader = new Type1FontLoader(fontFileURI, embedded, useKerning, resolver); | ||||
} else { | } else { | ||||
loader = new TTFFontLoader(fontFileURI, subFontName, | loader = new TTFFontLoader(fontFileURI, subFontName, | ||||
embedded, encodingMode, useKerning, useAdvanced, resolver); | |||||
embedded, embeddingMode, encodingMode, useKerning, useAdvanced, resolver); | |||||
} | } | ||||
return loader.getFont(); | return loader.getFont(); | ||||
} | } |
private Map<Integer, Integer> currentKerning = null; | private Map<Integer, Integer> currentKerning = null; | ||||
private List<BFEntry> bfranges = null; | |||||
private List<CMapSegment> bfranges = null; | |||||
private void createFont(InputSource source) throws FOPException { | private void createFont(InputSource source) throws FOPException { | ||||
XMLReader parser = null; | XMLReader parser = null; | ||||
/** | /** | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
*/ | */ | ||||
@Override | |||||
public void startDocument() { | public void startDocument() { | ||||
} | } | ||||
/** | /** | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
*/ | */ | ||||
@Override | |||||
public void setDocumentLocator(Locator locator) { | public void setDocumentLocator(Locator locator) { | ||||
// this.locator = locator; // not used at present | // this.locator = locator; // not used at present | ||||
} | } | ||||
/** | /** | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
*/ | */ | ||||
@Override | |||||
public void startElement(String uri, String localName, String qName, | public void startElement(String uri, String localName, String qName, | ||||
Attributes attributes) throws SAXException { | Attributes attributes) throws SAXException { | ||||
if (localName.equals("font-metrics")) { | if (localName.equals("font-metrics")) { | ||||
returnFont.putKerningEntry(new Integer(attributes.getValue("kpx1")), | returnFont.putKerningEntry(new Integer(attributes.getValue("kpx1")), | ||||
currentKerning); | currentKerning); | ||||
} else if ("bfranges".equals(localName)) { | } else if ("bfranges".equals(localName)) { | ||||
bfranges = new ArrayList<BFEntry>(); | |||||
bfranges = new ArrayList<CMapSegment>(); | |||||
} else if ("bf".equals(localName)) { | } 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("ue")), | ||||
getInt(attributes.getValue("gi"))); | getInt(attributes.getValue("gi"))); | ||||
bfranges.add(entry); | bfranges.add(entry); | ||||
/** | /** | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
*/ | */ | ||||
@Override | |||||
public void endElement(String uri, String localName, String qName) throws SAXException { | public void endElement(String uri, String localName, String qName) throws SAXException { | ||||
String content = text.toString().trim(); | String content = text.toString().trim(); | ||||
if ("font-name".equals(localName)) { | if ("font-name".equals(localName)) { | ||||
multiFont.setWidthArray(wds); | multiFont.setWidthArray(wds); | ||||
} else if ("bfranges".equals(localName)) { | } 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()) | text.setLength(0); //Reset text buffer (see characters()) | ||||
} | } | ||||
/** | /** | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
*/ | */ | ||||
@Override | |||||
public void characters(char[] ch, int start, int length) { | public void characters(char[] ch, int start, int length) { | ||||
text.append(ch, start, length); | text.append(ch, start, length); | ||||
} | } |
return value; | return value; | ||||
} | } | ||||
@Override | |||||
public String toString() { | |||||
return name; | |||||
} | |||||
} | } |
import org.apache.fop.complexscripts.fonts.Positionable; | import org.apache.fop.complexscripts.fonts.Positionable; | ||||
import org.apache.fop.complexscripts.fonts.Substitutable; | import org.apache.fop.complexscripts.fonts.Substitutable; | ||||
/** | /** | ||||
* This class is used to defer the loading of a font until it is really used. | * This class is used to defer the loading of a font until it is really used. | ||||
*/ | */ | ||||
private boolean useKerning; | private boolean useKerning; | ||||
private boolean useAdvanced; | private boolean useAdvanced; | ||||
private EncodingMode encodingMode = EncodingMode.AUTO; | private EncodingMode encodingMode = EncodingMode.AUTO; | ||||
private boolean embedded; | |||||
private EmbeddingMode embeddingMode = EmbeddingMode.AUTO; | |||||
private boolean embedded = true; | |||||
private String subFontName; | private String subFontName; | ||||
private boolean isMetricsLoaded; | private boolean isMetricsLoaded; | ||||
this.useAdvanced = fontInfo.getAdvanced(); | this.useAdvanced = fontInfo.getAdvanced(); | ||||
} | } | ||||
this.encodingMode = fontInfo.getEncodingMode(); | this.encodingMode = fontInfo.getEncodingMode(); | ||||
this.embeddingMode = fontInfo.getEmbeddingMode(); | |||||
this.subFontName = fontInfo.getSubFontName(); | this.subFontName = fontInfo.getSubFontName(); | ||||
this.embedded = fontInfo.isEmbedded(); | this.embedded = fontInfo.isEmbedded(); | ||||
this.resolver = resolver; | this.resolver = resolver; | ||||
if (fontEmbedPath == null) { | if (fontEmbedPath == null) { | ||||
throw new RuntimeException("Cannot load font. No font URIs available."); | 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) { | if (realFont instanceof FontDescriptor) { | ||||
realFontDescriptor = (FontDescriptor) realFont; | realFontDescriptor = (FontDescriptor) realFont; |
private CIDSubset subset = new CIDSubset(); | 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 */ | /* advanced typographic support */ | ||||
private GlyphDefinitionTable gdef; | private GlyphDefinitionTable gdef; | ||||
private GlyphSubstitutionTable gsub; | private GlyphSubstitutionTable gsub; | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
public int getDefaultWidth() { | public int getDefaultWidth() { | ||||
return defaultWidth; | return defaultWidth; | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
public String getRegistry() { | public String getRegistry() { | ||||
return "Adobe"; | return "Adobe"; | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
public String getOrdering() { | public String getOrdering() { | ||||
return "UCS"; | return "UCS"; | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
public int getSupplement() { | public int getSupplement() { | ||||
return 0; | return 0; | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
public CIDFontType getCIDType() { | public CIDFontType getCIDType() { | ||||
return cidType; | return cidType; | ||||
} | } | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
public String getEmbedFontName() { | public String getEmbedFontName() { | ||||
if (isEmbeddable()) { | if (isEmbeddable()) { | ||||
return FontUtil.stripWhiteSpace(super.getFontName()); | return FontUtil.stripWhiteSpace(super.getFontName()); | ||||
return !(getEmbedFileName() == null && getEmbedResourceName() == null); | return !(getEmbedFileName() == null && getEmbedResourceName() == null); | ||||
} | } | ||||
/** {@inheritDoc} */ | |||||
public boolean isSubsetEmbedded() { | public boolean isSubsetEmbedded() { | ||||
return true; | return true; | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
public CIDSubset getCIDSubset() { | public CIDSubset getCIDSubset() { | ||||
return this.subset; | return this.subset; | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
public String getEncodingName() { | public String getEncodingName() { | ||||
return encoding; | return encoding; | ||||
} | } | ||||
int idx = c; | int idx = c; | ||||
int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT; | 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 | + idx | ||||
- bfentries[i].getUnicodeStart(); | |||||
- cmap[i].getUnicodeStart(); | |||||
} | } | ||||
} | } | ||||
return retIdx; | 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. | * N.B. Does not insert in order, merely appends to end of existing map. | ||||
*/ | */ | ||||
private synchronized void addPrivateUseMapping ( int pu, int gi ) { | private synchronized void addPrivateUseMapping ( int pu, int gi ) { | ||||
assert findGlyphIndex ( pu ) == SingleByteEncoding.NOT_FOUND_CODE_POINT; | 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; | |||||
} | } | ||||
/** | /** | ||||
// [TBD] - needs optimization, i.e., change from linear search to binary search | // [TBD] - needs optimization, i.e., change from linear search to binary search | ||||
private int findCharacterFromGlyphIndex ( int gi, boolean augment ) { | private int findCharacterFromGlyphIndex ( int gi, boolean augment ) { | ||||
int cc = 0; | 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 ) ) { | if ( ( gi >= s ) && ( gi <= e ) ) { | ||||
cc = be.getUnicodeStart() + ( gi - s ); | |||||
cc = segment.getUnicodeStart() + ( gi - s ); | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
public char mapChar(char c) { | public char mapChar(char c) { | ||||
notifyMapOperation(); | notifyMapOperation(); | ||||
int glyphIndex = findGlyphIndex(c); | int glyphIndex = findGlyphIndex(c); | ||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
public boolean hasChar(char c) { | public boolean hasChar(char c) { | ||||
return (findGlyphIndex(c) != SingleByteEncoding.NOT_FOUND_CODE_POINT); | 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. | * Sets the defaultWidth. | ||||
* @param defaultWidth The defaultWidth to set | * @param defaultWidth The defaultWidth to set |
*/ | */ | ||||
void setEmbedResourceName(String name); | void setEmbedResourceName(String name); | ||||
/** | |||||
* Sets the embedding mode. | |||||
* @param embeddingMode the embedding mode | |||||
*/ | |||||
void setEmbeddingMode(EmbeddingMode embeddingMode); | |||||
/** | /** | ||||
* Sets the capital height value. | * Sets the capital height value. | ||||
* @param capHeight capital height | * @param capHeight capital height |
import org.apache.xmlgraphics.fonts.Glyphs; | import org.apache.xmlgraphics.fonts.Glyphs; | ||||
import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion; | |||||
/** | /** | ||||
* Generic SingleByte font | * Generic SingleByte font | ||||
*/ | */ | ||||
private List<SimpleSingleByteEncoding> additionalEncodings; | private List<SimpleSingleByteEncoding> additionalEncodings; | ||||
private Map<Character, Character> alternativeCodes; | private Map<Character, Character> alternativeCodes; | ||||
private PostScriptVersion ttPostScriptVersion; | |||||
/** | /** | ||||
* Main constructor. | * Main constructor. | ||||
} | } | ||||
} | } | ||||
/** | |||||
* 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; | |||||
} | |||||
} | } | ||||
package org.apache.fop.fonts.apps; | package org.apache.fop.fonts.apps; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.Iterator; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | import java.util.Set; | ||||
import org.apache.commons.logging.LogFactory; | import org.apache.commons.logging.LogFactory; | ||||
import org.apache.fop.Version; | import org.apache.fop.Version; | ||||
import org.apache.fop.fonts.CMapSegment; | |||||
import org.apache.fop.fonts.FontUtil; | import org.apache.fop.fonts.FontUtil; | ||||
import org.apache.fop.fonts.truetype.FontFileReader; | 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.fonts.truetype.TTFFile; | ||||
import org.apache.fop.util.CommandLineLogger; | import org.apache.fop.util.CommandLineLogger; | ||||
root.appendChild(el); | root.appendChild(el); | ||||
el.appendChild(doc.createTextNode(ttf.getFullName())); | el.appendChild(doc.createTextNode(ttf.getFullName())); | ||||
} | } | ||||
Set familyNames = ttf.getFamilyNames(); | |||||
Set<String> familyNames = ttf.getFamilyNames(); | |||||
if (familyNames.size() > 0) { | if (familyNames.size() > 0) { | ||||
String familyName = (String)familyNames.iterator().next(); | |||||
String familyName = familyNames.iterator().next(); | |||||
el = doc.createElement("family-name"); | el = doc.createElement("family-name"); | ||||
root.appendChild(el); | root.appendChild(el); | ||||
el.appendChild(doc.createTextNode(familyName)); | el.appendChild(doc.createTextNode(familyName)); | ||||
el = doc.createElement("bfranges"); | el = doc.createElement("bfranges"); | ||||
mel.appendChild(el); | 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"); | Element el2 = doc.createElement("bf"); | ||||
el.appendChild(el2); | el.appendChild(el2); | ||||
el2.setAttribute("us", String.valueOf(ce.getUnicodeStart())); | el2.setAttribute("us", String.valueOf(ce.getUnicodeStart())); | ||||
Document doc = parent.getOwnerDocument(); | Document doc = parent.getOwnerDocument(); | ||||
// Get kerning | // Get kerning | ||||
Iterator iter; | |||||
Set<Integer> kerningKeys; | |||||
if (isCid) { | if (isCid) { | ||||
iter = ttf.getKerning().keySet().iterator(); | |||||
kerningKeys = ttf.getKerning().keySet(); | |||||
} else { | } 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 = doc.createElement("kerning"); | ||||
el.setAttribute("kpx1", kpx1.toString()); | el.setAttribute("kpx1", kpx1.toString()); | ||||
parent.appendChild(el); | parent.appendChild(el); | ||||
Element el2 = null; | Element el2 = null; | ||||
Map h2; | |||||
Map<Integer, Integer> h2; | |||||
if (isCid) { | if (isCid) { | ||||
h2 = (Map)ttf.getKerning().get(kpx1); | |||||
h2 = ttf.getKerning().get(kpx1); | |||||
} else { | } 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) { | if (isCid || kpx2.intValue() < 256) { | ||||
el2 = doc.createElement("pair"); | el2 = doc.createElement("pair"); | ||||
el2.setAttribute("kpx2", kpx2.toString()); | el2.setAttribute("kpx2", kpx2.toString()); |
import org.apache.fop.fonts.CustomFont; | import org.apache.fop.fonts.CustomFont; | ||||
import org.apache.fop.fonts.EmbedFontInfo; | import org.apache.fop.fonts.EmbedFontInfo; | ||||
import org.apache.fop.fonts.EmbeddingMode; | |||||
import org.apache.fop.fonts.EncodingMode; | import org.apache.fop.fonts.EncodingMode; | ||||
import org.apache.fop.fonts.Font; | import org.apache.fop.fonts.Font; | ||||
import org.apache.fop.fonts.FontCache; | import org.apache.fop.fonts.FontCache; | ||||
} | } | ||||
try { | try { | ||||
TTFFontLoader ttfLoader = new TTFFontLoader( | TTFFontLoader ttfLoader = new TTFFontLoader( | ||||
fontFileURL, fontName, true, EncodingMode.AUTO, | |||||
fontFileURL, fontName, true, EmbeddingMode.AUTO, EncodingMode.AUTO, | |||||
useKerning, useAdvanced, resolver); | useKerning, useAdvanced, resolver); | ||||
customFont = ttfLoader.getFont(); | customFont = ttfLoader.getFont(); | ||||
if (this.eventListener != null) { | if (this.eventListener != null) { | ||||
} else { | } else { | ||||
// The normal case | // The normal case | ||||
try { | 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) { | if (this.eventListener != null) { | ||||
customFont.setEventListener(this.eventListener); | customFont.setEventListener(this.eventListener); | ||||
} | } |
current = (int)offset; | 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. | * Skip a given number of bytes. | ||||
* | * | ||||
* @throws IOException In case of an I/O problem | * @throws IOException In case of an I/O problem | ||||
*/ | */ | ||||
public void skip(long add) throws IOException { | public void skip(long add) throws IOException { | ||||
seekAdd(add); | |||||
seekSet(current + add); | |||||
} | } | ||||
/** | /** | ||||
* @return One byte | * @return One byte | ||||
* @throws IOException If EOF is reached | * @throws IOException If EOF is reached | ||||
*/ | */ | ||||
public byte read() throws IOException { | |||||
private byte read() throws IOException { | |||||
if (current >= fsize) { | if (current >= fsize) { | ||||
throw new java.io.EOFException("Reached EOF, file size=" + fsize); | throw new java.io.EOFException("Reached EOF, file size=" + fsize); | ||||
} | } | ||||
public final String readTTFString() throws IOException { | public final String readTTFString() throws IOException { | ||||
int i = current; | int i = current; | ||||
while (file[i++] != 0) { | while (file[i++] != 0) { | ||||
if (i > fsize) { | |||||
if (i >= fsize) { | |||||
throw new java.io.EOFException("Reached EOF, file size=" | throw new java.io.EOFException("Reached EOF, file size=" | ||||
+ fsize); | + 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"); | return new String(tmp, "ISO-8859-1"); | ||||
} | } | ||||
System.arraycopy(file, offset, ret, 0, length); | System.arraycopy(file, offset, ret, 0, length); | ||||
return ret; | return ret; | ||||
} | } | ||||
/** | |||||
* Returns the full byte array representation of the file. | |||||
* @return byte array. | |||||
*/ | |||||
public byte[] getAllBytes() { | |||||
return file; | |||||
} | |||||
} | } |
/* | |||||
* 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; | |||||
} | |||||
} |
private long offset; | private long offset; | ||||
private long length; | private long length; | ||||
public TTFDirTabEntry() { | |||||
} | |||||
public TTFDirTabEntry(long offset, long length) { | |||||
this.offset = offset; | |||||
this.length = length; | |||||
} | |||||
/** | /** | ||||
* Read Dir Tab. | * Read Dir Tab. | ||||
* @param in font file reader | * @param in font file reader |
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.util.Iterator; | |||||
import java.util.List; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | import java.util.Set; | ||||
import org.apache.commons.io.IOUtils; | 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.CIDFontType; | ||||
import org.apache.fop.fonts.CMapSegment; | |||||
import org.apache.fop.fonts.EmbeddingMode; | |||||
import org.apache.fop.fonts.EncodingMode; | import org.apache.fop.fonts.EncodingMode; | ||||
import org.apache.fop.fonts.FontLoader; | import org.apache.fop.fonts.FontLoader; | ||||
import org.apache.fop.fonts.FontResolver; | import org.apache.fop.fonts.FontResolver; | ||||
import org.apache.fop.fonts.MultiByteFont; | import org.apache.fop.fonts.MultiByteFont; | ||||
import org.apache.fop.fonts.NamedCharacter; | import org.apache.fop.fonts.NamedCharacter; | ||||
import org.apache.fop.fonts.SingleByteFont; | 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. | * Loads a TrueType font into memory directly from the original font file. | ||||
private SingleByteFont singleFont; | private SingleByteFont singleFont; | ||||
private final String subFontName; | private final String subFontName; | ||||
private EncodingMode encodingMode; | private EncodingMode encodingMode; | ||||
private EmbeddingMode embeddingMode; | |||||
/** | /** | ||||
* Default constructor | * Default constructor | ||||
* @param resolver the FontResolver for font URI resolution | * @param resolver the FontResolver for font URI resolution | ||||
*/ | */ | ||||
public TTFFontLoader(String fontFileURI, FontResolver resolver) { | 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); | |||||
} | } | ||||
/** | /** | ||||
* @param subFontName the sub-fontname of a font in a TrueType Collection (or null for normal | * @param subFontName the sub-fontname of a font in a TrueType Collection (or null for normal | ||||
* TrueType fonts) | * TrueType fonts) | ||||
* @param embedded indicates whether the font is embedded or referenced | * @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 encodingMode the requested encoding mode | ||||
* @param useKerning true to enable loading kerning info if available, false to disable | * @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 useAdvanced true to enable loading advanced info if available, false to disable | ||||
* @param resolver the FontResolver for font URI resolution | * @param resolver the FontResolver for font URI resolution | ||||
*/ | */ | ||||
public TTFFontLoader(String fontFileURI, String subFontName, | 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); | super(fontFileURI, embedded, useKerning, useAdvanced, resolver); | ||||
this.subFontName = subFontName; | this.subFontName = subFontName; | ||||
this.encodingMode = encodingMode; | this.encodingMode = encodingMode; | ||||
this.embeddingMode = embeddingMode; | |||||
if (this.encodingMode == EncodingMode.AUTO) { | if (this.encodingMode == EncodingMode.AUTO) { | ||||
this.encodingMode = EncodingMode.CID; //Default to CID mode for TrueType | this.encodingMode = EncodingMode.CID; //Default to CID mode for TrueType | ||||
} | } | ||||
if (this.embeddingMode == EmbeddingMode.AUTO) { | |||||
this.embeddingMode = EmbeddingMode.SUBSET; | |||||
} | |||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | |||||
protected void read() throws IOException { | protected void read() throws IOException { | ||||
read(this.subFontName); | read(this.subFontName); | ||||
} | } | ||||
returnFont.setItalicAngle(Integer.parseInt(ttf.getItalicAngle())); | returnFont.setItalicAngle(Integer.parseInt(ttf.getItalicAngle())); | ||||
returnFont.setMissingWidth(0); | returnFont.setMissingWidth(0); | ||||
returnFont.setWeight(ttf.getWeightClass()); | returnFont.setWeight(ttf.getWeightClass()); | ||||
returnFont.setEmbeddingMode(this.embeddingMode); | |||||
if (isCid) { | if (isCid) { | ||||
multiFont.setCIDType(CIDFontType.CIDTYPE2); | multiFont.setCIDType(CIDFontType.CIDTYPE2); | ||||
int[] wx = ttf.getWidths(); | int[] wx = ttf.getWidths(); | ||||
multiFont.setWidthArray(wx); | 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 { | } else { | ||||
singleFont.setFontType(FontType.TRUETYPE); | singleFont.setFontType(FontType.TRUETYPE); | ||||
singleFont.setEncoding(ttf.getCharSetName()); | singleFont.setEncoding(ttf.getCharSetName()); | ||||
returnFont.setFirstChar(ttf.getFirstChar()); | returnFont.setFirstChar(ttf.getFirstChar()); | ||||
returnFont.setLastChar(ttf.getLastChar()); | returnFont.setLastChar(ttf.getLastChar()); | ||||
singleFont.setTrueTypePostScriptVersion(ttf.getPostScriptVersion()); | |||||
copyWidthsSingleByte(ttf); | copyWidthsSingleByte(ttf); | ||||
} | } | ||||
returnFont.setCMap(getCMap(ttf)); | |||||
if (useKerning) { | if (useKerning) { | ||||
copyKerning(ttf, isCid); | copyKerning(ttf, isCid); | ||||
} | } | ||||
} | } | ||||
private CMapSegment[] getCMap(TTFFile ttf) { | |||||
CMapSegment[] array = new CMapSegment[ttf.getCMaps().size()]; | |||||
return ttf.getCMaps().toArray(array); | |||||
} | |||||
private void copyWidthsSingleByte(TTFFile ttf) { | private void copyWidthsSingleByte(TTFFile ttf) { | ||||
int[] wx = ttf.getWidths(); | int[] wx = ttf.getWidths(); | ||||
for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) { | for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) { | ||||
singleFont.setWidth(i, ttf.getCharWidth(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); | int codePoint = singleFont.getEncoding().mapChar(u); | ||||
if (codePoint <= 0) { | 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]); | singleFont.addUnencodedCharacter(nc, wx[glyphIndex]); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
for (Integer kpx1 : kerningSet) { | for (Integer kpx1 : kerningSet) { | ||||
Map<Integer, Integer> h2; | Map<Integer, Integer> h2; | ||||
if (isCid) { | if (isCid) { | ||||
h2 = ttf.getKerning().get(kpx1); | h2 = ttf.getKerning().get(kpx1); |
/* | |||||
* 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; | |||||
} |
/* | |||||
* 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; | |||||
} |
package org.apache.fop.fonts.truetype; | package org.apache.fop.fonts.truetype; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.Iterator; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.SortedSet; | |||||
/** | /** | ||||
* Offsets in name table to be filled out by table. | * Offsets in name table to be filled out by table. | ||||
* The offsets are to the checkSum field | * 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 checkSumAdjustmentOffset = 0; | ||||
private int locaOffset = 0; | private int locaOffset = 0; | ||||
/** Stores the glyph offsets so that we can end strings at glyph boundaries */ | |||||
private int[] glyphOffsets; | |||||
/** | /** | ||||
* Default Constructor | * Default Constructor | ||||
*/ | */ | ||||
public TTFSubSetFile() { | public TTFSubSetFile() { | ||||
this(false, false); | |||||
} | } | ||||
/** | /** | ||||
super(useKerning, useAdvanced); | 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() { | private int determineTableCount() { | ||||
int numTables = 4; //4 req'd tables: head,hhea,hmtx,maxp | int numTables = 4; //4 req'd tables: head,hhea,hmtx,maxp | ||||
throw new UnsupportedOperationException( | throw new UnsupportedOperationException( | ||||
"OpenType fonts with CFF glyphs are not supported"); | "OpenType fonts with CFF glyphs are not supported"); | ||||
} else { | } else { | ||||
numTables += 2; //1 req'd table: glyf,loca | |||||
numTables += 5; //5 req'd tables: glyf,loca,post,name,OS/2 | |||||
if (hasCvt()) { | if (hasCvt()) { | ||||
numTables++; | numTables++; | ||||
} | } | ||||
// Create searchRange, entrySelector and rangeShift | // Create searchRange, entrySelector and rangeShift | ||||
int maxPow = maxPow2(numTables); | int maxPow = maxPow2(numTables); | ||||
int searchRange = maxPow * 16; | |||||
int searchRange = (int) Math.pow(2, maxPow) * 16; | |||||
writeUShort(searchRange); | writeUShort(searchRange); | ||||
realSize += 2; | realSize += 2; | ||||
writeUShort((numTables * 16) - searchRange); | writeUShort((numTables * 16) - searchRange); | ||||
realSize += 2; | 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()) { | if (hasCvt()) { | ||||
writeString("cvt "); | |||||
cvtDirOffset = currentPos; | |||||
currentPos += 12; | |||||
realSize += 16; | |||||
writeTableName(TTFTableName.CVT); | |||||
} | } | ||||
if (hasFpgm()) { | 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; | currentPos += 12; | ||||
realSize += 16; | 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) { | if (entry != null) { | ||||
pad4(); | pad4(); | ||||
seekTab(in, "cvt ", 0); | |||||
seekTab(in, tableName, 0); | |||||
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), | System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), | ||||
0, output, currentPos, (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; | return true; | ||||
} else { | } else { | ||||
return false; | 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 | * Copy the fpgm table as is from original font to subset font | ||||
*/ | */ | ||||
private boolean createFpgm(FontFileReader in) throws IOException { | 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 | * Copy the maxp table as is from original font to subset font | ||||
* and set num glyphs to size | * and set num glyphs to size | ||||
*/ | */ | ||||
private void createMaxp(FontFileReader in, int size) throws IOException { | 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) { | if (entry != null) { | ||||
pad4(); | pad4(); | ||||
seekTab(in, "maxp", 0); | |||||
seekTab(in, maxp, 0); | |||||
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), | System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), | ||||
0, output, currentPos, (int)entry.getLength()); | 0, output, currentPos, (int)entry.getLength()); | ||||
writeUShort(currentPos + 4, size); | 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(); | currentPos += (int)entry.getLength(); | ||||
realSize += (int)entry.getLength(); | realSize += (int)entry.getLength(); | ||||
} else { | } else { | ||||
} | } | ||||
} | } | ||||
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 | * Copy the prep table as is from original font to subset font | ||||
*/ | */ | ||||
private boolean createPrep(FontFileReader in) throws IOException { | 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); | |||||
} | } | ||||
* and fill in size of hmtx table | * and fill in size of hmtx table | ||||
*/ | */ | ||||
private void createHhea(FontFileReader in, int size) throws IOException { | private void createHhea(FontFileReader in, int size) throws IOException { | ||||
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hhea"); | |||||
TTFDirTabEntry entry = dirTabs.get(TTFTableName.HHEA); | |||||
if (entry != null) { | if (entry != null) { | ||||
pad4(); | 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 { | } else { | ||||
throw new IOException("Can't find hhea table"); | throw new IOException("Can't find hhea table"); | ||||
} | } | ||||
* in checkSumAdjustmentOffset | * in checkSumAdjustmentOffset | ||||
*/ | */ | ||||
private void createHead(FontFileReader in) throws IOException { | 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) { | if (entry != null) { | ||||
pad4(); | pad4(); | ||||
seekTab(in, "head", 0); | |||||
seekTab(in, head, 0); | |||||
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), | System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), | ||||
0, output, currentPos, (int)entry.getLength()); | 0, output, currentPos, (int)entry.getLength()); | ||||
output[currentPos + 50] = 0; // long locaformat | output[currentPos + 50] = 0; // long locaformat | ||||
output[currentPos + 51] = 1; // 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(); | currentPos += (int)entry.getLength(); | ||||
realSize += (int)entry.getLength(); | realSize += (int)entry.getLength(); | ||||
} else { | } else { | ||||
* Create the glyf table and fill in loca table | * Create the glyf table and fill in loca table | ||||
*/ | */ | ||||
private void createGlyf(FontFileReader in, | 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 size = 0; | ||||
int start = 0; | |||||
int startPos = 0; | |||||
int endOffset = 0; // Store this as the last loca | int endOffset = 0; // Store this as the last loca | ||||
if (entry != null) { | if (entry != null) { | ||||
pad4(); | pad4(); | ||||
start = currentPos; | |||||
startPos = currentPos; | |||||
/* Loca table must be in order by glyph index, so build | /* Loca table must be in order by glyph index, so build | ||||
* an array first and then write the glyph info and | * an array first and then write the glyph info and | ||||
* location offset. | * 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++) { | for (int i = 0; i < origIndexes.length; i++) { | ||||
int glyphLength = 0; | |||||
int nextOffset = 0; | int nextOffset = 0; | ||||
int origGlyphIndex = origIndexes[i]; | int origGlyphIndex = origIndexes[i]; | ||||
if (origGlyphIndex >= (mtxTab.length - 1)) { | if (origGlyphIndex >= (mtxTab.length - 1)) { | ||||
} else { | } else { | ||||
nextOffset = (int)mtxTab[origGlyphIndex + 1].getOffset(); | 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 | // Copy glyph | ||||
System.arraycopy( | System.arraycopy( | ||||
in.getBytes((int)entry.getOffset() + (int)mtxTab[origGlyphIndex].getOffset(), | |||||
glyphLength), 0, | |||||
glyphData, 0, | |||||
output, currentPos, | output, currentPos, | ||||
glyphLength); | glyphLength); | ||||
// Update loca table | // 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; | currentPos += glyphLength; | ||||
realSize += 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; | currentPos += 12; | ||||
realSize += 12; | realSize += 12; | ||||
updateCheckSum(startPos, size + 12, glyf); | |||||
// Update loca checksum and last loca index | // Update loca checksum and last loca index | ||||
writeULong(locaOffset + glyphs.size() * 4, endOffset); | 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 { | } else { | ||||
throw new IOException("Can't find glyf table"); | 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 | * Create the hmtx table by copying metrics from original | ||||
* metric (key) to the subset metric (value) | * metric (key) to the subset metric (value) | ||||
*/ | */ | ||||
private void createHmtx(FontFileReader in, | 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 longHorMetricSize = glyphs.size() * 2; | ||||
int leftSideBearingSize = glyphs.size() * 2; | int leftSideBearingSize = glyphs.size() * 2; | ||||
if (entry != null) { | if (entry != null) { | ||||
pad4(); | pad4(); | ||||
//int offset = (int)entry.offset; | //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, | writeUShort(currentPos + subsetIndex.intValue() * 4, | ||||
mtxTab[origIndex.intValue()].getWx()); | mtxTab[origIndex.intValue()].getWx()); | ||||
mtxTab[origIndex.intValue()].getLsb()); | 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; | currentPos += hmtxSize; | ||||
realSize += hmtxSize; | realSize += hmtxSize; | ||||
} else { | } else { | ||||
} | } | ||||
/** | /** | ||||
* Returns a subset of the original font. | |||||
* Reads a font and creates a subset of the font. | |||||
* | * | ||||
* @param in FontFileReader to read from | * @param in FontFileReader to read from | ||||
* @param name Name to be checked for in the font file | * @param name Name to be checked for in the font file | ||||
* @param glyphs Map of glyphs (glyphs has old index as (Integer) key and | * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and | ||||
* new index as (Integer) value) | * new index as (Integer) value) | ||||
* @return A subset of the original font | |||||
* @throws IOException in case of an I/O problem | * @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 { | Map<Integer, Integer> glyphs) throws IOException { | ||||
fontFile = in; | |||||
//Check if TrueType collection, and that the name exists in the collection | //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"); | throw new IOException("Failed to read font"); | ||||
} | } | ||||
//Copy the Map as we're going to modify it | //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()]; | 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); | 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; | boolean optionalTableFound; | ||||
optionalTableFound = createCvt(in); // copy the cvt table | optionalTableFound = createCvt(in); // copy the cvt table | ||||
// fpgm is optional (used in TrueType fonts only) | // fpgm is optional (used in TrueType fonts only) | ||||
log.debug("TrueType: fpgm table not present. Skipped."); | 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 | optionalTableFound = createPrep(in); // copy prep table | ||||
if (!optionalTableFound) { | if (!optionalTableFound) { | ||||
log.debug("TrueType: prep table not present. Skipped."); | 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(); | pad4(); | ||||
createCheckSumAdjustment(); | 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]; | byte[] ret = new byte[realSize]; | ||||
System.arraycopy(output, 0, ret, 0, realSize); | System.arraycopy(output, 0, ret, 0, realSize); | ||||
return ret; | 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) | private void scanGlyphs(FontFileReader in, Map<Integer, Integer> subsetGlyphs) | ||||
throws IOException { | throws IOException { | ||||
TTFDirTabEntry glyfTableInfo = (TTFDirTabEntry) dirTabs.get("glyf"); | |||||
TTFDirTabEntry glyfTableInfo = dirTabs.get(TTFTableName.GLYF); | |||||
if (glyfTableInfo == null) { | if (glyfTableInfo == null) { | ||||
throw new IOException("Glyf table could not be found"); | throw new IOException("Glyf table could not be found"); | ||||
} | } | ||||
output[pos + 1] = b2; | 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, | * Appends a ULONG to the output array, | ||||
output[pos + 3] = b4; | 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 | * Create a padding in the fontfile to align | ||||
* on a 4-byte boundary | * on a 4-byte boundary | ||||
*/ | */ | ||||
private void pad4() { | 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++; | |||||
} | |||||
} | } | ||||
} | } | ||||
*/ | */ | ||||
private int maxPow2(int max) { | private int maxPow2(int max) { | ||||
int i = 0; | int i = 0; | ||||
while (Math.pow(2, i) < max) { | |||||
while (Math.pow(2, i) <= max) { | |||||
i++; | i++; | ||||
} | } | ||||
return (i - 1); | 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 | // All the tables here are aligned on four byte boundaries | ||||
// Add remainder to size if it's not a multiple of 4 | // Add remainder to size if it's not a multiple of 4 | ||||
int remainder = size % 4; | int remainder = size % 4; | ||||
long sum = 0; | long sum = 0; | ||||
for (int i = 0; i < size; i += 4) { | 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() { | private void createCheckSumAdjustment() { | ||||
long sum = getLongCheckSum(0, realSize); | |||||
long sum = getCheckSum(output, 0, realSize); | |||||
int checksum = (int)(0xb1b0afba - sum); | int checksum = (int)(0xb1b0afba - sum); | ||||
writeULong(checkSumAdjustmentOffset, checksum); | writeULong(checkSumAdjustmentOffset, checksum); | ||||
} | } | ||||
} | } | ||||
/* | |||||
* 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; | |||||
} | |||||
} |
/* | |||||
* 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; | |||||
} |
private PDFColor transparent = null; | private PDFColor transparent = null; | ||||
private String key; | private String key; | ||||
private PDFDocument pdfDoc; | private PDFDocument pdfDoc; | ||||
private PDFFilter pdfFilter; | |||||
/** | /** | ||||
* Create a bitmap image. | * Create a bitmap image. | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
*/ | */ | ||||
public PDFFilter getPDFFilter() { | public PDFFilter getPDFFilter() { | ||||
return null; | |||||
return pdfFilter; | |||||
} | } | ||||
public void setPDFFilter(PDFFilter pdfFilter) { | |||||
this.pdfFilter = pdfFilter; | |||||
} | |||||
} | } | ||||
import org.apache.xmlgraphics.java2d.color.ColorUtil; | import org.apache.xmlgraphics.java2d.color.ColorUtil; | ||||
import org.apache.xmlgraphics.java2d.color.NamedColorSpace; | import org.apache.xmlgraphics.java2d.color.NamedColorSpace; | ||||
import org.apache.xmlgraphics.xmp.Metadata; | import org.apache.xmlgraphics.xmp.Metadata; | ||||
import org.apache.fop.fonts.CIDFont; | import org.apache.fop.fonts.CIDFont; | ||||
FontFileReader reader = new FontFileReader(in); | FontFileReader reader = new FontFileReader(in); | ||||
TTFSubSetFile subset = new TTFSubSetFile(); | 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 | // Only TrueType CID fonts are supported now | ||||
embeddedFont = new PDFTTFStream(subsetFont.length); | embeddedFont = new PDFTTFStream(subsetFont.length); |
font = new CustomFontMetricsMapper(fontMetrics, fontSource); | font = new CustomFontMetricsMapper(fontMetrics, fontSource); | ||||
} else { | } else { | ||||
CustomFont fontMetrics = FontLoader.loadFont( | CustomFont fontMetrics = FontLoader.loadFont( | ||||
fontFile, null, true, EncodingMode.AUTO, | |||||
fontFile, null, true, configFontInfo.getEmbeddingMode(), | |||||
EncodingMode.AUTO, | |||||
configFontInfo.getKerning(), | configFontInfo.getKerning(), | ||||
configFontInfo.getAdvanced(), fontResolver); | configFontInfo.getAdvanced(), fontResolver); | ||||
font = new CustomFontMetricsMapper(fontMetrics); | font = new CustomFontMetricsMapper(fontMetrics); |
package org.apache.fop.render.pdf; | package org.apache.fop.render.pdf; | ||||
import java.awt.color.ColorSpace; | import java.awt.color.ColorSpace; | ||||
import java.awt.color.ICC_Profile; | 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.Log; | ||||
import org.apache.commons.logging.LogFactory; | import org.apache.commons.logging.LogFactory; | ||||
import org.apache.xmlgraphics.image.loader.Image; | import org.apache.xmlgraphics.image.loader.Image; | ||||
import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil; | 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.PDFColor; | ||||
import org.apache.fop.pdf.PDFConformanceException; | import org.apache.fop.pdf.PDFConformanceException; | ||||
import org.apache.fop.pdf.PDFDeviceColorSpace; | import org.apache.fop.pdf.PDFDeviceColorSpace; | ||||
/** the image */ | /** the image */ | ||||
protected Image 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. | * Creates a new PDFImage from an Image instance. | ||||
//nop | //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. | * Converts a ColorSpace object to a PDFColorSpace object. | ||||
* @param cs ColorSpace instance | * @param cs ColorSpace instance |
/* | |||||
* 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); | |||||
} | |||||
} | |||||
} |
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.OutputStream; | 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.Log; | ||||
import org.apache.commons.logging.LogFactory; | import org.apache.commons.logging.LogFactory; | ||||
import org.apache.xmlgraphics.ps.ImageEncodingHelper; | import org.apache.xmlgraphics.ps.ImageEncodingHelper; | ||||
import org.apache.fop.pdf.AlphaRasterImage; | import org.apache.fop.pdf.AlphaRasterImage; | ||||
import org.apache.fop.pdf.PDFArray; | |||||
import org.apache.fop.pdf.PDFColor; | import org.apache.fop.pdf.PDFColor; | ||||
import org.apache.fop.pdf.PDFDeviceColorSpace; | import org.apache.fop.pdf.PDFDeviceColorSpace; | ||||
import org.apache.fop.pdf.PDFDictionary; | import org.apache.fop.pdf.PDFDictionary; | ||||
import org.apache.fop.pdf.PDFDocument; | import org.apache.fop.pdf.PDFDocument; | ||||
import org.apache.fop.pdf.PDFFilter; | import org.apache.fop.pdf.PDFFilter; | ||||
import org.apache.fop.pdf.PDFFilterList; | import org.apache.fop.pdf.PDFFilterList; | ||||
import org.apache.fop.pdf.PDFName; | |||||
import org.apache.fop.pdf.PDFReference; | import org.apache.fop.pdf.PDFReference; | ||||
/** | /** | ||||
return (getImage().getTransparentColor() != null); | 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} */ | /** {@inheritDoc} */ | ||||
@Override | @Override | ||||
public PDFColor getTransparentColor() { | public PDFColor getTransparentColor() { | ||||
} | } | ||||
} | } | ||||
private static final int MAX_HIVAL = 255; | |||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | @Override | ||||
public void populateXObjectDictionary(PDFDictionary dict) { | public void populateXObjectDictionary(PDFDictionary dict) { | ||||
ColorModel cm = getEffectiveColorModel(); | ColorModel cm = getEffectiveColorModel(); | ||||
if (cm instanceof IndexColorModel) { | 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); | |||||
} | } | ||||
} | } | ||||
/* | |||||
* 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; | |||||
} | |||||
} |
} | } | ||||
/** | /** | ||||
* Returns the PSResource for the given font key. | |||||
* Returns the PSFontResource for the given font key. | |||||
* @param key the font key ("F*") | * @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) { | if (this.fontResources != null) { | ||||
res = (PSResource)this.fontResources.get(key); | |||||
res = (PSFontResource)this.fontResources.get(key); | |||||
} else { | } else { | ||||
this.fontResources = new java.util.HashMap(); | this.fontResources = new java.util.HashMap(); | ||||
} | } | ||||
if (res == null) { | 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); | this.fontResources.put(key, res); | ||||
} | } | ||||
return res; | return res; | ||||
throw new IllegalStateException("Font not available: " + key); | throw new IllegalStateException("Font not available: " + key); | ||||
} | } | ||||
if (postFix == null) { | if (postFix == null) { | ||||
return tf.getFontName(); | |||||
return tf.getEmbedFontName(); | |||||
} else { | } else { | ||||
return tf.getFontName() + postFix; | |||||
return tf.getEmbedFontName() + postFix; | |||||
} | } | ||||
} | } | ||||
/* | |||||
* 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; | |||||
} | |||||
} |
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBoundingBox; | import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBoundingBox; | ||||
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox; | import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox; | ||||
import org.apache.fop.apps.FOUserAgent; | |||||
import org.apache.fop.apps.MimeConstants; | import org.apache.fop.apps.MimeConstants; | ||||
import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; | import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; | ||||
import org.apache.fop.render.intermediate.IFContext; | import org.apache.fop.render.intermediate.IFContext; | ||||
private static final int COMMENT_PAGE_TRAILER = 2; | private static final int COMMENT_PAGE_TRAILER = 2; | ||||
private static final int PAGE_TRAILER_CODE_BEFORE = 3; | private static final int PAGE_TRAILER_CODE_BEFORE = 3; | ||||
private PSEventProducer eventProducer; | |||||
/** | /** | ||||
* Default constructor. | * Default constructor. | ||||
*/ | */ | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
public void setContext(IFContext context) { | public void setContext(IFContext context) { | ||||
super.setContext(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} */ | /** {@inheritDoc} */ | ||||
try { | try { | ||||
OutputStream out; | OutputStream out; | ||||
if (psUtil.isOptimizeResources()) { | 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.FileOutputStream(this.tempFile); | ||||
out = new java.io.BufferedOutputStream(out); | out = new java.io.BufferedOutputStream(out); | ||||
} else { | } else { | ||||
gen.writeDSCComment(DSCConstants.BEGIN_SETUP); | gen.writeDSCComment(DSCConstants.BEGIN_SETUP); | ||||
PSRenderingUtil.writeSetupCodeList(gen, setupCodeList, "SetupCode"); | PSRenderingUtil.writeSetupCodeList(gen, setupCodeList, "SetupCode"); | ||||
if (!psUtil.isOptimizeResources()) { | if (!psUtil.isOptimizeResources()) { | ||||
this.fontResources.addAll(PSFontUtils.writeFontDict(gen, fontInfo)); | |||||
this.fontResources.addAll(PSFontUtils.writeFontDict(gen, fontInfo, eventProducer)); | |||||
} else { | } else { | ||||
gen.commentln("%FOPFontSetup"); //Place-holder, will be replaced in the second pass | gen.commentln("%FOPFontSetup"); //Place-holder, will be replaced in the second pass | ||||
} | } | ||||
in = new java.io.BufferedInputStream(in); | in = new java.io.BufferedInputStream(in); | ||||
try { | try { | ||||
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, | handler.process(in, this.outputStream, | ||||
this.currentPageNumber, this.documentBoundingBox); | this.currentPageNumber, this.documentBoundingBox); | ||||
this.outputStream.flush(); | this.outputStream.flush(); | ||||
* @param key the font key ("F*") | * @param key the font key ("F*") | ||||
* @return the matching PSResource | * @return the matching PSResource | ||||
*/ | */ | ||||
protected PSResource getPSResourceForFontKey(String key) { | |||||
return this.fontResources.getPSResourceForFontKey(key); | |||||
protected PSFontResource getPSResourceForFontKey(String key) { | |||||
return this.fontResources.getFontResourceForFontKey(key); | |||||
} | } | ||||
/** | /** |
*/ | */ | ||||
void postscriptDictionaryParseError(Object source, String content, Exception e); | 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); | |||||
} | } |
<?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||
<catalogue xml:lang="en"> | <catalogue xml:lang="en"> | ||||
<message key="postscriptDictionaryParseError">Failed to parse dictionary string. Reason: {e}, content = "{content}"</message> | <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> | </catalogue> |
/* | |||||
* 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); | |||||
} |
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.net.MalformedURLException; | import java.net.MalformedURLException; | ||||
import java.util.HashMap; | |||||
import java.util.HashSet; | |||||
import java.util.Locale; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | |||||
import javax.xml.transform.Source; | import javax.xml.transform.Source; | ||||
import javax.xml.transform.stream.StreamSource; | import javax.xml.transform.stream.StreamSource; | ||||
import org.apache.xmlgraphics.ps.dsc.ResourceTracker; | import org.apache.xmlgraphics.ps.dsc.ResourceTracker; | ||||
import org.apache.fop.fonts.Base14Font; | 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.CustomFont; | ||||
import org.apache.fop.fonts.EmbeddingMode; | |||||
import org.apache.fop.fonts.Font; | import org.apache.fop.fonts.Font; | ||||
import org.apache.fop.fonts.FontInfo; | import org.apache.fop.fonts.FontInfo; | ||||
import org.apache.fop.fonts.FontType; | import org.apache.fop.fonts.FontType; | ||||
import org.apache.fop.fonts.LazyFont; | import org.apache.fop.fonts.LazyFont; | ||||
import org.apache.fop.fonts.MultiByteFont; | |||||
import org.apache.fop.fonts.SingleByteEncoding; | import org.apache.fop.fonts.SingleByteEncoding; | ||||
import org.apache.fop.fonts.SingleByteFont; | import org.apache.fop.fonts.SingleByteFont; | ||||
import org.apache.fop.fonts.Typeface; | 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. | * Utility code for font handling in PostScript. | ||||
/** logging instance */ | /** logging instance */ | ||||
protected static final Log log = LogFactory.getLog(PSFontUtils.class); | protected static final Log log = LogFactory.getLog(PSFontUtils.class); | ||||
/** | /** | ||||
* Generates the PostScript code for the font dictionary. This method should only be | * 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 | * used if no "resource optimization" is performed, i.e. when the fonts are not embedded | ||||
*/ | */ | ||||
public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo) | public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo) | ||||
throws IOException { | 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); | |||||
} | } | ||||
/** | /** | ||||
* @param gen PostScript generator to use for output | * @param gen PostScript generator to use for output | ||||
* @param fontInfo available fonts | * @param fontInfo available fonts | ||||
* @param fonts the set of fonts to work with | * @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) | * @return a Map of PSResource instances representing all defined fonts (key: font key) | ||||
* @throws IOException in case of an I/O problem | * @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); | |||||
} | } | ||||
/** | /** | ||||
* @throws IOException in case of an I/O problem | * @throws IOException in case of an I/O problem | ||||
*/ | */ | ||||
private static Map writeFontDict(PSGenerator gen, FontInfo fontInfo, | 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"); | gen.commentln("%FOPBeginFontDict"); | ||||
Map fontResources = new java.util.HashMap(); | |||||
Map fontResources = new HashMap(); | |||||
for (String key : fonts.keySet()) { | for (String key : fonts.keySet()) { | ||||
Typeface tf = getTypeFace(fontInfo, fonts, key); | 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) { | if (tf instanceof SingleByteFont) { | ||||
SingleByteFont sbf = (SingleByteFont)tf; | SingleByteFont sbf = (SingleByteFont)tf; | ||||
SingleByteEncoding encoding = sbf.getAdditionalEncoding(i); | SingleByteEncoding encoding = sbf.getAdditionalEncoding(i); | ||||
defineEncoding(gen, encoding); | defineEncoding(gen, encoding); | ||||
String postFix = "_" + (i + 1); | 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)); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} else { | } else { | ||||
if (tf instanceof Base14Font) { | if (tf instanceof Base14Font) { | ||||
//Our Base 14 fonts don't use the default encoding | //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) { | } else if (tf instanceof SingleByteFont) { | ||||
SingleByteFont sbf = (SingleByteFont)tf; | SingleByteFont sbf = (SingleByteFont)tf; | ||||
if (!sbf.isUsingNativeEncoding()) { | if (!sbf.isUsingNativeEncoding()) { | ||||
//Font has been configured to use an encoding other than the default one | //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()); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return tf; | 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) { | private static boolean isEmbeddable(CustomFont font) { | ||||
Map fontResources = new java.util.HashMap(); | Map fontResources = new java.util.HashMap(); | ||||
for (String key : fonts.keySet()) { | for (String key : fonts.keySet()) { | ||||
Typeface tf = getTypeFace(fontInfo, fonts, key); | Typeface tf = getTypeFace(fontInfo, fonts, key); | ||||
PSResource fontRes = new PSResource("font", tf.getFontName()); | |||||
PSResource fontRes = new PSResource("font", tf.getEmbedFontName()); | |||||
fontResources.put(key, fontRes); | 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) { | if (tf instanceof CustomFont) { | ||||
CustomFont cf = (CustomFont)tf; | CustomFont cf = (CustomFont)tf; | ||||
if (isEmbeddable(cf)) { | 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); | resTracker.registerSuppliedResource(fontRes); | ||||
} | } | ||||
if (tf instanceof SingleByteFont) { | if (tf instanceof SingleByteFont) { | ||||
PSResource.TYPE_ENCODING, encoding.getName()); | PSResource.TYPE_ENCODING, encoding.getName()); | ||||
resTracker.registerSuppliedResource(encodingRes); | resTracker.registerSuppliedResource(encodingRes); | ||||
PSResource derivedFontRes = new PSResource( | PSResource derivedFontRes = new PSResource( | ||||
PSResource.TYPE_FONT, tf.getFontName() + "_" + (i + 1)); | |||||
PSResource.TYPE_FONT, tf.getEmbedFontName() + "_" + (i + 1)); | |||||
resTracker.registerSuppliedResource(derivedFontRes); | resTracker.registerSuppliedResource(derivedFontRes); | ||||
} | } | ||||
} | } | ||||
return res; | 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; | |||||
} | |||||
} | } |
/* | |||||
* 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; | |||||
} | |||||
} |
package org.apache.fop.render.ps; | package org.apache.fop.render.ps; | ||||
import java.awt.Dimension; | |||||
import java.awt.Rectangle; | import java.awt.Rectangle; | ||||
import java.awt.geom.Rectangle2D; | |||||
import java.awt.image.ColorModel; | |||||
import java.awt.image.RenderedImage; | import java.awt.image.RenderedImage; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import org.apache.xmlgraphics.image.loader.ImageInfo; | import org.apache.xmlgraphics.image.loader.ImageInfo; | ||||
import org.apache.xmlgraphics.image.loader.impl.ImageRendered; | import org.apache.xmlgraphics.image.loader.impl.ImageRendered; | ||||
import org.apache.xmlgraphics.ps.FormGenerator; | 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.ImageFormGenerator; | ||||
import org.apache.xmlgraphics.ps.PSGenerator; | import org.apache.xmlgraphics.ps.PSGenerator; | ||||
import org.apache.xmlgraphics.ps.PSImageUtils; | import org.apache.xmlgraphics.ps.PSImageUtils; | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
public void handleImage(RenderingContext context, Image image, Rectangle pos) | public void handleImage(RenderingContext context, Image image, Rectangle pos) | ||||
throws IOException { | throws IOException { | ||||
PSRenderingContext psContext = (PSRenderingContext)context; | |||||
PSRenderingContext psContext = (PSRenderingContext) context; | |||||
PSGenerator gen = psContext.getGenerator(); | 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(); | 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} */ | /** {@inheritDoc} */ |
import org.apache.fop.fonts.FontInfo; | import org.apache.fop.fonts.FontInfo; | ||||
import org.apache.fop.fonts.FontTriplet; | import org.apache.fop.fonts.FontTriplet; | ||||
import org.apache.fop.fonts.LazyFont; | import org.apache.fop.fonts.LazyFont; | ||||
import org.apache.fop.fonts.MultiByteFont; | |||||
import org.apache.fop.fonts.SingleByteFont; | import org.apache.fop.fonts.SingleByteFont; | ||||
import org.apache.fop.fonts.Typeface; | import org.apache.fop.fonts.Typeface; | ||||
import org.apache.fop.render.RenderingContext; | import org.apache.fop.render.RenderingContext; | ||||
import org.apache.fop.traits.BorderProps; | import org.apache.fop.traits.BorderProps; | ||||
import org.apache.fop.traits.RuleStyle; | import org.apache.fop.traits.RuleStyle; | ||||
import org.apache.fop.util.CharUtilities; | import org.apache.fop.util.CharUtilities; | ||||
import org.apache.fop.util.HexEncoder; | |||||
/** | /** | ||||
* IFPainter implementation that produces PostScript. | * IFPainter implementation that produces PostScript. | ||||
if (currentEncoding != encoding) { | if (currentEncoding != encoding) { | ||||
if (i > 0) { | if (i > 0) { | ||||
writeText(text, start, i - start, | writeText(text, start, i - start, | ||||
letterSpacing, wordSpacing, dp, font, tf); | |||||
letterSpacing, wordSpacing, dp, font, tf, false); | |||||
} | } | ||||
if (encoding == 0) { | if (encoding == 0) { | ||||
useFont(fontKey, sizeMillipoints); | useFont(fontKey, sizeMillipoints); | ||||
} | } | ||||
} | } | ||||
} else { | } else { | ||||
//Simple single-font painting | |||||
useFont(fontKey, sizeMillipoints); | 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) { | } catch (IOException ioe) { | ||||
throw new IFException("I/O error in drawText()", 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, | int letterSpacing, int wordSpacing, int[][] dp, | ||||
Font font, Typeface tf) throws IOException { | |||||
Font font, Typeface tf, boolean multiByte) throws IOException { | |||||
PSGenerator generator = getGenerator(); | PSGenerator generator = getGenerator(); | ||||
int end = start + len; | int end = start + len; | ||||
int initialSize = len; | int initialSize = len; | ||||
if (dx != null && i < dxl - 1) { | if (dx != null && i < dxl - 1) { | ||||
glyphAdjust -= dx[i + 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) { | if (glyphAdjust != 0) { | ||||
needTJ = true; | needTJ = true; | ||||
if (sb.length() == 0) { | if (sb.length() == 0) { | ||||
sb.append(PSGenerator.LF); | sb.append(PSGenerator.LF); | ||||
lineStart = sb.length(); | lineStart = sb.length(); | ||||
} | } | ||||
sb.append('('); | |||||
sb.append(accText); | |||||
sb.append(") "); | |||||
lineStart = writePostScriptString(sb, accText, multiByte, lineStart); | |||||
sb.append(' '); | |||||
accText.setLength(0); //reset accumulated text | accText.setLength(0); //reset accumulated text | ||||
} | } | ||||
sb.append(Integer.toString(glyphAdjust)).append(' '); | sb.append(Integer.toString(glyphAdjust)).append(' '); | ||||
} | } | ||||
if (needTJ) { | if (needTJ) { | ||||
if (accText.length() > 0) { | 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) { | if (hasLetterSpacing) { | ||||
sb.append("] " + formatMptAsPt(generator, letterSpacing) + " ATJ"); | sb.append("] " + formatMptAsPt(generator, letterSpacing) + " ATJ"); | ||||
sb.append("] TJ"); | sb.append("] TJ"); | ||||
} | } | ||||
} else { | } else { | ||||
sb.append('(').append(accText).append(")"); | |||||
writePostScriptString(sb, accText, multiByte); | |||||
if (hasLetterSpacing) { | if (hasLetterSpacing) { | ||||
StringBuffer spb = new StringBuffer(); | StringBuffer spb = new StringBuffer(); | ||||
spb.append(formatMptAsPt(generator, letterSpacing)) | spb.append(formatMptAsPt(generator, letterSpacing)) | ||||
generator.writeln(sb.toString()); | 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 { | private void useFont(String key, int size) throws IOException { | ||||
PSResource res = this.documentHandler.getPSResourceForFontKey(key); | |||||
PSFontResource res = this.documentHandler.getPSResourceForFontKey(key); | |||||
PSGenerator generator = getGenerator(); | PSGenerator generator = getGenerator(); | ||||
generator.useFont("/" + res.getName(), size / 1000f); | generator.useFont("/" + res.getName(), size / 1000f); | ||||
generator.getResourceTracker().notifyResourceUsageOnPage(res); | |||||
res.notifyResourceUsageOnPage(generator.getResourceTracker()); | |||||
} | } | ||||
} | } |
import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; | import org.apache.xmlgraphics.java2d.ps.PSGraphics2D; | ||||
import org.apache.xmlgraphics.ps.PSGenerator; | import org.apache.xmlgraphics.ps.PSGenerator; | ||||
import org.apache.xmlgraphics.ps.PSResource; | |||||
import org.apache.fop.fonts.Font; | import org.apache.fop.fonts.Font; | ||||
import org.apache.fop.fonts.FontInfo; | 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.svg.NativeTextPainter; | ||||
import org.apache.fop.util.CharUtilities; | import org.apache.fop.util.CharUtilities; | ||||
import org.apache.fop.util.HexEncoder; | |||||
/** | /** | ||||
* Renders the attributed character iterator of a text node. | * Renders the attributed character iterator of a text node. | ||||
} | } | ||||
} | } | ||||
private PSResource getResourceForFont(Font f, String postfix) { | |||||
private PSFontResource getResourceForFont(Font f, String postfix) { | |||||
String key = (postfix != null ? f.getFontName() + '_' + postfix : f.getFontName()); | 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 { | private void clip(PSGraphics2D ps, Shape shape) throws IOException { | ||||
public void selectFont(Font f, char mapped) throws IOException { | public void selectFont(Font f, char mapped) throws IOException { | ||||
int encoding = mapped / 256; | int encoding = mapped / 256; | ||||
String postfix = (encoding == 0 ? null : Integer.toString(encoding)); | 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.useFont("/" + res.getName(), f.getFontSize() / 1000f); | ||||
gen.getResourceTracker().notifyResourceUsageOnPage(res); | |||||
res.notifyResourceUsageOnPage(gen.getResourceTracker()); | |||||
} | } | ||||
public Font getCurrentFont() { | public Font getCurrentFont() { | ||||
textUtil.setCurrentFont(f, mapped); | textUtil.setCurrentFont(f, mapped); | ||||
applyColor(paint, gen); | applyColor(paint, gen); | ||||
FontMetrics metrics = f.getFontMetrics(); | |||||
boolean multiByte = metrics instanceof MultiByteFont | |||||
|| metrics instanceof LazyFont | |||||
&& ((LazyFont) metrics).getRealFont() instanceof MultiByteFont; | |||||
StringBuffer sb = new StringBuffer(); | StringBuffer sb = new StringBuffer(); | ||||
sb.append('('); | |||||
sb.append(multiByte ? '<' : '('); | |||||
for (int i = 0, c = this.currentChars.length(); i < c; i++) { | for (int i = 0, c = this.currentChars.length(); i < c; i++) { | ||||
char ch = this.currentChars.charAt(i); | char ch = this.currentChars.charAt(i); | ||||
mapped = f.mapChar(ch); | 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) { | if (x || y) { | ||||
sb.append("\n["); | sb.append("\n["); | ||||
int idx = 0; | int idx = 0; | ||||
textUtil.selectFont(f, mapped); | textUtil.selectFont(f, mapped); | ||||
textUtil.setCurrentFont(f, mapped); | textUtil.setCurrentFont(f, mapped); | ||||
} | } | ||||
mapped = f.mapChar(this.currentChars.charAt(i)); | |||||
//add glyph outlines to current path | //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"); | gen.writeln(" false charpath"); | ||||
if (iter.hasNext()) { | if (iter.hasNext()) { |
private FOUserAgent userAgent; | private FOUserAgent userAgent; | ||||
private FontInfo fontInfo; | private FontInfo fontInfo; | ||||
private PSEventProducer eventProducer; | |||||
private ResourceTracker resTracker; | private ResourceTracker resTracker; | ||||
//key: URI, values PSImageFormResource | //key: URI, values PSImageFormResource | ||||
/** | /** | ||||
* Main constructor. | * Main constructor. | ||||
* @param userAgent the FO user agent | * @param userAgent the FO user agent | ||||
* @param eventProducer the event producer | |||||
* @param fontInfo the font information | * @param fontInfo the font information | ||||
* @param resTracker the resource tracker to use | * @param resTracker the resource tracker to use | ||||
* @param formResources Contains all forms used by this document (maintained by PSRenderer) | * @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.userAgent = userAgent; | ||||
this.eventProducer = eventProducer; | |||||
this.fontInfo = fontInfo; | this.fontInfo = fontInfo; | ||||
this.resTracker = resTracker; | this.resTracker = resTracker; | ||||
determineInlineForms(formResources); | determineInlineForms(formResources); | ||||
if (fontSetupPlaceholder == null) { | if (fontSetupPlaceholder == null) { | ||||
throw new DSCException("Didn't find %FOPFontSetup comment in stream"); | 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); | generateForms(globalFormResources, gen); | ||||
//Skip the prolog and to the first page | //Skip the prolog and to the first page |
/* | |||||
* 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> "); | |||||
} | |||||
} |
/* | |||||
* 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(); | |||||
} | |||||
} |
/* | |||||
* 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"); | |||||
} | |||||
} |
/* | |||||
* 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(); | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
documents. Example: the fix of marks layering will be such a case when it's done. | 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="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"> | <action context="Code" dev="GA" type="fix"> | ||||
Eliminate javadocs warnings. | Eliminate javadocs warnings. | ||||
</action> | </action> | ||||
<action context="Renderers" dev="GA" type="fix" fixes-bug="53304,53306"> | <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. | Add version attribute to AT and IF intermediate formats. Also eliminate redundant use of reversed attribute in AT format. | ||||
</action> | </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"> | <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. | Add extension to place code just before PostScript %PageTrailer. | ||||
</action> | </action> |
import org.apache.fop.pdf.PDFDocumentGraphics2DTestCase; | import org.apache.fop.pdf.PDFDocumentGraphics2DTestCase; | ||||
import org.apache.fop.pdf.PDFEncryptionJCETestCase; | import org.apache.fop.pdf.PDFEncryptionJCETestCase; | ||||
import org.apache.fop.pdf.PDFFactoryTestCase; | 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.traits.BorderPropsTestCase; | ||||
import org.apache.fop.util.BitmapImageUtilTestCase; | import org.apache.fop.util.BitmapImageUtilTestCase; | ||||
import org.apache.fop.util.ColorUtilTestCase; | import org.apache.fop.util.ColorUtilTestCase; | ||||
import org.apache.fop.util.ElementListUtilsTestCase; | import org.apache.fop.util.ElementListUtilsTestCase; | ||||
import org.apache.fop.util.HexEncoderTestCase; | |||||
import org.apache.fop.util.XMLResourceBundleTestCase; | import org.apache.fop.util.XMLResourceBundleTestCase; | ||||
/** | /** | ||||
PDFFactoryTestCase.class, | PDFFactoryTestCase.class, | ||||
PDFEncryptionJCETestCase.class, | PDFEncryptionJCETestCase.class, | ||||
BitmapImageUtilTestCase.class, | BitmapImageUtilTestCase.class, | ||||
PDFDocumentGraphics2DTestCase.class | |||||
PDFDocumentGraphics2DTestCase.class, | |||||
PDFNumberTestCase.class, | |||||
PDFObjectTestCase.class, | |||||
HexEncoderTestCase.class | |||||
}) | }) | ||||
public class UtilityCodeTestSuite { | public class UtilityCodeTestSuite { | ||||
} | } |
@Before | @Before | ||||
public void setUp() throws Exception { | public void setUp() throws Exception { | ||||
File file = new File("test/resources/fonts/ttf/DejaVuLGCSerif.ttf"); | 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); | |||||
} | } | ||||
/** | /** |
package org.apache.fop.fonts; | package org.apache.fop.fonts; | ||||
import static org.junit.Assert.assertEquals; | |||||
import org.junit.Test; | import org.junit.Test; | ||||
import static org.junit.Assert.assertEquals; | |||||
/** | |||||
* Tests {@link EncodingMode}. | |||||
*/ | |||||
public class EncodingModeTestCase { | public class EncodingModeTestCase { | ||||
@Test | @Test | ||||
@Test | @Test | ||||
public void testGetValue() { | 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"); | |||||
} | } | ||||
} | } |
/* | |||||
* 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 { | |||||
} |
/* | |||||
* 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)); | |||||
} | |||||
} |
package org.apache.fop.fonts.truetype; | package org.apache.fop.fonts.truetype; | ||||
import static org.junit.Assert.assertTrue; | |||||
import java.io.ByteArrayInputStream; | import java.io.ByteArrayInputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import static org.junit.Assert.assertTrue; | |||||
/** | /** | ||||
* Tests {@link GlyfTable}. | * Tests {@link GlyfTable}. | ||||
*/ | */ | ||||
private void setupSubsetReader(Map<Integer, Integer> glyphs) throws IOException { | private void setupSubsetReader(Map<Integer, Integer> glyphs) throws IOException { | ||||
TTFSubSetFile fontFile = new TTFSubSetFile(); | 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); | InputStream intputStream = new ByteArrayInputStream(subsetFont); | ||||
subsetReader = new FontFileReader(intputStream); | subsetReader = new FontFileReader(intputStream); | ||||
} | } |
/* | |||||
* 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 | |||||
} | |||||
} |
package org.apache.fop.fonts.truetype; | 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.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.apache.fop.fonts.EmbeddingMode; | |||||
import org.apache.fop.fonts.EncodingMode; | import org.apache.fop.fonts.EncodingMode; | ||||
import org.apache.fop.fonts.FontManager; | import org.apache.fop.fonts.FontManager; | ||||
import org.apache.fop.fonts.FontResolver; | import org.apache.fop.fonts.FontResolver; | ||||
import static org.junit.Assert.assertFalse; | |||||
import static org.junit.Assert.assertTrue; | |||||
/** | /** | ||||
* Test case for {@link TTFFontLoader}. | * Test case for {@link TTFFontLoader}. | ||||
*/ | */ | ||||
boolean useKerning = true; | boolean useKerning = true; | ||||
TTFFontLoader fontLoader = new TTFFontLoader(absoluteFilePath, fontName, embedded, | TTFFontLoader fontLoader = new TTFFontLoader(absoluteFilePath, fontName, embedded, | ||||
EncodingMode.AUTO, useKerning, useComplexScriptFeatures, resolver); | |||||
EmbeddingMode.AUTO, EncodingMode.AUTO, useKerning, useComplexScriptFeatures, resolver); | |||||
assertTrue(fontLoader.getFont().hasKerningInfo()); | assertTrue(fontLoader.getFont().hasKerningInfo()); | ||||
useKerning = false; | 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()); | assertFalse(fontLoader.getFont().hasKerningInfo()); | ||||
} | } | ||||
} | } |
/* | |||||
* 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"); | |||||
} | |||||
} |
/* | |||||
* 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)); | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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()); | |||||
} | |||||
} | |||||
} |
/* | |||||
* 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()); | |||||
} | |||||
} |
/* | |||||
* 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 { | |||||
} |
/* | |||||
* 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> ")); | |||||
} | |||||
} |
/* | |||||
* 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(); | |||||
} | |||||
} |
/* | |||||
* 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"); | |||||
} | |||||
} |
/* | |||||
* 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(); | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} | |||||
} |
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. |