瀏覽代碼

Bugzilla #50483: Improved support for TrueType fonts in PostScript

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-ffa450edef68
pull/26/head
Vincent Hennebert 12 年之前
父節點
當前提交
ac5c61bd29
共有 45 個文件被更改,包括 2952 次插入930 次删除
  1. 13
    2
      build.xml
  2. 3
    5
      src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java
  3. 30
    5
      src/java/org/apache/fop/fonts/BFEntry.java
  4. 17
    1
      src/java/org/apache/fop/fonts/CustomFont.java
  5. 1
    1
      src/java/org/apache/fop/fonts/CustomFontCollection.java
  6. 23
    0
      src/java/org/apache/fop/fonts/EmbedFontInfo.java
  7. 56
    0
      src/java/org/apache/fop/fonts/EmbeddingMode.java
  8. 1
    1
      src/java/org/apache/fop/fonts/EncodingMode.java
  9. 5
    1
      src/java/org/apache/fop/fonts/FontInfoConfigurator.java
  10. 15
    8
      src/java/org/apache/fop/fonts/FontLoader.java
  11. 6
    5
      src/java/org/apache/fop/fonts/LazyFont.java
  12. 6
    0
      src/java/org/apache/fop/fonts/MutableFont.java
  13. 12
    18
      src/java/org/apache/fop/fonts/apps/TTFReader.java
  14. 5
    2
      src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
  15. 12
    17
      src/java/org/apache/fop/fonts/truetype/FontFileReader.java
  16. 0
    122
      src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java
  17. 8
    0
      src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java
  18. 328
    259
      src/java/org/apache/fop/fonts/truetype/TTFFile.java
  19. 15
    21
      src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
  20. 48
    0
      src/java/org/apache/fop/fonts/truetype/TTFGlyphOutputStream.java
  21. 51
    0
      src/java/org/apache/fop/fonts/truetype/TTFOutputStream.java
  22. 211
    321
      src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
  23. 158
    0
      src/java/org/apache/fop/fonts/truetype/TTFTableName.java
  24. 37
    0
      src/java/org/apache/fop/fonts/truetype/TTFTableOutputStream.java
  25. 3
    5
      src/java/org/apache/fop/pdf/PDFFactory.java
  26. 2
    3
      src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
  27. 126
    108
      src/java/org/apache/fop/render/ps/PSFontUtils.java
  28. 33
    20
      src/java/org/apache/fop/render/ps/PSPainter.java
  29. 104
    0
      src/java/org/apache/fop/render/ps/fonts/PSTTFGenerator.java
  30. 72
    0
      src/java/org/apache/fop/render/ps/fonts/PSTTFGlyphOutputStream.java
  31. 64
    0
      src/java/org/apache/fop/render/ps/fonts/PSTTFOutputStream.java
  32. 58
    0
      src/java/org/apache/fop/render/ps/fonts/PSTTFTableOutputStream.java
  33. 2
    2
      test/java/org/apache/fop/fonts/DejaVuLGCSerifTest.java
  34. 20
    3
      test/java/org/apache/fop/fonts/EncodingModeTest.java
  35. 54
    0
      test/java/org/apache/fop/fonts/FOPFontsTestSuite.java
  36. 283
    0
      test/java/org/apache/fop/fonts/truetype/FontFileReaderTest.java
  37. 399
    0
      test/java/org/apache/fop/fonts/truetype/TTFFileTest.java
  38. 69
    0
      test/java/org/apache/fop/fonts/truetype/TTFSubSetFileTest.java
  39. 145
    0
      test/java/org/apache/fop/fonts/truetype/TTFTableNameTest.java
  40. 55
    0
      test/java/org/apache/fop/render/ps/RenderPSTestSuite.java
  41. 111
    0
      test/java/org/apache/fop/render/ps/fonts/PSTTFGeneratorTest.java
  42. 105
    0
      test/java/org/apache/fop/render/ps/fonts/PSTTFGlyphOutputStreamTest.java
  43. 82
    0
      test/java/org/apache/fop/render/ps/fonts/PSTTFOutputStreamTest.java
  44. 86
    0
      test/java/org/apache/fop/render/ps/fonts/PSTTFTableOutputStreamTest.java
  45. 18
    0
      test/resources/fonts/DroidSansMono.LICENSE

+ 13
- 2
build.xml 查看文件

