Refactored code and added unit tests Patch by Mehdi Houshmand git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_TrueTypeInPostScript@1307574 13f79535-47bb-0310-9956-ffa450edef68pull/26/head
<include name="lib/build/jaxen*"/> | <include name="lib/build/jaxen*"/> | ||||
<include name="lib/build/pmd*"/> | <include name="lib/build/pmd*"/> | ||||
<include name="lib/build/qdox*"/> | <include name="lib/build/qdox*"/> | ||||
<include name="lib/build/xmlunit*"/> | |||||
<include name="lib/build/xmlunit*"/> | |||||
<include name="lib/build/mockito*"/> | |||||
<include name="lib/build/hamcrest*"/> | |||||
<include name="lib/build/objenesis*"/> | |||||
</patternset> | </patternset> | ||||
<fileset dir="${basedir}" id="dist.src"> | <fileset dir="${basedir}" id="dist.src"> | ||||
<include name="src/**"/> | <include name="src/**"/> | ||||
<test name="org.apache.fop.text.linebreak.LineBreakStatusTest" todir="${junit.reports.dir}"/> | <test name="org.apache.fop.text.linebreak.LineBreakStatusTest" todir="${junit.reports.dir}"/> | ||||
</junit> | </junit> | ||||
</target> | </target> | ||||
<target name="junit-reduced" depends="junit-userconfig, junit-basic, junit-transcoder, junit-text-linebreak, junit-fotree"/> | |||||
<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-reduced" depends="junit-userconfig, junit-basic, junit-transcoder, junit-text-linebreak, junit-fotree, junit-fonts, junit-render-ps"/> | |||||
<target name="junit-full" depends="junit-reduced, junit-layout, junit-area-tree-xml-format, junit-intermediate-format"/> | <target name="junit-full" depends="junit-reduced, junit-layout, junit-area-tree-xml-format, junit-intermediate-format"/> | ||||
<target name="junit" depends="junit-full" description="Runs all of FOP's JUnit tests" if="junit.present"> | <target name="junit" depends="junit-full" description="Runs all of FOP's JUnit tests" if="junit.present"> | ||||
<fail><condition><or><isset property="fop.junit.error"/><isset property="fop.junit.failure"/><not><isset property="hyphenation.present"/></not></or></condition> | <fail><condition><or><isset property="fop.junit.error"/><isset property="fop.junit.failure"/><not><isset property="hyphenation.present"/></not></or></condition> |
import javax.xml.transform.stream.StreamResult; | import javax.xml.transform.stream.StreamResult; | ||||
import javax.xml.transform.stream.StreamSource; | import javax.xml.transform.stream.StreamSource; | ||||
import org.w3c.dom.Node; | |||||
import org.apache.commons.io.IOUtils; | import org.apache.commons.io.IOUtils; | ||||
import org.apache.fop.events.model.EventModel; | |||||
import org.apache.fop.events.model.EventProducerModel; | |||||
import org.apache.tools.ant.BuildException; | import org.apache.tools.ant.BuildException; | ||||
import org.apache.tools.ant.DirectoryScanner; | import org.apache.tools.ant.DirectoryScanner; | ||||
import org.apache.tools.ant.Project; | import org.apache.tools.ant.Project; | ||||
import org.apache.tools.ant.Task; | import org.apache.tools.ant.Task; | ||||
import org.apache.tools.ant.types.FileSet; | import org.apache.tools.ant.types.FileSet; | ||||
import org.apache.tools.ant.types.selectors.FilenameSelector; | import org.apache.tools.ant.types.selectors.FilenameSelector; | ||||
import org.apache.fop.events.model.EventModel; | |||||
import org.apache.fop.events.model.EventProducerModel; | |||||
import org.w3c.dom.Node; | |||||
/** | /** | ||||
* Ant task which inspects a file set for Java interfaces which extend the | * Ant task which inspects a file set for Java interfaces which extend the |
/** | /** | ||||
* This is just a holder class for bfentries, groups of characters of a base font (bf). | * This is just a holder class for bfentries, groups of characters of a base font (bf). | ||||
*/ | */ | ||||
public class BFEntry { | |||||
public final class BFEntry { | |||||
//TODO Think about renaming this class to CMapRange or something. | //TODO Think about renaming this class to CMapRange or something. | ||||
//TODO Copy equals() and hashCode() from TTFCmapEntry | |||||
private int unicodeStart; | |||||
private int unicodeEnd; | |||||
private int glyphStartIndex; | |||||
private final int unicodeStart; | |||||
private final int unicodeEnd; | |||||
private final int glyphStartIndex; | |||||
/** | /** | ||||
* Main constructor. | * Main constructor. | ||||
this.glyphStartIndex = glyphStartIndex; | this.glyphStartIndex = glyphStartIndex; | ||||
} | } | ||||
/** | |||||
* {@inheritDoc} | |||||
*/ | |||||
@Override | |||||
public int hashCode() { | |||||
int hc = 17; | |||||
hc = 31 * hc + unicodeStart; | |||||
hc = 31 * hc + unicodeEnd; | |||||
hc = 31 * hc + glyphStartIndex; | |||||
return hc; | |||||
} | |||||
/** | |||||
* {@inheritDoc} | |||||
*/ | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (o instanceof BFEntry) { | |||||
BFEntry ce = (BFEntry) 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 |
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; | ||||
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 | ||||
return lastChar; | return lastChar; | ||||
} | } | ||||
/** | |||||
/**MutableFont | |||||
* Used to determine if kerning is enabled. | * Used to determine if kerning is enabled. | ||||
* @return True if kerning is enabled. | * @return True if kerning is enabled. | ||||
*/ | */ | ||||
this.embedResourceName = name; | this.embedResourceName = name; | ||||
} | } | ||||
/** | |||||
* {@inheritDoc} | |||||
*/ | |||||
public void setEmbeddingMode(EmbeddingMode embeddingMode) { | |||||
this.embeddingMode = embeddingMode; | |||||
} | |||||
/** | /** | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
*/ | */ |
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 kerning; | protected boolean kerning; | ||||
/** 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 type1 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; | |||||
/** | |||||
* This enumerates the embedding mode of fonts; full; subset; auto (auto defaults to full for | |||||
* type1 fonts and subset for truetype fonts. | |||||
*/ | |||||
public enum EmbeddingMode { | |||||
/** Default option: assumes FULL for type1 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 String - lower case. | |||||
*/ | |||||
public String getName() { | |||||
return this.toString().toLowerCase(); | |||||
} | |||||
/** | |||||
* Returns {@link EmbeddingMode} by name. | |||||
* @param value String - the name of the embedding mode (not case sensitive). | |||||
* @return embedding mode constant. | |||||
*/ | |||||
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); | ||||
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, tripletList, embedUrl, subFont); | = new EmbedFontInfo(metricsUrl, useKerning, tripletList, embedUrl, subFont); | ||||
embedFontInfo.setEncodingMode(encodingMode); | embedFontInfo.setEncodingMode(encodingMode); | ||||
embedFontInfo.setEmbeddingMode(embeddingMode); | |||||
if (fontCache != null) { | if (fontCache != null) { | ||||
if (!fontCache.containsFont(embedFontInfo)) { | if (!fontCache.containsFont(embedFontInfo)) { | ||||
fontCache.addFont(embedFontInfo); | fontCache.addFont(embedFontInfo); |
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.fop.fonts.truetype.TTFFontLoader; | import org.apache.fop.fonts.truetype.TTFFontLoader; | ||||
import org.apache.fop.fonts.type1.Type1FontLoader; | import org.apache.fop.fonts.type1.Type1FontLoader; | ||||
* @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, | |||||
embedded, embeddingMode, encodingMode, 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 resolver the font resolver to use when resolving URIs | * @param resolver the font resolver to use when resolving URIs | ||||
* @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, | |||||
FontResolver resolver) throws IOException { | |||||
boolean embedded, EmbeddingMode embeddingMode, EncodingMode encodingMode, | |||||
boolean useKerning, 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, resolver); | |||||
embedded, embeddingMode, encodingMode, useKerning, resolver); | |||||
} | } | ||||
return loader.getFont(); | return loader.getFont(); | ||||
} | } |
import javax.xml.transform.Source; | import javax.xml.transform.Source; | ||||
import javax.xml.transform.stream.StreamSource; | import javax.xml.transform.stream.StreamSource; | ||||
import org.xml.sax.InputSource; | |||||
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.fop.apps.FOPException; | import org.apache.fop.apps.FOPException; | ||||
import org.xml.sax.InputSource; | |||||
/** | /** | ||||
* This class is used to defer the loading of a font until it is really used. | * This class is used to defer the loading of a font until it is really used. | ||||
private String fontEmbedPath = null; | private String fontEmbedPath = null; | ||||
private boolean useKerning = false; | private boolean useKerning = false; | ||||
private EncodingMode encodingMode = EncodingMode.AUTO; | private EncodingMode encodingMode = EncodingMode.AUTO; | ||||
private EmbeddingMode embeddingMode = EmbeddingMode.AUTO; | |||||
private boolean embedded = true; | private boolean embedded = true; | ||||
private String subFontName = null; | private String subFontName = null; | ||||
this.fontEmbedPath = fontInfo.getEmbedFile(); | this.fontEmbedPath = fontInfo.getEmbedFile(); | ||||
this.useKerning = fontInfo.getKerning(); | this.useKerning = fontInfo.getKerning(); | ||||
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, resolver); | |||||
realFont = FontLoader.loadFont(fontEmbedPath, subFontName, | |||||
embedded, embeddingMode, encodingMode, | |||||
useKerning, resolver); | |||||
} | } | ||||
if (realFont instanceof FontDescriptor) { | if (realFont instanceof FontDescriptor) { | ||||
realFontDescriptor = (FontDescriptor) realFont; | realFontDescriptor = (FontDescriptor) realFont; |
*/ | */ | ||||
void setEmbedResourceName(String name); | void setEmbedResourceName(String name); | ||||
/** | |||||
* Set the embedding mode for this font. | |||||
* @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 |
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.BFEntry; | |||||
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; | ||||
import org.w3c.dom.Document; | import org.w3c.dom.Document; | ||||
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 (BFEntry 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.commons.logging.LogFactory; | import org.apache.commons.logging.LogFactory; | ||||
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, true, resolver); | |||||
fontFileURL, fontName, true, EmbeddingMode.AUTO, EncodingMode.AUTO, | |||||
true, resolver); | |||||
customFont = ttfLoader.getFont(); | customFont = ttfLoader.getFont(); | ||||
if (this.eventListener != null) { | if (this.eventListener != null) { | ||||
customFont.setEventListener(this.eventListener); | customFont.setEventListener(this.eventListener); | ||||
} 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 { | |||||
//TODO this class is redundant: BFEntry does the same but doesn't have an intuitive name | |||||
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} | |||||
*/ | |||||
@Override | |||||
public int hashCode() { | |||||
int hc = super.hashCode(); | |||||
hc ^= ( hc * 11 ) + unicodeStart; | |||||
hc ^= ( hc * 19 ) + unicodeEnd; | |||||
hc ^= ( hc * 23 ) + glyphStartIndex; | |||||
return hc; | |||||
} | |||||
/** | |||||
* {@inheritDoc} | |||||
*/ | |||||
@Override | |||||
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, return tag name | * Read Dir Tab, return tag name | ||||
*/ | */ |
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.fop.fonts.BFEntry; | import org.apache.fop.fonts.BFEntry; | ||||
import org.apache.fop.fonts.CIDFontType; | import org.apache.fop.fonts.CIDFontType; | ||||
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; | ||||
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, resolver); | |||||
this(fontFileURI, null, true, EmbeddingMode.AUTO, EncodingMode.AUTO, 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 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, | |||||
FontResolver resolver) { | |||||
boolean embedded, EmbeddingMode embeddingMode, EncodingMode encodingMode, | |||||
boolean useKerning, FontResolver resolver) { | |||||
super(fontFileURI, embedded, true, resolver); | super(fontFileURI, embedded, true, 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(); | ||||
} | } | ||||
private BFEntry[] getCMap(TTFFile ttf) { | private BFEntry[] getCMap(TTFFile ttf) { | ||||
List<TTFCmapEntry> entries = ttf.getCMaps(); | |||||
BFEntry[] bfentries = new BFEntry[entries.size()]; | |||||
int pos = 0; | |||||
for (TTFCmapEntry ce : ttf.getCMaps()) { | |||||
bfentries[pos] = new BFEntry(ce.getUnicodeStart(), ce.getUnicodeEnd(), | |||||
ce.getGlyphStartIndex()); | |||||
pos++; | |||||
} | |||||
return bfentries; | |||||
BFEntry[] array = new BFEntry[ttf.getCMaps().size()]; | |||||
return ttf.getCMaps().toArray(array); | |||||
} | } | ||||
private void copyWidthsSingleByte(TTFFile ttf) { | private void copyWidthsSingleByte(TTFFile ttf) { | ||||
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(); | |||||
for (BFEntry ce : ttf.getCMaps()) { | |||||
if (ce.getUnicodeStart() < 0xFFFE) { | if (ce.getUnicodeStart() < 0xFFFE) { | ||||
for (char u = (char)ce.getUnicodeStart(); u <= ce.getUnicodeEnd(); u++) { | for (char u = (char)ce.getUnicodeStart(); u <= ce.getUnicodeEnd(); u++) { | ||||
int codePoint = singleFont.getEncoding().mapChar(u); | int codePoint = singleFont.getEncoding().mapChar(u); | ||||
} | } | ||||
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; | |||||
/** | |||||
* This is an interface for streaming individual glyphs from the glyf table in a True Type font. | |||||
*/ | |||||
public interface TTFGlyphOutputStream { | |||||
/** | |||||
* Begins the streaming of glyphs. | |||||
* @throws IOException file write exception | |||||
*/ | |||||
void startGlyphStream() throws IOException; | |||||
/** | |||||
* Streams an individual glyph at offset from a byte array. | |||||
* @param byteArray byte[] the font byte array. | |||||
* @param offset int the starting position to stream from. | |||||
* @param length int the number of bytes to stream. | |||||
* @throws IOException file write exception. | |||||
*/ | |||||
void streamGlyph(byte[] byteArray, int offset, int length) throws IOException; | |||||
/** | |||||
* Ends the streaming of glyphs. | |||||
* @throws IOException file write exception. | |||||
*/ | |||||
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; | |||||
/** | |||||
* This is an interface for streaming True Type font. | |||||
*/ | |||||
public interface TTFOutputStream { | |||||
/** | |||||
* Starts writing the font to file. | |||||
* @throws IOException file write exception. | |||||
*/ | |||||
void startFontStream() throws IOException; | |||||
/** | |||||
* Returns an object for streaming True Type tables. | |||||
* @return {@link TTFTableOutputStream} | |||||
*/ | |||||
TTFTableOutputStream getTableOutputStream(); | |||||
/** | |||||
* Returns an object for streaming True Type glyphs in the glyf table. | |||||
* @return {@link TTFGlyphOutputStream} | |||||
*/ | |||||
TTFGlyphOutputStream getGlyphOutputStream(); | |||||
/** | |||||
* Ends writing the font to file. | |||||
* @throws IOException file write exception. | |||||
*/ | |||||
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.HashMap; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.SortedSet; | |||||
/** | /** | ||||
*/ | */ | ||||
public class TTFSubSetFile extends TTFFile { | public class TTFSubSetFile extends TTFFile { | ||||
private static enum OperatingMode { | |||||
PDF, POSTSCRIPT_GLYPH_DIRECTORY; | |||||
} | |||||
private byte[] output = null; | private byte[] output = null; | ||||
private int realSize = 0; | private int realSize = 0; | ||||
private int currentPos = 0; | private int currentPos = 0; | ||||
* 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 Map<String, Integer> offsets = new java.util.HashMap<String, Integer>(); | |||||
private int glyfDirOffset = 0; | |||||
private int headDirOffset = 0; | |||||
private int hmtxDirOffset = 0; | |||||
private int locaDirOffset = 0; | |||||
private int maxpDirOffset = 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; | ||||
private int determineTableCount(OperatingMode operatingMode) { | |||||
int numTables = 4; //4 req'd tables: head,hhea,hmtx,maxp | |||||
/** Stores the glyph offsets so that we can end strings at glyph boundaries */ | |||||
private int[] glyphOffsets; | |||||
/** The dir tab entries in the new subset font. */ | |||||
private Map<TTFTableName, TTFDirTabEntry> newDirTabs | |||||
= new HashMap<TTFTableName, TTFDirTabEntry>(); | |||||
private int determineTableCount() { | |||||
int numTables = 4; //4 req'd tables: head,hhea,hmtx,maxp, | |||||
if (isCFF()) { | if (isCFF()) { | ||||
throw new UnsupportedOperationException( | throw new UnsupportedOperationException( | ||||
"OpenType fonts with CFF glyphs are not supported"); | "OpenType fonts with CFF glyphs are not supported"); | ||||
} else { | } else { | ||||
if (operatingMode == OperatingMode.POSTSCRIPT_GLYPH_DIRECTORY) { | |||||
numTables++; //1 table: gdir | |||||
} else { | |||||
numTables += 2; //2 req'd tables: glyf,loca | |||||
} | |||||
numTables += 5; //5 req'd tables: glyf,loca,post,name,OS/2 | |||||
if (hasCvt()) { | if (hasCvt()) { | ||||
numTables++; | numTables++; | ||||
} | } | ||||
/** | /** | ||||
* Create the directory table | * Create the directory table | ||||
*/ | */ | ||||
private void createDirectory(OperatingMode operatingMode) { | |||||
int numTables = determineTableCount(operatingMode); | |||||
private void createDirectory() { | |||||
int numTables = determineTableCount(); | |||||
// Create the TrueType header | // Create the TrueType header | ||||
writeByte((byte)0); | writeByte((byte)0); | ||||
writeByte((byte)1); | writeByte((byte)1); | ||||
// 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 "); | |||||
offsets.put("cvt ", currentPos); | |||||
currentPos += 12; | |||||
realSize += 16; | |||||
writeTableName(TTFTableName.CVT); | |||||
} | } | ||||
if (hasFpgm()) { | if (hasFpgm()) { | ||||
writeString("fpgm"); | |||||
offsets.put("fpgm", currentPos); | |||||
currentPos += 12; | |||||
realSize += 16; | |||||
} | |||||
if (operatingMode != OperatingMode.POSTSCRIPT_GLYPH_DIRECTORY) { | |||||
writeString("glyf"); | |||||
glyfDirOffset = currentPos; | |||||
currentPos += 12; | |||||
realSize += 16; | |||||
} | |||||
writeString("head"); | |||||
headDirOffset = currentPos; | |||||
currentPos += 12; | |||||
realSize += 16; | |||||
writeString("hhea"); | |||||
offsets.put("hhea", currentPos); | |||||
currentPos += 12; | |||||
realSize += 16; | |||||
writeString("hmtx"); | |||||
hmtxDirOffset = currentPos; | |||||
currentPos += 12; | |||||
realSize += 16; | |||||
if (operatingMode != OperatingMode.POSTSCRIPT_GLYPH_DIRECTORY) { | |||||
writeString("loca"); | |||||
locaDirOffset = 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.DIRECTORY_TABLE, new TTFDirTabEntry(0, currentPos)); | |||||
} | |||||
writeString("maxp"); | |||||
maxpDirOffset = currentPos; | |||||
private void writeTableName(TTFTableName tableName) { | |||||
writeString(tableName.getName()); | |||||
offsets.put(tableName, currentPos); | |||||
currentPos += 12; | currentPos += 12; | ||||
realSize += 16; | realSize += 16; | ||||
if (hasPrep()) { | |||||
writeString("prep"); | |||||
offsets.put("prep", currentPos); | |||||
currentPos += 12; | |||||
realSize += 16; | |||||
} | |||||
if (operatingMode == OperatingMode.POSTSCRIPT_GLYPH_DIRECTORY) { | |||||
//"gdir" indicates to the PostScript interpreter that the GlyphDirectory approach | |||||
//is in use. | |||||
writeString("gdir"); | |||||
currentPos += 12; | |||||
realSize += 16; | |||||
} | |||||
} | } | ||||
private boolean hasCvt() { | private boolean hasCvt() { | ||||
return dirTabs.containsKey("cvt "); | |||||
return dirTabs.containsKey(TTFTableName.CVT); | |||||
} | } | ||||
private boolean hasFpgm() { | private boolean hasFpgm() { | ||||
return dirTabs.containsKey("fpgm"); | |||||
return dirTabs.containsKey(TTFTableName.FPGM); | |||||
} | } | ||||
private boolean hasPrep() { | private boolean hasPrep() { | ||||
return dirTabs.containsKey("prep"); | |||||
return dirTabs.containsKey(TTFTableName.PREP); | |||||
} | } | ||||
/** | /** | ||||
private void createLoca(int size) throws IOException { | private void createLoca(int size) throws IOException { | ||||
pad4(); | pad4(); | ||||
locaOffset = currentPos; | locaOffset = currentPos; | ||||
writeULong(locaDirOffset + 4, currentPos); | |||||
writeULong(locaDirOffset + 8, size * 4 + 4); | |||||
int dirTableOffset = offsets.get(TTFTableName.LOCA); | |||||
writeULong(dirTableOffset + 4, currentPos); | |||||
writeULong(dirTableOffset + 8, size * 4 + 4); | |||||
currentPos += size * 4 + 4; | currentPos += size * 4 + 4; | ||||
realSize += size * 4 + 4; | realSize += size * 4 + 4; | ||||
} | } | ||||
private boolean copyTable(FontFileReader in, String tableName) throws IOException { | |||||
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get(tableName); | |||||
private boolean copyTable(FontFileReader in, TTFTableName tableName) throws IOException { | |||||
TTFDirTabEntry entry = dirTabs.get(tableName); | |||||
if (entry != null) { | if (entry != null) { | ||||
pad4(); | pad4(); | ||||
seekTab(in, tableName, 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()); | |||||
int offset = offsets.get(tableName); | |||||
writeULong(offset, checksum); | |||||
writeULong(offset + 4, currentPos); | |||||
writeULong(offset + 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; | ||||
* Copy the cvt table as is from original font to subset font | * Copy the cvt table as is from original font to subset font | ||||
*/ | */ | ||||
private boolean createCvt(FontFileReader in) throws IOException { | private boolean createCvt(FontFileReader in) throws IOException { | ||||
return copyTable(in, "cvt "); | |||||
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 { | ||||
return copyTable(in, "fpgm"); | |||||
return copyTable(in, TTFTableName.FPGM); | |||||
} | |||||
/** | |||||
* Copy the name table as is from the original. | |||||
* @param in FontFileReader | |||||
* @return boolean | |||||
* @throws IOException exception | |||||
*/ | |||||
private boolean createName(FontFileReader in) throws IOException { | |||||
return copyTable(in, TTFTableName.NAME); | |||||
} | |||||
/** | |||||
* Copy the OS/2 table as is from the original. | |||||
* @param in | |||||
* @return | |||||
* @throws IOException | |||||
*/ | |||||
private boolean createOS2(FontFileReader in) throws IOException { | |||||
return copyTable(in, TTFTableName.OS2); | |||||
} | } | ||||
/** | /** | ||||
* 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 { | ||||
return copyTable(in, "prep"); | |||||
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 { | ||||
boolean copied = copyTable(in, "hhea"); | |||||
if (!copied) { | |||||
TTFDirTabEntry entry = dirTabs.get(TTFTableName.HHEA); | |||||
if (entry != null) { | |||||
pad4(); | |||||
seekTab(in, TTFTableName.HHEA, 0); | |||||
System.arraycopy(in.getBytes((int) entry.getOffset(), (int) entry.getLength()), 0, | |||||
output, currentPos, (int) entry.getLength()); | |||||
writeUShort((int) entry.getLength() + currentPos - 2, size); | |||||
updateCheckSum(currentPos, (int) entry.getLength(), TTFTableName.HHEA); | |||||
currentPos += (int) entry.getLength(); | |||||
realSize += (int) entry.getLength(); | |||||
} else { | |||||
throw new IOException("Can't find hhea table"); | 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<Integer, Integer> 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 startPos = 0; | int startPos = 0; | ||||
int endOffset = 0; // Store this as the last loca | int endOffset = 0; // Store this as the last loca | ||||
* location offset. | * location offset. | ||||
*/ | */ | ||||
int[] origIndexes = buildSubsetIndexToOrigIndexMap(glyphs); | 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 nextOffset = 0; | int nextOffset = 0; | ||||
endOffset1 = (currentPos - startPos + glyphLength); | endOffset1 = (currentPos - startPos + glyphLength); | ||||
} | } | ||||
// Store the glyph boundary positions relative to the start the font | |||||
glyphOffsets[i] = currentPos; | |||||
currentPos += glyphLength; | currentPos += glyphLength; | ||||
realSize += glyphLength; | realSize += glyphLength; | ||||
endOffset = endOffset1; | |||||
endOffset = endOffset1; | |||||
} | } | ||||
size = currentPos - startPos; | size = currentPos - startPos; | ||||
int checksum = getCheckSum(startPos, size); | |||||
writeULong(glyfDirOffset, checksum); | |||||
writeULong(glyfDirOffset + 4, startPos); | |||||
writeULong(glyfDirOffset + 8, size); | |||||
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 void createHmtx(FontFileReader in, | private void createHmtx(FontFileReader in, | ||||
Map<Integer, Integer> glyphs) throws IOException { | Map<Integer, Integer> glyphs) throws IOException { | ||||
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hmtx"); | |||||
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; | ||||
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 { | ||||
*/ | */ | ||||
private void scanGlyphs(FontFileReader in, | private void scanGlyphs(FontFileReader in, | ||||
Map<Integer, Integer> glyphs) throws IOException { | Map<Integer, Integer> glyphs) throws IOException { | ||||
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf"); | |||||
TTFDirTabEntry entry = dirTabs.get(TTFTableName.GLYF); | |||||
Map<Integer, Integer> newComposites = null; | Map<Integer, Integer> newComposites = null; | ||||
Map<Integer, Integer> allComposites = new java.util.HashMap<Integer, Integer>(); | |||||
Map<Integer, Integer> allComposites = new HashMap<Integer, Integer>(); | |||||
int newIndex = glyphs.size(); | int newIndex = glyphs.size(); | ||||
if (entry != null) { | if (entry != null) { | ||||
while (newComposites == null || newComposites.size() > 0) { | while (newComposites == null || newComposites.size() > 0) { | ||||
// Inefficient to iterate through all glyphs | // Inefficient to iterate through all glyphs | ||||
newComposites = new java.util.HashMap<Integer, Integer>(); | |||||
newComposites = new HashMap<Integer, Integer>(); | |||||
for (Map.Entry<Integer, Integer> glyph : glyphs.entrySet()) { | for (Map.Entry<Integer, Integer> glyph : glyphs.entrySet()) { | ||||
int origIndex = glyph.getKey(); | int origIndex = glyph.getKey(); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* 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(OperatingMode.PDF); // 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."); | ||||
} | } | ||||
optionalTableFound = createPrep(in); // copy prep table | |||||
if (!optionalTableFound) { | |||||
// prep is optional (used in TrueType fonts only) | |||||
log.debug("TrueType: prep table not present. Skipped."); | |||||
} | |||||
createLoca(subsetGlyphs.size()); // create empty loca table | createLoca(subsetGlyphs.size()); // create empty loca table | ||||
createGlyf(in, subsetGlyphs); //create glyf table and update loca table | |||||
pad4(); | |||||
createCheckSumAdjustment(); | |||||
byte[] ret = new byte[realSize]; | |||||
System.arraycopy(output, 0, ret, 0, realSize); | |||||
return ret; | |||||
} | |||||
/** | |||||
* Returns a subset of the original font suitable for use in PostScript programs. | |||||
* | |||||
* @param in FontFileReader to read from | |||||
* @param name Name to be checked for in the font file | |||||
* @param glyphs Map of glyphs (glyphs has old index as (Integer) key and | |||||
* new index as (Integer) value) | |||||
* @param glyphHandler the handler to receive all glyphs of the subset | |||||
* @return A subset of the original font | |||||
* @throws IOException in case of an I/O problem | |||||
*/ | |||||
public byte[] toPostScriptSubset(FontFileReader in, String name, | |||||
Map glyphs, GlyphHandler glyphHandler) throws IOException { | |||||
//Check if TrueType collection, and that the name exists in the collection | |||||
if (!checkTTC(in, name)) { | |||||
throw new IOException("Failed to read font"); | |||||
} | |||||
//Copy the Map as we're going to modify it | |||||
Map<Integer, Integer> subsetGlyphs = new java.util.HashMap(glyphs); | |||||
output = new byte[in.getFileSize()]; | |||||
readDirTabs(in); | |||||
readFontHeader(in); | |||||
getNumGlyphs(in); | |||||
readHorizontalHeader(in); | |||||
readHorizontalMetrics(in); | |||||
readIndexToLocation(in); | |||||
scanGlyphs(in, subsetGlyphs); | |||||
// Create the TrueType header and directory | |||||
createDirectory(OperatingMode.POSTSCRIPT_GLYPH_DIRECTORY); | |||||
createGlyf(in, subsetGlyphs); //create glyf table and update loca table | |||||
createOS2(in); // copy the OS/2 table | |||||
createHead(in); | createHead(in); | ||||
createHhea(in, subsetGlyphs.size()); // Create the hhea table | createHhea(in, subsetGlyphs.size()); // Create the hhea table | ||||
createHmtx(in, subsetGlyphs); // Create hmtx table | createHmtx(in, subsetGlyphs); // Create hmtx table | ||||
createMaxp(in, subsetGlyphs.size()); // copy the maxp table | createMaxp(in, subsetGlyphs.size()); // copy the maxp table | ||||
boolean optionalTableFound; | |||||
optionalTableFound = createCvt(in); // copy the cvt table | |||||
if (!optionalTableFound) { | |||||
// cvt is optional (used in TrueType fonts only) | |||||
log.debug("TrueType: ctv table not present. Skipped."); | |||||
} | |||||
optionalTableFound = createFpgm(in); // copy fpgm table | |||||
if (!optionalTableFound) { | |||||
// fpgm is optional (used in TrueType fonts only) | |||||
log.debug("TrueType: fpgm table not present. Skipped."); | |||||
} | |||||
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."); | ||||
} | } | ||||
//Send all the glyphs from the subset | |||||
handleGlyphSubset(in, subsetGlyphs, glyphHandler); | |||||
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(FontFileReader in, Map<Integer, Integer> glyphs, | |||||
GlyphHandler glyphHandler) throws IOException { | |||||
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf"); | |||||
if (entry != null) { | |||||
int[] origIndexes = buildSubsetIndexToOrigIndexMap(glyphs); | |||||
for (int i = 0; i < origIndexes.length; i++) { | |||||
int nextOffset = 0; | |||||
int origGlyphIndex = origIndexes[i]; | |||||
if (origGlyphIndex >= (mtxTab.length - 1)) { | |||||
nextOffset = (int)lastLoca; | |||||
} else { | |||||
nextOffset = (int)mtxTab[origGlyphIndex + 1].getOffset(); | |||||
} | |||||
int glyphOffset = (int)mtxTab[origGlyphIndex].getOffset(); | |||||
int glyphLength = nextOffset - glyphOffset; | |||||
byte[] glyphData = in.getBytes( | |||||
(int)entry.getOffset() + glyphOffset, | |||||
glyphLength); | |||||
glyphHandler.addGlyph(glyphData); | |||||
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()); | |||||
} | } | ||||
} else { | |||||
throw new IOException("Can't find glyf table"); | |||||
} | } | ||||
} | |||||
/** | |||||
* Used as callback to handle a number of glyphs. | |||||
*/ | |||||
public static interface GlyphHandler { | |||||
/** | |||||
* Adds a glyph. | |||||
* @param glyphData the glyph data | |||||
* @throws IOException if an I/O error occurs | |||||
*/ | |||||
void addGlyph(byte[] glyphData) throws IOException; | |||||
ttfOut.endFontStream(); | |||||
} | } | ||||
/** | /** | ||||
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(output, 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 static long getLongCheckSum(byte[] data, 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 = (data[start + i] << 24); | |||||
l += (data[start + i + 1] << 16); | |||||
l += (data[start + i + 2] << 16); | |||||
l += (data[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(output, 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; | |||||
/** | |||||
* This class holds the True Type Format table names as in the Directory Table of a TTF font file. | |||||
* This class must also support custom tables found in fonts (thus an enum wasn't used). | |||||
*/ | |||||
public final class TTFTableName { | |||||
/** The first table in a True Type font file containing metadata about other tables. */ | |||||
public static final TTFTableName DIRECTORY_TABLE = new TTFTableName("dirTable"); | |||||
/** 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 font forge 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 Table Directory. | |||||
* @return String | |||||
*/ | |||||
public String getName() { | |||||
return name; | |||||
} | |||||
/** | |||||
* Returns the appropriate TTFTableName object when given the string representation. | |||||
* @param tableName table name as in the Directory Table. | |||||
* @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()); | |||||
} | |||||
} |
/* | |||||
* 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 streaming full True Type tables from a TTF file. | |||||
*/ | |||||
public interface TTFTableOutputStream { | |||||
/** | |||||
* Streams a table defined in byteArray at offset of length bytes. | |||||
* @param byteArray The source of the table to stream from. | |||||
* @param offset The position in byteArray to begin streaming from. | |||||
* @param length The number of bytes to stream. | |||||
* @throws IOException write error. | |||||
*/ | |||||
void streamTable(byte[] byteArray, int offset, int length) throws IOException; | |||||
} |
import org.apache.commons.io.output.ByteArrayOutputStream; | 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.xmp.Metadata; | |||||
import org.apache.fop.fonts.CIDFont; | import org.apache.fop.fonts.CIDFont; | ||||
import org.apache.fop.fonts.CIDSubset; | import org.apache.fop.fonts.CIDSubset; | ||||
import org.apache.fop.fonts.CodePointMapping; | import org.apache.fop.fonts.CodePointMapping; | ||||
import org.apache.fop.fonts.truetype.TTFSubSetFile; | import org.apache.fop.fonts.truetype.TTFSubSetFile; | ||||
import org.apache.fop.fonts.type1.PFBData; | import org.apache.fop.fonts.type1.PFBData; | ||||
import org.apache.fop.fonts.type1.PFBParser; | import org.apache.fop.fonts.type1.PFBParser; | ||||
import org.apache.xmlgraphics.xmp.Metadata; | |||||
/** | /** | ||||
* This class provides method to create and register PDF objects. | * This class provides method to create and register PDF objects. | ||||
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); |
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.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.EncodingMode; | import org.apache.fop.fonts.EncodingMode; | ||||
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, | |||||
configFontInfo.getKerning(), fontResolver); | |||||
fontFile, null, true, configFontInfo.getEmbeddingMode(), | |||||
EncodingMode.AUTO, configFontInfo.getKerning(), fontResolver); | |||||
font = new CustomFontMetricsMapper(fontMetrics); | font = new CustomFontMetricsMapper(fontMetrics); | ||||
} | } | ||||
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.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.PSGenerator; | import org.apache.xmlgraphics.ps.PSGenerator; | ||||
import org.apache.xmlgraphics.ps.PSResource; | import org.apache.xmlgraphics.ps.PSResource; | ||||
import org.apache.xmlgraphics.ps.dsc.ResourceTracker; | import org.apache.xmlgraphics.ps.dsc.ResourceTracker; | ||||
import org.apache.xmlgraphics.util.io.ASCIIHexOutputStream; | |||||
import org.apache.fop.fonts.BFEntry; | import org.apache.fop.fonts.BFEntry; | ||||
import org.apache.fop.fonts.Base14Font; | import org.apache.fop.fonts.Base14Font; | ||||
import org.apache.fop.fonts.CIDFontType; | import org.apache.fop.fonts.CIDFontType; | ||||
import org.apache.fop.fonts.CIDSubset; | import org.apache.fop.fonts.CIDSubset; | ||||
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.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.FontFileReader; | ||||
import org.apache.fop.fonts.truetype.TTFFile; | |||||
import org.apache.fop.fonts.truetype.TTFOutputStream; | |||||
import org.apache.fop.fonts.truetype.TTFSubSetFile; | import org.apache.fop.fonts.truetype.TTFSubSetFile; | ||||
import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion; | import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion; | ||||
import org.apache.fop.render.ps.fonts.PSTTFOutputStream; | |||||
import org.apache.fop.util.HexEncoder; | import org.apache.fop.util.HexEncoder; | ||||
/** | /** | ||||
/** 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 | ||||
* @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 | ||||
*/ | */ | ||||
private static Map writeFontDict(PSGenerator gen, FontInfo fontInfo, Map<String, Typeface> fonts, | |||||
boolean encodeAllCharacters, PSEventProducer eventProducer) throws IOException { | |||||
private static Map writeFontDict(PSGenerator gen, FontInfo fontInfo, | |||||
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.getEmbedFontName()); | PSResource fontRes = new PSResource(PSResource.TYPE_FONT, tf.getEmbedFontName()); | ||||
private static PSFontResource embedFont(PSGenerator gen, Typeface tf, PSResource fontRes, | private static PSFontResource embedFont(PSGenerator gen, Typeface tf, PSResource fontRes, | ||||
PSEventProducer eventProducer) throws IOException { | PSEventProducer eventProducer) throws IOException { | ||||
boolean embeddedFont = false; | |||||
FontType fontType = tf.getFontType(); | FontType fontType = tf.getFontType(); | ||||
PSFontResource fontResource = null; | PSFontResource fontResource = null; | ||||
if (fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE | |||||
|| fontType == FontType.TYPE0) { | |||||
if (tf instanceof CustomFont) { | |||||
CustomFont cf = (CustomFont)tf; | |||||
if (isEmbeddable(cf)) { | |||||
InputStream in = getInputStreamOnFont(gen, cf); | |||||
if (in != null) { | |||||
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); | |||||
embeddedFont = true; | |||||
} else { | |||||
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!"); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
if (!embeddedFont) { | |||||
if (!(fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE | |||||
|| fontType == FontType.TYPE0) || !(tf instanceof CustomFont)) { | |||||
gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); | gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); | ||||
fontResource = PSFontResource.createFontResource(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; | return fontResource; | ||||
} | } | ||||
/* See Adobe Technical Note #5012, "The Type 42 Font Format Specification" */ | /* See Adobe Technical Note #5012, "The Type 42 Font Format Specification" */ | ||||
gen.commentln("%!PS-TrueTypeFont-65536-65536-1"); // TODO TrueType & font versions | gen.commentln("%!PS-TrueTypeFont-65536-65536-1"); // TODO TrueType & font versions | ||||
gen.writeln("11 dict begin"); | gen.writeln("11 dict begin"); | ||||
createType42DictionaryEntries(gen, font, fontStream, font.getCMap()); | |||||
if (font.getEmbeddingMode() == EmbeddingMode.AUTO) { | |||||
font.setEmbeddingMode(EmbeddingMode.SUBSET); | |||||
} | |||||
FontFileReader reader = new FontFileReader(fontStream); | |||||
// TODO is subset-embedding working? In which case the following can be factorized | |||||
// with what is in composeType0Font | |||||
// TTFFile ttfFile; | |||||
// if (font.getEmbeddingMode() != EmbeddingMode.FULL) { | |||||
// ttfFile = new TTFSubSetFile(); | |||||
// ttfFile.readFont(reader, font.getFullName()(), font.getUsedGlyphs()); | |||||
// } else { | |||||
// ttfFile = new TTFFile(); | |||||
// ttfFile.readFont(reader, font.getFullName()); | |||||
// } | |||||
TTFFile ttfFile = new TTFFile(); | |||||
ttfFile.readFont(reader, font.getFullName()); | |||||
createType42DictionaryEntries(gen, font, font.getCMap(), ttfFile); | |||||
gen.writeln("FontName currentdict end definefont pop"); | gen.writeln("FontName currentdict end definefont pop"); | ||||
} | } | ||||
private static void createType42DictionaryEntries(PSGenerator gen, CustomFont font, | private static void createType42DictionaryEntries(PSGenerator gen, CustomFont font, | ||||
InputStream fontStream, BFEntry[] cmap) throws IOException { | |||||
BFEntry[] cmap, TTFFile ttfFile) throws IOException { | |||||
gen.write("/FontName /"); | gen.write("/FontName /"); | ||||
gen.write(font.getEmbedFontName()); | gen.write(font.getEmbedFontName()); | ||||
gen.writeln(" def"); | gen.writeln(" def"); | ||||
gen.writeln("/Encoding 256 array"); | gen.writeln("/Encoding 256 array"); | ||||
gen.writeln("0 1 255{1 index exch/.notdef put}for"); | gen.writeln("0 1 255{1 index exch/.notdef put}for"); | ||||
boolean buildCharStrings; | boolean buildCharStrings; | ||||
if (font.getFontType() == FontType.TYPE0) { | |||||
Set<String> glyphNames = new HashSet<String>(); | |||||
if (font.getFontType() == FontType.TYPE0 && font.getEmbeddingMode() != EmbeddingMode.FULL) { | |||||
//"/Encoding" is required but ignored for CID fonts | //"/Encoding" is required but ignored for CID fonts | ||||
//so we keep it minimal to save space | //so we keep it minimal to save space | ||||
buildCharStrings = false; | buildCharStrings = false; | ||||
gen.write(Glyphs.NOTDEF); | gen.write(Glyphs.NOTDEF); | ||||
} else { | } else { | ||||
gen.write(glyphName); | gen.write(glyphName); | ||||
glyphNames.add(glyphName); | |||||
} | } | ||||
gen.writeln(" put"); | gen.writeln(" put"); | ||||
} | } | ||||
} | } | ||||
gen.writeln("readonly def"); | gen.writeln("readonly def"); | ||||
gen.write("/sfnts["); | |||||
/* | |||||
* 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, one newline character will be added every 40 bytes, each | |||||
* string should start at a 4-byte boundary | |||||
* => buffer size = floor((65535 - 1) * 40 / 81 / 4) * 4 | |||||
* TODO this is not robust: depends on how often ASCIIHexOutputStream adds a newline | |||||
*/ | |||||
// TODO does not follow Technical Note #5012's requirements: | |||||
// "strings must begin at TrueType table boundaries, or at individual glyph | |||||
// boundaries within the glyf table." | |||||
// There may be compatibility issues with older PostScript interpreters | |||||
byte[] buffer = new byte[32360]; | |||||
int readCount; | |||||
while ((readCount = fontStream.read(buffer)) > 0) { | |||||
ASCIIHexOutputStream hexOut = new ASCIIHexOutputStream(gen.getOutputStream()); | |||||
gen.writeln("<"); | |||||
hexOut.write(buffer, 0, readCount); | |||||
gen.write("> "); | |||||
} | |||||
gen.writeln("]def"); | |||||
TTFOutputStream ttfOut = new PSTTFOutputStream(gen); | |||||
ttfFile.stream(ttfOut); | |||||
buildCharStrings(gen, buildCharStrings, cmap, glyphNames, font); | |||||
} | |||||
private static void buildCharStrings(PSGenerator gen, boolean buildCharStrings, | |||||
BFEntry[] cmap, Set<String> glyphNames, CustomFont font) throws IOException { | |||||
gen.write("/CharStrings "); | gen.write("/CharStrings "); | ||||
if (buildCharStrings) { | |||||
if (!buildCharStrings) { | |||||
gen.write(1); | |||||
} else if (font.getEmbeddingMode() != EmbeddingMode.FULL) { | |||||
int charCount = 1; //1 for .notdef | int charCount = 1; //1 for .notdef | ||||
for (BFEntry entry : cmap) { | for (BFEntry entry : cmap) { | ||||
charCount += entry.getUnicodeEnd() - entry.getUnicodeStart() + 1; | charCount += entry.getUnicodeEnd() - entry.getUnicodeStart() + 1; | ||||
} | } | ||||
gen.write(charCount); | gen.write(charCount); | ||||
} else { | } else { | ||||
gen.write(1); | |||||
gen.write(font.getCMap().length); | |||||
} | } | ||||
gen.writeln(" dict dup begin"); | gen.writeln(" dict dup begin"); | ||||
gen.write("/"); | gen.write("/"); | ||||
gen.write(Glyphs.NOTDEF); | gen.write(Glyphs.NOTDEF); | ||||
gen.writeln(" 0 def"); // .notdef always has to be at index 0 | gen.writeln(" 0 def"); // .notdef always has to be at index 0 | ||||
if (buildCharStrings) { | |||||
//Only performed in singly-byte mode, ignored for CID fonts | |||||
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 (BFEntry entry : cmap) { | for (BFEntry entry : cmap) { | ||||
int glyphIndex = entry.getGlyphStartIndex(); | int glyphIndex = entry.getGlyphStartIndex(); | ||||
for (int ch = entry.getUnicodeStart(); ch <= entry.getUnicodeEnd(); ch++) { | for (int ch = entry.getUnicodeStart(); ch <= entry.getUnicodeEnd(); ch++) { | ||||
char ch16 = (char)ch; //TODO Handle Unicode characters beyond 16bit | char ch16 = (char)ch; //TODO Handle Unicode characters beyond 16bit | ||||
String glyphName = Glyphs.charToGlyphName(ch16); | String glyphName = Glyphs.charToGlyphName(ch16); | ||||
if ("".equals(glyphName)) { | if ("".equals(glyphName)) { | ||||
glyphName = "u" + Integer.toHexString(ch).toUpperCase(); | glyphName = "u" + Integer.toHexString(ch).toUpperCase(); | ||||
} | } | ||||
gen.write("/"); | |||||
gen.write(glyphName); | |||||
gen.write(" "); | |||||
gen.write(glyphIndex); | |||||
gen.writeln(" def"); | |||||
writeGlyphDefs(gen, glyphName, glyphIndex); | |||||
glyphIndex++; | glyphIndex++; | ||||
} | } | ||||
} | } | ||||
} else { | |||||
for (String name : glyphNames) { | |||||
writeGlyphDefs(gen, name, | |||||
getGlyphIndex(Glyphs.getUnicodeSequenceForGlyphName(name).charAt(0), | |||||
font.getCMap())); | |||||
} | |||||
} | } | ||||
gen.writeln("end readonly def"); | 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, BFEntry[] cmap) { | private static int getGlyphIndex(char c, BFEntry[] cmap) { | ||||
for (BFEntry entry : cmap) { | for (BFEntry entry : cmap) { | ||||
if (entry.getUnicodeStart() <= c && c <= entry.getUnicodeEnd()) { | if (entry.getUnicodeStart() <= c && c <= entry.getUnicodeEnd()) { | ||||
gen.writeln("] composefont pop"); | gen.writeln("] composefont pop"); | ||||
} | } | ||||
private static PSResource embedType2CIDFont(final PSGenerator gen, | |||||
private static PSResource embedType2CIDFont(PSGenerator gen, | |||||
MultiByteFont font, InputStream fontStream) throws IOException { | MultiByteFont font, InputStream fontStream) throws IOException { | ||||
assert font.getCIDType() == CIDFontType.CIDTYPE2; | assert font.getCIDType() == CIDFontType.CIDTYPE2; | ||||
lineCount = 1; | lineCount = 1; | ||||
} | } | ||||
} | } | ||||
String gid = HexEncoder.encode(cid, 4); | |||||
String gid; | |||||
if (font.getEmbeddingMode() != EmbeddingMode.FULL) { | |||||
gid = HexEncoder.encode(cid, 4); | |||||
} else { | |||||
gid = HexEncoder.encode(cidSubset.getGlyphIndexForSubsetIndex(cid), 4); | |||||
} | |||||
gen.write(gid); | gen.write(gid); | ||||
} | } | ||||
gen.writeln(">] def"); | gen.writeln(">] def"); | ||||
FontFileReader reader = new FontFileReader(fontStream); | |||||
//Create tables for subset | |||||
TTFSubSetFile subset = new TTFSubSetFile(); | |||||
TTFSubSetFile.GlyphHandler glyphHandler = new TTFSubSetFile.GlyphHandler() { | |||||
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()); | |||||
} | |||||
public void addGlyph(byte[] glyphData) throws IOException { | |||||
ASCIIHexOutputStream hexOut = new ASCIIHexOutputStream(gen.getOutputStream()); | |||||
gen.writeln("<"); | |||||
hexOut.write(glyphData); | |||||
gen.writeln(">"); | |||||
} | |||||
}; | |||||
gen.writeln("/GlyphDirectory ["); | |||||
FontFileReader reader = new FontFileReader(fontStream); | |||||
byte[] subsetFont = subset.toPostScriptSubset(reader, | |||||
font.getTTCName(), font.getUsedGlyphs(), glyphHandler); | |||||
gen.writeln("] def"); | |||||
InputStream subsetInput = new java.io.ByteArrayInputStream(subsetFont); | |||||
createType42DictionaryEntries(gen, font, subsetInput, new BFEntry[0]); | |||||
createType42DictionaryEntries(gen, font, new BFEntry[0], ttfFile); | |||||
gen.writeln("CIDFontName currentdict end /CIDFont defineresource pop"); | gen.writeln("CIDFontName currentdict end /CIDFont defineresource pop"); | ||||
gen.writeln("end"); | gen.writeln("end"); | ||||
gen.writeln("%%EndResource"); | gen.writeln("%%EndResource"); | ||||
gen.getResourceTracker().registerSuppliedResource(res); | gen.getResourceTracker().registerSuppliedResource(res); | ||||
return res; | return res; | ||||
} | } | ||||
} | } |
} | } | ||||
} | } | ||||
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[] dx, | int letterSpacing, int wordSpacing, int[] dx, | ||||
Font font, Typeface tf, boolean multiByte) throws IOException { | Font font, Typeface tf, boolean multiByte) throws IOException { | ||||
PSGenerator generator = getGenerator(); | PSGenerator generator = getGenerator(); | ||||
boolean hasLetterSpacing = (letterSpacing != 0); | boolean hasLetterSpacing = (letterSpacing != 0); | ||||
boolean needTJ = false; | boolean needTJ = false; | ||||
char strOpen; | |||||
char strClose; | |||||
if (multiByte) { | |||||
strOpen = '<'; | |||||
strClose = '>'; | |||||
} else { | |||||
strOpen = '('; | |||||
strClose = ')'; | |||||
} | |||||
int lineStart = 0; | int lineStart = 0; | ||||
StringBuffer accText = new StringBuffer(initialSize); | StringBuffer accText = new StringBuffer(initialSize); | ||||
StringBuffer sb = new StringBuffer(initialSize); | StringBuffer sb = new StringBuffer(initialSize); | ||||
sb.append(PSGenerator.LF); | sb.append(PSGenerator.LF); | ||||
lineStart = sb.length(); | lineStart = sb.length(); | ||||
} | } | ||||
sb.append(strOpen); | |||||
sb.append(accText); | |||||
sb.append(strClose); | |||||
lineStart = writePostScriptString(sb, accText, multiByte, lineStart); | |||||
sb.append(' '); | sb.append(' '); | ||||
accText.setLength(0); //reset accumulated text | accText.setLength(0); //reset accumulated text | ||||
} | } | ||||
} | } | ||||
if (needTJ) { | if (needTJ) { | ||||
if (accText.length() > 0) { | if (accText.length() > 0) { | ||||
sb.append(strOpen); | |||||
sb.append(accText); | |||||
sb.append(strClose); | |||||
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(strOpen).append(accText).append(strClose); | |||||
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 { | ||||
PSFontResource res = this.documentHandler.getPSResourceForFontKey(key); | PSFontResource res = this.documentHandler.getPSResourceForFontKey(key); | ||||
PSGenerator generator = getGenerator(); | PSGenerator generator = getGenerator(); | ||||
res.notifyResourceUsageOnPage(generator.getResourceTracker()); | res.notifyResourceUsageOnPage(generator.getResourceTracker()); | ||||
} | } | ||||
} | } |
/* | |||||
* 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 | |||||
* True Type 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; | |||||
/** | |||||
* Constructor - initialises the PSGenerator in this wrapper class. | |||||
* @param gen PSGenerator | |||||
*/ | |||||
public PSTTFGenerator(PSGenerator gen) { | |||||
this.gen = gen; | |||||
hexOut = new ASCIIHexOutputStream(gen.getOutputStream()); | |||||
} | |||||
/** | |||||
* Begins writing a string by writing '<' to the begin. | |||||
* @throws IOException file write exception. | |||||
*/ | |||||
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("<"); | |||||
} | |||||
/** | |||||
* Streams a string to a PostScript document (wraps PSGenerator.write(String)). | |||||
* @param cmd String | |||||
* @throws IOException file write exception | |||||
*/ | |||||
public void write(String cmd) throws IOException { | |||||
gen.write(cmd); | |||||
} | |||||
/** | |||||
* Streams a string followed by a new line char to a PostScript document (wraps | |||||
* PSGenerator.writeln(String)). | |||||
* @param cmd String | |||||
* @throws IOException file write exception | |||||
*/ | |||||
public void writeln(String cmd) throws IOException { | |||||
gen.writeln(cmd); | |||||
} | |||||
/** | |||||
* Streams the bytes. | |||||
* @param byteArray byte[] the byte array to stream to file. | |||||
* @param offset int the starting position in the byte array to stream to file. | |||||
* @param length the number of bytes to stream to file. This MUST be less than | |||||
* MAX_BUFFER_SIZE - 1 since strings are suffixed by '00' (as in spec). | |||||
* @throws IOException file write exception | |||||
*/ | |||||
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. | |||||
* @throws IOException file write exception | |||||
*/ | |||||
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; | |||||
/** | |||||
* This class streams glyphs from the "glyf" table in a True Type font. | |||||
*/ | |||||
public class PSTTFGlyphOutputStream implements TTFGlyphOutputStream { | |||||
/** This counts the total number of bytes written that have been streamed. */ | |||||
private int byteCounter = 0; | |||||
/** This is a place-holder for the offset of the last string boundary. */ | |||||
private int lastStringBoundary = 0; | |||||
private PSTTFGenerator ttfGen; | |||||
/** | |||||
* Constructor | |||||
* @param ttfGen PSTTFGenerator | |||||
*/ | |||||
public PSTTFGlyphOutputStream(PSTTFGenerator ttfGen) { | |||||
this.ttfGen = ttfGen; | |||||
} | |||||
/** {@inheritDoc} */ | |||||
public void startGlyphStream() throws IOException { | |||||
ttfGen.startString(); | |||||
} | |||||
/** {@inheritDoc} */ | |||||
public void streamGlyph(byte[] byteArray, int offset, int length) throws IOException { | |||||
if (length > PSTTFGenerator.MAX_BUFFER_SIZE) { | |||||
throw new UnsupportedOperationException("The glyph is " + length + " there may be an " | |||||
+ "error in the font file."); | |||||
} | |||||
if (length + (byteCounter - lastStringBoundary) < PSTTFGenerator.MAX_BUFFER_SIZE) { | |||||
ttfGen.streamBytes(byteArray, offset, length); | |||||
} else { | |||||
ttfGen.endString(); | |||||
lastStringBoundary = byteCounter; | |||||
ttfGen.startString(); | |||||
ttfGen.streamBytes(byteArray, offset, length); | |||||
} | |||||
byteCounter += length; | |||||
} | |||||
/** {@inheritDoc} */ | |||||
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.fop.fonts.truetype.TTFGlyphOutputStream; | |||||
import org.apache.fop.fonts.truetype.TTFOutputStream; | |||||
import org.apache.fop.fonts.truetype.TTFTableOutputStream; | |||||
import org.apache.xmlgraphics.ps.PSGenerator; | |||||
/** | |||||
* Implements TTFOutputStream and streams font tables to a PostScript file. | |||||
*/ | |||||
public class PSTTFOutputStream implements TTFOutputStream { | |||||
/** The wrapper class for PSGenerator */ | |||||
private final PSTTFGenerator ttfGen; | |||||
/** | |||||
* Constructor - assigns a PSGenerator to stream the font. | |||||
* @param gen PSGenerator. | |||||
*/ | |||||
public PSTTFOutputStream(PSGenerator gen) { | |||||
this.ttfGen = new PSTTFGenerator(gen); | |||||
} | |||||
/** {@inheritDoc} */ | |||||
public void startFontStream() throws IOException { | |||||
ttfGen.write("/sfnts["); | |||||
} | |||||
/** {@inheritDoc} */ | |||||
public TTFTableOutputStream getTableOutputStream() { | |||||
return new PSTTFTableOutputStream(ttfGen); | |||||
} | |||||
/** {@inheritDoc} */ | |||||
public TTFGlyphOutputStream getGlyphOutputStream() { | |||||
return new PSTTFGlyphOutputStream(ttfGen); | |||||
} | |||||
/** {@inheritDoc} */ | |||||
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; | |||||
/** | |||||
* This class streams a truetype table to a PostScript file. | |||||
* | |||||
*/ | |||||
public class PSTTFTableOutputStream implements TTFTableOutputStream { | |||||
private PSTTFGenerator ttfGen; | |||||
/** | |||||
* Constructor. | |||||
* @param ttfGen PSGenerator the streamer class used for streaming bytes. | |||||
*/ | |||||
public PSTTFTableOutputStream(PSTTFGenerator ttfGen) { | |||||
this.ttfGen = ttfGen; | |||||
} | |||||
/** {@inheritDoc} */ | |||||
public void streamTable(byte[] byteArray, int offset, int length) throws IOException { | |||||
int offsetPosition = offset; | |||||
// Need to split the table into MAX_BUFFER_SIZE chunks | |||||
for (int i = 0; i < length / PSTTFGenerator.MAX_BUFFER_SIZE; i++) { | |||||
streamString(byteArray, offsetPosition, PSTTFGenerator.MAX_BUFFER_SIZE); | |||||
offsetPosition += PSTTFGenerator.MAX_BUFFER_SIZE; | |||||
} | |||||
if (length % PSTTFGenerator.MAX_BUFFER_SIZE > 0) { | |||||
streamString(byteArray, offsetPosition, length % PSTTFGenerator.MAX_BUFFER_SIZE); | |||||
} | |||||
} | |||||
private void streamString(byte[] byteArray, int offset, int length) throws IOException { | |||||
ttfGen.startString(); | |||||
ttfGen.streamBytes(byteArray, offset, length); | |||||
ttfGen.endString(); | |||||
} | |||||
} |
/** | /** | ||||
* sets up the testcase by loading the DejaVu Font. | * sets up the testcase by loading the DejaVu Font. | ||||
* | |||||
* | |||||
* @throws Exception | * @throws Exception | ||||
* if the test fails. | * if the test fails. | ||||
*/ | */ | ||||
public void setUp() throws Exception { | public void setUp() throws Exception { | ||||
File file = new File("test/resources/fonts/DejaVuLGCSerif.ttf"); | File file = new File("test/resources/fonts/DejaVuLGCSerif.ttf"); | ||||
font = FontLoader.loadFont(file, "", true, EncodingMode.AUTO, | |||||
font = FontLoader.loadFont(file, "", true, EmbeddingMode.AUTO, EncodingMode.AUTO, | |||||
fontResolver); | fontResolver); | ||||
} | } | ||||
import junit.framework.TestCase; | import junit.framework.TestCase; | ||||
/** | |||||
* Tests the enum org.apache.fop.fonts.EncodingMode. | |||||
*/ | |||||
public class EncodingModeTest extends TestCase { | public class EncodingModeTest extends TestCase { | ||||
/** | |||||
* Test getName() - tests the getName() method returns the expected String. | |||||
*/ | |||||
public void testGetName() { | public void testGetName() { | ||||
assertEquals("auto", EncodingMode.AUTO.getName()); | assertEquals("auto", EncodingMode.AUTO.getName()); | ||||
assertEquals("single-byte", EncodingMode.SINGLE_BYTE.getName()); | assertEquals("single-byte", EncodingMode.SINGLE_BYTE.getName()); | ||||
assertEquals("cid", EncodingMode.CID.getName()); | assertEquals("cid", EncodingMode.CID.getName()); | ||||
} | } | ||||
/** | |||||
* Test getValue() - test that getValue() method returns the expected enum value when given | |||||
* an appropriate String. | |||||
*/ | |||||
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")); | |||||
try { | |||||
// We expect this to fail | |||||
assertEquals(EncodingMode.AUTO, EncodingMode.getValue("fail")); | |||||
fail("Encoding mode fails to throw an appropriate exception"); | |||||
} catch (IllegalArgumentException e) { | |||||
// PASS | |||||
} | |||||
} | } | ||||
} | } |
/* | |||||
* 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 junit.framework.Test; | |||||
import junit.framework.TestSuite; | |||||
import org.apache.fop.fonts.truetype.FontFileReaderTest; | |||||
import org.apache.fop.fonts.truetype.TTFFileTest; | |||||
import org.apache.fop.fonts.truetype.TTFSubSetFileTest; | |||||
import org.apache.fop.fonts.truetype.TTFTableNameTest; | |||||
/** | |||||
* A test suite designed for org.apache.fop.fonts.* | |||||
*/ | |||||
public final class FOPFontsTestSuite { | |||||
/** | |||||
* Constructor | |||||
*/ | |||||
private FOPFontsTestSuite() { | |||||
} | |||||
/** | |||||
* Testing org.apache.fop.fonts.* | |||||
* @return test | |||||
*/ | |||||
public static Test suite() { | |||||
TestSuite testSuite = new TestSuite("Test suite for FOPs fonts classes"); | |||||
//$JUnit-BEGIN$ | |||||
testSuite.addTest(new TestSuite(EncodingModeTest.class)); | |||||
testSuite.addTest(new TestSuite(FontFileReaderTest.class)); | |||||
testSuite.addTest(new TestSuite(TTFFileTest.class)); | |||||
testSuite.addTest(new TestSuite(TTFSubSetFileTest.class)); | |||||
testSuite.addTest(new TestSuite(TTFTableNameTest.class)); | |||||
//$JUnit-END$ | |||||
return testSuite; | |||||
} | |||||
} |
/* | |||||
* 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 junit.framework.TestCase; | |||||
/** | |||||
* A test class for org.apache.fop.truetype.FontFileReader | |||||
*/ | |||||
public class FontFileReaderTest extends TestCase { | |||||
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 FontFileReaderTest() { | |||||
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. | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
public void testGetCurrentPos() throws IOException { | |||||
fontReader.seekSet(10); | |||||
fontReader.skip(100); | |||||
assertEquals(110, fontReader.getCurrentPos()); | |||||
fontReader.readTTFByte(); | |||||
assertEquals(111, fontReader.getCurrentPos()); | |||||
} | |||||
/** | |||||
* Test getFileSize() | |||||
*/ | |||||
public void testGetFileSize() { | |||||
assertEquals(256, fontReader.getFileSize()); | |||||
} | |||||
/** | |||||
* Test readTTFUByte() | |||||
* @throws IOException exception | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
public void testGetBytes() throws IOException { | |||||
byte[] retrievedBytes = fontReader.getBytes(0, 256); | |||||
assertTrue(Arrays.equals(byteArray, retrievedBytes)); | |||||
} | |||||
} |
/* | |||||
* 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 junit.framework.TestCase; | |||||
import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion; | |||||
/** | |||||
* Class for testing org.apache.fop.fonts.truetype.TTFFile | |||||
*/ | |||||
public class TTFFileTest extends TestCase { | |||||
// 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 TTFFileTest() throws IOException { | |||||
dejavuTTFFile = new TTFFile(); | |||||
dejavuReader = new FontFileReader("test/resources/fonts/DejaVuLGCSerif.ttf"); | |||||
dejavuTTFFile.readFont(dejavuReader); | |||||
droidmonoTTFFile = new TTFFile(); | |||||
droidmonoReader = new FontFileReader("test/resources/fonts/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) | |||||
*/ | |||||
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 | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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". | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
public void testGetItalicAngle() { | |||||
assertEquals("0", dejavuTTFFile.getItalicAngle()); | |||||
assertEquals("0", droidmonoTTFFile.getItalicAngle()); | |||||
} | |||||
/** | |||||
* Test getKerning() - Test values retrieved from the kern table in the font file. | |||||
*/ | |||||
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 | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
public void testGetPostScriptName() { | |||||
assertEquals(PostScriptVersion.V2, dejavuTTFFile.getPostScriptVersion()); | |||||
assertEquals(PostScriptVersion.V2, droidmonoTTFFile.getPostScriptVersion()); | |||||
} | |||||
/** | |||||
* Test getStemV() - Undefined. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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 | |||||
} | |||||
} |
/* | |||||
* 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; | |||||
/** | |||||
* This class tests TTFSubSetFile | |||||
* TODO: Test with more than just a single font | |||||
*/ | |||||
public class TTFSubSetFileTest extends TTFFileTest { | |||||
private TTFSubSetFile ttfSubset; | |||||
private byte[] subset; | |||||
/** | |||||
* Constructor | |||||
* @throws IOException exception | |||||
*/ | |||||
public TTFSubSetFileTest() throws IOException { | |||||
super(); | |||||
} | |||||
/** | |||||
* setUp() | |||||
* @exception IOException file read error | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 junit.framework.TestCase; | |||||
/** | |||||
* This class tests the enum org.apache.fop.fonts.truetype.TTFTableName | |||||
* | |||||
*/ | |||||
public class TTFTableNameTest extends TestCase { | |||||
/** | |||||
* Test getName() - tests that the getName() method returns the expected String as expected in | |||||
* the Directory Table. | |||||
* @exception IllegalAccessException error | |||||
*/ | |||||
public void testGetName() throws IllegalAccessException { | |||||
assertEquals("dirTable", TTFTableName.DIRECTORY_TABLE.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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 | |||||
*/ | |||||
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.ps; | |||||
import junit.framework.Test; | |||||
import junit.framework.TestSuite; | |||||
import org.apache.fop.render.ps.fonts.PSTTFGeneratorTest; | |||||
import org.apache.fop.render.ps.fonts.PSTTFGlyphOutputStreamTest; | |||||
import org.apache.fop.render.ps.fonts.PSTTFOutputStreamTest; | |||||
import org.apache.fop.render.ps.fonts.PSTTFTableOutputStreamTest; | |||||
/** | |||||
* A test Suite for org.apache.fop.render.ps.* | |||||
*/ | |||||
public final class RenderPSTestSuite { | |||||
/** | |||||
* Constructor. | |||||
*/ | |||||
private RenderPSTestSuite() { | |||||
} | |||||
/** | |||||
* Testing org.apache.fop.render.ps.* | |||||
* @return test | |||||
*/ | |||||
public static Test suite() { | |||||
TestSuite suite = new TestSuite(); | |||||
//$JUnit-BEGIN$ | |||||
suite.addTest(new TestSuite(PSTTFGeneratorTest.class)); | |||||
suite.addTest(new TestSuite(PSTTFOutputStreamTest.class)); | |||||
suite.addTest(new TestSuite(PSTTFGlyphOutputStreamTest.class)); | |||||
suite.addTest(new TestSuite(PSTTFTableOutputStreamTest.class)); | |||||
//$JUnit-END$ | |||||
return suite; | |||||
} | |||||
} |
/* | |||||
* 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 junit.framework.TestCase; | |||||
import org.apache.xmlgraphics.ps.PSGenerator; | |||||
/** | |||||
* The test class for org.apache.fop.render.ps.fonts.PSGenerator | |||||
*/ | |||||
public class PSTTFGeneratorTest extends TestCase { | |||||
private PSTTFGenerator ttfGen; | |||||
private ByteArrayOutputStream out = new ByteArrayOutputStream(); | |||||
private PSGenerator gen = new PSGenerator(out); | |||||
private byte[] byteArray; | |||||
/** | |||||
* Constructor | |||||
*/ | |||||
public PSTTFGeneratorTest() { | |||||
byteArray = new byte[65536]; | |||||
for (int i = 0; i < 65536; i++) { | |||||
byteArray[i] = (byte) i; | |||||
} | |||||
} | |||||
@Override | |||||
public void setUp() { | |||||
ttfGen = new PSTTFGenerator(gen); | |||||
} | |||||
/** | |||||
* Tests startString() - starts the string in an appropriate way for a PostScript file. | |||||
* @exception IOException write error | |||||
*/ | |||||
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. | |||||
*/ | |||||
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. | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 static org.mockito.Mockito.inOrder; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.times; | |||||
import static org.mockito.Mockito.verify; | |||||
import java.io.IOException; | |||||
import junit.framework.TestCase; | |||||
import org.mockito.InOrder; | |||||
/** | |||||
* Test class for PSTTFGlyphOutputStream | |||||
*/ | |||||
public class PSTTFGlyphOutputStreamTest extends TestCase { | |||||
private PSTTFGenerator mockGen; | |||||
private PSTTFGlyphOutputStream glyphOut; | |||||
@Override | |||||
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 | |||||
*/ | |||||
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. | |||||
*/ | |||||
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 | |||||
*/ | |||||
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 static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.verify; | |||||
import java.io.IOException; | |||||
import junit.framework.TestCase; | |||||
import org.apache.fop.fonts.truetype.TTFGlyphOutputStream; | |||||
import org.apache.fop.fonts.truetype.TTFTableOutputStream; | |||||
import org.apache.xmlgraphics.ps.PSGenerator; | |||||
/** | |||||
* Tests PSTTFOuputStream | |||||
*/ | |||||
public class PSTTFOutputStreamTest extends TestCase { | |||||
private PSGenerator gen; | |||||
private PSTTFOutputStream out; | |||||
/** | |||||
* Assigns an OutputStream to the PSGenerator. | |||||
*/ | |||||
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. | |||||
*/ | |||||
public void testStartFontStream() throws IOException { | |||||
out.startFontStream(); | |||||
verify(gen).write("/sfnts["); | |||||
} | |||||
/** | |||||
* Test getTableOutputStream() - we need to test that the inheritance model is properly obeyed. | |||||
*/ | |||||
public void testGetTableOutputStream() { | |||||
TTFTableOutputStream tableOut = out.getTableOutputStream(); | |||||
assertTrue(tableOut instanceof PSTTFTableOutputStream); | |||||
} | |||||
/** | |||||
* Test getGlyphOutputStream() - we need to test that the inheritance model is properly obeyed. | |||||
*/ | |||||
public void testGetGlyphOutputStream() { | |||||
TTFGlyphOutputStream glyphOut = out.getGlyphOutputStream(); | |||||
assertTrue(glyphOut instanceof PSTTFGlyphOutputStream); | |||||
} | |||||
/** | |||||
* Test endFontStream() | |||||
* @exception IOException write error. | |||||
*/ | |||||
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 static org.mockito.Mockito.inOrder; | |||||
import static org.mockito.Mockito.mock; | |||||
import java.io.IOException; | |||||
import junit.framework.TestCase; | |||||
import org.mockito.InOrder; | |||||
/** | |||||
* Test class for unit testing PSTTFTableOutputStream | |||||
*/ | |||||
public class PSTTFTableOutputStreamTest extends TestCase { | |||||
private PSTTFGenerator mockGen; | |||||
private PSTTFTableOutputStream tableOut; | |||||
@Override | |||||
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. | |||||
*/ | |||||
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(); | |||||
} | |||||
} |
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. |