diff options
author | Vincent Hennebert <vhennebert@apache.org> | 2012-03-30 19:09:29 +0000 |
---|---|---|
committer | Vincent Hennebert <vhennebert@apache.org> | 2012-03-30 19:09:29 +0000 |
commit | 7c8f02c57302d672fe4d0a8fc5332af6d9f0e610 (patch) | |
tree | 9be4d0493b7aa44854254ae99dc842de739d9bea /src | |
parent | 7a51f3fdb1f7ae1f526ea75f804e8f016d653cb8 (diff) | |
download | xmlgraphics-fop-7c8f02c57302d672fe4d0a8fc5332af6d9f0e610.tar.gz xmlgraphics-fop-7c8f02c57302d672fe4d0a8fc5332af6d9f0e610.zip |
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
Diffstat (limited to 'src')
31 files changed, 1510 insertions, 923 deletions
diff --git a/src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java b/src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java index 6786e43f0..0945c52bb 100644 --- a/src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java +++ b/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 diff --git a/src/java/org/apache/fop/fonts/BFEntry.java b/src/java/org/apache/fop/fonts/BFEntry.java index c8311a9d2..654c1952b 100644 --- a/src/java/org/apache/fop/fonts/BFEntry.java +++ b/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. @@ -44,6 +43,32 @@ public class BFEntry { } /** + * {@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 */ diff --git a/src/java/org/apache/fop/fonts/CustomFont.java b/src/java/org/apache/fop/fonts/CustomFont.java index bc64e0f33..a7b630578 100644 --- a/src/java/org/apache/fop/fonts/CustomFont.java +++ b/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; @@ -114,6 +115,14 @@ public abstract class CustomFont extends Typeface } /** + * 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 * @throws IOException if embedFileName is not null but Source is not found @@ -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. */ @@ -330,6 +339,13 @@ public abstract class CustomFont extends Typeface /** * {@inheritDoc} */ + public void setEmbeddingMode(EmbeddingMode embeddingMode) { + this.embeddingMode = embeddingMode; + } + + /** + * {@inheritDoc} + */ public void setCapHeight(int capHeight) { this.capHeight = capHeight; } diff --git a/src/java/org/apache/fop/fonts/CustomFontCollection.java b/src/java/org/apache/fop/fonts/CustomFontCollection.java index 9f98814a3..3bd1fa7c7 100644 --- a/src/java/org/apache/fop/fonts/CustomFontCollection.java +++ b/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); } } diff --git a/src/java/org/apache/fop/fonts/EmbedFontInfo.java b/src/java/org/apache/fop/fonts/EmbedFontInfo.java index b53cdfdd6..2bc8bb452 100644 --- a/src/java/org/apache/fop/fonts/EmbedFontInfo.java +++ b/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; @@ -137,6 +141,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(); diff --git a/src/java/org/apache/fop/fonts/EmbeddingMode.java b/src/java/org/apache/fop/fonts/EmbeddingMode.java new file mode 100644 index 000000000..89f56cfd7 --- /dev/null +++ b/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); + } +} diff --git a/src/java/org/apache/fop/fonts/EncodingMode.java b/src/java/org/apache/fop/fonts/EncodingMode.java index 8a40d6593..78ffb7ac6 100644 --- a/src/java/org/apache/fop/fonts/EncodingMode.java +++ b/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; diff --git a/src/java/org/apache/fop/fonts/FontInfoConfigurator.java b/src/java/org/apache/fop/fonts/FontInfoConfigurator.java index 67bb2e295..bf4c01453 100644 --- a/src/java/org/apache/fop/fonts/FontInfoConfigurator.java +++ b/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); diff --git a/src/java/org/apache/fop/fonts/FontLoader.java b/src/java/org/apache/fop/fonts/FontLoader.java index 02c09a1a1..b033e517e 100644 --- a/src/java/org/apache/fop/fonts/FontLoader.java +++ b/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(); } diff --git a/src/java/org/apache/fop/fonts/LazyFont.java b/src/java/org/apache/fop/fonts/LazyFont.java index a8fd447c4..4d63dafea 100644 --- a/src/java/org/apache/fop/fonts/LazyFont.java +++ b/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; diff --git a/src/java/org/apache/fop/fonts/MutableFont.java b/src/java/org/apache/fop/fonts/MutableFont.java index bcbcadbdc..c19dca6b5 100644 --- a/src/java/org/apache/fop/fonts/MutableFont.java +++ b/src/java/org/apache/fop/fonts/MutableFont.java @@ -61,6 +61,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 */ diff --git a/src/java/org/apache/fop/fonts/apps/TTFReader.java b/src/java/org/apache/fop/fonts/apps/TTFReader.java index 6e64f9144..bd2fe1257 100644 --- a/src/java/org/apache/fop/fonts/apps/TTFReader.java +++ b/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()); diff --git a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java index 714af0e57..a3e74ada7 100644 --- a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java +++ b/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); } diff --git a/src/java/org/apache/fop/fonts/truetype/FontFileReader.java b/src/java/org/apache/fop/fonts/truetype/FontFileReader.java index a6db7b6d0..e394b98ed 100644 --- a/src/java/org/apache/fop/fonts/truetype/FontFileReader.java +++ b/src/java/org/apache/fop/fonts/truetype/FontFileReader.java @@ -90,23 +90,13 @@ public class FontFileReader { } /** - * 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. * * @param add The number of bytes to advance * @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; + } }
\ No newline at end of file diff --git a/src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java b/src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java deleted file mode 100644 index a63f76def..000000000 --- a/src/java/org/apache/fop/fonts/truetype/TTFCmapEntry.java +++ /dev/null @@ -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; - } - -} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java b/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java index 405a71395..ca121a609 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java +++ b/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 */ diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFile.java b/src/java/org/apache/fop/fonts/truetype/TTFFile.java index 5aea4a66b..74d32a769 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFFile.java +++ b/src/java/org/apache/fop/fonts/truetype/TTFFile.java @@ -20,14 +20,22 @@ package org.apache.fop.fonts.truetype; import java.io.IOException; +import java.util.ArrayList; import java.util.BitSet; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.fonts.BFEntry; import org.apache.fop.fonts.FontUtil; import org.apache.xmlgraphics.fonts.Glyphs; @@ -127,6 +135,8 @@ public class TTFFile { "ccaron", "dcroat" }; + /** The FontFileReader used to read this truetype font */ + protected FontFileReader fontFile; /** Set to true to get even more debug output than with level DEBUG */ public static final boolean TRACE_ENABLED = false; @@ -138,11 +148,11 @@ public class TTFFile { /** * Table directory */ - protected Map dirTabs; - private Map<Integer, Map<Integer, Integer>> kerningTab; // for CIDs + protected Map<TTFTableName, TTFDirTabEntry> dirTabs; + private Map<Integer, Map<Integer, Integer>> kerningTab; // for CIDs private Map<Integer, Map<Integer, Integer>> ansiKerningTab; // For winAnsiEncoding - private List<TTFCmapEntry> cmaps; - private List unicodeMapping; + private List<BFEntry> cmaps; + private List<UnicodeMapping> unicodeMapping; private int upem; // unitsPerEm from "head" table private int nhmtx; // Number of horizontal metrics @@ -153,18 +163,16 @@ public class TTFFile { */ protected long lastLoca = 0; private int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table) - private int nmGlyphs; // Used in fixWidths - remove? /** * Contains glyph data */ protected TTFMtxEntry[] mtxTab; // Contains glyph data - private final int[] mtxEncoded = null; private String postScriptName = ""; private String fullName = ""; private String notice = ""; - private final Set familyNames = new java.util.HashSet(); //Set<String> + private Set<String> familyNames = new HashSet<String>(); private String subFamilyName = ""; private long italicAngle = 0; @@ -193,12 +201,12 @@ public class TTFFile { private short lastChar = 0; private int[] ansiWidth; - private Map ansiIndex; + private Map<Integer, List<Integer>> ansiIndex; // internal mapping of glyph indexes to unicode indexes // used for quick mappings in this class - private final Map glyphToUnicodeMap = new java.util.HashMap(); - private final Map unicodeToGlyphMap = new java.util.HashMap(); + private Map<Integer, Integer> glyphToUnicodeMap = new HashMap<Integer, Integer> (); + private Map<Integer, Integer> unicodeToGlyphMap = new HashMap<Integer, Integer> (); private TTFDirTabEntry currentDirTab; @@ -210,9 +218,9 @@ public class TTFFile { protected Log log = LogFactory.getLog(TTFFile.class); /** - * Key-value helper class + * Key-value helper class (immutable) */ - class UnicodeMapping { + final class UnicodeMapping { private final int unicodeIndex; private final int glyphIndex; @@ -244,33 +252,26 @@ public class TTFFile { /** * Version of the PostScript table (<q>post</q>) contained in this font. */ - public static final class PostScriptVersion { - + public static enum PostScriptVersion { /** PostScript table version 1.0. */ - public static final PostScriptVersion V1 = new PostScriptVersion(); - + V1, /** PostScript table version 2.0. */ - public static final PostScriptVersion V2 = new PostScriptVersion(); - + V2, /** PostScript table version 3.0. */ - public static final PostScriptVersion V3 = new PostScriptVersion(); - + V3, /** Unknown version of the PostScript table. */ - public static final PostScriptVersion UNKNOWN = new PostScriptVersion(); - - private PostScriptVersion() { } - + UNKNOWN; } /** * Position inputstream to position indicated * in the dirtab offset + offset */ - boolean seekTab(FontFileReader in, String name, + boolean seekTab(FontFileReader in, TTFTableName tableName, long offset) throws IOException { - TTFDirTabEntry dt = (TTFDirTabEntry)dirTabs.get(name); + TTFDirTabEntry dt = dirTabs.get(tableName); if (dt == null) { - log.error("Dirtab " + name + " not found."); + log.error("Dirtab " + tableName.getName() + " not found."); return false; } else { in.seekSet(dt.getOffset() + offset); @@ -306,12 +307,12 @@ public class TTFFile { * Set the unicodeIndex in the TTFMtxEntries and fills in the * cmaps vector. */ - private boolean readCMAP(FontFileReader in) throws IOException { + private boolean readCMAP() throws IOException { - unicodeMapping = new java.util.ArrayList(); + unicodeMapping = new ArrayList<UnicodeMapping>(); - seekTab(in, "cmap", 2); - int numCMap = in.readTTFUShort(); // Number of cmap subtables + seekTab(fontFile, TTFTableName.CMAP, 2); + int numCMap = fontFile.readTTFUShort(); // Number of cmap subtables long cmapUniOffset = 0; long symbolMapOffset = 0; @@ -321,9 +322,9 @@ public class TTFFile { //Read offset for all tables. We are only interested in the unicode table for (int i = 0; i < numCMap; i++) { - int cmapPID = in.readTTFUShort(); - int cmapEID = in.readTTFUShort(); - long cmapOffset = in.readTTFULong(); + int cmapPID = fontFile.readTTFUShort(); + int cmapEID = fontFile.readTTFUShort(); + long cmapOffset = fontFile.readTTFLong(); if (log.isDebugEnabled()) { log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID); @@ -338,9 +339,9 @@ public class TTFFile { } if (cmapUniOffset > 0) { - return readUnicodeCmap(in, cmapUniOffset, 1); + return readUnicodeCmap(cmapUniOffset, 1); } else if (symbolMapOffset > 0) { - return readUnicodeCmap(in, symbolMapOffset, 0); + return readUnicodeCmap(symbolMapOffset, 0); } else { log.fatal("Unsupported TrueType font: No Unicode or Symbol cmap table" + " not present. Aborting"); @@ -349,26 +350,26 @@ public class TTFFile { } private boolean readUnicodeCmap // CSOK: MethodLength - (FontFileReader in, long cmapUniOffset, int encodingID) + (long cmapUniOffset, int encodingID) throws IOException { //Read CMAP table and correct mtxTab.index int mtxPtr = 0; // Read unicode cmap - seekTab(in, "cmap", cmapUniOffset); - int cmapFormat = in.readTTFUShort(); - /*int cmap_length =*/ in.readTTFUShort(); //skip cmap length + seekTab(fontFile, TTFTableName.CMAP, cmapUniOffset); + int cmapFormat = fontFile.readTTFUShort(); + /*int cmap_length =*/ fontFile.readTTFUShort(); //skip cmap length if (log.isDebugEnabled()) { log.debug("CMAP format: " + cmapFormat); } if (cmapFormat == 4) { - in.skip(2); // Skip version number - int cmapSegCountX2 = in.readTTFUShort(); - int cmapSearchRange = in.readTTFUShort(); - int cmapEntrySelector = in.readTTFUShort(); - int cmapRangeShift = in.readTTFUShort(); + fontFile.skip(2); // Skip version number + int cmapSegCountX2 = fontFile.readTTFUShort(); + int cmapSearchRange = fontFile.readTTFUShort(); + int cmapEntrySelector = fontFile.readTTFUShort(); + int cmapRangeShift = fontFile.readTTFUShort(); if (log.isDebugEnabled()) { log.debug("segCountX2 : " + cmapSegCountX2); @@ -384,26 +385,26 @@ public class TTFFile { int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2]; for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapEndCounts[i] = in.readTTFUShort(); + cmapEndCounts[i] = fontFile.readTTFUShort(); } - in.skip(2); // Skip reservedPad + fontFile.skip(2); // Skip reservedPad for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapStartCounts[i] = in.readTTFUShort(); + cmapStartCounts[i] = fontFile.readTTFUShort(); } for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapDeltas[i] = in.readTTFShort(); + cmapDeltas[i] = fontFile.readTTFShort(); } //int startRangeOffset = in.getCurrentPos(); for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapRangeOffsets[i] = in.readTTFUShort(); + cmapRangeOffsets[i] = fontFile.readTTFUShort(); } - int glyphIdArrayOffset = in.getCurrentPos(); + int glyphIdArrayOffset = fontFile.getCurrentPos(); BitSet eightBitGlyphs = new BitSet(256); @@ -445,8 +446,8 @@ public class TTFFile { + (j - cmapStartCounts[i]) + (i) - cmapSegCountX2 / 2) * 2; - in.seekSet(glyphOffset); - glyphIdx = (in.readTTFUShort() + cmapDeltas[i]) + fontFile.seekSet(glyphOffset); + glyphIdx = (fontFile.readTTFUShort() + cmapDeltas[i]) & 0xffff; unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); @@ -465,11 +466,9 @@ public class TTFFile { } // Also add winAnsiWidth - List v = (List)ansiIndex.get(new Integer(j)); + List<Integer> v = ansiIndex.get(new Integer(j)); if (v != null) { - Iterator e = v.listIterator(); - while (e.hasNext()) { - Integer aIdx = (Integer)e.next(); + for (Integer aIdx : v) { ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); @@ -510,11 +509,9 @@ public class TTFFile { } // Also add winAnsiWidth - List v = (List)ansiIndex.get(new Integer(j)); + List<Integer> v = ansiIndex.get(new Integer(j)); if (v != null) { - Iterator e = v.listIterator(); - while (e.hasNext()) { - Integer aIdx = (Integer)e.next(); + for (Integer aIdx : v) { ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); } } @@ -592,14 +589,14 @@ public class TTFFile { // Create an index hash to the ansiWidth // Can't just index the winAnsiEncoding when inserting widths // same char (eg bullet) is repeated more than one place - ansiIndex = new java.util.HashMap(); + ansiIndex = new HashMap<Integer, List<Integer>>(); for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { Integer ansi = new Integer(i); Integer uni = new Integer(Glyphs.WINANSI_ENCODING[i]); - List v = (List)ansiIndex.get(uni); + List<Integer> v = ansiIndex.get(uni); if (v == null) { - v = new java.util.ArrayList(); + v = new ArrayList<Integer>(); ansiIndex.put(uni, v); } v.add(ansi); @@ -618,12 +615,12 @@ public class TTFFile { * @throws IOException In case of an I/O problem */ public boolean readFont(FontFileReader in, String name) throws IOException { - + fontFile = in; /* * Check if TrueType collection, and that the name * exists in the collection */ - if (!checkTTC(in, name)) { + if (!checkTTC(name)) { if (name == null) { throw new IllegalArgumentException( "For TrueType collection you must specify which font " @@ -634,26 +631,26 @@ public class TTFFile { } } - readDirTabs(in); - readFontHeader(in); - getNumGlyphs(in); + readDirTabs(); + readFontHeader(); + getNumGlyphs(); if (log.isDebugEnabled()) { log.debug("Number of glyphs in font: " + numberOfGlyphs); } - readHorizontalHeader(in); - readHorizontalMetrics(in); + readHorizontalHeader(); + readHorizontalMetrics(); initAnsiWidths(); - readPostScript(in); - readOS2(in); + readPostScript(); + readOS2(); determineAscDesc(); if (!isCFF) { - readIndexToLocation(in); - readGlyf(in); + readIndexToLocation(); + readGlyf(); } - readName(in); - boolean pcltFound = readPCLT(in); + readName(); + boolean pcltFound = readPCLT(); // Read cmap table and fill in ansiwidths - boolean valid = readCMAP(in); + boolean valid = readCMAP(); if (!valid) { return false; } @@ -661,38 +658,52 @@ public class TTFFile { createCMaps(); // print_max_min(); - readKerning(in); + readKerning(); guessVerticalMetricsFromGlyphBBox(); return true; } + /** + * Reads a 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) + * @throws IOException in case of an I/O problem + */ + public void readFont(FontFileReader in, String name, + Map<Integer, Integer> glyphs) throws IOException { + readFont(in, name); + } + private void createCMaps() { - this.cmaps = new java.util.ArrayList<TTFCmapEntry>(); - TTFCmapEntry tce = new TTFCmapEntry(); + cmaps = new ArrayList<BFEntry>(); + int unicodeStart; + int glyphStart; + int unicodeEnd; - Iterator e = unicodeMapping.listIterator(); - UnicodeMapping um = (UnicodeMapping)e.next(); + Iterator<UnicodeMapping> e = unicodeMapping.listIterator(); + UnicodeMapping um = e.next(); UnicodeMapping lastMapping = um; - tce.setUnicodeStart(um.getUnicodeIndex()); - tce.setGlyphStartIndex(um.getGlyphIndex()); + unicodeStart = um.getUnicodeIndex(); + glyphStart = um.getGlyphIndex(); while (e.hasNext()) { - um = (UnicodeMapping)e.next(); + um = e.next(); if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex()) || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) { - tce.setUnicodeEnd(lastMapping.getUnicodeIndex()); - cmaps.add(tce); - - tce = new TTFCmapEntry(); - tce.setUnicodeStart(um.getUnicodeIndex()); - tce.setGlyphStartIndex(um.getGlyphIndex()); + unicodeEnd = lastMapping.getUnicodeIndex(); + cmaps.add(new BFEntry(unicodeStart, unicodeEnd, glyphStart)); + unicodeStart = um.getUnicodeIndex(); + glyphStart = um.getGlyphIndex(); } lastMapping = um; } - tce.setUnicodeEnd(um.getUnicodeIndex()); - cmaps.add(tce); + unicodeEnd = lastMapping.getUnicodeIndex(); + cmaps.add(new BFEntry(unicodeStart, unicodeEnd, glyphStart)); } /** @@ -715,7 +726,7 @@ public class TTFFile { * Returns the font family names of the font. * @return Set The family names (a Set of Strings) */ - public Set getFamilyNames() { + public Set<String> getFamilyNames() { return familyNames; } @@ -760,19 +771,30 @@ public class TTFFile { } /** + * Returns the number of bytes necessary to pad the currentPosition so that a table begins + * on a 4-byte boundary. + * @param currentPosition the position to pad. + * @return int the number of bytes to pad. + */ + protected int getPadSize(int currentPosition) { + int padSize = 4 - (currentPosition % 4); + return padSize < 4 ? padSize : 0; + } + + /** * Returns the Flags attribute of the font. * @return int The Flags */ public int getFlags() { int flags = 32; // Use Adobe Standard charset if (italicAngle != 0) { - flags = flags | 64; + flags |= 64; } if (isFixedPitch != 0) { - flags = flags | 2; + flags |= 2; } if (hasSerifs) { - flags = flags | 1; + flags |= 1; } return flags; } @@ -810,7 +832,6 @@ public class TTFFile { } /** - * Returns the font bounding box. * @return int[] The font bbox */ public int[] getFontBBox() { @@ -916,11 +937,10 @@ public class TTFFile { * FontFileReader and fill the global HashMap dirTabs * with the table name (String) as key and a TTFDirTabEntry * as value. - * @param in FontFileReader to read the table directory from * @throws IOException in case of an I/O problem */ - protected void readDirTabs(FontFileReader in) throws IOException { - int sfntVersion = in.readTTFLong(); // TTF_FIXED_SIZE (4 bytes) + protected void readDirTabs() throws IOException { + int sfntVersion = fontFile.readTTFLong(); // TTF_FIXED_SIZE (4 bytes) switch (sfntVersion) { case 0x10000: log.debug("sfnt version: OpenType 1.0"); @@ -939,42 +959,45 @@ public class TTFFile { log.debug("Unknown sfnt version: " + Integer.toHexString(sfntVersion)); break; } - int ntabs = in.readTTFUShort(); - in.skip(6); // 3xTTF_USHORT_SIZE + int ntabs = fontFile.readTTFUShort(); + fontFile.skip(6); // 3xTTF_USHORT_SIZE - dirTabs = new java.util.HashMap(); + dirTabs = new HashMap<TTFTableName, TTFDirTabEntry>(); TTFDirTabEntry[] pd = new TTFDirTabEntry[ntabs]; log.debug("Reading " + ntabs + " dir tables"); + for (int i = 0; i < ntabs; i++) { pd[i] = new TTFDirTabEntry(); - dirTabs.put(pd[i].read(in), pd[i]); + String tableName = pd[i].read(fontFile); + dirTabs.put(TTFTableName.getValue(tableName), pd[i]); } + dirTabs.put(TTFTableName.DIRECTORY_TABLE, + new TTFDirTabEntry(0L, fontFile.getCurrentPos())); log.debug("dir tables: " + dirTabs.keySet()); } /** * Read the "head" table, this reads the bounding box and * sets the upem (unitsPerEM) variable - * @param in FontFileReader to read the header from * @throws IOException in case of an I/O problem */ - protected void readFontHeader(FontFileReader in) throws IOException { - seekTab(in, "head", 2 * 4 + 2 * 4); - int flags = in.readTTFUShort(); + protected void readFontHeader() throws IOException { + seekTab(fontFile, TTFTableName.HEAD, 2 * 4 + 2 * 4); + int flags = fontFile.readTTFUShort(); if (log.isDebugEnabled()) { log.debug("flags: " + flags + " - " + Integer.toString(flags, 2)); } - upem = in.readTTFUShort(); + upem = fontFile.readTTFUShort(); if (log.isDebugEnabled()) { log.debug("unit per em: " + upem); } - in.skip(16); + fontFile.skip(16); - fontBBox1 = in.readTTFShort(); - fontBBox2 = in.readTTFShort(); - fontBBox3 = in.readTTFShort(); - fontBBox4 = in.readTTFShort(); + fontBBox1 = fontFile.readTTFShort(); + fontBBox2 = fontFile.readTTFShort(); + fontBBox3 = fontFile.readTTFShort(); + fontBBox4 = fontFile.readTTFShort(); if (log.isDebugEnabled()) { log.debug("font bbox: xMin=" + fontBBox1 + " yMin=" + fontBBox2 @@ -982,19 +1005,18 @@ public class TTFFile { + " yMax=" + fontBBox4); } - in.skip(2 + 2 + 2); + fontFile.skip(2 + 2 + 2); - locaFormat = in.readTTFShort(); + locaFormat = fontFile.readTTFShort(); } /** * Read the number of glyphs from the "maxp" table - * @param in FontFileReader to read the number of glyphs from * @throws IOException in case of an I/O problem */ - protected void getNumGlyphs(FontFileReader in) throws IOException { - seekTab(in, "maxp", 4); - numberOfGlyphs = in.readTTFUShort(); + protected void getNumGlyphs() throws IOException { + seekTab(fontFile, TTFTableName.MAXP, 4); + numberOfGlyphs = fontFile.readTTFUShort(); } @@ -1002,17 +1024,16 @@ public class TTFFile { * Read the "hhea" table to find the ascender and descender and * size of "hmtx" table, as a fixed size font might have only * one width. - * @param in FontFileReader to read the hhea table from * @throws IOException in case of an I/O problem */ - protected void readHorizontalHeader(FontFileReader in) + protected void readHorizontalHeader() throws IOException { - seekTab(in, "hhea", 4); - hheaAscender = in.readTTFShort(); - hheaDescender = in.readTTFShort(); + seekTab(fontFile, TTFTableName.HHEA, 4); + hheaAscender = fontFile.readTTFShort(); + hheaDescender = fontFile.readTTFShort(); - in.skip(2 + 2 + 3 * 2 + 8 * 2); - nhmtx = in.readTTFUShort(); + fontFile.skip(2 + 2 + 3 * 2 + 8 * 2); + nhmtx = fontFile.readTTFUShort(); if (log.isDebugEnabled()) { log.debug("hhea.Ascender: " + formatUnitsForDebug(hheaAscender)); @@ -1026,12 +1047,11 @@ public class TTFFile { * in the mtxTab array. If the number of metrics is less * than the number of glyphs (eg fixed size fonts), extend * the mtxTab array and fill in the missing widths - * @param in FontFileReader to read the hmtx table from * @throws IOException in case of an I/O problem */ - protected void readHorizontalMetrics(FontFileReader in) + protected void readHorizontalMetrics() throws IOException { - seekTab(in, "hmtx", 0); + seekTab(fontFile, TTFTableName.HMTX, 0); int mtxSize = Math.max(numberOfGlyphs, nhmtx); mtxTab = new TTFMtxEntry[mtxSize]; @@ -1043,8 +1063,8 @@ public class TTFFile { mtxTab[i] = new TTFMtxEntry(); } for (int i = 0; i < nhmtx; i++) { - mtxTab[i].setWx(in.readTTFUShort()); - mtxTab[i].setLsb(in.readTTFUShort()); + mtxTab[i].setWx(fontFile.readTTFUShort()); + mtxTab[i].setLsb(fontFile.readTTFUShort()); if (TRACE_ENABLED) { if (log.isDebugEnabled()) { @@ -1059,7 +1079,7 @@ public class TTFFile { int lastWidth = mtxTab[nhmtx - 1].getWx(); for (int i = nhmtx; i < mtxSize; i++) { mtxTab[i].setWx(lastWidth); - mtxTab[i].setLsb(in.readTTFUShort()); + mtxTab[i].setLsb(fontFile.readTTFUShort()); } } } @@ -1069,16 +1089,16 @@ public class TTFFile { * Read the "post" table * containing the PostScript names of the glyphs. */ - private void readPostScript(FontFileReader in) throws IOException { - seekTab(in, "post", 0); - int postFormat = in.readTTFLong(); - italicAngle = in.readTTFULong(); - underlinePosition = in.readTTFShort(); - underlineThickness = in.readTTFShort(); - isFixedPitch = in.readTTFULong(); + private void readPostScript() throws IOException { + seekTab(fontFile, TTFTableName.POST, 0); + int postFormat = fontFile.readTTFLong(); + italicAngle = fontFile.readTTFULong(); + underlinePosition = fontFile.readTTFShort(); + underlineThickness = fontFile.readTTFShort(); + isFixedPitch = fontFile.readTTFULong(); //Skip memory usage values - in.skip(4 * 4); + fontFile.skip(4 * 4); log.debug("PostScript format: 0x" + Integer.toHexString(postFormat)); switch (postFormat) { @@ -1095,11 +1115,11 @@ public class TTFFile { int numGlyphStrings = 0; // Read Number of Glyphs - int l = in.readTTFUShort(); + int l = fontFile.readTTFUShort(); // Read indexes for (int i = 0; i < l; i++) { - mtxTab[i].setIndex(in.readTTFUShort()); + mtxTab[i].setIndex(fontFile.readTTFUShort()); if (mtxTab[i].getIndex() > 257) { //Index is not in the Macintosh standard set @@ -1119,7 +1139,7 @@ public class TTFFile { + " set. Total number of glyphs=" + l); } for (int i = 0; i < psGlyphsBuffer.length; i++) { - psGlyphsBuffer[i] = in.readTTFString(in.readTTFUByte()); + psGlyphsBuffer[i] = fontFile.readTTFString(fontFile.readTTFUByte()); } //Set glyph names @@ -1156,60 +1176,60 @@ public class TTFFile { /** * Read the "OS/2" table */ - private void readOS2(FontFileReader in) throws IOException { + private void readOS2() throws IOException { // Check if font is embeddable - TTFDirTabEntry os2Entry = (TTFDirTabEntry)dirTabs.get("OS/2"); + TTFDirTabEntry os2Entry = dirTabs.get(TTFTableName.OS2); if (os2Entry != null) { - seekTab(in, "OS/2", 0); - int version = in.readTTFUShort(); + seekTab(fontFile, TTFTableName.OS2, 0); + int version = fontFile.readTTFUShort(); if (log.isDebugEnabled()) { log.debug("OS/2 table: version=" + version + ", offset=" + os2Entry.getOffset() + ", len=" + os2Entry.getLength()); } - in.skip(2); //xAvgCharWidth - this.usWeightClass = in.readTTFUShort(); + fontFile.skip(2); //xAvgCharWidth + this.usWeightClass = fontFile.readTTFUShort(); // usWidthClass - in.skip(2); + fontFile.skip(2); - int fsType = in.readTTFUShort(); + int fsType = fontFile.readTTFUShort(); if (fsType == 2) { isEmbeddable = false; } else { isEmbeddable = true; } - in.skip(11 * 2); - in.skip(10); //panose array - in.skip(4 * 4); //unicode ranges - in.skip(4); - in.skip(3 * 2); + fontFile.skip(11 * 2); + fontFile.skip(10); //panose array + fontFile.skip(4 * 4); //unicode ranges + fontFile.skip(4); + fontFile.skip(3 * 2); int v; - os2Ascender = in.readTTFShort(); //sTypoAscender - os2Descender = in.readTTFShort(); //sTypoDescender + os2Ascender = fontFile.readTTFShort(); //sTypoAscender + os2Descender = fontFile.readTTFShort(); //sTypoDescender if (log.isDebugEnabled()) { log.debug("sTypoAscender: " + os2Ascender + " -> internal " + convertTTFUnit2PDFUnit(os2Ascender)); log.debug("sTypoDescender: " + os2Descender + " -> internal " + convertTTFUnit2PDFUnit(os2Descender)); } - v = in.readTTFShort(); //sTypoLineGap + v = fontFile.readTTFShort(); //sTypoLineGap if (log.isDebugEnabled()) { log.debug("sTypoLineGap: " + v); } - v = in.readTTFUShort(); //usWinAscent + v = fontFile.readTTFUShort(); //usWinAscent if (log.isDebugEnabled()) { log.debug("usWinAscent: " + formatUnitsForDebug(v)); } - v = in.readTTFUShort(); //usWinDescent + v = fontFile.readTTFUShort(); //usWinDescent if (log.isDebugEnabled()) { log.debug("usWinDescent: " + formatUnitsForDebug(v)); } //version 1 OS/2 table might end here if (os2Entry.getLength() >= 78 + (2 * 4) + (2 * 2)) { - in.skip(2 * 4); - this.os2xHeight = in.readTTFShort(); //sxHeight - this.os2CapHeight = in.readTTFShort(); //sCapHeight + fontFile.skip(2 * 4); + this.os2xHeight = fontFile.readTTFShort(); //sxHeight + this.os2CapHeight = fontFile.readTTFShort(); //sCapHeight if (log.isDebugEnabled()) { log.debug("sxHeight: " + this.os2xHeight); log.debug("sCapHeight: " + this.os2CapHeight); @@ -1223,42 +1243,40 @@ public class TTFFile { /** * Read the "loca" table. - * @param in FontFileReader to read from * @throws IOException In case of a I/O problem */ - protected final void readIndexToLocation(FontFileReader in) + protected final void readIndexToLocation() throws IOException { - if (!seekTab(in, "loca", 0)) { + if (!seekTab(fontFile, TTFTableName.LOCA, 0)) { throw new IOException("'loca' table not found, happens when the font file doesn't" + " contain TrueType outlines (trying to read an OpenType CFF font maybe?)"); } for (int i = 0; i < numberOfGlyphs; i++) { - mtxTab[i].setOffset(locaFormat == 1 ? in.readTTFULong() - : (in.readTTFUShort() << 1)); + mtxTab[i].setOffset(locaFormat == 1 ? fontFile.readTTFULong() + : (fontFile.readTTFUShort() << 1)); } - lastLoca = (locaFormat == 1 ? in.readTTFULong() - : (in.readTTFUShort() << 1)); + lastLoca = (locaFormat == 1 ? fontFile.readTTFULong() + : (fontFile.readTTFUShort() << 1)); } /** * Read the "glyf" table to find the bounding boxes. - * @param in FontFileReader to read from * @throws IOException In case of a I/O problem */ - private void readGlyf(FontFileReader in) throws IOException { - TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("glyf"); + private void readGlyf() throws IOException { + TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.GLYF); if (dirTab == null) { throw new IOException("glyf table not found, cannot continue"); } for (int i = 0; i < (numberOfGlyphs - 1); i++) { if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { - in.seekSet(dirTab.getOffset() + mtxTab[i].getOffset()); - in.skip(2); + fontFile.seekSet(dirTab.getOffset() + mtxTab[i].getOffset()); + fontFile.skip(2); final int[] bbox = { - in.readTTFShort(), - in.readTTFShort(), - in.readTTFShort(), - in.readTTFShort()}; + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort()}; mtxTab[i].setBoundingBox(bbox); } else { mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox()); @@ -1266,17 +1284,17 @@ public class TTFFile { } - long n = ((TTFDirTabEntry)dirTabs.get("glyf")).getOffset(); + long n = (dirTabs.get(TTFTableName.GLYF)).getOffset(); for (int i = 0; i < numberOfGlyphs; i++) { if ((i + 1) >= mtxTab.length || mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { - in.seekSet(n + mtxTab[i].getOffset()); - in.skip(2); + fontFile.seekSet(n + mtxTab[i].getOffset()); + fontFile.skip(2); final int[] bbox = { - in.readTTFShort(), - in.readTTFShort(), - in.readTTFShort(), - in.readTTFShort()}; + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort(), + fontFile.readTTFShort()}; mtxTab[i].setBoundingBox(bbox); } else { /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/ @@ -1297,34 +1315,33 @@ public class TTFFile { /** * Read the "name" table. - * @param in FontFileReader to read from * @throws IOException In case of a I/O problem */ - private void readName(FontFileReader in) throws IOException { - seekTab(in, "name", 2); - int i = in.getCurrentPos(); - int n = in.readTTFUShort(); - int j = in.readTTFUShort() + i - 2; + private void readName() throws IOException { + seekTab(fontFile, TTFTableName.NAME, 2); + int i = fontFile.getCurrentPos(); + int n = fontFile.readTTFUShort(); + int j = fontFile.readTTFUShort() + i - 2; i += 2 * 2; while (n-- > 0) { // getLogger().debug("Iteration: " + n); - in.seekSet(i); - final int platformID = in.readTTFUShort(); - final int encodingID = in.readTTFUShort(); - final int languageID = in.readTTFUShort(); + fontFile.seekSet(i); + final int platformID = fontFile.readTTFUShort(); + final int encodingID = fontFile.readTTFUShort(); + final int languageID = fontFile.readTTFUShort(); - int k = in.readTTFUShort(); - int l = in.readTTFUShort(); + int k = fontFile.readTTFUShort(); + int l = fontFile.readTTFUShort(); if (((platformID == 1 || platformID == 3) && (encodingID == 0 || encodingID == 1))) { - in.seekSet(j + in.readTTFUShort()); + fontFile.seekSet(j + fontFile.readTTFUShort()); String txt; if (platformID == 3) { - txt = in.readTTFString(l, encodingID); + txt = fontFile.readTTFString(l, encodingID); } else { - txt = in.readTTFString(l); + txt = fontFile.readTTFString(l); } if (log.isDebugEnabled()) { @@ -1368,21 +1385,20 @@ public class TTFFile { /** * Read the "PCLT" table to find xHeight and capHeight. - * @param in FontFileReader to read from * @throws IOException In case of a I/O problem */ - private boolean readPCLT(FontFileReader in) throws IOException { - TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("PCLT"); + private boolean readPCLT() throws IOException { + TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.PCLT); if (dirTab != null) { - in.seekSet(dirTab.getOffset() + 4 + 4 + 2); - xHeight = in.readTTFUShort(); + fontFile.seekSet(dirTab.getOffset() + 4 + 4 + 2); + xHeight = fontFile.readTTFUShort(); log.debug("xHeight from PCLT: " + formatUnitsForDebug(xHeight)); - in.skip(2 * 2); - capHeight = in.readTTFUShort(); + fontFile.skip(2 * 2); + capHeight = fontFile.readTTFUShort(); log.debug("capHeight from PCLT: " + formatUnitsForDebug(capHeight)); - in.skip(2 + 16 + 8 + 6 + 1 + 1); + fontFile.skip(2 + 16 + 8 + 6 + 1 + 1); - int serifStyle = in.readTTFUByte(); + int serifStyle = fontFile.readTTFUByte(); serifStyle = serifStyle >> 6; serifStyle = serifStyle & 3; if (serifStyle == 1) { @@ -1512,19 +1528,18 @@ public class TTFFile { /** * Read the kerning table, create a table for both CIDs and * winAnsiEncoding. - * @param in FontFileReader to read from * @throws IOException In case of a I/O problem */ - private void readKerning(FontFileReader in) throws IOException { + private void readKerning() throws IOException { // Read kerning - kerningTab = new java.util.HashMap(); - ansiKerningTab = new java.util.HashMap(); - TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("kern"); + kerningTab = new HashMap<Integer, Map<Integer, Integer>>(); + ansiKerningTab = new HashMap<Integer, Map<Integer, Integer>>(); + TTFDirTabEntry dirTab = dirTabs.get(TTFTableName.KERN); if (dirTab != null) { - seekTab(in, "kern", 2); - for (int n = in.readTTFUShort(); n > 0; n--) { - in.skip(2 * 2); - int k = in.readTTFUShort(); + seekTab(fontFile, TTFTableName.KERN, 2); + for (int n = fontFile.readTTFUShort(); n > 0; n--) { + fontFile.skip(2 * 2); + int k = fontFile.readTTFUShort(); if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { return; } @@ -1532,12 +1547,12 @@ public class TTFFile { continue; } - k = in.readTTFUShort(); - in.skip(3 * 2); + k = fontFile.readTTFUShort(); + fontFile.skip(3 * 2); while (k-- > 0) { - int i = in.readTTFUShort(); - int j = in.readTTFUShort(); - int kpx = in.readTTFShort(); + int i = fontFile.readTTFUShort(); + int j = fontFile.readTTFUShort(); + int kpx = fontFile.readTTFShort(); if (kpx != 0) { // CID kerning table entry, using unicode indexes final Integer iObj = glyphToUnicode(i); @@ -1551,9 +1566,9 @@ public class TTFFile { log.debug("Ignoring kerning pair because Unicode index was" + " found for the second glyph " + i); } else { - Map adjTab = kerningTab.get(iObj); + Map<Integer, Integer> adjTab = kerningTab.get(iObj); if (adjTab == null) { - adjTab = new java.util.HashMap(); + adjTab = new HashMap<Integer, Integer>(); } adjTab.put(u2, new Integer(convertTTFUnit2PDFUnit(kpx))); kerningTab.put(iObj, adjTab); @@ -1565,16 +1580,12 @@ public class TTFFile { // Create winAnsiEncoded kerning table from kerningTab // (could probably be simplified, for now we remap back to CID indexes and // then to winAnsi) - Iterator ae = kerningTab.keySet().iterator(); - while (ae.hasNext()) { - Integer unicodeKey1 = (Integer)ae.next(); + for (Integer unicodeKey1 : kerningTab.keySet()) { Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue()); - Map<Integer, Integer> akpx = new java.util.HashMap(); - Map ckpx = kerningTab.get(unicodeKey1); + Map<Integer, Integer> akpx = new HashMap<Integer, Integer>(); + Map<Integer, Integer> ckpx = kerningTab.get(unicodeKey1); - Iterator aee = ckpx.keySet().iterator(); - while (aee.hasNext()) { - Integer unicodeKey2 = (Integer)aee.next(); + for (Integer unicodeKey2 : ckpx.keySet()) { Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue()); Integer kern = (Integer)ckpx.get(unicodeKey2); @@ -1603,10 +1614,70 @@ public class TTFFile { } /** + * Streams a font. + * @param ttfOut The interface for streaming True Type tables. + * @exception IOException file write error + */ + public void stream(TTFOutputStream ttfOut) throws IOException { + SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> sortedDirTabs = sortDirTabMap(dirTabs); + byte[] file = fontFile.getAllBytes(); + TTFTableOutputStream tableOut = ttfOut.getTableOutputStream(); + TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream(); + ttfOut.startFontStream(); + for (Map.Entry<TTFTableName, TTFDirTabEntry> entry : sortedDirTabs) { + int offset = (int) entry.getValue().getOffset(); + int paddedLength = (int) entry.getValue().getLength(); + paddedLength += getPadSize(offset + paddedLength); + if (entry.getKey().equals(TTFTableName.GLYF)) { + streamGlyf(glyphOut, file, offset, paddedLength); + } else { + tableOut.streamTable(file, offset, paddedLength); + } + } + ttfOut.endFontStream(); + } + + private void streamGlyf(TTFGlyphOutputStream glyphOut, byte[] fontFile, int tableOffset, + int tableLength) throws IOException { + //Stream all but the last glyph + int glyphStart = 0; + int glyphEnd = 0; + glyphOut.startGlyphStream(); + for (int i = 0; i < mtxTab.length - 1; i++) { + glyphStart = (int) mtxTab[i].getOffset() + tableOffset; + glyphEnd = (int) mtxTab[i + 1].getOffset() + tableOffset; + glyphOut.streamGlyph(fontFile, glyphStart, glyphEnd - glyphStart); + } + glyphOut.streamGlyph(fontFile, glyphEnd, (tableOffset + tableLength) - glyphEnd); + glyphOut.endGlyphStream(); + } + + /** + * This returns the order in which the tables in a truetype font should be written to file. + * @param directoryTabs the map that is to be sorted. + * @return TTFTablesNames[] an array of table names sorted in the order they should appear in + * the TTF file. + */ + SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> + sortDirTabMap(Map<TTFTableName, TTFDirTabEntry> directoryTabs) { + SortedSet<Map.Entry<TTFTableName, TTFDirTabEntry>> sortedSet + = new TreeSet<Map.Entry<TTFTableName, TTFDirTabEntry>>( + new Comparator<Map.Entry<TTFTableName, TTFDirTabEntry>>() { + + public int compare(Entry<TTFTableName, TTFDirTabEntry> o1, + Entry<TTFTableName, TTFDirTabEntry> o2) { + return (int) (o1.getValue().getOffset() - o2.getValue().getOffset()); + } + }); + sortedSet.addAll(directoryTabs.entrySet()); + return sortedSet; + } + + /** * Return a List with TTFCmapEntry. * @return A list of TTFCmapEntry objects */ - public List<TTFCmapEntry> getCMaps() { + public List<BFEntry> getCMaps() { return cmaps; } @@ -1615,24 +1686,23 @@ public class TTFFile { * name exists in the collection. * If it does, set offset in fontfile to the beginning of * the Table Directory for that font. - * @param in FontFileReader to read from * @param name The name to check * @return True if not collection or font name present, false otherwise * @throws IOException In case of an I/O problem */ - protected final boolean checkTTC(FontFileReader in, String name) throws IOException { - String tag = in.readTTFString(4); + protected final boolean checkTTC(String name) throws IOException { + String tag = fontFile.readTTFString(4); if ("ttcf".equals(tag)) { // This is a TrueType Collection - in.skip(4); + fontFile.skip(4); // Read directory offsets - int numDirectories = (int)in.readTTFULong(); + int numDirectories = (int)fontFile.readTTFULong(); // int numDirectories=in.readTTFUShort(); long[] dirOffsets = new long[numDirectories]; for (int i = 0; i < numDirectories; i++) { - dirOffsets[i] = in.readTTFULong(); + dirOffsets[i] = fontFile.readTTFULong(); } log.info("This is a TrueType collection file with " @@ -1646,10 +1716,10 @@ public class TTFFile { // Is found, just to show all the names long dirTabOffset = 0; for (int i = 0; (i < numDirectories); i++) { - in.seekSet(dirOffsets[i]); - readDirTabs(in); + fontFile.seekSet(dirOffsets[i]); + readDirTabs(); - readName(in); + readName(); if (fullName.equals(name)) { found = true; @@ -1667,10 +1737,10 @@ public class TTFFile { subFamilyName = ""; } - in.seekSet(dirTabOffset); + fontFile.seekSet(dirTabOffset); return found; } else { - in.seekSet(0); + fontFile.seekSet(0); return true; } } @@ -1682,8 +1752,7 @@ public class TTFFile { * @throws IOException In case of an I/O problem */ public final List<String> getTTCnames(FontFileReader in) throws IOException { - List<String> fontNames = new java.util.ArrayList<String>(); - + List<String> fontNames = new ArrayList<String>(); String tag = in.readTTFString(4); if ("ttcf".equals(tag)) { @@ -1705,9 +1774,9 @@ public class TTFFile { for (int i = 0; (i < numDirectories); i++) { in.seekSet(dirOffsets[i]); - readDirTabs(in); + readDirTabs(); - readName(in); + readName(); log.debug(fullName); fontNames.add(fullName); @@ -1733,13 +1802,13 @@ public class TTFFile { * doesn't matter... */ private Integer[] unicodeToWinAnsi(int unicode) { - List ret = new java.util.ArrayList(); + List<Integer> ret = new ArrayList<Integer>(); for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { if (unicode == Glyphs.WINANSI_ENCODING[i]) { ret.add(new Integer(i)); } } - return (Integer[])ret.toArray(new Integer[0]); + return ret.toArray(new Integer[0]); } /** @@ -1783,7 +1852,7 @@ public class TTFFile { * @throws IOException if glyphIndex not found */ private Integer glyphToUnicode(int glyphIndex) throws IOException { - return (Integer) glyphToUnicodeMap.get(new Integer(glyphIndex)); + return glyphToUnicodeMap.get(new Integer(glyphIndex)); } /** @@ -1795,7 +1864,7 @@ public class TTFFile { */ private Integer unicodeToGlyph(int unicodeIndex) throws IOException { final Integer result - = (Integer) unicodeToGlyphMap.get(new Integer(unicodeIndex)); + = unicodeToGlyphMap.get(new Integer(unicodeIndex)); if (result == null) { throw new IOException( "Glyph index not found for unicode value " + unicodeIndex); @@ -1830,4 +1899,4 @@ public class TTFFile { ioe.printStackTrace(System.err); } } -}
\ No newline at end of file +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java b/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java index dd8cb27a4..7a1a7e5c8 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java +++ b/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); diff --git a/src/java/org/apache/fop/fonts/truetype/TTFGlyphOutputStream.java b/src/java/org/apache/fop/fonts/truetype/TTFGlyphOutputStream.java new file mode 100644 index 000000000..ee3101f9b --- /dev/null +++ b/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; +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFOutputStream.java b/src/java/org/apache/fop/fonts/truetype/TTFOutputStream.java new file mode 100644 index 000000000..8fb8a5cc7 --- /dev/null +++ b/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; +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java index cf3f55b7d..5bd93ce81 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java +++ b/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, @@ -858,40 +777,16 @@ public class TTFSubSetFile extends TTFFile { } /** - * 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); } - } - - - diff --git a/src/java/org/apache/fop/fonts/truetype/TTFTableName.java b/src/java/org/apache/fop/fonts/truetype/TTFTableName.java new file mode 100644 index 000000000..e8e66a8ba --- /dev/null +++ b/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()); + } + +} diff --git a/src/java/org/apache/fop/fonts/truetype/TTFTableOutputStream.java b/src/java/org/apache/fop/fonts/truetype/TTFTableOutputStream.java new file mode 100644 index 000000000..75f0ef63d --- /dev/null +++ b/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; +} diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index 752d14207..2f83f76d1 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/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); diff --git a/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java b/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java index fb88b8bce..08cfd3fc4 100644 --- a/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java +++ b/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); } diff --git a/src/java/org/apache/fop/render/ps/PSFontUtils.java b/src/java/org/apache/fop/render/ps/PSFontUtils.java index 9df0aa030..157f4f419 100644 --- a/src/java/org/apache/fop/render/ps/PSFontUtils.java +++ b/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; } - } diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index 5ed217db1..2821f6c67 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/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()); } - } diff --git a/src/java/org/apache/fop/render/ps/fonts/PSTTFGenerator.java b/src/java/org/apache/fop/render/ps/fonts/PSTTFGenerator.java new file mode 100644 index 000000000..f8ce37505 --- /dev/null +++ b/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> "); + } +} diff --git a/src/java/org/apache/fop/render/ps/fonts/PSTTFGlyphOutputStream.java b/src/java/org/apache/fop/render/ps/fonts/PSTTFGlyphOutputStream.java new file mode 100644 index 000000000..9727af764 --- /dev/null +++ b/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(); + } +} diff --git a/src/java/org/apache/fop/render/ps/fonts/PSTTFOutputStream.java b/src/java/org/apache/fop/render/ps/fonts/PSTTFOutputStream.java new file mode 100644 index 000000000..bf3803eb4 --- /dev/null +++ b/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"); + } + +} diff --git a/src/java/org/apache/fop/render/ps/fonts/PSTTFTableOutputStream.java b/src/java/org/apache/fop/render/ps/fonts/PSTTFTableOutputStream.java new file mode 100644 index 000000000..24d96878e --- /dev/null +++ b/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(); + } +} |