@@ -90,7 +90,10 @@ list of possible build targets.
<include name="lib/build/jaxen*"/>
<include name="lib/build/pmd*"/>
<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>
<fileset dir="${basedir}" id="dist.src">
<include name="src/**"/>
@@ -944,7 +947,15 @@ list of possible build targets.
<test name="org.apache.fop.text.linebreak.LineBreakStatusTest" todir="${junit.reports.dir}"/>
</junit>
</target>
<target name="junit-reduced" depends="junit-userconfig, junit-basic, junit-transcoder, junit-text-linebreak, junit-fotree"/>
<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" 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>

+ 3
- 5
src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java 查看文件

@@ -37,18 +37,16 @@ import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.Node;

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.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
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

+ 30
- 5
src/java/org/apache/fop/fonts/BFEntry.java 查看文件

@@ -22,14 +22,13 @@ package org.apache.fop.fonts;
/**
* 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 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.
@@ -43,6 +42,32 @@ public class BFEntry {
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.
* @return the Unicode start index

+ 17
- 1
src/java/org/apache/fop/fonts/CustomFont.java 查看文件

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

private int capHeight = 0;
private int xHeight = 0;
@@ -113,6 +114,14 @@ public abstract class CustomFont extends Typeface
return embedFileName;
}

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

/**
* Returns a Source representing an embeddable font file.
* @return Source for an embeddable font file
@@ -262,7 +271,7 @@ public abstract class CustomFont extends Typeface
return lastChar;
}

/**
/**MutableFont
* Used to determine if kerning is enabled.
* @return True if kerning is enabled.
*/
@@ -327,6 +336,13 @@ public abstract class CustomFont extends Typeface
this.embedResourceName = name;
}

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

/**
* {@inheritDoc}
*/

+ 1
- 1
src/java/org/apache/fop/fonts/CustomFontCollection.java 查看文件

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

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

+ 23
- 0
src/java/org/apache/fop/fonts/EmbedFontInfo.java 查看文件

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

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

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

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

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

/**
* Defines whether the font is embedded or not.
* @param value true to embed the font, false to reference it
@@ -163,6 +175,17 @@ public class EmbedFontInfo implements Serializable {
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)
throws IOException, ClassNotFoundException {
in.defaultReadObject();

+ 56
- 0
src/java/org/apache/fop/fonts/EmbeddingMode.java 查看文件

@@ -0,0 +1,56 @@
/*
* 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);
}
}

+ 1
- 1
src/java/org/apache/fop/fonts/EncodingMode.java 查看文件

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

+ 5
- 1
src/java/org/apache/fop/fonts/FontInfoConfigurator.java 查看文件

@@ -251,11 +251,15 @@ public class FontInfoConfigurator {
}

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

if (fontCache != null) {
if (!fontCache.containsFont(embedFontInfo)) {
fontCache.addFont(embedFontInfo);

+ 15
- 8
src/java/org/apache/fop/fonts/FontLoader.java 查看文件

@@ -30,7 +30,6 @@ import javax.xml.transform.stream.StreamSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.fonts.truetype.TTFFontLoader;
import org.apache.fop.fonts.type1.Type1FontLoader;

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

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

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

+ 6
- 5
src/java/org/apache/fop/fonts/LazyFont.java 查看文件

@@ -27,12 +27,10 @@ import java.util.Set;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

import org.xml.sax.InputSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.apps.FOPException;
import org.xml.sax.InputSource;

/**
* This class is used to defer the loading of a font until it is really used.
@@ -45,6 +43,7 @@ public class LazyFont extends Typeface implements FontDescriptor {
private String fontEmbedPath = null;
private boolean useKerning = false;
private EncodingMode encodingMode = EncodingMode.AUTO;
private EmbeddingMode embeddingMode = EmbeddingMode.AUTO;
private boolean embedded = true;
private String subFontName = null;

@@ -65,6 +64,7 @@ public class LazyFont extends Typeface implements FontDescriptor {
this.fontEmbedPath = fontInfo.getEmbedFile();
this.useKerning = fontInfo.getKerning();
this.encodingMode = fontInfo.getEncodingMode();
this.embeddingMode = fontInfo.getEmbeddingMode();
this.subFontName = fontInfo.getSubFontName();
this.embedded = fontInfo.isEmbedded();
this.resolver = resolver;
@@ -131,8 +131,9 @@ public class LazyFont extends Typeface implements FontDescriptor {
if (fontEmbedPath == null) {
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) {
realFontDescriptor = (FontDescriptor) realFont;

+ 6
- 0
src/java/org/apache/fop/fonts/MutableFont.java 查看文件

@@ -60,6 +60,12 @@ public interface MutableFont {
*/
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.
* @param capHeight capital height

+ 12
- 18
src/java/org/apache/fop/fonts/apps/TTFReader.java 查看文件

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

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

@@ -28,9 +27,9 @@ import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.logging.LogFactory;
import org.apache.fop.Version;
import org.apache.fop.fonts.BFEntry;
import org.apache.fop.fonts.FontUtil;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFCmapEntry;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.util.CommandLineLogger;
import org.w3c.dom.Document;
@@ -272,9 +271,9 @@ public class TTFReader extends AbstractFontReader {
root.appendChild(el);
el.appendChild(doc.createTextNode(ttf.getFullName()));
}
Set familyNames = ttf.getFamilyNames();
Set<String> familyNames = ttf.getFamilyNames();
if (familyNames.size() > 0) {
String familyName = (String)familyNames.iterator().next();
String familyName = familyNames.iterator().next();
el = doc.createElement("family-name");
root.appendChild(el);
el.appendChild(doc.createTextNode(familyName));
@@ -370,9 +369,7 @@ public class TTFReader extends AbstractFontReader {

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

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

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

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

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

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

+ 5
- 2
src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java 查看文件

@@ -31,6 +31,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.EmbedFontInfo;
import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.EncodingMode;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontCache;
@@ -218,7 +219,8 @@ public class FontInfoFinder {
}
try {
TTFFontLoader ttfLoader = new TTFFontLoader(
fontFileURL, fontName, true, EncodingMode.AUTO, true, resolver);
fontFileURL, fontName, true, EmbeddingMode.AUTO, EncodingMode.AUTO,
true, resolver);
customFont = ttfLoader.getFont();
if (this.eventListener != null) {
customFont.setEventListener(this.eventListener);
@@ -242,7 +244,8 @@ public class FontInfoFinder {
} else {
// The normal case
try {
customFont = FontLoader.loadFont(fontURL, null, true, EncodingMode.AUTO, resolver);
customFont = FontLoader.loadFont(fontURL, null, true, EmbeddingMode.AUTO,
EncodingMode.AUTO, resolver);
if (this.eventListener != null) {
customFont.setEventListener(this.eventListener);
}

+ 12
- 17
src/java/org/apache/fop/fonts/truetype/FontFileReader.java 查看文件

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

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

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

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

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

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


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

+ 0
- 122
src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java 查看文件

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

/* $Id$ */

package org.apache.fop.fonts.truetype;

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

//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;
}

}

+ 8
- 0
src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java 查看文件

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

public TTFDirTabEntry() {
}

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

/**
* Read Dir Tab, return tag name
*/

+ 328
- 259
src/java/org/apache/fop/fonts/truetype/TTFFile.java
文件差異過大導致無法顯示
查看文件


+ 15
- 21
src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java 查看文件

@@ -21,15 +21,13 @@ package org.apache.fop.fonts.truetype;

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

import org.apache.commons.io.IOUtils;

import org.apache.fop.fonts.BFEntry;
import org.apache.fop.fonts.CIDFontType;
import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.EncodingMode;
import org.apache.fop.fonts.FontLoader;
import org.apache.fop.fonts.FontResolver;
@@ -49,6 +47,7 @@ public class TTFFontLoader extends FontLoader {
private SingleByteFont singleFont;
private final String subFontName;
private EncodingMode encodingMode;
private EmbeddingMode embeddingMode;

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

/**
@@ -65,23 +64,27 @@ public class TTFFontLoader extends FontLoader {
* @param subFontName the sub-fontname of a font in a TrueType Collection (or null for normal
* TrueType fonts)
* @param embedded indicates whether the font is embedded or referenced
* @param embeddingMode the embedding mode of the font
* @param encodingMode the requested encoding mode
* @param useKerning true to enable loading kerning info if available, false to disable
* @param resolver the FontResolver for font URI resolution
*/
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);
this.subFontName = subFontName;
this.encodingMode = encodingMode;
this.embeddingMode = embeddingMode;
if (this.encodingMode == EncodingMode.AUTO) {
this.encodingMode = EncodingMode.CID; //Default to CID mode for TrueType
}
if (this.embeddingMode == EmbeddingMode.AUTO) {
this.embeddingMode = EmbeddingMode.SUBSET;
}
}

/** {@inheritDoc} */
@Override
protected void read() throws IOException {
read(this.subFontName);
}
@@ -144,7 +147,7 @@ public class TTFFontLoader extends FontLoader {
returnFont.setItalicAngle(Integer.parseInt(ttf.getItalicAngle()));
returnFont.setMissingWidth(0);
returnFont.setWeight(ttf.getWeightClass());
returnFont.setEmbeddingMode(this.embeddingMode);
if (isCid) {
multiFont.setCIDType(CIDFontType.CIDTYPE2);
int[] wx = ttf.getWidths();
@@ -168,15 +171,8 @@ public class TTFFontLoader extends FontLoader {
}

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) {
@@ -184,9 +180,8 @@ public class TTFFontLoader extends FontLoader {
for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) {
singleFont.setWidth(i, ttf.getCharWidth(i));
}
Iterator iter = ttf.getCMaps().listIterator();
while (iter.hasNext()) {
TTFCmapEntry ce = (TTFCmapEntry)iter.next();

for (BFEntry ce : ttf.getCMaps()) {
if (ce.getUnicodeStart() < 0xFFFE) {
for (char u = (char)ce.getUnicodeStart(); u <= ce.getUnicodeEnd(); u++) {
int codePoint = singleFont.getEncoding().mapChar(u);
@@ -221,7 +216,6 @@ public class TTFFontLoader extends FontLoader {
}

for (Integer kpx1 : kerningSet) {

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

+ 48
- 0
src/java/org/apache/fop/fonts/truetype/TTFGlyphOutputStream.java 查看文件

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

/* $Id$ */

package org.apache.fop.fonts.truetype;

import java.io.IOException;

/**
* 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;
}

+ 51
- 0
src/java/org/apache/fop/fonts/truetype/TTFOutputStream.java 查看文件

@@ -0,0 +1,51 @@
/*
* 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;
}

+ 211
- 321
src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java 查看文件

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

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


/**
@@ -34,10 +36,6 @@ import java.util.Map;
*/
public class TTFSubSetFile extends TTFFile {

private static enum OperatingMode {
PDF, POSTSCRIPT_GLYPH_DIRECTORY;
}

private byte[] output = null;
private int realSize = 0;
private int currentPos = 0;
@@ -46,27 +44,25 @@ public class TTFSubSetFile extends TTFFile {
* Offsets in name table to be filled out by table.
* 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 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()) {
throw new UnsupportedOperationException(
"OpenType fonts with CFF glyphs are not supported");
} 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()) {
numTables++;
}
@@ -83,8 +79,8 @@ public class TTFSubSetFile extends TTFFile {
/**
* Create the directory table
*/
private void createDirectory(OperatingMode operatingMode) {
int numTables = determineTableCount(operatingMode);
private void createDirectory() {
int numTables = determineTableCount();
// Create the TrueType header
writeByte((byte)0);
writeByte((byte)1);
@@ -97,7 +93,7 @@ public class TTFSubSetFile extends TTFFile {

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

@@ -106,83 +102,47 @@ public class TTFSubSetFile extends TTFFile {

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

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

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;
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() {
return dirTabs.containsKey("cvt ");
return dirTabs.containsKey(TTFTableName.CVT);
}

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

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

/**
@@ -191,26 +151,24 @@ public class TTFSubSetFile extends TTFFile {
private void createLoca(int size) throws IOException {
pad4();
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;
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) {
pad4();
seekTab(in, tableName, 0);
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
0, output, currentPos, (int)entry.getLength());
int checksum = getCheckSum(currentPos, (int)entry.getLength());
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;
} else {
return false;
@@ -221,14 +179,34 @@ public class TTFSubSetFile extends TTFFile {
* Copy the cvt table as is from original font to subset font
*/
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
*/
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);
}

/**
@@ -236,18 +214,16 @@ public class TTFSubSetFile extends TTFFile {
* and set num glyphs to size
*/
private void createMaxp(FontFileReader in, int size) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("maxp");
TTFTableName maxp = TTFTableName.MAXP;
TTFDirTabEntry entry = dirTabs.get(maxp);
if (entry != null) {
pad4();
seekTab(in, "maxp", 0);
seekTab(in, maxp, 0);
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
0, output, currentPos, (int)entry.getLength());
writeUShort(currentPos + 4, size);

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

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


/**
* Copy the prep table as is from original font to subset font
*/
private boolean createPrep(FontFileReader in) throws IOException {
return copyTable(in, "prep");
return copyTable(in, TTFTableName.PREP);
}


@@ -269,8 +267,18 @@ public class TTFSubSetFile extends TTFFile {
* and fill in size of hmtx table
*/
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");
}
}
@@ -283,10 +291,11 @@ public class TTFSubSetFile extends TTFFile {
* in checkSumAdjustmentOffset
*/
private void createHead(FontFileReader in) throws IOException {
TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("head");
TTFTableName head = TTFTableName.HEAD;
TTFDirTabEntry entry = dirTabs.get(head);
if (entry != null) {
pad4();
seekTab(in, "head", 0);
seekTab(in, head, 0);
System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()),
0, output, currentPos, (int)entry.getLength());

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

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

updateCheckSum(currentPos, (int)entry.getLength(), head);
currentPos += (int)entry.getLength();
realSize += (int)entry.getLength();
} else {
@@ -315,8 +320,9 @@ public class TTFSubSetFile extends TTFFile {
* Create the glyf table and fill in loca table
*/
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 startPos = 0;
int endOffset = 0; // Store this as the last loca
@@ -329,6 +335,7 @@ public class TTFSubSetFile extends TTFFile {
* location offset.
*/
int[] origIndexes = buildSubsetIndexToOrigIndexMap(glyphs);
glyphOffsets = new int[origIndexes.length];

for (int i = 0; i < origIndexes.length; i++) {
int nextOffset = 0;
@@ -358,27 +365,30 @@ public class TTFSubSetFile extends TTFFile {
endOffset1 = (currentPos - startPos + glyphLength);
}

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

endOffset = endOffset1;

endOffset = endOffset1;
}


size = currentPos - startPos;

int checksum = getCheckSum(startPos, size);
writeULong(glyfDirOffset, checksum);
writeULong(glyfDirOffset + 4, startPos);
writeULong(glyfDirOffset + 8, size);
currentPos += 12;
realSize += 12;
updateCheckSum(startPos, size + 12, glyf);

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

checksum = getCheckSum(locaOffset, glyphs.size() * 4 + 4);
writeULong(locaDirOffset, checksum);
int locaSize = glyphs.size() * 4 + 4;
int checksum = getCheckSum(output, locaOffset, locaSize);
writeULong(offsets.get(TTFTableName.LOCA), checksum);
int padSize = (locaOffset + locaSize) % 4;
newDirTabs.put(TTFTableName.LOCA,
new TTFDirTabEntry(locaOffset, locaSize + padSize));
} else {
throw new IOException("Can't find glyf table");
}
@@ -402,7 +412,8 @@ public class TTFSubSetFile extends TTFFile {
*/
private void createHmtx(FontFileReader in,
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 leftSideBearingSize = glyphs.size() * 2;
@@ -421,10 +432,7 @@ public class TTFSubSetFile extends TTFFile {
mtxTab[origIndex.intValue()].getLsb());
}

int checksum = getCheckSum(currentPos, hmtxSize);
writeULong(hmtxDirOffset, checksum);
writeULong(hmtxDirOffset + 4, currentPos);
writeULong(hmtxDirOffset + 8, hmtxSize);
updateCheckSum(currentPos, hmtxSize, hmtx);
currentPos += hmtxSize;
realSize += hmtxSize;
} else {
@@ -540,16 +548,16 @@ public class TTFSubSetFile extends TTFFile {
*/
private void scanGlyphs(FontFileReader in,
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> allComposites = new java.util.HashMap<Integer, Integer>();
Map<Integer, Integer> allComposites = new HashMap<Integer, Integer>();

int newIndex = glyphs.size();

if (entry != null) {
while (newComposites == null || newComposites.size() > 0) {
// 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()) {
int origIndex = glyph.getKey();
@@ -591,46 +599,38 @@ public class TTFSubSetFile extends TTFFile {
}
}



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

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

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

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

scanGlyphs(in, subsetGlyphs);

createDirectory(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;
optionalTableFound = createCvt(in); // copy the cvt table
@@ -644,78 +644,16 @@ public class TTFSubSetFile extends TTFFile {
// fpgm is optional (used in TrueType fonts only)
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
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);
createHhea(in, subsetGlyphs.size()); // Create the hhea table
createHmtx(in, subsetGlyphs); // Create hmtx 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
if (!optionalTableFound) {
@@ -723,59 +661,54 @@ public class TTFSubSetFile extends TTFFile {
log.debug("TrueType: prep table not present. Skipped.");
}

//Send all the glyphs from the subset
handleGlyphSubset(in, subsetGlyphs, glyphHandler);

pad4();
createCheckSumAdjustment();
}

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

return ret;
}

private void handleGlyphSubset(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();
}

/**
@@ -827,20 +760,6 @@ public class TTFSubSetFile extends TTFFile {
output[pos + 1] = b2;
}

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

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

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

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

return ret;
}

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

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

return (i - 1);
}

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


private int getCheckSum(int start, int size) {
return (int)getLongCheckSum(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
// Add remainder to size if it's not a multiple of 4
int remainder = size % 4;
@@ -927,26 +824,19 @@ public class TTFSubSetFile extends TTFFile {
long sum = 0;

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() {
long sum = getLongCheckSum(output, 0, realSize);
long sum = getCheckSum(output, 0, realSize);
int checksum = (int)(0xb1b0afba - sum);
writeULong(checkSumAdjustmentOffset, checksum);
}

}




+ 158
- 0
src/java/org/apache/fop/fonts/truetype/TTFTableName.java 查看文件

@@ -0,0 +1,158 @@
/*
* 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());
}

}

+ 37
- 0
src/java/org/apache/fop/fonts/truetype/TTFTableOutputStream.java 查看文件

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

/* $Id$ */

package org.apache.fop.fonts.truetype;

import java.io.IOException;

/**
* An interface for 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;
}

+ 3
- 5
src/java/org/apache/fop/pdf/PDFFactory.java 查看文件

@@ -39,9 +39,6 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.xmp.Metadata;

import org.apache.fop.fonts.CIDFont;
import org.apache.fop.fonts.CIDSubset;
import org.apache.fop.fonts.CodePointMapping;
@@ -59,6 +56,7 @@ import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFSubSetFile;
import org.apache.fop.fonts.type1.PFBData;
import org.apache.fop.fonts.type1.PFBParser;
import org.apache.xmlgraphics.xmp.Metadata;

/**
* This class provides method to create and register PDF objects.
@@ -1663,8 +1661,8 @@ public class PDFFactory {
FontFileReader reader = new FontFileReader(in);

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

embeddedFont = new PDFTTFStream(subsetFont.length);

+ 2
- 3
src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java 查看文件

@@ -25,7 +25,6 @@ import javax.xml.transform.Source;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.EmbedFontInfo;
import org.apache.fop.fonts.EncodingMode;
@@ -88,8 +87,8 @@ public class ConfiguredFontCollection implements FontCollection {
font = new CustomFontMetricsMapper(fontMetrics, fontSource);
} else {
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);
}


+ 126
- 108
src/java/org/apache/fop/render/ps/PSFontUtils.java 查看文件

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

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
@@ -36,13 +39,13 @@ import org.apache.xmlgraphics.ps.DSCConstants;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSResource;
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.Base14Font;
import org.apache.fop.fonts.CIDFontType;
import org.apache.fop.fonts.CIDSubset;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontType;
@@ -52,8 +55,11 @@ import org.apache.fop.fonts.SingleByteEncoding;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.fonts.truetype.TTFOutputStream;
import org.apache.fop.fonts.truetype.TTFSubSetFile;
import org.apache.fop.fonts.truetype.TTFFile.PostScriptVersion;
import org.apache.fop.render.ps.fonts.PSTTFOutputStream;
import org.apache.fop.util.HexEncoder;

/**
@@ -63,7 +69,6 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {

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

/**
* Generates the PostScript code for the font dictionary. This method should only be
* used if no "resource optimization" is performed, i.e. when the fonts are not embedded
@@ -119,11 +124,12 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
* @return a Map of PSResource instances representing all defined fonts (key: font key)
* @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");

Map fontResources = new java.util.HashMap();
Map fontResources = new HashMap();
for (String key : fonts.keySet()) {
Typeface tf = getTypeFace(fontInfo, fonts, key);
PSResource fontRes = new PSResource(PSResource.TYPE_FONT, tf.getEmbedFontName());
@@ -219,57 +225,52 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {

private static PSFontResource embedFont(PSGenerator gen, Typeface tf, PSResource fontRes,
PSEventProducer eventProducer) throws IOException {
boolean embeddedFont = false;
FontType fontType = tf.getFontType();
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);
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;
}
@@ -292,12 +293,28 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
/* See Adobe Technical Note #5012, "The Type 42 Font Format Specification" */
gen.commentln("%!PS-TrueTypeFont-65536-65536-1"); // TODO TrueType & font versions
gen.writeln("11 dict begin");
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");
}

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(font.getEmbedFontName());
gen.writeln(" def");
@@ -308,7 +325,8 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
gen.writeln("/Encoding 256 array");
gen.writeln("0 1 255{1 index exch/.notdef put}for");
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
//so we keep it minimal to save space
buildCharStrings = false;
@@ -323,72 +341,75 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
gen.write(Glyphs.NOTDEF);
} else {
gen.write(glyphName);
glyphNames.add(glyphName);
}
gen.writeln(" put");
}
}
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 ");
if (buildCharStrings) {
if (!buildCharStrings) {
gen.write(1);
} else if (font.getEmbeddingMode() != EmbeddingMode.FULL) {
int charCount = 1; //1 for .notdef
for (BFEntry entry : cmap) {
charCount += entry.getUnicodeEnd() - entry.getUnicodeStart() + 1;
}
gen.write(charCount);
} else {
gen.write(1);
gen.write(font.getCMap().length);
}
gen.writeln(" dict dup begin");
gen.write("/");
gen.write(Glyphs.NOTDEF);
gen.writeln(" 0 def"); // .notdef always has to be at index 0
if (buildCharStrings) {
//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) {
int glyphIndex = entry.getGlyphStartIndex();
for (int ch = entry.getUnicodeStart(); ch <= entry.getUnicodeEnd(); ch++) {
char ch16 = (char)ch; //TODO Handle Unicode characters beyond 16bit
String glyphName = Glyphs.charToGlyphName(ch16);

if ("".equals(glyphName)) {
glyphName = "u" + Integer.toHexString(ch).toUpperCase();
}
gen.write("/");
gen.write(glyphName);
gen.write(" ");
gen.write(glyphIndex);
gen.writeln(" def");
writeGlyphDefs(gen, glyphName, glyphIndex);

glyphIndex++;
}
}
} else {
for (String name : glyphNames) {
writeGlyphDefs(gen, name,
getGlyphIndex(Glyphs.getUnicodeSequenceForGlyphName(name).charAt(0),
font.getCMap()));
}
}
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) {
for (BFEntry entry : cmap) {
if (entry.getUnicodeStart() <= c && c <= entry.getUnicodeEnd()) {
@@ -408,7 +429,7 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
gen.writeln("] composefont pop");
}

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

@@ -467,30 +488,28 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
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.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("end");
gen.writeln("%%EndResource");
@@ -702,5 +721,4 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils {
gen.getResourceTracker().registerSuppliedResource(res);
return res;
}

}

+ 33
- 20
src/java/org/apache/fop/render/ps/PSPainter.java 查看文件

@@ -404,8 +404,7 @@ public class PSPainter extends AbstractIFPainter {
}
}

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,
Font font, Typeface tf, boolean multiByte) throws IOException {
PSGenerator generator = getGenerator();
@@ -416,16 +415,6 @@ public class PSPainter extends AbstractIFPainter {
boolean hasLetterSpacing = (letterSpacing != 0);
boolean needTJ = false;

char strOpen;
char strClose;
if (multiByte) {
strOpen = '<';
strClose = '>';
} else {
strOpen = '(';
strClose = ')';
}

int lineStart = 0;
StringBuffer accText = new StringBuffer(initialSize);
StringBuffer sb = new StringBuffer(initialSize);
@@ -467,9 +456,7 @@ public class PSPainter extends AbstractIFPainter {
sb.append(PSGenerator.LF);
lineStart = sb.length();
}
sb.append(strOpen);
sb.append(accText);
sb.append(strClose);
lineStart = writePostScriptString(sb, accText, multiByte, lineStart);
sb.append(' ');
accText.setLength(0); //reset accumulated text
}
@@ -478,9 +465,10 @@ public class PSPainter extends AbstractIFPainter {
}
if (needTJ) {
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) {
sb.append("] " + formatMptAsPt(generator, letterSpacing) + " ATJ");
@@ -488,7 +476,7 @@ public class PSPainter extends AbstractIFPainter {
sb.append("] TJ");
}
} else {
sb.append(strOpen).append(accText).append(strClose);
writePostScriptString(sb, accText, multiByte);
if (hasLetterSpacing) {
StringBuffer spb = new StringBuffer();
spb.append(formatMptAsPt(generator, letterSpacing))
@@ -502,6 +490,32 @@ public class PSPainter extends AbstractIFPainter {
generator.writeln(sb.toString());
}

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

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

private void useFont(String key, int size) throws IOException {
PSFontResource res = this.documentHandler.getPSResourceForFontKey(key);
PSGenerator generator = getGenerator();
@@ -509,5 +523,4 @@ public class PSPainter extends AbstractIFPainter {
res.notifyResourceUsageOnPage(generator.getResourceTracker());
}


}

+ 104
- 0
src/java/org/apache/fop/render/ps/fonts/PSTTFGenerator.java 查看文件

@@ -0,0 +1,104 @@
/*
* 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> ");
}
}

+ 72
- 0
src/java/org/apache/fop/render/ps/fonts/PSTTFGlyphOutputStream.java 查看文件

@@ -0,0 +1,72 @@
/*
* 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();
}
}

+ 64
- 0
src/java/org/apache/fop/render/ps/fonts/PSTTFOutputStream.java 查看文件

@@ -0,0 +1,64 @@
/*
* 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");
}

}

+ 58
- 0
src/java/org/apache/fop/render/ps/fonts/PSTTFTableOutputStream.java 查看文件

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

/* $Id$ */

package org.apache.fop.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();
}
}

+ 2
- 2
test/java/org/apache/fop/fonts/DejaVuLGCSerifTest.java 查看文件

@@ -33,13 +33,13 @@ public class DejaVuLGCSerifTest extends TestCase {

/**
* sets up the testcase by loading the DejaVu Font.
*
*
* @throws Exception
* if the test fails.
*/
public void setUp() throws Exception {
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);
}


+ 20
- 3
test/java/org/apache/fop/fonts/EncodingModeTest.java 查看文件

@@ -21,16 +21,33 @@ package org.apache.fop.fonts;

import junit.framework.TestCase;

/**
* Tests the enum org.apache.fop.fonts.EncodingMode.
*/
public class EncodingModeTest extends TestCase {
/**
* Test getName() - tests the getName() method returns the expected String.
*/
public void testGetName() {
assertEquals("auto", EncodingMode.AUTO.getName());
assertEquals("single-byte", EncodingMode.SINGLE_BYTE.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() {
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
}
}
}

+ 54
- 0
test/java/org/apache/fop/fonts/FOPFontsTestSuite.java 查看文件

@@ -0,0 +1,54 @@
/*
* 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;
}
}

+ 283
- 0
test/java/org/apache/fop/fonts/truetype/FontFileReaderTest.java 查看文件

@@ -0,0 +1,283 @@
/*
* 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));
}
}

+ 399
- 0
test/java/org/apache/fop/fonts/truetype/TTFFileTest.java 查看文件

@@ -0,0 +1,399 @@
/*
* 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
}
}

+ 69
- 0
test/java/org/apache/fop/fonts/truetype/TTFSubSetFileTest.java 查看文件

@@ -0,0 +1,69 @@
/*
* 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");
}
}

+ 145
- 0
test/java/org/apache/fop/fonts/truetype/TTFTableNameTest.java 查看文件

@@ -0,0 +1,145 @@
/*
* 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));
}
}

+ 55
- 0
test/java/org/apache/fop/render/ps/RenderPSTestSuite.java 查看文件

@@ -0,0 +1,55 @@
/*
* 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;
}
}

+ 111
- 0
test/java/org/apache/fop/render/ps/fonts/PSTTFGeneratorTest.java 查看文件

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

/* $Id$ */

package org.apache.fop.render.ps.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> "));
}
}

+ 105
- 0
test/java/org/apache/fop/render/ps/fonts/PSTTFGlyphOutputStreamTest.java 查看文件

@@ -0,0 +1,105 @@
/*
* 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();
}
}

+ 82
- 0
test/java/org/apache/fop/render/ps/fonts/PSTTFOutputStreamTest.java 查看文件

@@ -0,0 +1,82 @@
/*
* 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");
}
}

+ 86
- 0
test/java/org/apache/fop/render/ps/fonts/PSTTFTableOutputStreamTest.java 查看文件

@@ -0,0 +1,86 @@
/*
* 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();
}
}

+ 18
- 0
test/resources/fonts/DroidSansMono.LICENSE 查看文件

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

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

##########

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

Loading…
取消
儲存