From: Jeremias Maerki Date: Wed, 8 Jan 2003 13:54:04 +0000 (+0000) Subject: First part of my refactoring of fonts. X-Git-Tag: Alt-Design-integration-base~198 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=400e7e64a5d6d86875f3904cacd155652733cac2;p=xmlgraphics-fop.git First part of my refactoring of fonts. TrueType font classes have moved to subpackage (like Type1 before) Lots of Javadocs Fixed Checkstyle errors git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@195819 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/org/apache/fop/fonts/BFEntry.java b/src/org/apache/fop/fonts/BFEntry.java new file mode 100644 index 000000000..726f28af0 --- /dev/null +++ b/src/org/apache/fop/fonts/BFEntry.java @@ -0,0 +1,55 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +/** + * This is just a holder class for bfentries. + */ +public class BFEntry { + + private int unicodeStart; + private int unicodeEnd; + private int glyphStartIndex; + + /** + * Main constructor. + * @param unicodeStart Unicode start index + * @param unicodeEnd Unicode end index + * @param glyphStartIndex glyph start index + */ + public BFEntry(int unicodeStart, int unicodeEnd, int glyphStartIndex) { + this.unicodeStart = unicodeStart; + this.unicodeEnd = unicodeEnd; + this.glyphStartIndex = glyphStartIndex; + } + + /** + * Returns the unicodeStart. + * @return the Unicode start index + */ + public int getUnicodeStart() { + return unicodeStart; + } + + /** + * Returns the unicodeEnd. + * @return the Unicode end index + */ + public int getUnicodeEnd() { + return unicodeEnd; + } + + /** + * Returns the glyphStartIndex. + * @return the glyph start index + */ + public int getGlyphStartIndex() { + return glyphStartIndex; + } + +} diff --git a/src/org/apache/fop/fonts/CIDFont.java b/src/org/apache/fop/fonts/CIDFont.java new file mode 100644 index 000000000..fc9cc90f5 --- /dev/null +++ b/src/org/apache/fop/fonts/CIDFont.java @@ -0,0 +1,64 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +/** + * Abstract base class for CID fonts. + */ +public abstract class CIDFont extends CustomFont { + + // ---- Required ---- + /** + * Returns the name of the base font. + * @return the name of the base font + */ + public abstract String getCidBaseFont(); + + /** + * Returns the type of the CID font. + * @return the type of the CID font + */ + public abstract CIDFontType getCIDType(); + + /** + * Returns the name of the issuer of the font. + * @return a String identifying an issuer of character collections — + * for example, Adobe + */ + public abstract String getRegistry(); + + /** + * Returns a font name for use within a registry. + * @return a String that uniquely names a character collection issued by + * a specific registry — for example, Japan1. + */ + public abstract String getOrdering(); + + /** + * Returns the supplement number of the character collection. + * @return the supplement number + */ + public abstract int getSupplement(); + + + // ---- Optional ---- + /** + * Returns the default width for this font. + * @return the default width + */ + public int getDefaultWidth() { + return 0; + } + + /** + * @see org.apache.fop.fonts.Font#isMultiByte() + */ + public boolean isMultiByte() { + return true; + } +} diff --git a/src/org/apache/fop/fonts/CIDFontType.java b/src/org/apache/fop/fonts/CIDFontType.java new file mode 100644 index 000000000..938b7ae79 --- /dev/null +++ b/src/org/apache/fop/fonts/CIDFontType.java @@ -0,0 +1,67 @@ +/* + * $Id$ + * Copyright (C) 2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +import org.apache.avalon.framework.ValuedEnum; + +/** + * This class enumerates all supported CID font types. + */ +public class CIDFontType extends ValuedEnum { + + /** + * CID Font Type 0 + */ + public static final CIDFontType CIDTYPE0 = new CIDFontType("CIDFontType0", 0); + + /** + * CID Font Type 2 + */ + public static final CIDFontType CIDTYPE2 = new CIDFontType("CIDFontType2", 1); + + + /** + * @see org.apache.avalon.framework.Enum#Enum(String) + */ + protected CIDFontType(String name, int value) { + super(name, value); + } + + + /** + * Returns the CIDFontType by name. + * @param name Name of the CID font type to look up + * @return FontType the CID font type + */ + public static CIDFontType byName(String name) { + if (name.equalsIgnoreCase(CIDFontType.CIDTYPE0.getName())) { + return CIDFontType.CIDTYPE0; + } else if (name.equalsIgnoreCase(CIDFontType.CIDTYPE2.getName())) { + return CIDFontType.CIDTYPE2; + } else { + throw new IllegalArgumentException("Invalid CID font type: " + name); + } + } + + + /** + * Returns the CID FontType by value. + * @param value Value of the CID font type to look up + * @return FontType the CID font type + */ + public static CIDFontType byValue(int value) { + if (value == CIDFontType.CIDTYPE0.getValue()) { + return CIDFontType.CIDTYPE0; + } else if (value == CIDFontType.CIDTYPE2.getValue()) { + return CIDFontType.CIDTYPE2; + } else { + throw new IllegalArgumentException("Invalid CID font type: " + value); + } + } + +} diff --git a/src/org/apache/fop/fonts/CustomFont.java b/src/org/apache/fop/fonts/CustomFont.java new file mode 100644 index 000000000..02e4cd6f0 --- /dev/null +++ b/src/org/apache/fop/fonts/CustomFont.java @@ -0,0 +1,327 @@ +/* + * $Id$ + * Copyright (C) 2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +import java.util.Map; + + +/** + * Abstract base class for custom fonts loaded from files, for example. + */ +public abstract class CustomFont extends Font + implements FontDescriptor, MutableFont { + + private String fontName = null; + private String embedFileName = null; + private String embedResourceName = null; + + private int capHeight = 0; + private int xHeight = 0; + private int ascender = 0; + private int descender = 0; + private int[] fontBBox = {0, 0, 0, 0}; + private int flags = 4; + private int stemV = 0; + private int italicAngle = 0; + private int missingWidth = 0; + private FontType fontType = FontType.TYPE1; + private int firstChar = 0; + private int lastChar = 255; + + private Map kerning = new java.util.HashMap(); + + + private boolean useKerning = true; + + + /** + * @see org.apache.fop.fonts.FontMetrics#getFontName() + */ + public String getFontName() { + return fontName; + } + + /** + * Returns an URI representing an embeddable font file. The URI will often + * be a filename or an URL. + * @return URI to an embeddable font file or null if not available. + */ + public String getEmbedFileName() { + return embedFileName; + } + + /** + * Returns the lookup name to an embeddable font file available as a + * resource. + * @todo Remove this method, this should be done using a resource: URI. + * @return the lookup name + */ + public String getEmbedResourceName() { + return embedResourceName; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getAscender() + */ + public int getAscender() { + return ascender; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getDescender() + */ + public int getDescender() { + return descender; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getCapHeight() + */ + public int getCapHeight() { + return capHeight; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getAscender(int) + */ + public int getAscender(int size) { + return size * ascender; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getDescender(int) + */ + public int getDescender(int size) { + return size * descender; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getCapHeight(int) + */ + public int getCapHeight(int size) { + return size * capHeight; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getXHeight(int) + */ + public int getXHeight(int size) { + return size * xHeight; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFontBBox() + */ + public int[] getFontBBox() { + return fontBBox; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFlags() + */ + public int getFlags() { + return flags; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getStemV() + */ + public int getStemV() { + return stemV; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getItalicAngle() + */ + public int getItalicAngle() { + return italicAngle; + } + + /** + * Returns the width to be used when no width is available. + * @return a character width + */ + public int getMissingWidth() { + return missingWidth; + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFontType() + */ + public FontType getFontType() { + return fontType; + } + + /** + * Returns the index of the first character defined in this font. + * @return the index of the first character + */ + public int getFirstChar() { + return 0; + // return firstChar; + /**@todo Why is this hardcoded??? This code was in SingleByteFont.java */ + } + + /** + * Returns the index of the last character defined in this font. + * @return the index of the last character + */ + public int getLastChar() { + return lastChar; + } + + /** + * Used to determine if kerning is enabled. + * @return True if kerning is enabled. + */ + public boolean isKerningEnabled() { + return useKerning; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#hasKerningInfo() + */ + public final boolean hasKerningInfo() { + return (isKerningEnabled() & kerning.isEmpty()); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getKerningInfo() + */ + public final Map getKerningInfo() { + if (isKerningEnabled()) { + return kerning; + } else { + return java.util.Collections.EMPTY_MAP; + } + } + + + /* ---- MutableFont interface ---- */ + + /** + * @see org.apache.fop.fonts.MutableFont#setFontName(String) + */ + public void setFontName(String name) { + this.fontName = name; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setEmbedFileName(String) + */ + public void setEmbedFileName(String path) { + this.embedFileName = path; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setEmbedResourceName(String) + */ + public void setEmbedResourceName(String name) { + this.embedResourceName = name; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setCapHeight(int) + */ + public void setCapHeight(int capHeight) { + this.capHeight = capHeight; + } + + /** + * Returns the XHeight value of the font. + * @param xHeight the XHeight value + */ + public void setXHeight(int xHeight) { + this.xHeight = xHeight; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setAscender(int) + */ + public void setAscender(int ascender) { + this.ascender = ascender; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setDescender(int) + */ + public void setDescender(int descender) { + this.descender = descender; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setFontBBox(int[]) + */ + public void setFontBBox(int[] bbox) { + this.fontBBox = bbox; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setFlags(int) + */ + public void setFlags(int flags) { + this.flags = flags; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setStemV(int) + */ + public void setStemV(int stemV) { + this.stemV = stemV; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setItalicAngle(int) + */ + public void setItalicAngle(int italicAngle) { + this.italicAngle = italicAngle; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setMissingWidth(int) + */ + public void setMissingWidth(int width) { + this.missingWidth = width; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setFontType(FontType) + */ + public void setFontType(FontType fontType) { + this.fontType = fontType; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setFirstChar(int) + */ + public void setFirstChar(int index) { + this.firstChar = index; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setLastChar(int) + */ + public void setLastChar(int index) { + this.lastChar = index; + } + + /** + * @see org.apache.fop.fonts.MutableFont#setKerningEnabled(boolean) + */ + public void setKerningEnabled(boolean enabled) { + this.useKerning = enabled; + } + + /** + * @see org.apache.fop.fonts.MutableFont#putKerningEntry(Integer, Map) + */ + public void putKerningEntry(Integer key, Map value) { + this.kerning.put(key, value); + } + +} diff --git a/src/org/apache/fop/fonts/Font.java b/src/org/apache/fop/fonts/Font.java new file mode 100644 index 000000000..a51a54564 --- /dev/null +++ b/src/org/apache/fop/fonts/Font.java @@ -0,0 +1,40 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +// FOP + + +/** + * Base class for PDF font classes + */ +public abstract class Font implements FontMetrics { + + /** + * Get the encoding of the font. + * @return the encoding + */ + public abstract String getEncoding(); + + /** + * Map a Unicode character to a code point in the font. + * @param c character to map + * @return the mapped character + */ + public abstract char mapChar(char c); + + /** + * Determines whether the font is a multibyte font. + * @return True if it is multibyte + */ + public boolean isMultiByte() { + return false; + } + +} + diff --git a/src/org/apache/fop/fonts/FontDescriptor.java b/src/org/apache/fop/fonts/FontDescriptor.java new file mode 100644 index 000000000..0f929ff0d --- /dev/null +++ b/src/org/apache/fop/fonts/FontDescriptor.java @@ -0,0 +1,73 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +/** + * This interface enhances the font metrics interface with access methods to + * value needed to register fonts in various target formats like PDF or + * PostScript. + */ +public interface FontDescriptor extends FontMetrics { + + /** + * Returns the ascender value of the font. (Ascent in pdf spec) + * @return the ascender + */ + int getAscender(); + + + /** + * Returns the capital height of the font. + * @return the capiptal height + */ + int getCapHeight(); + + + /** + * Returns the descender value of the font. (Descent in pdf spec) + * @return the descender value + */ + int getDescender(); + + + /** + * Returns the flags for the font. (See pdf spec) + * @return the flags + */ + int getFlags(); + + + /** + * Returns the font's bounding box. + * @return the bounding box + */ + int[] getFontBBox(); + + + /** + * Returns the italic angle for the font. + * @return the italic angle + */ + int getItalicAngle(); + + + /** + * Returns the vertical stem width for the font. + * @return the vertical stem width + */ + int getStemV(); + + + /** + * Indicates if this font may be embedded. + * @return True, if embedding is possible/permitted + */ + boolean isEmbeddable(); + + +} diff --git a/src/org/apache/fop/fonts/FontFileReader.java b/src/org/apache/fop/fonts/FontFileReader.java deleted file mode 100644 index ff205a79c..000000000 --- a/src/org/apache/fop/fonts/FontFileReader.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * $Id$ - * Copyright (C) 2001-2002 The Apache Software Foundation. All rights reserved. - * For details on use and redistribution please refer to the - * LICENSE file included with these sources. - */ - -package org.apache.fop.fonts; - -import java.io.InputStream; -import java.io.OutputStream; -import java.io.File; -import java.io.IOException; - -/** - * Reads a file into an array and - * provides file like functions for array access. - */ -public class FontFileReader { - - private int fsize; // file size - private int current; // current position in file - private byte[] file; - - /** - * Initializes class and reads stream. Init does not close stream. - * - * @param in InputStream to read from new array with size + inc - * @throws IOException In case of an I/O problem - */ - private void init(InputStream in) throws java.io.IOException { - java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream(); - try { - copyStream(in, bout); - this.file = bout.toByteArray(); - this.fsize = this.file.length; - this.current = 0; - } finally { - bout.close(); - } - } - - /**@todo Use method from Avalon Excalibur IO or Jakarta Commons IO*/ - private void copyStream(InputStream in, OutputStream out) throws IOException { - final int bufferSize = 2048; - final byte[] buf = new byte[bufferSize]; - int bytesRead; - while ((bytesRead = in.read(buf)) != -1) { - out.write(buf, 0, bytesRead); - } - } - - /** - * Constructor - * - * @param fileName filename to read - * @throws IOException In case of an I/O problem - */ - public FontFileReader(String fileName) throws IOException { - final File f = new File(fileName); - InputStream in = new java.io.FileInputStream(f); - try { - init(in); - } finally { - in.close(); - } - } - - - /** - * Constructor - * - * @param in InputStream to read from - * @throws IOException In case of an I/O problem - */ - public FontFileReader(InputStream in) throws IOException { - init(in); - } - - - /** - * Set current file position to offset - * - * @param offset The new offset to set - * @throws IOException In case of an I/O problem - */ - public void seekSet(long offset) throws IOException { - if (offset > fsize || offset < 0) { - throw new java.io.EOFException("Reached EOF, file size=" + fsize - + " offset=" + offset); - } - current = (int)offset; - } - - /** - * Set current file position to offset - * - * @param add The number of bytes to advance - * @throws IOException In case of an I/O problem - */ - public void seekAdd(long add) throws IOException { - seekSet(current + add); - } - - /** - * Skip a given number of bytes. - * - * @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); - } - - /** - * Returns current file position. - * - * @return int The current position. - */ - public int getCurrentPos() { - return current; - } - - /** - * Returns the size of the file. - * - * @return int The filesize - */ - public int getFileSize() { - return fsize; - } - - /** - * Read 1 byte. - * - * @return One byte - * @throws IOException If EOF is reached - */ - public byte read() throws IOException { - if (current > fsize) { - throw new java.io.EOFException("Reached EOF, file size=" + fsize); - } - - final byte ret = file[current++]; - return ret; - } - - /** - * Read 1 signed byte. - * - * @return One byte - * @throws IOException If EOF is reached - */ - public final byte readTTFByte() throws IOException { - return read(); - } - - /** - * Read 1 unsigned byte. - * - * @return One unsigned byte - * @throws IOException If EOF is reached - */ - public final int readTTFUByte() throws IOException { - final byte buf = read(); - - if (buf < 0) { - return (int)(256 + buf); - } else { - return (int)buf; - } - } - - /** - * Read 2 bytes signed. - * - * @return One signed short - * @throws IOException If EOF is reached - */ - public final short readTTFShort() throws IOException { - final int ret = (readTTFUByte() << 8) + readTTFUByte(); - final short sret = (short)ret; - return sret; - } - - /** - * Read 2 bytes unsigned. - * - * @return One unsigned short - * @throws IOException If EOF is reached - */ - public final int readTTFUShort() throws IOException { - final int ret = (readTTFUByte() << 8) + readTTFUByte(); - return (int)ret; - } - - /** - * Write a USHort at a given position. - * - * @param pos The absolute position to write to - * @param val The value to write - * @throws IOException If EOF is reached - */ - public final void writeTTFUShort(int pos, int val) throws IOException { - if ((pos + 2) > fsize) { - throw new java.io.EOFException("Reached EOF"); - } - final byte b1 = (byte)((val >> 8) & 0xff); - final byte b2 = (byte)(val & 0xff); - file[pos] = b1; - file[pos + 1] = b2; - } - - /** - * Read 2 bytes signed at position pos without changing current position. - * - * @param pos The absolute position to read from - * @return One signed short - * @throws IOException If EOF is reached - */ - public final short readTTFShort(long pos) throws IOException { - final long cp = getCurrentPos(); - seekSet(pos); - final short ret = readTTFShort(); - seekSet(cp); - return ret; - } - - /** - * Read 2 bytes unsigned at position pos without changing current position. - * - * @param pos The absolute position to read from - * @return One unsigned short - * @throws IOException If EOF is reached - */ - public final int readTTFUShort(long pos) throws IOException { - long cp = getCurrentPos(); - seekSet(pos); - int ret = readTTFUShort(); - seekSet(cp); - return ret; - } - - /** - * Read 4 bytes. - * - * @return One signed integer - * @throws IOException If EOF is reached - */ - public final int readTTFLong() throws IOException { - long ret = readTTFUByte(); // << 8; - ret = (ret << 8) + readTTFUByte(); - ret = (ret << 8) + readTTFUByte(); - ret = (ret << 8) + readTTFUByte(); - - return (int)ret; - } - - /** - * Read 4 bytes. - * - * @return One unsigned integer - * @throws IOException If EOF is reached - */ - public final long readTTFULong() throws IOException { - long ret = readTTFUByte(); - ret = (ret << 8) + readTTFUByte(); - ret = (ret << 8) + readTTFUByte(); - ret = (ret << 8) + readTTFUByte(); - - return ret; - } - - /** - * Read a NUL terminated ISO-8859-1 string. - * - * @return A String - * @throws IOException If EOF is reached - */ - public final String readTTFString() throws IOException { - int i = current; - while (file[i++] != 0) { - 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); - return new String(tmp, "ISO-8859-1"); - } - - - /** - * Read an ISO-8859-1 string of len bytes. - * - * @param len The length of the string to read - * @return A String - * @throws IOException If EOF is reached - */ - public final String readTTFString(int len) throws IOException { - if ((len + current) > fsize) { - throw new java.io.EOFException("Reached EOF, file size=" + fsize); - } - - byte[] tmp = new byte[len]; - System.arraycopy(file, current, tmp, 0, len); - current += len; - return new String(tmp, "ISO-8859-1"); - } - - /** - * Return a copy of the internal array - * - * @param offset The absolute offset to start reading from - * @param length The number of bytes to read - * @return An array of bytes - * @throws IOException if out of bounds - */ - public byte[] getBytes(int offset, - int length) throws IOException { - if ((offset + length) > fsize) { - throw new java.io.IOException("Reached EOF"); - } - - byte[] ret = new byte[length]; - System.arraycopy(file, offset, ret, 0, length); - return ret; - } - - -} \ No newline at end of file diff --git a/src/org/apache/fop/fonts/FontMetrics.java b/src/org/apache/fop/fonts/FontMetrics.java new file mode 100644 index 000000000..4d7905117 --- /dev/null +++ b/src/org/apache/fop/fonts/FontMetrics.java @@ -0,0 +1,95 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +import java.util.Map; + + +/** + * Main interface for access to font metrics. + */ +public interface FontMetrics { + + /** + * Returns the font name. + * @return the font name + */ + String getFontName(); + + + /** + * Returns the type of the font. + * @return the font type + */ + FontType getFontType(); + + + /** + * Returns the ascent of the font described by this + * FontMetrics object. + * @param size font size + * @return ascent in milliponts + */ + int getAscender(int size); + + /** + * Returns the size of a capital letter measured from the font's baseline. + * @param size font size + * @return height of capital characters + */ + int getCapHeight(int size); + + + /** + * Returns the descent of the font described by this + * FontMetrics object. + * @param size font size + * @return descent in milliponts + */ + int getDescender(int size); + + + /** + * Determines the typical font height of this + * FontMetrics object + * @param size font size + * @return font height in millipoints + */ + int getXHeight(int size); + + /** + * Return the width (in 1/1000ths of point size) of the character at + * code point i. + * @param i code point index + * @param size font size + * @return the width of the character + */ + int getWidth(int i, int size); + + /** + * Return the array of widths. + *

+ * This is used to get an array for inserting in an output format. + * It should not be used for lookup. + * @return an array of widths + */ + int[] getWidths(); + + /** + * Indicates if the font has kering information. + * @return True, if kerning is available. + */ + boolean hasKerningInfo(); + + /** + * Returns the kerning map for the font. + * @return the kerning map + */ + Map getKerningInfo(); + +} diff --git a/src/org/apache/fop/fonts/FontType.java b/src/org/apache/fop/fonts/FontType.java new file mode 100644 index 000000000..cfaaa438c --- /dev/null +++ b/src/org/apache/fop/fonts/FontType.java @@ -0,0 +1,98 @@ +/* + * $Id$ + * Copyright (C) 2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +import org.apache.avalon.framework.ValuedEnum; + +/** + * This class enumerates all supported font types. + */ +public class FontType extends ValuedEnum { + + /** + * Collective identifier for "other" font types + */ + public static final FontType OTHER = new FontType("Other", 0); + /** + * Adobe Type 0 fonts + */ + public static final FontType TYPE0 = new FontType("Type0", 1); + /** + * Adobe Type 1 fonts + */ + public static final FontType TYPE1 = new FontType("Type1", 2); + /** + * Adobe Multiple Master Type 1 fonts + */ + public static final FontType MMTYPE1 = new FontType("MMType1", 3); + /** + * Adobe Type 3 fonts ("user-defined" fonts) + */ + public static final FontType TYPE3 = new FontType("Type3", 4); + /** + * TrueType fonts + */ + public static final FontType TRUETYPE = new FontType("TrueType", 5); + + + /** + * @see org.apache.avalon.framework.Enum#Enum(String) + */ + protected FontType(String name, int value) { + super(name, value); + } + + + /** + * Returns the FontType by name. + * @param name Name of the font type to look up + * @return the font type + */ + public static FontType byName(String name) { + if (name.equalsIgnoreCase(FontType.OTHER.getName())) { + return FontType.OTHER; + } else if (name.equalsIgnoreCase(FontType.TYPE0.getName())) { + return FontType.TYPE0; + } else if (name.equalsIgnoreCase(FontType.TYPE1.getName())) { + return FontType.TYPE1; + } else if (name.equalsIgnoreCase(FontType.MMTYPE1.getName())) { + return FontType.MMTYPE1; + } else if (name.equalsIgnoreCase(FontType.TYPE3.getName())) { + return FontType.TYPE3; + } else if (name.equalsIgnoreCase(FontType.TRUETYPE.getName())) { + return FontType.TRUETYPE; + } else { + throw new IllegalArgumentException("Invalid font type: " + name); + } + } + + + /** + * Returns the FontType by value. + * @param value Value of the font type to look up + * @return the font type + */ + public static FontType byValue(int value) { + if (value == FontType.OTHER.getValue()) { + return FontType.OTHER; + } else if (value == FontType.TYPE0.getValue()) { + return FontType.TYPE0; + } else if (value == FontType.TYPE1.getValue()) { + return FontType.TYPE1; + } else if (value == FontType.MMTYPE1.getValue()) { + return FontType.MMTYPE1; + } else if (value == FontType.TYPE3.getValue()) { + return FontType.TYPE3; + } else if (value == FontType.TRUETYPE.getValue()) { + return FontType.TRUETYPE; + } else { + throw new IllegalArgumentException("Invalid font type: " + value); + } + } + +} diff --git a/src/org/apache/fop/fonts/LazyFont.java b/src/org/apache/fop/fonts/LazyFont.java new file mode 100644 index 000000000..7f90f924b --- /dev/null +++ b/src/org/apache/fop/fonts/LazyFont.java @@ -0,0 +1,244 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +//Java +import java.util.Map; + +//FOP +import org.apache.fop.render.pdf.FontReader; + +/** + * This class is used to defer the loading of a font until it is really used. + */ +public class LazyFont extends Font implements FontDescriptor { + + private String metricsFileName = null; + private String fontEmbedPath = null; + private boolean useKerning = false; + + private boolean isMetricsLoaded = false; + private Font realFont = null; + private FontDescriptor realFontDescriptor = null; + + /** + * Main constructor + * @param fontEmbedPath path to embeddable file (may be null) + * @param metricsFileName path to the metrics XML file + * @param useKerning True, if kerning should be enabled + */ + public LazyFont(String fontEmbedPath, String metricsFileName, boolean useKerning) { + this.metricsFileName = metricsFileName; + this.fontEmbedPath = fontEmbedPath; + this.useKerning = useKerning; + } + + private void load() { + if (!isMetricsLoaded) { + isMetricsLoaded = true; + try { + /**@todo Possible thread problem here */ + + FontReader reader = new FontReader(metricsFileName); + reader.setKerningEnabled(useKerning); + reader.setFontEmbedPath(fontEmbedPath); + realFont = reader.getFont(); + if (realFont instanceof FontDescriptor) { + realFontDescriptor = (FontDescriptor) realFont; + } + // System.out.println("Metrics " + metricsFileName + " loaded."); + } catch (Exception ex) { + /**@todo Log this exception */ + //log.error("Failed to read font metrics file " + // + metricsFileName + // + " : " + ex.getMessage()); + } + } + } + + /** + * Gets the real font. + * @return the real font + */ + public Font getRealFont() { + load(); + return realFont; + } + + // ---- Font ---- + /** + * @see org.apache.fop.fonts.Font#getEncoding() + */ + public String getEncoding() { + load(); + return realFont.getEncoding(); + } + + /** + * @see org.apache.fonts.pdf.Font#mapChar(char) + */ + public char mapChar(char c) { + load(); + return realFont.mapChar(c); + } + + /** + * @see org.apache.fop.fonts.Font#isMultiByte() + */ + public boolean isMultiByte() { + return realFont.isMultiByte(); + } + + // ---- FontMetrics interface ---- + /** + * @see org.apache.fop.fonts.FontMetrics#getFontName() + */ + public String getFontName() { + load(); + return realFont.getFontName(); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getAscender(int) + */ + public int getAscender(int size) { + load(); + return realFont.getAscender(size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getCapHeight(int) + */ + public int getCapHeight(int size) { + load(); + return realFont.getCapHeight(size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getDescender(int) + */ + public int getDescender(int size) { + load(); + return realFont.getDescender(size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getXHeight(int) + */ + public int getXHeight(int size) { + load(); + return realFont.getXHeight(size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidth(int, int) + */ + public int getWidth(int i, int size) { + load(); + return realFont.getWidth(i, size); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidths() + */ + public int[] getWidths() { + load(); + return realFont.getWidths(); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#hasKerningInfo() + */ + public boolean hasKerningInfo() { + load(); + return realFont.hasKerningInfo(); + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getKerningInfo() + */ + public Map getKerningInfo() { + load(); + return realFont.getKerningInfo(); + } + + // ---- FontDescriptor interface ---- + /** + * @see org.apache.fop.fonts.FontDescriptor#getCapHeight() + */ + public int getCapHeight() { + load(); + return realFontDescriptor.getCapHeight(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getDescender() + */ + public int getDescender() { + load(); + return realFontDescriptor.getDescender(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getAscender() + */ + public int getAscender() { + load(); + return realFontDescriptor.getAscender(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFlags() + */ + public int getFlags() { + load(); + return realFontDescriptor.getFlags(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFontBBox() + */ + public int[] getFontBBox() { + load(); + return realFontDescriptor.getFontBBox(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getItalicAngle() + */ + public int getItalicAngle() { + load(); + return realFontDescriptor.getItalicAngle(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getStemV() + */ + public int getStemV() { + load(); + return realFontDescriptor.getStemV(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#getFontType() + */ + public FontType getFontType() { + load(); + return realFontDescriptor.getFontType(); + } + + /** + * @see org.apache.fop.fonts.FontDescriptor#isEmbeddable() + */ + public boolean isEmbeddable() { + load(); + return realFontDescriptor.isEmbeddable(); + } + +} + diff --git a/src/org/apache/fop/fonts/MultiByteFont.java b/src/org/apache/fop/fonts/MultiByteFont.java new file mode 100644 index 000000000..1384d3405 --- /dev/null +++ b/src/org/apache/fop/fonts/MultiByteFont.java @@ -0,0 +1,316 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +//Java +import java.util.Map; + +//FOP +import org.apache.fop.pdf.PDFWArray; + +/** + * Generic MultiByte (CID) font + */ +public class MultiByteFont extends CIDFont { + + private static int uniqueCounter = 1; + + + private String ttcName = null; + private String encoding = "Identity-H"; + + private String embedResourceName = null; + + private int defaultWidth = 0; + private CIDFontType cidType = CIDFontType.CIDTYPE2; + + private String namePrefix = null; // Quasi unique prefix + private PDFWArray warray = new PDFWArray(); + private int width[] = null; + + private BFEntry[] bfentries = null; + + /** + * usedGlyphs contains orginal, new glyph index + */ + private Map usedGlyphs = new java.util.HashMap(); + + /** + * usedGlyphsIndex contains new glyph, original index + */ + private Map usedGlyphsIndex = new java.util.HashMap(); + private int usedGlyphsCount = 0; + + + /** + * Default constructor + */ + public MultiByteFont() { + // Make sure that the 3 first glyphs are included + usedGlyphs.put(new Integer(0), new Integer(0)); + usedGlyphsIndex.put(new Integer(0), new Integer(0)); + usedGlyphsCount++; + usedGlyphs.put(new Integer(1), new Integer(1)); + usedGlyphsIndex.put(new Integer(1), new Integer(1)); + usedGlyphsCount++; + usedGlyphs.put(new Integer(2), new Integer(2)); + usedGlyphsIndex.put(new Integer(2), new Integer(2)); + usedGlyphsCount++; + + // Create a quasiunique prefix for fontname + int cnt = 0; + synchronized (this.getClass()) { + cnt = uniqueCounter++; + } + int ctm = (int)(System.currentTimeMillis() & 0xffff); + namePrefix = new String(cnt + "E" + Integer.toHexString(ctm)); + + setFontType(FontType.TYPE0); + } + + /** + * @see org.apache.fop.fonts.CIDFont#getDefaultWidth() + */ + public int getDefaultWidth() { + return defaultWidth; + } + + /** + * @see org.apache.fop.fonts.CIDFont#getRegistry() + */ + public String getRegistry() { + return "Adobe"; + } + + /** + * @see org.apache.fop.fonts.CIDFont#getOrdering() + */ + public String getOrdering() { + return "UCS"; + } + + /** + * @see org.apache.fop.fonts.CIDFont#getSupplement() + */ + public int getSupplement() { + return 0; + } + + /** + * @see org.apache.fop.fonts.CIDFont#getCIDType() + */ + public CIDFontType getCIDType() { + return cidType; + } + + /** + * Sets the CIDType. + * @param cidType The cidType to set + */ + public void setCIDType(CIDFontType cidType) { + this.cidType = cidType; + } + + /** + * @see org.apache.fop.fonts.CIDFont#getCidBaseFont() + */ + public String getCidBaseFont() { + if (isEmbeddable()) { + return namePrefix + super.getFontName(); + } else { + return super.getFontName(); + } + } + +/* unused + public PDFWArray getWidthsAsPDFWArray() { + if (isEmbeddable()) { + // Create widths for reencoded chars + warray = new PDFWArray(); + int[] tmpWidth = new int[usedGlyphsCount]; + + for (int i = 0; i < usedGlyphsCount; i++) { + Integer nw = (Integer)usedGlyphsIndex.get(new Integer(i)); + int nwx = (nw == null) ? 0 : nw.intValue(); + tmpWidth[i] = width[nwx]; + } + warray.addEntry(0, tmpWidth); + } + return warray; + }*/ + + /** + * @see org.apache.fop.fonts.FontDescriptor#isEmbeddable() + */ + public boolean isEmbeddable() { + if (getEmbedFileName() == null + && embedResourceName == null) { + return false; + } else { + return true; + } + } + + /** + * @see org.apache.fop.fonts.Font#getEncoding() + */ + public String getEncoding() { + return encoding; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getFontName() + */ + public String getFontName() { + if (isEmbeddable()) { + return namePrefix + super.getFontName(); + } else { + return super.getFontName(); + } + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidth(int, int) + */ + public int getWidth(int i, int size) { + if (isEmbeddable()) { + Integer idx = (Integer)usedGlyphsIndex.get(new Integer(i)); + return size * width[idx.intValue()]; + } else { + return size * width[i]; + } + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidths() + */ + public int[] getWidths() { + int[] arr = new int[width.length]; + System.arraycopy(width, 0, arr, 0, width.length - 1); + /* + for (int i = 0; i < arr.length; i++) + arr[i] *= size; + */ + return arr; + } + + /** + * Remaps a codepoint based. + * @param i codepoint to remap + * @return new codepoint + */ +/* unused + public Integer reMap(Integer i) { + if (isEmbeddable()) { + Integer ret = (Integer)usedGlyphsIndex.get(i); + if (ret == null) { + ret = i; + } + return ret; + } else { + return i; + } + + } +*/ + + /** + * @see org.apache.fop.fonts.Font#mapChar(char) + */ + public char mapChar(char c) { + int idx = (int)c; + int retIdx = 0; + + for (int i = 0; (i < bfentries.length) && retIdx == 0; i++) { + if (bfentries[i].getUnicodeStart() <= idx + && bfentries[i].getUnicodeEnd() >= idx) { + retIdx = bfentries[i].getGlyphStartIndex() + idx + - bfentries[i].getUnicodeStart(); + } + } + + if (isEmbeddable()) { + // Reencode to a new subset font or get + // the reencoded value + Integer newIdx = (Integer)usedGlyphs.get(new Integer(retIdx)); + if (newIdx == null) { + usedGlyphs.put(new Integer(retIdx), + new Integer(usedGlyphsCount)); + usedGlyphsIndex.put(new Integer(usedGlyphsCount), + new Integer(retIdx)); + retIdx = usedGlyphsCount; + // System.out.println(c+"("+(int)c+") = "+retIdx); + usedGlyphsCount++; + } else { + retIdx = newIdx.intValue(); + } + } + + return (char)retIdx; + } + + /** + * Sets the bfentries. + * @param bfentries The bfentries to set + */ + public void setBFEntries(BFEntry[] bfentries) { + this.bfentries = bfentries; + } + + /** + * Sets the defaultWidth. + * @param defaultWidth The defaultWidth to set + */ + public void setDefaultWidth(int defaultWidth) { + this.defaultWidth = defaultWidth; + } + + /** + * Returns the TrueType Collection Name. + * @return the TrueType Collection Name + */ + public String getTTCName() { + return ttcName; + } + + /** + * Sets the the TrueType Collection Name. + * @param ttcName the TrueType Collection Name + */ + public void setTTCName(String ttcName) { + this.ttcName = ttcName; + } + + /** + * Adds a new CID width entry to the font. + * @param cidWidthIndex index + * @param wds array of widths + */ + public void addCIDWidthEntry(int cidWidthIndex, int[] wds) { + this.warray.addEntry(cidWidthIndex, wds); + } + + + /** + * Sets the width array. + * @param wds array of widths. + */ + public void setWidthArray(int[] wds) { + this.width = wds; + } + + /** + * Returns a Map of used Glyphs. + * @return Map Map of used Glyphs + */ + public Map getUsedGlyphs() { + return usedGlyphs; + } + +} + diff --git a/src/org/apache/fop/fonts/MutableFont.java b/src/org/apache/fop/fonts/MutableFont.java new file mode 100644 index 000000000..f13c568af --- /dev/null +++ b/src/org/apache/fop/fonts/MutableFont.java @@ -0,0 +1,115 @@ +/* + * $Id$ + * Copyright (C) 2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +import java.util.Map; + + +/** + * This interface is used to set the values of a font during configuration time. + */ +public interface MutableFont { + + /** + * Sets the font name. + * @param name font name + */ + void setFontName(String name); + + /** + * Sets the path to the embeddable font file. + * @param path URI to the file + */ + void setEmbedFileName(String path); + + /** + * Sets the resource name of the embeddable font file. + * @param name resource name + */ + void setEmbedResourceName(String name); + + /** + * Sets the capital height value. + * @param capHeight capital height + */ + void setCapHeight(int capHeight); + + /** + * Sets the ascent value. + * @param ascender ascent height + */ + void setAscender(int ascender); + + /** + * Sets the descent value. + * @param descender descent value + */ + void setDescender(int descender); + + /** + * Sets the font's bounding box + * @param bbox bounding box + */ + void setFontBBox(int[] bbox); + + /** + * Sets the font's flags + * @param flags flags + */ + void setFlags(int flags); + + /** + * Sets the font's StemV value. + * @param stemV StemV + */ + void setStemV(int stemV); + + /** + * Sets the font's italic angle. + * @param italicAngle italic angle + */ + void setItalicAngle(int italicAngle); + + /** + * Sets the font's default width + * @param width default width + */ + void setMissingWidth(int width); + + /** + * Sets the font type. + * @param fontType font type + */ + void setFontType(FontType fontType); + + /** + * Sets the index of the first character in the character table. + * @param index index of first character + */ + void setFirstChar(int index); + + /** + * Sets the index of the last character in the character table. + * @param index index of the last character + */ + void setLastChar(int index); + + /** + * Enables/disabled kerning. + * @param enabled True if kerning should be enabled if available + */ + void setKerningEnabled(boolean enabled); + + /** + * Adds an entry to the kerning table. + * @param key Kerning key + * @param value Kerning value + */ + void putKerningEntry(Integer key, Map value); + +} diff --git a/src/org/apache/fop/fonts/SingleByteFont.java b/src/org/apache/fop/fonts/SingleByteFont.java new file mode 100644 index 000000000..9ad776f98 --- /dev/null +++ b/src/org/apache/fop/fonts/SingleByteFont.java @@ -0,0 +1,85 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts; + +/** + * Generic SingleByte font + */ +public class SingleByteFont extends CustomFont { + + private final CodePointMapping mapping + = CodePointMapping.getMapping("WinAnsiEncoding"); + + private String encoding = "WinAnsiEncoding"; + + private int width[] = null; + + + /** + * @see org.apache.fop.fonts.FontDescriptor#isEmbeddable() + */ + public boolean isEmbeddable() { + return (getEmbedFileName() == null && getEmbedResourceName() == null) ? false + : true; + } + + /** + * @see org.apache.fop.fonts.Font#getEncoding() + */ + public String getEncoding() { + return encoding; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidth(int, int) + */ + public int getWidth(int i, int size) { + return size * width[i]; + } + + /** + * @see org.apache.fop.fonts.FontMetrics#getWidths() + */ + public int[] getWidths() { + int[] arr = new int[width.length]; + System.arraycopy(width, 0, arr, 0, width.length - 1); + /* + for (int i = 0; i < arr.length; i++) + arr[i] *= size; + */ + return arr; + } + + /** + * @see org.apache.fop.fonts.Font#mapChar(char) + */ + public char mapChar(char c) { + char d = mapping.mapChar(c); + if (d != 0) { + return d; + } else { + return '#'; + } + } + + /* ---- single byte font specific setters --- */ + + /** + * Sets a width for a character. + * @param index index of the character + * @param width the width of the character + */ + public void setWidth(int index, int width) { + if (this.width == null) { + this.width = new int[256]; + } + this.width[index] = width; + } + +} + diff --git a/src/org/apache/fop/fonts/TTFCmapEntry.java b/src/org/apache/fop/fonts/TTFCmapEntry.java deleted file mode 100644 index d3e900d9f..000000000 --- a/src/org/apache/fop/fonts/TTFCmapEntry.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * $Id$ - * Copyright (C) 2001-2002 The Apache Software Foundation. All rights reserved. - * For details on use and redistribution please refer to the - * LICENSE file included with these sources. - */ - -package org.apache.fop.fonts; - -/** - * The CMap entry contains information of a Unicode range and the - * the glyph indexes related to the range - */ -public class TTFCmapEntry { - - private int unicodeStart; - private int unicodeEnd; - private int glyphStartIndex; - - TTFCmapEntry() { - unicodeStart = 0; - unicodeEnd = 0; - glyphStartIndex = 0; - } - - TTFCmapEntry(int unicodeStart, int unicodeEnd, int glyphStartIndex) { - this.unicodeStart = unicodeStart; - this.unicodeEnd = unicodeEnd; - this.glyphStartIndex = glyphStartIndex; - } - - /** - * @see java.lang.Object#equals(Object) - */ - 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/org/apache/fop/fonts/TTFDirTabEntry.java b/src/org/apache/fop/fonts/TTFDirTabEntry.java deleted file mode 100644 index 2c6213336..000000000 --- a/src/org/apache/fop/fonts/TTFDirTabEntry.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * $Id$ - * Copyright (C) 2001-2002 The Apache Software Foundation. All rights reserved. - * For details on use and redistribution please refer to the - * LICENSE file included with these sources. - */ - -package org.apache.fop.fonts; - -import java.io.IOException; - -class TTFDirTabEntry { - - private byte[] tag = new byte[4]; - private int checksum; - private long offset; - private long length; - - /** - * Read Dir Tab, return tag name - */ - public String read(FontFileReader in) throws IOException { - tag[0] = in.readTTFByte(); - tag[1] = in.readTTFByte(); - tag[2] = in.readTTFByte(); - tag[3] = in.readTTFByte(); - - in.skip(4); // Skip checksum - - offset = in.readTTFULong(); - length = in.readTTFULong(); - - //System.out.println(this.toString()); - return new String(tag, "ISO-8859-1"); - } - - - public String toString() { - return "Read dir tab [" - + tag[0] + " " + tag[1] + " " + tag[2] + " " + tag[3] + "]" - + " offset: " + offset - + " length: " + length - + " name: " + tag; - } - - /** - * Returns the checksum. - * @return int - */ - public int getChecksum() { - return checksum; - } - - /** - * Returns the length. - * @return long - */ - public long getLength() { - return length; - } - - /** - * Returns the offset. - * @return long - */ - public long getOffset() { - return offset; - } - - /** - * Returns the tag. - * @return byte[] - */ - public byte[] getTag() { - return tag; - } - -} diff --git a/src/org/apache/fop/fonts/TTFFile.java b/src/org/apache/fop/fonts/TTFFile.java deleted file mode 100644 index cef0b414b..000000000 --- a/src/org/apache/fop/fonts/TTFFile.java +++ /dev/null @@ -1,1248 +0,0 @@ -/* - * $Id$ - * Copyright (C) 2001-2002 The Apache Software Foundation. All rights reserved. - * For details on use and redistribution please refer to the - * LICENSE file included with these sources. - */ - -package org.apache.fop.fonts; - -import java.io.IOException; -import java.util.Iterator; -import java.util.Map; -import java.util.List; - -import org.apache.avalon.framework.logger.AbstractLogEnabled; -import org.apache.avalon.framework.logger.ConsoleLogger; -import org.apache.avalon.framework.logger.Logger; - -/** - * Reads a TrueType file or a TrueType Collection. - * The TrueType spec can be found at the Microsoft. - * Typography site: http://www.microsoft.com/truetype/ - */ -public class TTFFile extends AbstractLogEnabled { - - static final byte NTABS = 24; - static final int NMACGLYPHS = 258; - static final int MAX_CHAR_CODE = 255; - static final int ENC_BUF_SIZE = 1024; - - private String encoding = "WinAnsiEncoding"; // Default encoding - - private short firstChar = 0; - private boolean isEmbeddable = true; - private boolean hasSerifs = true; - /** - * Table directory - */ - protected Map dirTabs; - private Map kerningTab; // for CIDs - private Map ansiKerningTab; // For winAnsiEncoding - private List cmaps; - private List unicodeMapping; - - private int upem; // unitsPerEm from "head" table - private int nhmtx; // Number of horizontal metrics - private int postFormat; - private int locaFormat; - /** - * Offset to last loca - */ - 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 int[] mtxEncoded = null; - - private String fontName = ""; - private String fullName = ""; - private String notice = ""; - private String familyName = ""; - private String subFamilyName = ""; - - private long italicAngle = 0; - private long isFixedPitch = 0; - private int fontBBox1 = 0; - private int fontBBox2 = 0; - private int fontBBox3 = 0; - private int fontBBox4 = 0; - private int capHeight = 0; - private int underlinePosition = 0; - private int underlineThickness = 0; - private int xHeight = 0; - private int ascender = 0; - private int descender = 0; - - private short lastChar = 0; - - private int ansiWidth[]; - private Map ansiIndex; - - /** - * Position inputstream to position indicated - * in the dirtab offset + offset - */ - void seekTab(FontFileReader in, String name, - long offset) throws IOException { - TTFDirTabEntry dt = (TTFDirTabEntry)dirTabs.get(name); - if (dt == null) { - getLogger().error("Dirtab " + name + " not found."); - return; - } - - in.seekSet(dt.getOffset() + offset); - } - - /** - * Convert from truetype unit to pdf unit based on the - * unitsPerEm field in the "head" table - * @param n truetype unit - * @return pdf unit - */ - public int convertTTFUnit2PDFUnit(int n) { - int ret; - if (n < 0) { - long rest1 = n % upem; - long storrest = 1000 * rest1; - long ledd2 = rest1 / storrest; - ret = -((-1000 * n) / upem - (int)ledd2); - } else { - ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem; - } - - return ret; - } - - /** - * Read the cmap table, - * return false if the table is not present or only unsupported - * tables are present. Currently only unicode cmaps are supported. - * Set the unicodeIndex in the TTFMtxEntries and fills in the - * cmaps vector. - */ - private boolean readCMAP(FontFileReader in) throws IOException { - - unicodeMapping = new java.util.ArrayList(); - - //Read CMAP table and correct mtxTab.index - int mtxPtr = 0; - - seekTab(in, "cmap", 2); - int numCMap = in.readTTFUShort(); // Number of cmap subtables - long cmapUniOffset = 0; - - getLogger().info(numCMap + " cmap tables"); - - //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(); - - getLogger().debug("Platform ID: " + cmapPID - + " Encoding: " + cmapEID); - - if (cmapPID == 3 && cmapEID == 1) { - cmapUniOffset = cmapOffset; - } - } - - if (cmapUniOffset <= 0) { - getLogger().fatalError("Unicode cmap table not present"); - getLogger().fatalError("Unsupported format: Aborting"); - return false; - } - - // Read unicode cmap - seekTab(in, "cmap", cmapUniOffset); - int cmapFormat = in.readTTFUShort(); - /*int cmap_length =*/ in.readTTFUShort(); //skip cmap length - - getLogger().info("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(); - - getLogger().debug("segCountX2 : " + cmapSegCountX2); - getLogger().debug("searchRange : " + cmapSearchRange); - getLogger().debug("entrySelector: " + cmapEntrySelector); - getLogger().debug("rangeShift : " + cmapRangeShift); - - - int cmapEndCounts[] = new int[cmapSegCountX2 / 2]; - int cmapStartCounts[] = new int[cmapSegCountX2 / 2]; - int cmapDeltas[] = new int[cmapSegCountX2 / 2]; - int cmapRangeOffsets[] = new int[cmapSegCountX2 / 2]; - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapEndCounts[i] = in.readTTFUShort(); - } - - in.skip(2); // Skip reservedPad - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapStartCounts[i] = in.readTTFUShort(); - } - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapDeltas[i] = in.readTTFShort(); - } - - //int startRangeOffset = in.getCurrentPos(); - - for (int i = 0; i < (cmapSegCountX2 / 2); i++) { - cmapRangeOffsets[i] = in.readTTFUShort(); - } - - int glyphIdArrayOffset = in.getCurrentPos(); - - // Insert the unicode id for the glyphs in mtxTab - // and fill in the cmaps ArrayList - - for (int i = 0; i < cmapStartCounts.length; i++) { - - getLogger().debug(i + ": " + cmapStartCounts[i] - + " - " + cmapEndCounts[i]); - - for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) { - - // Update lastChar - if (j < 256 && j > lastChar) { - lastChar = (short)j; - } - - if (mtxPtr < mtxTab.length) { - int glyphIdx; - // the last character 65535 = .notdef - // may have a range offset - if (cmapRangeOffsets[i] != 0 && j != 65535) { - int glyphOffset = glyphIdArrayOffset - + ((cmapRangeOffsets[i] / 2) - + (j - cmapStartCounts[i]) - + (i) - - cmapSegCountX2 / 2) * 2; - in.seekSet(glyphOffset); - glyphIdx = (in.readTTFUShort() + cmapDeltas[i]) - & 0xffff; - - unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); - mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); - - - // Also add winAnsiWidth - List v = (List)ansiIndex.get(new Integer(j)); - if (v != null) { - Iterator e = v.listIterator(); - while (e.hasNext()) { - Integer aIdx = (Integer)e.next(); - ansiWidth[aIdx.intValue()] = - mtxTab[glyphIdx].getWx(); - - getLogger().debug("Added width " - + mtxTab[glyphIdx].getWx() - + " uni: " + j - + " ansi: " + aIdx.intValue()); - } - } - - getLogger().debug("Idx: " - + glyphIdx - + " Delta: " + cmapDeltas[i] - + " Unicode: " + j - + " name: " + mtxTab[glyphIdx].getName()); - } else { - glyphIdx = (j + cmapDeltas[i]) & 0xffff; - - if (glyphIdx < mtxTab.length) { - mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); - } else { - getLogger().debug("Glyph " + glyphIdx - + " out of range: " - + mtxTab.length); - } - - unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); - if (glyphIdx < mtxTab.length) { - mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); - } else { - getLogger().debug("Glyph " + glyphIdx - + " out of range: " - + mtxTab.length); - } - - // Also add winAnsiWidth - List v = (List)ansiIndex.get(new Integer(j)); - if (v != null) { - Iterator e = v.listIterator(); - while (e.hasNext()) { - Integer aIdx = (Integer)e.next(); - ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); - } - } - - //getLogger().debug("IIdx: " + - // mtxPtr + - // " Delta: " + cmap_deltas[i] + - // " Unicode: " + j + - // " name: " + - // mtxTab[(j+cmap_deltas[i]) & 0xffff].name); - - } - if (glyphIdx < mtxTab.length) { - if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) { - mtxPtr++; - } - } - } - } - } - } - return true; - } - - /** - * Print first char/last char - */ - private void printMaxMin() { - int min = 255; - int max = 0; - for (int i = 0; i < mtxTab.length; i++) { - if (mtxTab[i].getIndex() < min) { - min = mtxTab[i].getIndex(); - } - if (mtxTab[i].getIndex() > max) { - max = mtxTab[i].getIndex(); - } - } - getLogger().info("Min: " + min); - getLogger().info("Max: " + max); - } - - - /** - * Reads the font using a FontFileReader. - * - * @param in The FontFileReader to use - * @throws IOException In case of an I/O problem - */ - public void readFont(FontFileReader in) throws IOException { - readFont(in, (String)null); - } - - /** - * initialize the ansiWidths array (for winAnsiEncoding) - * and fill with the missingwidth - */ - private void initAnsiWidths() { - ansiWidth = new int[256]; - for (int i = 0; i < 256; i++) { - ansiWidth[i] = mtxTab[0].getWx(); - } - - // 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(); - for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { - Integer ansi = new Integer(i); - Integer uni = new Integer((int)Glyphs.WINANSI_ENCODING[i]); - - List v = (List)ansiIndex.get(uni); - if (v == null) { - v = new java.util.ArrayList(); - ansiIndex.put(uni, v); - } - v.add(ansi); - } - } - - /** - * Read the font data. - * If the fontfile is a TrueType Collection (.ttc file) - * the name of the font to read data for must be supplied, - * else the name is ignored. - * - * @param in The FontFileReader to use - * @param name The name of the font - * @return boolean Returns true if the font is valid - * @throws IOException In case of an I/O problem - */ - public boolean readFont(FontFileReader in, String name) 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"); - } - - readDirTabs(in); - readFontHeader(in); - getNumGlyphs(in); - getLogger().info("Number of glyphs in font: " + numberOfGlyphs); - readHorizontalHeader(in); - readHorizontalMetrics(in); - initAnsiWidths(); - readPostscript(in); - readOS2(in); - readIndexToLocation(in); - readGlyf(in); - readName(in); - readPCLT(in); - // Read cmap table and fill in ansiwidths - boolean valid = readCMAP(in); - if (!valid) { - return false; - } - // Create cmaps for bfentries - createCMaps(); - // print_max_min(); - - readKerning(in); - return true; - } - - private void createCMaps() { - cmaps = new java.util.ArrayList(); - TTFCmapEntry tce = new TTFCmapEntry(); - - Iterator e = unicodeMapping.listIterator(); - UnicodeMapping um = (UnicodeMapping)e.next(); - UnicodeMapping lastMapping = um; - - tce.setUnicodeStart(um.getUIdx()); - tce.setGlyphStartIndex(um.getGIdx()); - - while (e.hasNext()) { - um = (UnicodeMapping)e.next(); - if (((lastMapping.getUIdx() + 1) != um.getUIdx()) - || ((lastMapping.getGIdx() + 1) != um.getGIdx())) { - tce.setUnicodeEnd(lastMapping.getUIdx()); - cmaps.add(tce); - - tce = new TTFCmapEntry(); - tce.setUnicodeStart(um.getUIdx()); - tce.setGlyphStartIndex(um.getGIdx()); - } - lastMapping = um; - } - - tce.setUnicodeEnd(um.getUIdx()); - cmaps.add(tce); - } - - /** - * Returns the Windows name of the font. - * @return String The Windows name - */ - public String getWindowsName() { - return familyName + "," + subFamilyName; - } - - /** - * Returns the PostScript name of the font. - * @return String The PostScript name - */ - public String getPostscriptName() { - if ("Regular".equals(subFamilyName) || "Roman".equals(subFamilyName)) { - return familyName; - } else { - return familyName + "," + subFamilyName; - } - } - - /** - * Returns the font family name of the font. - * @return String The family name - */ - public String getFamilyName() { - return familyName; - } - - /** - * Returns the name of the character set used. - * @return String The caracter set - */ - public String getCharSetName() { - return encoding; - } - - /** - * Returns the CapHeight attribute of the font. - * @return int The CapHeight - */ - public int getCapHeight() { - return (int)convertTTFUnit2PDFUnit(capHeight); - } - - /** - * Returns the XHeight attribute of the font. - * @return int The XHeight - */ - public int getXHeight() { - return (int)convertTTFUnit2PDFUnit(xHeight); - } - - /** - * 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; - } - if (isFixedPitch != 0) { - flags = flags | 2; - } - if (hasSerifs) { - flags = flags | 1; - } - return flags; - } - - - /** - * Returns the StemV attribute of the font. - * @return String The StemV - */ - public String getStemV() { - return "0"; - } - - /** - * Returns the ItalicAngle attribute of the font. - * @return String The ItalicAngle - */ - public String getItalicAngle() { - String ia = Short.toString((short)(italicAngle / 0x10000)); - - // This is the correct italic angle, however only int italic - // angles are supported at the moment so this is commented out. - /* - * if ((italicAngle % 0x10000) > 0 ) - * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000))); - */ - return ia; - } - - /** - * Returns the font bounding box. - * @return int[] The font bbox - */ - public int[] getFontBBox() { - final int[] fbb = new int[4]; - fbb[0] = (int)convertTTFUnit2PDFUnit(fontBBox1); - fbb[1] = (int)convertTTFUnit2PDFUnit(fontBBox2); - fbb[2] = (int)convertTTFUnit2PDFUnit(fontBBox3); - fbb[3] = (int)convertTTFUnit2PDFUnit(fontBBox4); - - return fbb; - } - - /** - * Returns the LowerCaseAscent attribute of the font. - * @return int The LowerCaseAscent - */ - public int getLowerCaseAscent() { - return (int)convertTTFUnit2PDFUnit(ascender); - } - - /** - * Returns the LowerCaseDescent attribute of the font. - * @return int The LowerCaseDescent - */ - public int getLowerCaseDescent() { - return (int)convertTTFUnit2PDFUnit(descender); - } - - /** - * Returns the index of the last character, but this is for WinAnsiEncoding - * only, so the last char is < 256. - * @return short Index of the last character (<256) - */ - public short getLastChar() { - return lastChar; - } - - /** - * Returns the index of the first character. - * @return short Index of the first character - */ - public short getFirstChar() { - return firstChar; - } - - /** - * Returns an array of character widths. - * @return int[] The character widths - */ - public int[] getWidths() { - int[] wx = new int[mtxTab.length]; - for (int i = 0; i < wx.length; i++) { - wx[i] = (int)convertTTFUnit2PDFUnit(mtxTab[i].getWx()); - } - - return wx; - } - - /** - * Returns the width of a given character. - * @param idx Index of the character - * @return int Standard width - */ - public int getCharWidth(int idx) { - return (int)convertTTFUnit2PDFUnit(ansiWidth[idx]); - } - - /** - * Returns the kerning table. - * @return Map The kerning table - */ - public Map getKerning() { - return kerningTab; - } - - /** - * Returns the ANSI kerning table. - * @return Map The ANSI kerning table - */ - public Map getAnsiKerning() { - return ansiKerningTab; - } - - /** - * Indicates if the font may be embedded. - * @return boolean True if it may be embedded - */ - public boolean isEmbeddable() { - return isEmbeddable; - } - - - /** - * Read Table Directory from the current position in the - * 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 { - in.skip(4); // TTF_FIXED_SIZE - int ntabs = in.readTTFUShort(); - in.skip(6); // 3xTTF_USHORT_SIZE - - dirTabs = new java.util.HashMap(); - TTFDirTabEntry[] pd = new TTFDirTabEntry[ntabs]; - getLogger().debug("Reading " + ntabs + " dir tables"); - for (int i = 0; i < ntabs; i++) { - pd[i] = new TTFDirTabEntry(); - dirTabs.put(pd[i].read(in), pd[i]); - } - } - - /** - * 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 + 2); - upem = in.readTTFUShort(); - - in.skip(16); - - fontBBox1 = in.readTTFShort(); - fontBBox2 = in.readTTFShort(); - fontBBox3 = in.readTTFShort(); - fontBBox4 = in.readTTFShort(); - - in.skip(2 + 2 + 2); - - locaFormat = in.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(); - } - - - /** - * Read the "hhea" table to find the ascender and descender and - * size of "hmtx" table, i.e. 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) - throws IOException { - seekTab(in, "hhea", 4); - ascender = in.readTTFShort(); // Use sTypoAscender in "OS/2" table? - descender = in.readTTFShort(); // Use sTypoDescender in "OS/2" table? - - in.skip(2 + 2 + 3 * 2 + 8 * 2); - nhmtx = in.readTTFUShort(); - getLogger().debug("Number of horizontal metrics: " + nhmtx); - } - - /** - * Read "hmtx" table and put the horizontal metrics - * 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) - throws IOException { - seekTab(in, "hmtx", 0); - - int mtxSize = (numberOfGlyphs > nhmtx) ? numberOfGlyphs : nhmtx; - mtxTab = new TTFMtxEntry[mtxSize]; - - getLogger().debug("*** Widths array: \n"); - for (int i = 0; i < mtxSize; i++) { - mtxTab[i] = new TTFMtxEntry(); - } - for (int i = 0; i < nhmtx; i++) { - mtxTab[i].setWx(in.readTTFUShort()); - mtxTab[i].setLsb(in.readTTFUShort()); - - getLogger().debug(" width[" + i + "] = " - + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";"); - } - - if (nhmtx < mtxSize) { - // Fill in the missing widths - int lastWidth = mtxTab[nhmtx - 1].getWx(); - for (int i = nhmtx; i < mtxSize; i++) { - mtxTab[i].setWx(lastWidth); - mtxTab[i].setLsb(in.readTTFUShort()); - } - } - } - - - /** - * Read the "post" table - * containing the postscript names of the glyphs. - */ - private final void readPostscript(FontFileReader in) throws IOException { - String[] psGlyphsBuffer; - int i, k, l; - - seekTab(in, "post", 0); - postFormat = in.readTTFLong(); - italicAngle = in.readTTFULong(); - underlinePosition = in.readTTFShort(); - underlineThickness = in.readTTFShort(); - isFixedPitch = in.readTTFULong(); - - in.skip(4 * 4); - - getLogger().debug("Post format: " + postFormat); - switch (postFormat) { - case 0x00010000: - getLogger().debug("Postscript format 1"); - for (i = 0; i < Glyphs.MAC_GLYPH_NAMES.length; i++) { - mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[i]); - } - break; - case 0x00020000: - getLogger().debug("Postscript format 2"); - int numGlyphStrings = 0; - l = in.readTTFUShort(); // Num Glyphs - // short minIndex=256; - for (i = 0; i < l; i++) { // Read indexes - mtxTab[i].setIndex(in.readTTFUShort()); - // if (minIndex > mtxTab[i].index) - // minIndex=(short)mtxTab[i].index; - - if (mtxTab[i].getIndex() > 257) { - numGlyphStrings++; - } - - getLogger().debug("Post index: " + mtxTab[i].getIndex()); - } - // firstChar=minIndex; - psGlyphsBuffer = new String[numGlyphStrings]; - getLogger().debug("Reading " + numGlyphStrings - + " glyphnames" + ", was n num glyphs=" + l); - for (i = 0; i < psGlyphsBuffer.length; i++) { - psGlyphsBuffer[i] = in.readTTFString(in.readTTFUByte()); - } - - for (i = 0; i < l; i++) { - if (mtxTab[i].getIndex() < NMACGLYPHS) { - mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[mtxTab[i].getIndex()]); - } else { - k = mtxTab[i].getIndex() - NMACGLYPHS; - - getLogger().debug(k + " i=" + i + " mtx=" + mtxTab.length - + " ps=" + psGlyphsBuffer.length); - - mtxTab[i].setName(psGlyphsBuffer[k]); - } - } - - break; - case 0x00030000: - // Postscript format 3 contains no glyph names - getLogger().debug("Postscript format 3"); - break; - default: - getLogger().error("Unknown Postscript format: " + postFormat); - } - } - - - /** - * Read the "OS/2" table - */ - private final void readOS2(FontFileReader in) throws IOException { - // Check if font is embeddable - if (dirTabs.get("OS/2") != null) { - seekTab(in, "OS/2", 2 * 4); - int fsType = in.readTTFUShort(); - if (fsType == 2) { - isEmbeddable = false; - } else { - isEmbeddable = true; - } - } else { - isEmbeddable = true; - } - } - - /** - * 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) - throws IOException { - seekTab(in, "loca", 0); - for (int i = 0; i < numberOfGlyphs; i++) { - mtxTab[i].setOffset(locaFormat == 1 ? in.readTTFULong() - : (in.readTTFUShort() << 1)); - } - lastLoca = (locaFormat == 1 ? in.readTTFULong() - : (in.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 final void readGlyf(FontFileReader in) throws IOException { - TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("glyf"); - 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); - final int[] bbox = { - in.readTTFShort(), - in.readTTFShort(), - in.readTTFShort(), - in.readTTFShort()}; - mtxTab[i].setBoundingBox(bbox); - } else { - mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox()); - } - } - - - long n = ((TTFDirTabEntry)dirTabs.get("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); - final int[] bbox = { - in.readTTFShort(), - in.readTTFShort(), - in.readTTFShort(), - in.readTTFShort()}; - mtxTab[i].setBoundingBox(bbox); - } else { - /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/ - final int bbox0 = mtxTab[0].getBoundingBox()[0]; - final int[] bbox = {bbox0, bbox0, bbox0, bbox0}; - mtxTab[i].setBoundingBox(bbox); - /* Original code - mtxTab[i].bbox[0] = mtxTab[0].bbox[0]; - mtxTab[i].bbox[1] = mtxTab[0].bbox[0]; - mtxTab[i].bbox[2] = mtxTab[0].bbox[0]; - mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */ - } - getLogger().debug(mtxTab[i].toString(this)); - } - } - - /** - * Read the "name" table. - * @param in FontFileReader to read from - * @throws IOException In case of a I/O problem - */ - private final void readName(FontFileReader in) throws IOException { - seekTab(in, "name", 2); - int i = in.getCurrentPos(); - int n = in.readTTFUShort(); - int j = in.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 language_id =*/ in.readTTFUShort(); //Skip language id - - int k = in.readTTFUShort(); - int l = in.readTTFUShort(); - - if (((platformID == 1 || platformID == 3) && (encodingID == 0 || encodingID == 1)) - && (k == 1 || k == 2 || k == 0 || k == 4 || k == 6)) { - // if (k==1 || k==2 || k==0 || k==4 || k==6) { - in.seekSet(j + in.readTTFUShort()); - String txt = in.readTTFString(l); - // getLogger().debug(platform_id + " " + encoding_id - // + " " + k + " " + txt); - switch (k) { - case 0: - notice = txt; - break; - case 1: - familyName = txt; - break; - case 2: - subFamilyName = txt; - break; - case 4: - fullName = txt; - break; - case 6: - fontName = txt; - break; - } - if (!notice.equals("") - && !fullName.equals("") - && !fontName.equals("") - && !familyName.equals("") - && !subFamilyName.equals("")) { - break; - } - } - i += 6 * 2; - } - } - - /** - * 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 final void readPCLT(FontFileReader in) throws IOException { - TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("PCLT"); - if (dirTab != null) { - in.seekSet(dirTab.getOffset() + 4 + 4 + 2); - xHeight = in.readTTFUShort(); - in.skip(2 * 2); - capHeight = in.readTTFUShort(); - in.skip(2 + 16 + 8 + 6 + 1 + 1); - - int serifStyle = in.readTTFUByte(); - serifStyle = serifStyle >> 6; - serifStyle = serifStyle & 3; - if (serifStyle == 1) { - hasSerifs = false; - } else { - hasSerifs = true; - } - } else { - // Approximate capHeight from height of "H" - // It's most unlikly that a font misses the PCLT table - // This also assumes that psocriptnames exists ("H") - // Should look it up int the cmap (that wouldn't help - // for charsets without H anyway...) - for (int i = 0; i < mtxTab.length; i++) { - if ("H".equals(mtxTab[i].getName())) { - capHeight = mtxTab[i].getBoundingBox()[3] - mtxTab[i].getBoundingBox()[1]; - } - } - } - } - - /** - * 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 final void readKerning(FontFileReader in) throws IOException { - // Read kerning - kerningTab = new java.util.HashMap(); - ansiKerningTab = new java.util.HashMap(); - TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("kern"); - if (dirTab != null) { - seekTab(in, "kern", 2); - for (int n = in.readTTFUShort(); n > 0; n--) { - in.skip(2 * 2); - int k = in.readTTFUShort(); - if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { - return; - } - if ((k >> 8) != 0) { - continue; - } - - k = in.readTTFUShort(); - in.skip(3 * 2); - while (k-- > 0) { - int i = in.readTTFUShort(); - int j = in.readTTFUShort(); - int kpx = in.readTTFShort(); - if (kpx != 0) { - // CID table - Integer iObj = new Integer(i); - Map adjTab = (Map)kerningTab.get(iObj); - if (adjTab == null) { - adjTab = new java.util.HashMap(); - } - adjTab.put(new Integer(j), - new Integer((int)convertTTFUnit2PDFUnit(kpx))); - kerningTab.put(iObj, adjTab); - } - } - } - // getLogger().debug(kerningTab.toString()); - - // Create winAnsiEncoded kerning table - Iterator ae = kerningTab.keySet().iterator(); - while (ae.hasNext()) { - Integer cidKey = (Integer)ae.next(); - Map akpx = new java.util.HashMap(); - Map ckpx = (Map)kerningTab.get(cidKey); - - Iterator aee = ckpx.keySet().iterator(); - while (aee.hasNext()) { - Integer cidKey2 = (Integer)aee.next(); - Integer kern = (Integer)ckpx.get(cidKey2); - - Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator(); - while (uniMap.hasNext()) { - Integer unicodeKey = (Integer)uniMap.next(); - Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); - for (int u = 0; u < ansiKeys.length; u++) { - akpx.put(ansiKeys[u], kern); - } - } - } - - if (akpx.size() > 0) { - Iterator uniMap = mtxTab[cidKey.intValue()].getUnicodeIndex().listIterator(); - while (uniMap.hasNext()) { - Integer unicodeKey = (Integer)uniMap.next(); - Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); - for (int u = 0; u < ansiKeys.length; u++) { - ansiKerningTab.put(ansiKeys[u], akpx); - } - } - } - } - } - } - - /** - * Return a List with TTFCmapEntry. - * @return A list of TTFCmapEntry objects - */ - public List getCMaps() { - return cmaps; - } - - /** - * Check if this is a TrueType collection and that the given - * 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); - - if ("ttcf".equals(tag)) { - // This is a TrueType Collection - in.skip(4); - - // Read directory offsets - int numDirectories = (int)in.readTTFULong(); - // int numDirectories=in.readTTFUShort(); - long[] dirOffsets = new long[numDirectories]; - for (int i = 0; i < numDirectories; i++) { - dirOffsets[i] = in.readTTFULong(); - } - - getLogger().debug("This is a TrueType collection file with" - + numDirectories + " fonts"); - getLogger().debug("Containing the following fonts: "); - // Read all the directories and name tables to check - // If the font exists - this is a bit ugly, but... - boolean found = false; - - // Iterate through all name tables even if font - // Is found, just to show all the names - long dirTabOffset = 0; - for (int i = 0; (i < numDirectories); i++) { - in.seekSet(dirOffsets[i]); - readDirTabs(in); - - readName(in); - - if (fullName.equals(name)) { - found = true; - dirTabOffset = dirOffsets[i]; - getLogger().debug("* " + fullName); - } else { - getLogger().debug(fullName); - } - - // Reset names - notice = ""; - fullName = ""; - familyName = ""; - fontName = ""; - subFamilyName = ""; - } - - in.seekSet(dirTabOffset); - return found; - } else { - in.seekSet(0); - return true; - } - } - - /* - * Helper classes, they are not very efficient, but that really - * doesn't matter... - */ - private Integer[] unicodeToWinAnsi(int unicode) { - List ret = new java.util.ArrayList(); - 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]); - } - - /** - * Dumps a few informational values to System.out. - */ - public void printStuff() { - System.out.println("Font name: " + fontName); - System.out.println("Full name: " + fullName); - System.out.println("Family name: " + familyName); - System.out.println("Subfamily name: " + subFamilyName); - System.out.println("Notice: " + notice); - System.out.println("xHeight: " + (int)convertTTFUnit2PDFUnit(xHeight)); - System.out.println("capheight: " + (int)convertTTFUnit2PDFUnit(capHeight)); - - int italic = (int)(italicAngle >> 16); - System.out.println("Italic: " + italic); - System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000)); - if ((italicAngle % 0x10000) > 0) { - System.out.print("." - + (short)((italicAngle % 0x10000) * 1000) - / 0x10000); - } - System.out.println(); - System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender)); - System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender)); - System.out.println("FontBBox: [" + (int)convertTTFUnit2PDFUnit(fontBBox1) - + " " + (int)convertTTFUnit2PDFUnit(fontBBox2) + " " - + (int)convertTTFUnit2PDFUnit(fontBBox3) + " " - + (int)convertTTFUnit2PDFUnit(fontBBox4) + "]"); - } - - /** - * Static main method to get info about a TrueType font. - * @param args The command line arguments - */ - public static void main(String[] args) { - int level = ConsoleLogger.LEVEL_WARN; - Logger log = new ConsoleLogger(level); - try { - TTFFile ttfFile = new TTFFile(); - ttfFile.enableLogging(log); - - FontFileReader reader = new FontFileReader(args[0]); - - String name = null; - if (args.length >= 2) { - name = args[1]; - } - - ttfFile.readFont(reader, name); - ttfFile.printStuff(); - - } catch (IOException ioe) { - log.error("Problem reading font: " + ioe.toString(), ioe); - } - } - -} - - -/** - * Key-value helper class - */ -class UnicodeMapping { - - private int uIdx; - private int gIdx; - - UnicodeMapping(int gIdx, int uIdx) { - this.uIdx = uIdx; - this.gIdx = gIdx; - } - - /** - * Returns the gIdx. - * @return int - */ - public int getGIdx() { - return gIdx; - } - - /** - * Returns the uIdx. - * @return int - */ - public int getUIdx() { - return uIdx; - } - -} diff --git a/src/org/apache/fop/fonts/TTFMtxEntry.java b/src/org/apache/fop/fonts/TTFMtxEntry.java deleted file mode 100644 index 747be55f8..000000000 --- a/src/org/apache/fop/fonts/TTFMtxEntry.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * $Id$ - * Copyright (C) 2001-2002 The Apache Software Foundation. All rights reserved. - * For details on use and redistribution please refer to the - * LICENSE file included with these sources. - */ - -package org.apache.fop.fonts; - -import java.util.List; - -class TTFMtxEntry { - - private int wx; - private int lsb; - private String name = ""; - private int index; - private List unicodeIndex = new java.util.ArrayList(); - private int[] boundingBox = new int[4]; - private long offset; - private byte found = 0; - - public String toString(TTFFile t) { - return "Glyph " + name + " index: " + index + " bbox [ " - + t.convertTTFUnit2PDFUnit(boundingBox[0]) + " " - + t.convertTTFUnit2PDFUnit(boundingBox[1]) + " " - + t.convertTTFUnit2PDFUnit(boundingBox[2]) + " " - + t.convertTTFUnit2PDFUnit(boundingBox[3]) + "] wx: " - + t.convertTTFUnit2PDFUnit(wx); - } - - /** - * Returns the boundingBox. - * @return int[] - */ - public int[] getBoundingBox() { - return boundingBox; - } - - /** - * Sets the boundingBox. - * @param boundingBox The boundingBox to set - */ - public void setBoundingBox(int[] boundingBox) { - this.boundingBox = boundingBox; - } - - /** - * Returns the found. - * @return byte - */ - public byte getFound() { - return found; - } - - /** - * Returns the index. - * @return int - */ - public int getIndex() { - return index; - } - - /** - * Returns the lsb. - * @return int - */ - public int getLsb() { - return lsb; - } - - /** - * Returns the name. - * @return String - */ - public String getName() { - return name; - } - - /** - * Returns the offset. - * @return long - */ - public long getOffset() { - return offset; - } - - /** - * Returns the unicodeIndex. - * @return List - */ - public List getUnicodeIndex() { - return unicodeIndex; - } - - /** - * Returns the wx. - * @return int - */ - public int getWx() { - return wx; - } - - /** - * Sets the found. - * @param found The found to set - */ - public void setFound(byte found) { - this.found = found; - } - - /** - * Sets the index. - * @param index The index to set - */ - public void setIndex(int index) { - this.index = index; - } - - /** - * Sets the lsb. - * @param lsb The lsb to set - */ - public void setLsb(int lsb) { - this.lsb = lsb; - } - - /** - * Sets the name. - * @param name The name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * Sets the offset. - * @param offset The offset to set - */ - public void setOffset(long offset) { - this.offset = offset; - } - - /** - * Sets the wx. - * @param wx The wx to set - */ - public void setWx(int wx) { - this.wx = wx; - } - - -} diff --git a/src/org/apache/fop/fonts/TTFSubSetFile.java b/src/org/apache/fop/fonts/TTFSubSetFile.java deleted file mode 100644 index 7a1a98e95..000000000 --- a/src/org/apache/fop/fonts/TTFSubSetFile.java +++ /dev/null @@ -1,835 +0,0 @@ -/* - * $Id$ - * Copyright (C) 2001-2002 The Apache Software Foundation. All rights reserved. - * For details on use and redistribution please refer to the - * LICENSE file included with these sources. - */ - -package org.apache.fop.fonts; - -import java.io.IOException; -import java.util.Iterator; -import java.util.Map; -import java.util.List; - -/** - * Reads a TrueType file and generates a subset - * that can be used to embed a TrueType CID font - * TrueType tables needed for embedded CID fonts are: - * "head", "hhea", "loca", "maxp", "cvt ", "prep", "glyf", "hmtx" and "fpgm". - * The TrueType spec can be found at the Microsoft - * Typography site: http://www.microsoft.com/truetype/ - */ -public class TTFSubSetFile extends TTFFile { - - private byte[] output = null; - private int realSize = 0; - private int currentPos = 0; - - /* - * Offsets in name table to be filled out by table. - * The offsets are to the checkSum field - */ - private int cvtDirOffset = 0; - private int fpgmDirOffset = 0; - private int glyfDirOffset = 0; - private int headDirOffset = 0; - private int hheaDirOffset = 0; - private int hmtxDirOffset = 0; - private int locaDirOffset = 0; - private int maxpDirOffset = 0; - private int prepDirOffset = 0; - - private int checkSumAdjustmentOffset = 0; - private int locaOffset = 0; - - /** - * Initalize the output array - */ - private void init(int size) { - output = new byte[size]; - realSize = 0; - currentPos = 0; - - // createDirectory() - } - - /** - * Create the directory table - */ - private void createDirectory() { - int numTables = 9; - // Create the TrueType header - writeByte((byte)0); - writeByte((byte)1); - writeByte((byte)0); - writeByte((byte)0); - realSize += 4; - - writeUShort(numTables); - realSize += 2; - - // Create searchRange, entrySelector and rangeShift - int maxPow = maxPow2(numTables); - int searchRange = maxPow * 16; - writeUShort(searchRange); - realSize += 2; - - writeUShort(maxPow); - realSize += 2; - - writeUShort((numTables * 16) - searchRange); - realSize += 2; - - // Create space for the table entries - writeString("cvt "); - cvtDirOffset = currentPos; - currentPos += 12; - realSize += 16; - - writeString("fpgm"); - fpgmDirOffset = currentPos; - currentPos += 12; - realSize += 16; - - writeString("glyf"); - glyfDirOffset = currentPos; - currentPos += 12; - realSize += 16; - - writeString("head"); - headDirOffset = currentPos; - currentPos += 12; - realSize += 16; - - writeString("hhea"); - hheaDirOffset = currentPos; - currentPos += 12; - realSize += 16; - - writeString("hmtx"); - hmtxDirOffset = currentPos; - currentPos += 12; - realSize += 16; - - writeString("loca"); - locaDirOffset = currentPos; - currentPos += 12; - realSize += 16; - - writeString("maxp"); - maxpDirOffset = currentPos; - currentPos += 12; - realSize += 16; - - writeString("prep"); - prepDirOffset = currentPos; - currentPos += 12; - realSize += 16; - } - - - /** - * Copy the cvt table as is from original font to subset font - */ - private void createCvt(FontFileReader in) throws IOException { - TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("cvt "); - if (entry != null) { - pad4(); - seekTab(in, "cvt ", 0); - System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), - 0, output, currentPos, (int)entry.getLength()); - - int checksum = getCheckSum(currentPos, (int)entry.getLength()); - writeULong(cvtDirOffset, checksum); - writeULong(cvtDirOffset + 4, currentPos); - writeULong(cvtDirOffset + 8, (int)entry.getLength()); - currentPos += (int)entry.getLength(); - realSize += (int)entry.getLength(); - } else { - throw new IOException("Can't find cvt table"); - } - } - - - - /** - * Copy the fpgm table as is from original font to subset font - */ - private void createFpgm(FontFileReader in) throws IOException { - TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("fpgm"); - if (entry != null) { - pad4(); - seekTab(in, "fpgm", 0); - System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), - 0, output, currentPos, (int)entry.getLength()); - int checksum = getCheckSum(currentPos, (int)entry.getLength()); - writeULong(fpgmDirOffset, checksum); - writeULong(fpgmDirOffset + 4, currentPos); - writeULong(fpgmDirOffset + 8, (int)entry.getLength()); - currentPos += (int)entry.getLength(); - realSize += (int)entry.getLength(); - } else { - throw new IOException("Can't find fpgm table"); - } - } - - - - /** - * Create an empty loca table without updating checksum - */ - private void createLoca(int size) throws IOException { - pad4(); - locaOffset = currentPos; - writeULong(locaDirOffset + 4, currentPos); - writeULong(locaDirOffset + 8, size * 4 + 4); - currentPos += size * 4 + 4; - realSize += size * 4 + 4; - } - - - /** - * Copy the maxp table as is from original font to subset font - * and set num glyphs to size - */ - private void createMaxp(FontFileReader in, int size) throws IOException { - TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("maxp"); - if (entry != null) { - pad4(); - 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()); - currentPos += (int)entry.getLength(); - realSize += (int)entry.getLength(); - } else { - throw new IOException("Can't find maxp table"); - } - } - - - /** - * Copy the prep table as is from original font to subset font - */ - private void createPrep(FontFileReader in) throws IOException { - TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("prep"); - if (entry != null) { - pad4(); - seekTab(in, "prep", 0); - System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), - 0, output, currentPos, (int)entry.getLength()); - - int checksum = getCheckSum(currentPos, (int)entry.getLength()); - writeULong(prepDirOffset, checksum); - writeULong(prepDirOffset + 4, currentPos); - writeULong(prepDirOffset + 8, (int)entry.getLength()); - currentPos += (int)entry.getLength(); - realSize += (int)entry.getLength(); - } else { - throw new IOException("Can't find prep table"); - } - } - - - /** - * Copy the hhea table as is from original font to subset font - * and fill in size of hmtx table - */ - private void createHhea(FontFileReader in, int size) throws IOException { - TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hhea"); - if (entry != null) { - pad4(); - seekTab(in, "hhea", 0); - System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), - 0, output, currentPos, (int)entry.getLength()); - writeUShort((int)entry.getLength() + currentPos - 2, size); - - int checksum = getCheckSum(currentPos, (int)entry.getLength()); - writeULong(hheaDirOffset, checksum); - writeULong(hheaDirOffset + 4, currentPos); - writeULong(hheaDirOffset + 8, (int)entry.getLength()); - currentPos += (int)entry.getLength(); - realSize += (int)entry.getLength(); - } else { - throw new IOException("Can't find hhea table"); - } - } - - - /** - * Copy the head table as is from original font to subset font - * and set indexToLocaFormat to long and set - * checkSumAdjustment to 0, store offset to checkSumAdjustment - * in checkSumAdjustmentOffset - */ - private void createHead(FontFileReader in) throws IOException { - TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("head"); - if (entry != null) { - pad4(); - seekTab(in, "head", 0); - System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), - 0, output, currentPos, (int)entry.getLength()); - - checkSumAdjustmentOffset = currentPos + 8; - output[currentPos + 8] = 0; // Set checkSumAdjustment to 0 - output[currentPos + 9] = 0; - output[currentPos + 10] = 0; - output[currentPos + 11] = 0; - 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()); - - currentPos += (int)entry.getLength(); - realSize += (int)entry.getLength(); - } else { - throw new IOException("Can't find head table"); - } - } - - - /** - * Create the glyf table and fill in loca table - */ - private void createGlyf(FontFileReader in, - Map glyphs) throws IOException { - TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf"); - int size = 0; - int start = 0; - int endOffset = 0; // Store this as the last loca - if (entry != null) { - pad4(); - start = currentPos; - - /* Loca table must be in order by glyph index, so build - * an array first and then write the glyph info and - * location offset. - */ - int[] origIndexes = new int[glyphs.size()]; - - Iterator e = glyphs.keySet().iterator(); - while (e.hasNext()) { - Integer origIndex = (Integer)e.next(); - Integer subsetIndex = (Integer)glyphs.get(origIndex); - origIndexes[subsetIndex.intValue()] = origIndex.intValue(); - } - - for (int i = 0; i < origIndexes.length; i++) { - int glyphLength = 0; - int nextOffset = 0; - int origGlyphIndex = origIndexes[i]; - if (origGlyphIndex >= (mtxTab.length - 1)) { - nextOffset = (int)lastLoca; - } else { - nextOffset = (int)mtxTab[origGlyphIndex + 1].getOffset(); - } - glyphLength = nextOffset - (int)mtxTab[origGlyphIndex].getOffset(); - - // Copy glyph - System.arraycopy( - in.getBytes((int)entry.getOffset() + (int)mtxTab[origGlyphIndex].getOffset(), - glyphLength), 0, - output, currentPos, - glyphLength); - - - // Update loca table - writeULong(locaOffset + i * 4, currentPos - start); - if ((currentPos - start + glyphLength) > endOffset) { - endOffset = (currentPos - start + glyphLength); - } - - currentPos += glyphLength; - realSize += glyphLength; - - } - - size = currentPos - start; - - int checksum = getCheckSum(start, size); - writeULong(glyfDirOffset, checksum); - writeULong(glyfDirOffset + 4, start); - writeULong(glyfDirOffset + 8, size); - currentPos += 12; - realSize += 12; - - // Update loca checksum and last loca index - writeULong(locaOffset + glyphs.size() * 4, endOffset); - - checksum = getCheckSum(locaOffset, glyphs.size() * 4 + 4); - writeULong(locaDirOffset, checksum); - } else { - throw new IOException("Can't find glyf table"); - } - } - - - /** - * Create the hmtx table by copying metrics from original - * font to subset font. The glyphs Map contains an - * Integer key and Integer value that maps the original - * metric (key) to the subset metric (value) - */ - private void createHmtx(FontFileReader in, - Map glyphs) throws IOException { - TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hmtx"); - - int longHorMetricSize = glyphs.size() * 2; - int leftSideBearingSize = glyphs.size() * 2; - int hmtxSize = longHorMetricSize + leftSideBearingSize; - - if (entry != null) { - pad4(); - //int offset = (int)entry.offset; - Iterator e = glyphs.keySet().iterator(); - while (e.hasNext()) { - Integer origIndex = (Integer)e.next(); - Integer subsetIndex = (Integer)glyphs.get(origIndex); - - writeUShort(currentPos + subsetIndex.intValue() * 4, - mtxTab[origIndex.intValue()].getWx()); - writeUShort(currentPos + subsetIndex.intValue() * 4 + 2, - mtxTab[origIndex.intValue()].getLsb()); - } - - int checksum = getCheckSum(currentPos, hmtxSize); - writeULong(hmtxDirOffset, checksum); - writeULong(hmtxDirOffset + 4, currentPos); - writeULong(hmtxDirOffset + 8, hmtxSize); - currentPos += hmtxSize; - realSize += hmtxSize; - } else { - throw new IOException("Can't find hmtx table"); - } - } - - /** - * Returns a List containing the glyph itself plus all glyphs - * that this composite glyph uses - */ - private List getIncludedGlyphs(FontFileReader in, int glyphOffset, - Integer glyphIdx) throws IOException { - List ret = new java.util.ArrayList(); - ret.add(glyphIdx); - int offset = glyphOffset + (int)mtxTab[glyphIdx.intValue()].getOffset() + 10; - Integer compositeIdx = null; - int flags = 0; - boolean moreComposites = true; - while (moreComposites) { - flags = in.readTTFUShort(offset); - compositeIdx = new Integer(in.readTTFUShort(offset + 2)); - ret.add(compositeIdx); - - offset += 4; - if ((flags & 1) > 0) { - // ARG_1_AND_ARG_2_ARE_WORDS - offset += 4; - } else { - offset += 2; - } - - if ((flags & 8) > 0) { - offset += 2; // WE_HAVE_A_SCALE - } else if ((flags & 64) > 0) { - offset += 4; // WE_HAVE_AN_X_AND_Y_SCALE - } else if ((flags & 128) > 0) { - offset += 8; // WE_HAVE_A_TWO_BY_TWO - } - - if ((flags & 32) > 0) { - moreComposites = true; - } else { - moreComposites = false; - } - } - - return ret; - } - - - /** - * Rewrite all compositepointers in glyphindex glyphIdx - * - */ - private void remapComposite(FontFileReader in, Map glyphs, - int glyphOffset, - Integer glyphIdx) throws IOException { - int offset = glyphOffset + (int)mtxTab[glyphIdx.intValue()].getOffset() - + 10; - - Integer compositeIdx = null; - int flags = 0; - boolean moreComposites = true; - - while (moreComposites) { - flags = in.readTTFUShort(offset); - compositeIdx = new Integer(in.readTTFUShort(offset + 2)); - Integer newIdx = (Integer)glyphs.get(compositeIdx); - if (newIdx == null) { - // This errormessage would look much better - // if the fontname was printed to - //log.error("An embedded font " - // + "contains bad glyph data. " - // + "Characters might not display " - // + "correctly."); - moreComposites = false; - continue; - } - - in.writeTTFUShort(offset + 2, newIdx.intValue()); - - offset += 4; - - if ((flags & 1) > 0) { - // ARG_1_AND_ARG_2_ARE_WORDS - offset += 4; - } else { - offset += 2; - } - - if ((flags & 8) > 0) { - offset += 2; // WE_HAVE_A_SCALE - } else if ((flags & 64) > 0) { - offset += 4; // WE_HAVE_AN_X_AND_Y_SCALE - } else if ((flags & 128) > 0) { - offset += 8; // WE_HAVE_A_TWO_BY_TWO - } - - if ((flags & 32) > 0) { - moreComposites = true; - } else { - moreComposites = false; - } - } - } - - - /** - * Scan all the original glyphs for composite glyphs and add those glyphs - * to the glyphmapping also rewrite the composite glyph pointers to the new - * mapping - */ - private void scanGlyphs(FontFileReader in, - Map glyphs) throws IOException { - TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf"); - Map newComposites = null; - Map allComposites = new java.util.HashMap(); - - int newIndex = glyphs.size(); - - if (entry != null) { - while (newComposites == null || newComposites.size() > 0) { - // Inefficient to iterate through all glyphs - newComposites = new java.util.HashMap(); - - Iterator e = glyphs.keySet().iterator(); - while (e.hasNext()) { - Integer origIndex = (Integer)e.next(); - - if (in.readTTFShort(entry.getOffset() - + mtxTab[origIndex.intValue()].getOffset()) < 0) { - // origIndex is a composite glyph - allComposites.put(origIndex, glyphs.get(origIndex)); - List composites = - getIncludedGlyphs(in, (int)entry.getOffset(), - origIndex); - - // Iterate through all composites pointed to - // by this composite and check if they exists - // in the glyphs map, add them if not. - Iterator cps = composites.iterator(); - while (cps.hasNext()) { - Integer cIdx = (Integer)cps.next(); - if (glyphs.get(cIdx) == null - && newComposites.get(cIdx) == null) { - newComposites.put(cIdx, - new Integer(newIndex)); - newIndex++; - } - } - } - } - - // Add composites to glyphs - Iterator m = newComposites.keySet().iterator(); - while (m.hasNext()) { - Integer im = (Integer)m.next(); - glyphs.put(im, newComposites.get(im)); - } - } - - // Iterate through all composites to remap their composite index - Iterator ce = allComposites.keySet().iterator(); - while (ce.hasNext()) { - remapComposite(in, glyphs, (int)entry.getOffset(), - (Integer)ce.next()); - } - - } else { - throw new IOException("Can't find glyf table"); - } - } - - - - /** - * Returns a subset of the original 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, - Map glyphs) 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"); - } - - output = new byte[in.getFileSize()]; - - readDirTabs(in); - readFontHeader(in); - getNumGlyphs(in); - readHorizontalHeader(in); - readHorizontalMetrics(in); - readIndexToLocation(in); - - scanGlyphs(in, glyphs); - - createDirectory(); // Create the TrueType header and directory - - createHead(in); - createHhea(in, glyphs.size()); // Create the hhea table - createHmtx(in, glyphs); // Create hmtx table - createMaxp(in, glyphs.size()); // copy the maxp table - - try { - createCvt(in); // copy the cvt table - } catch (IOException ex) { - // Cvt is optional (only required for OpenType (MS) fonts) - //log.error("TrueType warning: " + ex.getMessage()); - } - - try { - createFpgm(in); // copy fpgm table - } catch (IOException ex) { - // Fpgm is optional (only required for OpenType (MS) fonts) - //log.error("TrueType warning: " + ex.getMessage()); - } - - try { - createPrep(in); // copy prep table - } catch (IOException ex) { - // Prep is optional (only required for OpenType (MS) fonts) - //log.error("TrueType warning: " + ex.getMessage()); - } - - try { - createLoca(glyphs.size()); // create empty loca table - } catch (IOException ex) { - // Loca is optional (only required for OpenType (MS) fonts) - //log.error("TrueType warning: " + ex.getMessage()); - } - - try { - createGlyf(in, glyphs); - } catch (IOException ex) { - // Glyf is optional (only required for OpenType (MS) fonts) - //log.error("TrueType warning: " + ex.getMessage()); - } - - pad4(); - createCheckSumAdjustment(); - - byte[] ret = new byte[realSize]; - System.arraycopy(output, 0, ret, 0, realSize); - - return ret; - } - - /** - * writes a ISO-8859-1 string at the currentPosition - * updates currentPosition but not realSize - * @return number of bytes written - */ - private int writeString(String str) { - int length = 0; - try { - byte[] buf = str.getBytes("ISO-8859-1"); - System.arraycopy(buf, 0, output, currentPos, buf.length); - length = buf.length; - currentPos += length; - } catch (java.io.UnsupportedEncodingException e) { - // This should never happen! - } - - return length; - } - - /** - * Appends a byte to the output array, - * updates currentPost but not realSize - */ - private void writeByte(byte b) { - output[currentPos++] = b; - } - - /** - * Appends a USHORT to the output array, - * updates currentPost but not realSize - */ - private void writeUShort(int s) { - byte b1 = (byte)((s >> 8) & 0xff); - byte b2 = (byte)(s & 0xff); - writeByte(b1); - writeByte(b2); - } - - /** - * Appends a USHORT to the output array, - * at the given position without changing currentPos - */ - private void writeUShort(int pos, int s) { - byte b1 = (byte)((s >> 8) & 0xff); - byte b2 = (byte)(s & 0xff); - output[pos] = b1; - 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, - * at the given position without changing currentPos - */ - private void writeULong(int pos, 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); - output[pos] = b1; - output[pos + 1] = b2; - output[pos + 2] = b3; - output[pos + 3] = b4; - } - - /** - * Read a signed short value at given position - */ - private short readShort(int pos) { - int ret = readUShort(pos); - return (short)ret; - } - - /** - * Read a unsigned short value at given position - */ - private int readUShort(int pos) { - int ret = (int)output[pos]; - if (ret < 0) { - ret += 256; - } - ret = ret << 8; - if ((int)output[pos + 1] < 0) { - ret |= (int)output[pos + 1] + 256; - } else { - ret |= (int)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++; - } - } - - /** - * Returns the maximum power of 2 <= max - */ - private int maxPow2(int max) { - int i = 0; - while (Math.pow(2, (double)i) < max) { - i++; - } - - return (i - 1); - } - - private int log2(int num) { - return (int)(Math.log((double)num) / Math.log(2)); - } - - - private int getCheckSum(int start, int size) { - return (int)getLongCheckSum(start, size); - } - - private long getLongCheckSum(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; - if (remainder != 0) { - size += remainder; - } - - long sum = 0; - - for (int i = 0; i < size; i += 4) { - int l = (int)(output[start + i] << 24); - l += (int)(output[start + i + 1] << 16); - l += (int)(output[start + i + 2] << 16); - l += (int)(output[start + i + 3] << 16); - sum += l; - if (sum > 0xffffffff) { - sum = sum - 0xffffffff; - } - } - - return sum; - } - - private void createCheckSumAdjustment() { - long sum = getLongCheckSum(0, realSize); - int checksum = (int)(0xb1b0afba - sum); - writeULong(checkSumAdjustmentOffset, checksum); - } - -} - - - diff --git a/src/org/apache/fop/fonts/apps/TTFReader.java b/src/org/apache/fop/fonts/apps/TTFReader.java index 239221492..424104bd9 100644 --- a/src/org/apache/fop/fonts/apps/TTFReader.java +++ b/src/org/apache/fop/fonts/apps/TTFReader.java @@ -25,9 +25,9 @@ import org.apache.avalon.framework.logger.ConsoleLogger; import org.apache.avalon.framework.logger.Logger; //FOP -import org.apache.fop.fonts.FontFileReader; -import org.apache.fop.fonts.TTFCmapEntry; -import org.apache.fop.fonts.TTFFile; +import org.apache.fop.fonts.truetype.FontFileReader; +import org.apache.fop.fonts.truetype.TTFCmapEntry; +import org.apache.fop.fonts.truetype.TTFFile; /** * A tool which reads TTF files and generates diff --git a/src/org/apache/fop/fonts/package.html b/src/org/apache/fop/fonts/package.html new file mode 100644 index 000000000..33e1e2cb3 --- /dev/null +++ b/src/org/apache/fop/fonts/package.html @@ -0,0 +1,6 @@ + +org.apache.fop.fonts Package + +

Classes for font handling. Subpackages contain command line applications for font metrics generation, font parsing classes etc.

+ + \ No newline at end of file diff --git a/src/org/apache/fop/fonts/truetype/FontFileReader.java b/src/org/apache/fop/fonts/truetype/FontFileReader.java new file mode 100644 index 000000000..a1c8ea5ff --- /dev/null +++ b/src/org/apache/fop/fonts/truetype/FontFileReader.java @@ -0,0 +1,324 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts.truetype; + +import java.io.InputStream; +import java.io.File; +import java.io.IOException; + +import org.apache.fop.util.StreamUtilities; + +/** + * Reads a TrueType font file into a byte array and + * provides file like functions for array access. + */ +public class FontFileReader { + + private int fsize; // file size + private int current; // current position in file + private byte[] file; + + /** + * Initializes class and reads stream. Init does not close stream. + * + * @param in InputStream to read from new array with size + inc + * @throws IOException In case of an I/O problem + */ + private void init(InputStream in) throws java.io.IOException { + java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream(); + try { + StreamUtilities.streamCopy(in, bout); + this.file = bout.toByteArray(); + this.fsize = this.file.length; + this.current = 0; + } finally { + bout.close(); + } + } + + /** + * Constructor + * + * @param fileName filename to read + * @throws IOException In case of an I/O problem + */ + public FontFileReader(String fileName) throws IOException { + final File f = new File(fileName); + InputStream in = new java.io.FileInputStream(f); + try { + init(in); + } finally { + in.close(); + } + } + + + /** + * Constructor + * + * @param in InputStream to read from + * @throws IOException In case of an I/O problem + */ + public FontFileReader(InputStream in) throws IOException { + init(in); + } + + + /** + * Set current file position to offset + * + * @param offset The new offset to set + * @throws IOException In case of an I/O problem + */ + public void seekSet(long offset) throws IOException { + if (offset > fsize || offset < 0) { + throw new java.io.EOFException("Reached EOF, file size=" + fsize + + " offset=" + offset); + } + current = (int)offset; + } + + /** + * Set current file position to offset + * + * @param add The number of bytes to advance + * @throws IOException In case of an I/O problem + */ + public void seekAdd(long add) throws IOException { + seekSet(current + add); + } + + /** + * Skip a given number of bytes. + * + * @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); + } + + /** + * Returns current file position. + * + * @return int The current position. + */ + public int getCurrentPos() { + return current; + } + + /** + * Returns the size of the file. + * + * @return int The filesize + */ + public int getFileSize() { + return fsize; + } + + /** + * Read 1 byte. + * + * @return One byte + * @throws IOException If EOF is reached + */ + public byte read() throws IOException { + if (current > fsize) { + throw new java.io.EOFException("Reached EOF, file size=" + fsize); + } + + final byte ret = file[current++]; + return ret; + } + + /** + * Read 1 signed byte. + * + * @return One byte + * @throws IOException If EOF is reached + */ + public final byte readTTFByte() throws IOException { + return read(); + } + + /** + * Read 1 unsigned byte. + * + * @return One unsigned byte + * @throws IOException If EOF is reached + */ + public final int readTTFUByte() throws IOException { + final byte buf = read(); + + if (buf < 0) { + return (int)(256 + buf); + } else { + return (int)buf; + } + } + + /** + * Read 2 bytes signed. + * + * @return One signed short + * @throws IOException If EOF is reached + */ + public final short readTTFShort() throws IOException { + final int ret = (readTTFUByte() << 8) + readTTFUByte(); + final short sret = (short)ret; + return sret; + } + + /** + * Read 2 bytes unsigned. + * + * @return One unsigned short + * @throws IOException If EOF is reached + */ + public final int readTTFUShort() throws IOException { + final int ret = (readTTFUByte() << 8) + readTTFUByte(); + return (int)ret; + } + + /** + * Write a USHort at a given position. + * + * @param pos The absolute position to write to + * @param val The value to write + * @throws IOException If EOF is reached + */ + public final void writeTTFUShort(int pos, int val) throws IOException { + if ((pos + 2) > fsize) { + throw new java.io.EOFException("Reached EOF"); + } + final byte b1 = (byte)((val >> 8) & 0xff); + final byte b2 = (byte)(val & 0xff); + file[pos] = b1; + file[pos + 1] = b2; + } + + /** + * Read 2 bytes signed at position pos without changing current position. + * + * @param pos The absolute position to read from + * @return One signed short + * @throws IOException If EOF is reached + */ + public final short readTTFShort(long pos) throws IOException { + final long cp = getCurrentPos(); + seekSet(pos); + final short ret = readTTFShort(); + seekSet(cp); + return ret; + } + + /** + * Read 2 bytes unsigned at position pos without changing current position. + * + * @param pos The absolute position to read from + * @return One unsigned short + * @throws IOException If EOF is reached + */ + public final int readTTFUShort(long pos) throws IOException { + long cp = getCurrentPos(); + seekSet(pos); + int ret = readTTFUShort(); + seekSet(cp); + return ret; + } + + /** + * Read 4 bytes. + * + * @return One signed integer + * @throws IOException If EOF is reached + */ + public final int readTTFLong() throws IOException { + long ret = readTTFUByte(); // << 8; + ret = (ret << 8) + readTTFUByte(); + ret = (ret << 8) + readTTFUByte(); + ret = (ret << 8) + readTTFUByte(); + + return (int)ret; + } + + /** + * Read 4 bytes. + * + * @return One unsigned integer + * @throws IOException If EOF is reached + */ + public final long readTTFULong() throws IOException { + long ret = readTTFUByte(); + ret = (ret << 8) + readTTFUByte(); + ret = (ret << 8) + readTTFUByte(); + ret = (ret << 8) + readTTFUByte(); + + return ret; + } + + /** + * Read a NUL terminated ISO-8859-1 string. + * + * @return A String + * @throws IOException If EOF is reached + */ + public final String readTTFString() throws IOException { + int i = current; + while (file[i++] != 0) { + 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); + return new String(tmp, "ISO-8859-1"); + } + + + /** + * Read an ISO-8859-1 string of len bytes. + * + * @param len The length of the string to read + * @return A String + * @throws IOException If EOF is reached + */ + public final String readTTFString(int len) throws IOException { + if ((len + current) > fsize) { + throw new java.io.EOFException("Reached EOF, file size=" + fsize); + } + + byte[] tmp = new byte[len]; + System.arraycopy(file, current, tmp, 0, len); + current += len; + return new String(tmp, "ISO-8859-1"); + } + + /** + * Return a copy of the internal array + * + * @param offset The absolute offset to start reading from + * @param length The number of bytes to read + * @return An array of bytes + * @throws IOException if out of bounds + */ + public byte[] getBytes(int offset, + int length) throws IOException { + if ((offset + length) > fsize) { + throw new java.io.IOException("Reached EOF"); + } + + byte[] ret = new byte[length]; + System.arraycopy(file, offset, ret, 0, length); + return ret; + } + + +} \ No newline at end of file diff --git a/src/org/apache/fop/fonts/truetype/TTFCmapEntry.java b/src/org/apache/fop/fonts/truetype/TTFCmapEntry.java new file mode 100644 index 000000000..cda19c1c5 --- /dev/null +++ b/src/org/apache/fop/fonts/truetype/TTFCmapEntry.java @@ -0,0 +1,95 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts.truetype; + +/** + * The CMap entry contains information of a Unicode range and the + * the glyph indexes related to the range + */ +public class TTFCmapEntry { + + private int unicodeStart; + private int unicodeEnd; + private int glyphStartIndex; + + TTFCmapEntry() { + unicodeStart = 0; + unicodeEnd = 0; + glyphStartIndex = 0; + } + + TTFCmapEntry(int unicodeStart, int unicodeEnd, int glyphStartIndex) { + this.unicodeStart = unicodeStart; + this.unicodeEnd = unicodeEnd; + this.glyphStartIndex = glyphStartIndex; + } + + /** + * @see java.lang.Object#equals(Object) + */ + 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/org/apache/fop/fonts/truetype/TTFDirTabEntry.java b/src/org/apache/fop/fonts/truetype/TTFDirTabEntry.java new file mode 100644 index 000000000..74db0274e --- /dev/null +++ b/src/org/apache/fop/fonts/truetype/TTFDirTabEntry.java @@ -0,0 +1,82 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts.truetype; + +import java.io.IOException; + + +/** + * This class represents an entry to a TrueType font's Dir Tab. + */ +class TTFDirTabEntry { + + private byte[] tag = new byte[4]; + private int checksum; + private long offset; + private long length; + + /** + * Read Dir Tab, return tag name + */ + public String read(FontFileReader in) throws IOException { + tag[0] = in.readTTFByte(); + tag[1] = in.readTTFByte(); + tag[2] = in.readTTFByte(); + tag[3] = in.readTTFByte(); + + in.skip(4); // Skip checksum + + offset = in.readTTFULong(); + length = in.readTTFULong(); + + //System.out.println(this.toString()); + return new String(tag, "ISO-8859-1"); + } + + + public String toString() { + return "Read dir tab [" + + tag[0] + " " + tag[1] + " " + tag[2] + " " + tag[3] + "]" + + " offset: " + offset + + " length: " + length + + " name: " + tag; + } + + /** + * Returns the checksum. + * @return int + */ + public int getChecksum() { + return checksum; + } + + /** + * Returns the length. + * @return long + */ + public long getLength() { + return length; + } + + /** + * Returns the offset. + * @return long + */ + public long getOffset() { + return offset; + } + + /** + * Returns the tag. + * @return byte[] + */ + public byte[] getTag() { + return tag; + } + +} diff --git a/src/org/apache/fop/fonts/truetype/TTFFile.java b/src/org/apache/fop/fonts/truetype/TTFFile.java new file mode 100644 index 000000000..c62b52b51 --- /dev/null +++ b/src/org/apache/fop/fonts/truetype/TTFFile.java @@ -0,0 +1,1249 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts.truetype; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.List; + +import org.apache.avalon.framework.logger.AbstractLogEnabled; +import org.apache.avalon.framework.logger.ConsoleLogger; +import org.apache.avalon.framework.logger.Logger; +import org.apache.fop.fonts.Glyphs; + +/** + * Reads a TrueType file or a TrueType Collection. + * The TrueType spec can be found at the Microsoft. + * Typography site: http://www.microsoft.com/truetype/ + */ +public class TTFFile extends AbstractLogEnabled { + + static final byte NTABS = 24; + static final int NMACGLYPHS = 258; + static final int MAX_CHAR_CODE = 255; + static final int ENC_BUF_SIZE = 1024; + + private String encoding = "WinAnsiEncoding"; // Default encoding + + private short firstChar = 0; + private boolean isEmbeddable = true; + private boolean hasSerifs = true; + /** + * Table directory + */ + protected Map dirTabs; + private Map kerningTab; // for CIDs + private Map ansiKerningTab; // For winAnsiEncoding + private List cmaps; + private List unicodeMapping; + + private int upem; // unitsPerEm from "head" table + private int nhmtx; // Number of horizontal metrics + private int postFormat; + private int locaFormat; + /** + * Offset to last loca + */ + 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 int[] mtxEncoded = null; + + private String fontName = ""; + private String fullName = ""; + private String notice = ""; + private String familyName = ""; + private String subFamilyName = ""; + + private long italicAngle = 0; + private long isFixedPitch = 0; + private int fontBBox1 = 0; + private int fontBBox2 = 0; + private int fontBBox3 = 0; + private int fontBBox4 = 0; + private int capHeight = 0; + private int underlinePosition = 0; + private int underlineThickness = 0; + private int xHeight = 0; + private int ascender = 0; + private int descender = 0; + + private short lastChar = 0; + + private int ansiWidth[]; + private Map ansiIndex; + + /** + * Position inputstream to position indicated + * in the dirtab offset + offset + */ + void seekTab(FontFileReader in, String name, + long offset) throws IOException { + TTFDirTabEntry dt = (TTFDirTabEntry)dirTabs.get(name); + if (dt == null) { + getLogger().error("Dirtab " + name + " not found."); + return; + } + + in.seekSet(dt.getOffset() + offset); + } + + /** + * Convert from truetype unit to pdf unit based on the + * unitsPerEm field in the "head" table + * @param n truetype unit + * @return pdf unit + */ + public int convertTTFUnit2PDFUnit(int n) { + int ret; + if (n < 0) { + long rest1 = n % upem; + long storrest = 1000 * rest1; + long ledd2 = rest1 / storrest; + ret = -((-1000 * n) / upem - (int)ledd2); + } else { + ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem; + } + + return ret; + } + + /** + * Read the cmap table, + * return false if the table is not present or only unsupported + * tables are present. Currently only unicode cmaps are supported. + * Set the unicodeIndex in the TTFMtxEntries and fills in the + * cmaps vector. + */ + private boolean readCMAP(FontFileReader in) throws IOException { + + unicodeMapping = new java.util.ArrayList(); + + //Read CMAP table and correct mtxTab.index + int mtxPtr = 0; + + seekTab(in, "cmap", 2); + int numCMap = in.readTTFUShort(); // Number of cmap subtables + long cmapUniOffset = 0; + + getLogger().info(numCMap + " cmap tables"); + + //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(); + + getLogger().debug("Platform ID: " + cmapPID + + " Encoding: " + cmapEID); + + if (cmapPID == 3 && cmapEID == 1) { + cmapUniOffset = cmapOffset; + } + } + + if (cmapUniOffset <= 0) { + getLogger().fatalError("Unicode cmap table not present"); + getLogger().fatalError("Unsupported format: Aborting"); + return false; + } + + // Read unicode cmap + seekTab(in, "cmap", cmapUniOffset); + int cmapFormat = in.readTTFUShort(); + /*int cmap_length =*/ in.readTTFUShort(); //skip cmap length + + getLogger().info("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(); + + getLogger().debug("segCountX2 : " + cmapSegCountX2); + getLogger().debug("searchRange : " + cmapSearchRange); + getLogger().debug("entrySelector: " + cmapEntrySelector); + getLogger().debug("rangeShift : " + cmapRangeShift); + + + int cmapEndCounts[] = new int[cmapSegCountX2 / 2]; + int cmapStartCounts[] = new int[cmapSegCountX2 / 2]; + int cmapDeltas[] = new int[cmapSegCountX2 / 2]; + int cmapRangeOffsets[] = new int[cmapSegCountX2 / 2]; + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapEndCounts[i] = in.readTTFUShort(); + } + + in.skip(2); // Skip reservedPad + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapStartCounts[i] = in.readTTFUShort(); + } + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapDeltas[i] = in.readTTFShort(); + } + + //int startRangeOffset = in.getCurrentPos(); + + for (int i = 0; i < (cmapSegCountX2 / 2); i++) { + cmapRangeOffsets[i] = in.readTTFUShort(); + } + + int glyphIdArrayOffset = in.getCurrentPos(); + + // Insert the unicode id for the glyphs in mtxTab + // and fill in the cmaps ArrayList + + for (int i = 0; i < cmapStartCounts.length; i++) { + + getLogger().debug(i + ": " + cmapStartCounts[i] + + " - " + cmapEndCounts[i]); + + for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) { + + // Update lastChar + if (j < 256 && j > lastChar) { + lastChar = (short)j; + } + + if (mtxPtr < mtxTab.length) { + int glyphIdx; + // the last character 65535 = .notdef + // may have a range offset + if (cmapRangeOffsets[i] != 0 && j != 65535) { + int glyphOffset = glyphIdArrayOffset + + ((cmapRangeOffsets[i] / 2) + + (j - cmapStartCounts[i]) + + (i) + - cmapSegCountX2 / 2) * 2; + in.seekSet(glyphOffset); + glyphIdx = (in.readTTFUShort() + cmapDeltas[i]) + & 0xffff; + + unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + + + // Also add winAnsiWidth + List v = (List)ansiIndex.get(new Integer(j)); + if (v != null) { + Iterator e = v.listIterator(); + while (e.hasNext()) { + Integer aIdx = (Integer)e.next(); + ansiWidth[aIdx.intValue()] = + mtxTab[glyphIdx].getWx(); + + getLogger().debug("Added width " + + mtxTab[glyphIdx].getWx() + + " uni: " + j + + " ansi: " + aIdx.intValue()); + } + } + + getLogger().debug("Idx: " + + glyphIdx + + " Delta: " + cmapDeltas[i] + + " Unicode: " + j + + " name: " + mtxTab[glyphIdx].getName()); + } else { + glyphIdx = (j + cmapDeltas[i]) & 0xffff; + + if (glyphIdx < mtxTab.length) { + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + } else { + getLogger().debug("Glyph " + glyphIdx + + " out of range: " + + mtxTab.length); + } + + unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); + if (glyphIdx < mtxTab.length) { + mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); + } else { + getLogger().debug("Glyph " + glyphIdx + + " out of range: " + + mtxTab.length); + } + + // Also add winAnsiWidth + List v = (List)ansiIndex.get(new Integer(j)); + if (v != null) { + Iterator e = v.listIterator(); + while (e.hasNext()) { + Integer aIdx = (Integer)e.next(); + ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); + } + } + + //getLogger().debug("IIdx: " + + // mtxPtr + + // " Delta: " + cmap_deltas[i] + + // " Unicode: " + j + + // " name: " + + // mtxTab[(j+cmap_deltas[i]) & 0xffff].name); + + } + if (glyphIdx < mtxTab.length) { + if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) { + mtxPtr++; + } + } + } + } + } + } + return true; + } + + /** + * Print first char/last char + */ + private void printMaxMin() { + int min = 255; + int max = 0; + for (int i = 0; i < mtxTab.length; i++) { + if (mtxTab[i].getIndex() < min) { + min = mtxTab[i].getIndex(); + } + if (mtxTab[i].getIndex() > max) { + max = mtxTab[i].getIndex(); + } + } + getLogger().info("Min: " + min); + getLogger().info("Max: " + max); + } + + + /** + * Reads the font using a FontFileReader. + * + * @param in The FontFileReader to use + * @throws IOException In case of an I/O problem + */ + public void readFont(FontFileReader in) throws IOException { + readFont(in, (String)null); + } + + /** + * initialize the ansiWidths array (for winAnsiEncoding) + * and fill with the missingwidth + */ + private void initAnsiWidths() { + ansiWidth = new int[256]; + for (int i = 0; i < 256; i++) { + ansiWidth[i] = mtxTab[0].getWx(); + } + + // 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(); + for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { + Integer ansi = new Integer(i); + Integer uni = new Integer((int)Glyphs.WINANSI_ENCODING[i]); + + List v = (List)ansiIndex.get(uni); + if (v == null) { + v = new java.util.ArrayList(); + ansiIndex.put(uni, v); + } + v.add(ansi); + } + } + + /** + * Read the font data. + * If the fontfile is a TrueType Collection (.ttc file) + * the name of the font to read data for must be supplied, + * else the name is ignored. + * + * @param in The FontFileReader to use + * @param name The name of the font + * @return boolean Returns true if the font is valid + * @throws IOException In case of an I/O problem + */ + public boolean readFont(FontFileReader in, String name) 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"); + } + + readDirTabs(in); + readFontHeader(in); + getNumGlyphs(in); + getLogger().info("Number of glyphs in font: " + numberOfGlyphs); + readHorizontalHeader(in); + readHorizontalMetrics(in); + initAnsiWidths(); + readPostscript(in); + readOS2(in); + readIndexToLocation(in); + readGlyf(in); + readName(in); + readPCLT(in); + // Read cmap table and fill in ansiwidths + boolean valid = readCMAP(in); + if (!valid) { + return false; + } + // Create cmaps for bfentries + createCMaps(); + // print_max_min(); + + readKerning(in); + return true; + } + + private void createCMaps() { + cmaps = new java.util.ArrayList(); + TTFCmapEntry tce = new TTFCmapEntry(); + + Iterator e = unicodeMapping.listIterator(); + UnicodeMapping um = (UnicodeMapping)e.next(); + UnicodeMapping lastMapping = um; + + tce.setUnicodeStart(um.getUIdx()); + tce.setGlyphStartIndex(um.getGIdx()); + + while (e.hasNext()) { + um = (UnicodeMapping)e.next(); + if (((lastMapping.getUIdx() + 1) != um.getUIdx()) + || ((lastMapping.getGIdx() + 1) != um.getGIdx())) { + tce.setUnicodeEnd(lastMapping.getUIdx()); + cmaps.add(tce); + + tce = new TTFCmapEntry(); + tce.setUnicodeStart(um.getUIdx()); + tce.setGlyphStartIndex(um.getGIdx()); + } + lastMapping = um; + } + + tce.setUnicodeEnd(um.getUIdx()); + cmaps.add(tce); + } + + /** + * Returns the Windows name of the font. + * @return String The Windows name + */ + public String getWindowsName() { + return familyName + "," + subFamilyName; + } + + /** + * Returns the PostScript name of the font. + * @return String The PostScript name + */ + public String getPostscriptName() { + if ("Regular".equals(subFamilyName) || "Roman".equals(subFamilyName)) { + return familyName; + } else { + return familyName + "," + subFamilyName; + } + } + + /** + * Returns the font family name of the font. + * @return String The family name + */ + public String getFamilyName() { + return familyName; + } + + /** + * Returns the name of the character set used. + * @return String The caracter set + */ + public String getCharSetName() { + return encoding; + } + + /** + * Returns the CapHeight attribute of the font. + * @return int The CapHeight + */ + public int getCapHeight() { + return (int)convertTTFUnit2PDFUnit(capHeight); + } + + /** + * Returns the XHeight attribute of the font. + * @return int The XHeight + */ + public int getXHeight() { + return (int)convertTTFUnit2PDFUnit(xHeight); + } + + /** + * 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; + } + if (isFixedPitch != 0) { + flags = flags | 2; + } + if (hasSerifs) { + flags = flags | 1; + } + return flags; + } + + + /** + * Returns the StemV attribute of the font. + * @return String The StemV + */ + public String getStemV() { + return "0"; + } + + /** + * Returns the ItalicAngle attribute of the font. + * @return String The ItalicAngle + */ + public String getItalicAngle() { + String ia = Short.toString((short)(italicAngle / 0x10000)); + + // This is the correct italic angle, however only int italic + // angles are supported at the moment so this is commented out. + /* + * if ((italicAngle % 0x10000) > 0 ) + * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000))); + */ + return ia; + } + + /** + * Returns the font bounding box. + * @return int[] The font bbox + */ + public int[] getFontBBox() { + final int[] fbb = new int[4]; + fbb[0] = (int)convertTTFUnit2PDFUnit(fontBBox1); + fbb[1] = (int)convertTTFUnit2PDFUnit(fontBBox2); + fbb[2] = (int)convertTTFUnit2PDFUnit(fontBBox3); + fbb[3] = (int)convertTTFUnit2PDFUnit(fontBBox4); + + return fbb; + } + + /** + * Returns the LowerCaseAscent attribute of the font. + * @return int The LowerCaseAscent + */ + public int getLowerCaseAscent() { + return (int)convertTTFUnit2PDFUnit(ascender); + } + + /** + * Returns the LowerCaseDescent attribute of the font. + * @return int The LowerCaseDescent + */ + public int getLowerCaseDescent() { + return (int)convertTTFUnit2PDFUnit(descender); + } + + /** + * Returns the index of the last character, but this is for WinAnsiEncoding + * only, so the last char is < 256. + * @return short Index of the last character (<256) + */ + public short getLastChar() { + return lastChar; + } + + /** + * Returns the index of the first character. + * @return short Index of the first character + */ + public short getFirstChar() { + return firstChar; + } + + /** + * Returns an array of character widths. + * @return int[] The character widths + */ + public int[] getWidths() { + int[] wx = new int[mtxTab.length]; + for (int i = 0; i < wx.length; i++) { + wx[i] = (int)convertTTFUnit2PDFUnit(mtxTab[i].getWx()); + } + + return wx; + } + + /** + * Returns the width of a given character. + * @param idx Index of the character + * @return int Standard width + */ + public int getCharWidth(int idx) { + return (int)convertTTFUnit2PDFUnit(ansiWidth[idx]); + } + + /** + * Returns the kerning table. + * @return Map The kerning table + */ + public Map getKerning() { + return kerningTab; + } + + /** + * Returns the ANSI kerning table. + * @return Map The ANSI kerning table + */ + public Map getAnsiKerning() { + return ansiKerningTab; + } + + /** + * Indicates if the font may be embedded. + * @return boolean True if it may be embedded + */ + public boolean isEmbeddable() { + return isEmbeddable; + } + + + /** + * Read Table Directory from the current position in the + * 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 { + in.skip(4); // TTF_FIXED_SIZE + int ntabs = in.readTTFUShort(); + in.skip(6); // 3xTTF_USHORT_SIZE + + dirTabs = new java.util.HashMap(); + TTFDirTabEntry[] pd = new TTFDirTabEntry[ntabs]; + getLogger().debug("Reading " + ntabs + " dir tables"); + for (int i = 0; i < ntabs; i++) { + pd[i] = new TTFDirTabEntry(); + dirTabs.put(pd[i].read(in), pd[i]); + } + } + + /** + * 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 + 2); + upem = in.readTTFUShort(); + + in.skip(16); + + fontBBox1 = in.readTTFShort(); + fontBBox2 = in.readTTFShort(); + fontBBox3 = in.readTTFShort(); + fontBBox4 = in.readTTFShort(); + + in.skip(2 + 2 + 2); + + locaFormat = in.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(); + } + + + /** + * Read the "hhea" table to find the ascender and descender and + * size of "hmtx" table, i.e. 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) + throws IOException { + seekTab(in, "hhea", 4); + ascender = in.readTTFShort(); // Use sTypoAscender in "OS/2" table? + descender = in.readTTFShort(); // Use sTypoDescender in "OS/2" table? + + in.skip(2 + 2 + 3 * 2 + 8 * 2); + nhmtx = in.readTTFUShort(); + getLogger().debug("Number of horizontal metrics: " + nhmtx); + } + + /** + * Read "hmtx" table and put the horizontal metrics + * 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) + throws IOException { + seekTab(in, "hmtx", 0); + + int mtxSize = (numberOfGlyphs > nhmtx) ? numberOfGlyphs : nhmtx; + mtxTab = new TTFMtxEntry[mtxSize]; + + getLogger().debug("*** Widths array: \n"); + for (int i = 0; i < mtxSize; i++) { + mtxTab[i] = new TTFMtxEntry(); + } + for (int i = 0; i < nhmtx; i++) { + mtxTab[i].setWx(in.readTTFUShort()); + mtxTab[i].setLsb(in.readTTFUShort()); + + getLogger().debug(" width[" + i + "] = " + + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";"); + } + + if (nhmtx < mtxSize) { + // Fill in the missing widths + int lastWidth = mtxTab[nhmtx - 1].getWx(); + for (int i = nhmtx; i < mtxSize; i++) { + mtxTab[i].setWx(lastWidth); + mtxTab[i].setLsb(in.readTTFUShort()); + } + } + } + + + /** + * Read the "post" table + * containing the postscript names of the glyphs. + */ + private final void readPostscript(FontFileReader in) throws IOException { + String[] psGlyphsBuffer; + int i, k, l; + + seekTab(in, "post", 0); + postFormat = in.readTTFLong(); + italicAngle = in.readTTFULong(); + underlinePosition = in.readTTFShort(); + underlineThickness = in.readTTFShort(); + isFixedPitch = in.readTTFULong(); + + in.skip(4 * 4); + + getLogger().debug("Post format: " + postFormat); + switch (postFormat) { + case 0x00010000: + getLogger().debug("Postscript format 1"); + for (i = 0; i < Glyphs.MAC_GLYPH_NAMES.length; i++) { + mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[i]); + } + break; + case 0x00020000: + getLogger().debug("Postscript format 2"); + int numGlyphStrings = 0; + l = in.readTTFUShort(); // Num Glyphs + // short minIndex=256; + for (i = 0; i < l; i++) { // Read indexes + mtxTab[i].setIndex(in.readTTFUShort()); + // if (minIndex > mtxTab[i].index) + // minIndex=(short)mtxTab[i].index; + + if (mtxTab[i].getIndex() > 257) { + numGlyphStrings++; + } + + getLogger().debug("Post index: " + mtxTab[i].getIndex()); + } + // firstChar=minIndex; + psGlyphsBuffer = new String[numGlyphStrings]; + getLogger().debug("Reading " + numGlyphStrings + + " glyphnames" + ", was n num glyphs=" + l); + for (i = 0; i < psGlyphsBuffer.length; i++) { + psGlyphsBuffer[i] = in.readTTFString(in.readTTFUByte()); + } + + for (i = 0; i < l; i++) { + if (mtxTab[i].getIndex() < NMACGLYPHS) { + mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[mtxTab[i].getIndex()]); + } else { + k = mtxTab[i].getIndex() - NMACGLYPHS; + + getLogger().debug(k + " i=" + i + " mtx=" + mtxTab.length + + " ps=" + psGlyphsBuffer.length); + + mtxTab[i].setName(psGlyphsBuffer[k]); + } + } + + break; + case 0x00030000: + // Postscript format 3 contains no glyph names + getLogger().debug("Postscript format 3"); + break; + default: + getLogger().error("Unknown Postscript format: " + postFormat); + } + } + + + /** + * Read the "OS/2" table + */ + private final void readOS2(FontFileReader in) throws IOException { + // Check if font is embeddable + if (dirTabs.get("OS/2") != null) { + seekTab(in, "OS/2", 2 * 4); + int fsType = in.readTTFUShort(); + if (fsType == 2) { + isEmbeddable = false; + } else { + isEmbeddable = true; + } + } else { + isEmbeddable = true; + } + } + + /** + * 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) + throws IOException { + seekTab(in, "loca", 0); + for (int i = 0; i < numberOfGlyphs; i++) { + mtxTab[i].setOffset(locaFormat == 1 ? in.readTTFULong() + : (in.readTTFUShort() << 1)); + } + lastLoca = (locaFormat == 1 ? in.readTTFULong() + : (in.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 final void readGlyf(FontFileReader in) throws IOException { + TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("glyf"); + 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); + final int[] bbox = { + in.readTTFShort(), + in.readTTFShort(), + in.readTTFShort(), + in.readTTFShort()}; + mtxTab[i].setBoundingBox(bbox); + } else { + mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox()); + } + } + + + long n = ((TTFDirTabEntry)dirTabs.get("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); + final int[] bbox = { + in.readTTFShort(), + in.readTTFShort(), + in.readTTFShort(), + in.readTTFShort()}; + mtxTab[i].setBoundingBox(bbox); + } else { + /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/ + final int bbox0 = mtxTab[0].getBoundingBox()[0]; + final int[] bbox = {bbox0, bbox0, bbox0, bbox0}; + mtxTab[i].setBoundingBox(bbox); + /* Original code + mtxTab[i].bbox[0] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[1] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[2] = mtxTab[0].bbox[0]; + mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */ + } + getLogger().debug(mtxTab[i].toString(this)); + } + } + + /** + * Read the "name" table. + * @param in FontFileReader to read from + * @throws IOException In case of a I/O problem + */ + private final void readName(FontFileReader in) throws IOException { + seekTab(in, "name", 2); + int i = in.getCurrentPos(); + int n = in.readTTFUShort(); + int j = in.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 language_id =*/ in.readTTFUShort(); //Skip language id + + int k = in.readTTFUShort(); + int l = in.readTTFUShort(); + + if (((platformID == 1 || platformID == 3) && (encodingID == 0 || encodingID == 1)) + && (k == 1 || k == 2 || k == 0 || k == 4 || k == 6)) { + // if (k==1 || k==2 || k==0 || k==4 || k==6) { + in.seekSet(j + in.readTTFUShort()); + String txt = in.readTTFString(l); + // getLogger().debug(platform_id + " " + encoding_id + // + " " + k + " " + txt); + switch (k) { + case 0: + notice = txt; + break; + case 1: + familyName = txt; + break; + case 2: + subFamilyName = txt; + break; + case 4: + fullName = txt; + break; + case 6: + fontName = txt; + break; + } + if (!notice.equals("") + && !fullName.equals("") + && !fontName.equals("") + && !familyName.equals("") + && !subFamilyName.equals("")) { + break; + } + } + i += 6 * 2; + } + } + + /** + * 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 final void readPCLT(FontFileReader in) throws IOException { + TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("PCLT"); + if (dirTab != null) { + in.seekSet(dirTab.getOffset() + 4 + 4 + 2); + xHeight = in.readTTFUShort(); + in.skip(2 * 2); + capHeight = in.readTTFUShort(); + in.skip(2 + 16 + 8 + 6 + 1 + 1); + + int serifStyle = in.readTTFUByte(); + serifStyle = serifStyle >> 6; + serifStyle = serifStyle & 3; + if (serifStyle == 1) { + hasSerifs = false; + } else { + hasSerifs = true; + } + } else { + // Approximate capHeight from height of "H" + // It's most unlikly that a font misses the PCLT table + // This also assumes that psocriptnames exists ("H") + // Should look it up int the cmap (that wouldn't help + // for charsets without H anyway...) + for (int i = 0; i < mtxTab.length; i++) { + if ("H".equals(mtxTab[i].getName())) { + capHeight = mtxTab[i].getBoundingBox()[3] - mtxTab[i].getBoundingBox()[1]; + } + } + } + } + + /** + * 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 final void readKerning(FontFileReader in) throws IOException { + // Read kerning + kerningTab = new java.util.HashMap(); + ansiKerningTab = new java.util.HashMap(); + TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("kern"); + if (dirTab != null) { + seekTab(in, "kern", 2); + for (int n = in.readTTFUShort(); n > 0; n--) { + in.skip(2 * 2); + int k = in.readTTFUShort(); + if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { + return; + } + if ((k >> 8) != 0) { + continue; + } + + k = in.readTTFUShort(); + in.skip(3 * 2); + while (k-- > 0) { + int i = in.readTTFUShort(); + int j = in.readTTFUShort(); + int kpx = in.readTTFShort(); + if (kpx != 0) { + // CID table + Integer iObj = new Integer(i); + Map adjTab = (Map)kerningTab.get(iObj); + if (adjTab == null) { + adjTab = new java.util.HashMap(); + } + adjTab.put(new Integer(j), + new Integer((int)convertTTFUnit2PDFUnit(kpx))); + kerningTab.put(iObj, adjTab); + } + } + } + // getLogger().debug(kerningTab.toString()); + + // Create winAnsiEncoded kerning table + Iterator ae = kerningTab.keySet().iterator(); + while (ae.hasNext()) { + Integer cidKey = (Integer)ae.next(); + Map akpx = new java.util.HashMap(); + Map ckpx = (Map)kerningTab.get(cidKey); + + Iterator aee = ckpx.keySet().iterator(); + while (aee.hasNext()) { + Integer cidKey2 = (Integer)aee.next(); + Integer kern = (Integer)ckpx.get(cidKey2); + + Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator(); + while (uniMap.hasNext()) { + Integer unicodeKey = (Integer)uniMap.next(); + Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); + for (int u = 0; u < ansiKeys.length; u++) { + akpx.put(ansiKeys[u], kern); + } + } + } + + if (akpx.size() > 0) { + Iterator uniMap = mtxTab[cidKey.intValue()].getUnicodeIndex().listIterator(); + while (uniMap.hasNext()) { + Integer unicodeKey = (Integer)uniMap.next(); + Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); + for (int u = 0; u < ansiKeys.length; u++) { + ansiKerningTab.put(ansiKeys[u], akpx); + } + } + } + } + } + } + + /** + * Return a List with TTFCmapEntry. + * @return A list of TTFCmapEntry objects + */ + public List getCMaps() { + return cmaps; + } + + /** + * Check if this is a TrueType collection and that the given + * 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); + + if ("ttcf".equals(tag)) { + // This is a TrueType Collection + in.skip(4); + + // Read directory offsets + int numDirectories = (int)in.readTTFULong(); + // int numDirectories=in.readTTFUShort(); + long[] dirOffsets = new long[numDirectories]; + for (int i = 0; i < numDirectories; i++) { + dirOffsets[i] = in.readTTFULong(); + } + + getLogger().debug("This is a TrueType collection file with" + + numDirectories + " fonts"); + getLogger().debug("Containing the following fonts: "); + // Read all the directories and name tables to check + // If the font exists - this is a bit ugly, but... + boolean found = false; + + // Iterate through all name tables even if font + // Is found, just to show all the names + long dirTabOffset = 0; + for (int i = 0; (i < numDirectories); i++) { + in.seekSet(dirOffsets[i]); + readDirTabs(in); + + readName(in); + + if (fullName.equals(name)) { + found = true; + dirTabOffset = dirOffsets[i]; + getLogger().debug("* " + fullName); + } else { + getLogger().debug(fullName); + } + + // Reset names + notice = ""; + fullName = ""; + familyName = ""; + fontName = ""; + subFamilyName = ""; + } + + in.seekSet(dirTabOffset); + return found; + } else { + in.seekSet(0); + return true; + } + } + + /* + * Helper classes, they are not very efficient, but that really + * doesn't matter... + */ + private Integer[] unicodeToWinAnsi(int unicode) { + List ret = new java.util.ArrayList(); + 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]); + } + + /** + * Dumps a few informational values to System.out. + */ + public void printStuff() { + System.out.println("Font name: " + fontName); + System.out.println("Full name: " + fullName); + System.out.println("Family name: " + familyName); + System.out.println("Subfamily name: " + subFamilyName); + System.out.println("Notice: " + notice); + System.out.println("xHeight: " + (int)convertTTFUnit2PDFUnit(xHeight)); + System.out.println("capheight: " + (int)convertTTFUnit2PDFUnit(capHeight)); + + int italic = (int)(italicAngle >> 16); + System.out.println("Italic: " + italic); + System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000)); + if ((italicAngle % 0x10000) > 0) { + System.out.print("." + + (short)((italicAngle % 0x10000) * 1000) + / 0x10000); + } + System.out.println(); + System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender)); + System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender)); + System.out.println("FontBBox: [" + (int)convertTTFUnit2PDFUnit(fontBBox1) + + " " + (int)convertTTFUnit2PDFUnit(fontBBox2) + " " + + (int)convertTTFUnit2PDFUnit(fontBBox3) + " " + + (int)convertTTFUnit2PDFUnit(fontBBox4) + "]"); + } + + /** + * Static main method to get info about a TrueType font. + * @param args The command line arguments + */ + public static void main(String[] args) { + int level = ConsoleLogger.LEVEL_WARN; + Logger log = new ConsoleLogger(level); + try { + TTFFile ttfFile = new TTFFile(); + ttfFile.enableLogging(log); + + FontFileReader reader = new FontFileReader(args[0]); + + String name = null; + if (args.length >= 2) { + name = args[1]; + } + + ttfFile.readFont(reader, name); + ttfFile.printStuff(); + + } catch (IOException ioe) { + log.error("Problem reading font: " + ioe.toString(), ioe); + } + } + +} + + +/** + * Key-value helper class + */ +class UnicodeMapping { + + private int uIdx; + private int gIdx; + + UnicodeMapping(int gIdx, int uIdx) { + this.uIdx = uIdx; + this.gIdx = gIdx; + } + + /** + * Returns the gIdx. + * @return int + */ + public int getGIdx() { + return gIdx; + } + + /** + * Returns the uIdx. + * @return int + */ + public int getUIdx() { + return uIdx; + } + +} diff --git a/src/org/apache/fop/fonts/truetype/TTFMtxEntry.java b/src/org/apache/fop/fonts/truetype/TTFMtxEntry.java new file mode 100644 index 000000000..965844cc8 --- /dev/null +++ b/src/org/apache/fop/fonts/truetype/TTFMtxEntry.java @@ -0,0 +1,162 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts.truetype; + +import java.util.List; + +/** + * This class represents a TrueType Mtx Entry. + */ +class TTFMtxEntry { + + private int wx; + private int lsb; + private String name = ""; + private int index; + private List unicodeIndex = new java.util.ArrayList(); + private int[] boundingBox = new int[4]; + private long offset; + private byte found = 0; + + /** + * Returns a String representation of this object. + * + * @param t TTFFile to use for unit conversion + * @return String String representation + */ + public String toString(TTFFile t) { + return "Glyph " + name + " index: " + index + " bbox [ " + + t.convertTTFUnit2PDFUnit(boundingBox[0]) + " " + + t.convertTTFUnit2PDFUnit(boundingBox[1]) + " " + + t.convertTTFUnit2PDFUnit(boundingBox[2]) + " " + + t.convertTTFUnit2PDFUnit(boundingBox[3]) + "] wx: " + + t.convertTTFUnit2PDFUnit(wx); + } + + /** + * Returns the boundingBox. + * @return int[] + */ + public int[] getBoundingBox() { + return boundingBox; + } + + /** + * Sets the boundingBox. + * @param boundingBox The boundingBox to set + */ + public void setBoundingBox(int[] boundingBox) { + this.boundingBox = boundingBox; + } + + /** + * Returns the found. + * @return byte + */ + public byte getFound() { + return found; + } + + /** + * Returns the index. + * @return int + */ + public int getIndex() { + return index; + } + + /** + * Returns the lsb. + * @return int + */ + public int getLsb() { + return lsb; + } + + /** + * Returns the name. + * @return String + */ + public String getName() { + return name; + } + + /** + * Returns the offset. + * @return long + */ + public long getOffset() { + return offset; + } + + /** + * Returns the unicodeIndex. + * @return List + */ + public List getUnicodeIndex() { + return unicodeIndex; + } + + /** + * Returns the wx. + * @return int + */ + public int getWx() { + return wx; + } + + /** + * Sets the found. + * @param found The found to set + */ + public void setFound(byte found) { + this.found = found; + } + + /** + * Sets the index. + * @param index The index to set + */ + public void setIndex(int index) { + this.index = index; + } + + /** + * Sets the lsb. + * @param lsb The lsb to set + */ + public void setLsb(int lsb) { + this.lsb = lsb; + } + + /** + * Sets the name. + * @param name The name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the offset. + * @param offset The offset to set + */ + public void setOffset(long offset) { + this.offset = offset; + } + + /** + * Sets the wx. + * @param wx The wx to set + */ + public void setWx(int wx) { + this.wx = wx; + } + + +} diff --git a/src/org/apache/fop/fonts/truetype/TTFSubSetFile.java b/src/org/apache/fop/fonts/truetype/TTFSubSetFile.java new file mode 100644 index 000000000..8014cf48e --- /dev/null +++ b/src/org/apache/fop/fonts/truetype/TTFSubSetFile.java @@ -0,0 +1,836 @@ +/* + * $Id$ + * Copyright (C) 2001-2003 The Apache Software Foundation. All rights reserved. + * For details on use and redistribution please refer to the + * LICENSE file included with these sources. + */ + +package org.apache.fop.fonts.truetype; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.List; + + +/** + * Reads a TrueType file and generates a subset + * that can be used to embed a TrueType CID font. + * TrueType tables needed for embedded CID fonts are: + * "head", "hhea", "loca", "maxp", "cvt ", "prep", "glyf", "hmtx" and "fpgm". + * The TrueType spec can be found at the Microsoft + * Typography site: http://www.microsoft.com/truetype/ + */ +public class TTFSubSetFile extends TTFFile { + + private byte[] output = null; + private int realSize = 0; + private int currentPos = 0; + + /* + * Offsets in name table to be filled out by table. + * The offsets are to the checkSum field + */ + private int cvtDirOffset = 0; + private int fpgmDirOffset = 0; + private int glyfDirOffset = 0; + private int headDirOffset = 0; + private int hheaDirOffset = 0; + private int hmtxDirOffset = 0; + private int locaDirOffset = 0; + private int maxpDirOffset = 0; + private int prepDirOffset = 0; + + private int checkSumAdjustmentOffset = 0; + private int locaOffset = 0; + + /** + * Initalize the output array + */ + private void init(int size) { + output = new byte[size]; + realSize = 0; + currentPos = 0; + + // createDirectory() + } + + /** + * Create the directory table + */ + private void createDirectory() { + int numTables = 9; + // Create the TrueType header + writeByte((byte)0); + writeByte((byte)1); + writeByte((byte)0); + writeByte((byte)0); + realSize += 4; + + writeUShort(numTables); + realSize += 2; + + // Create searchRange, entrySelector and rangeShift + int maxPow = maxPow2(numTables); + int searchRange = maxPow * 16; + writeUShort(searchRange); + realSize += 2; + + writeUShort(maxPow); + realSize += 2; + + writeUShort((numTables * 16) - searchRange); + realSize += 2; + + // Create space for the table entries + writeString("cvt "); + cvtDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("fpgm"); + fpgmDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("glyf"); + glyfDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("head"); + headDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("hhea"); + hheaDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("hmtx"); + hmtxDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("loca"); + locaDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("maxp"); + maxpDirOffset = currentPos; + currentPos += 12; + realSize += 16; + + writeString("prep"); + prepDirOffset = currentPos; + currentPos += 12; + realSize += 16; + } + + + /** + * Copy the cvt table as is from original font to subset font + */ + private void createCvt(FontFileReader in) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("cvt "); + if (entry != null) { + pad4(); + seekTab(in, "cvt ", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + + int checksum = getCheckSum(currentPos, (int)entry.getLength()); + writeULong(cvtDirOffset, checksum); + writeULong(cvtDirOffset + 4, currentPos); + writeULong(cvtDirOffset + 8, (int)entry.getLength()); + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find cvt table"); + } + } + + + + /** + * Copy the fpgm table as is from original font to subset font + */ + private void createFpgm(FontFileReader in) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("fpgm"); + if (entry != null) { + pad4(); + seekTab(in, "fpgm", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + int checksum = getCheckSum(currentPos, (int)entry.getLength()); + writeULong(fpgmDirOffset, checksum); + writeULong(fpgmDirOffset + 4, currentPos); + writeULong(fpgmDirOffset + 8, (int)entry.getLength()); + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find fpgm table"); + } + } + + + + /** + * Create an empty loca table without updating checksum + */ + private void createLoca(int size) throws IOException { + pad4(); + locaOffset = currentPos; + writeULong(locaDirOffset + 4, currentPos); + writeULong(locaDirOffset + 8, size * 4 + 4); + currentPos += size * 4 + 4; + realSize += size * 4 + 4; + } + + + /** + * Copy the maxp table as is from original font to subset font + * and set num glyphs to size + */ + private void createMaxp(FontFileReader in, int size) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("maxp"); + if (entry != null) { + pad4(); + 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()); + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find maxp table"); + } + } + + + /** + * Copy the prep table as is from original font to subset font + */ + private void createPrep(FontFileReader in) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("prep"); + if (entry != null) { + pad4(); + seekTab(in, "prep", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + + int checksum = getCheckSum(currentPos, (int)entry.getLength()); + writeULong(prepDirOffset, checksum); + writeULong(prepDirOffset + 4, currentPos); + writeULong(prepDirOffset + 8, (int)entry.getLength()); + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find prep table"); + } + } + + + /** + * Copy the hhea table as is from original font to subset font + * and fill in size of hmtx table + */ + private void createHhea(FontFileReader in, int size) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hhea"); + if (entry != null) { + pad4(); + seekTab(in, "hhea", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + writeUShort((int)entry.getLength() + currentPos - 2, size); + + int checksum = getCheckSum(currentPos, (int)entry.getLength()); + writeULong(hheaDirOffset, checksum); + writeULong(hheaDirOffset + 4, currentPos); + writeULong(hheaDirOffset + 8, (int)entry.getLength()); + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find hhea table"); + } + } + + + /** + * Copy the head table as is from original font to subset font + * and set indexToLocaFormat to long and set + * checkSumAdjustment to 0, store offset to checkSumAdjustment + * in checkSumAdjustmentOffset + */ + private void createHead(FontFileReader in) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("head"); + if (entry != null) { + pad4(); + seekTab(in, "head", 0); + System.arraycopy(in.getBytes((int)entry.getOffset(), (int)entry.getLength()), + 0, output, currentPos, (int)entry.getLength()); + + checkSumAdjustmentOffset = currentPos + 8; + output[currentPos + 8] = 0; // Set checkSumAdjustment to 0 + output[currentPos + 9] = 0; + output[currentPos + 10] = 0; + output[currentPos + 11] = 0; + 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()); + + currentPos += (int)entry.getLength(); + realSize += (int)entry.getLength(); + } else { + throw new IOException("Can't find head table"); + } + } + + + /** + * Create the glyf table and fill in loca table + */ + private void createGlyf(FontFileReader in, + Map glyphs) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf"); + int size = 0; + int start = 0; + int endOffset = 0; // Store this as the last loca + if (entry != null) { + pad4(); + start = currentPos; + + /* Loca table must be in order by glyph index, so build + * an array first and then write the glyph info and + * location offset. + */ + int[] origIndexes = new int[glyphs.size()]; + + Iterator e = glyphs.keySet().iterator(); + while (e.hasNext()) { + Integer origIndex = (Integer)e.next(); + Integer subsetIndex = (Integer)glyphs.get(origIndex); + origIndexes[subsetIndex.intValue()] = origIndex.intValue(); + } + + for (int i = 0; i < origIndexes.length; i++) { + int glyphLength = 0; + int nextOffset = 0; + int origGlyphIndex = origIndexes[i]; + if (origGlyphIndex >= (mtxTab.length - 1)) { + nextOffset = (int)lastLoca; + } else { + nextOffset = (int)mtxTab[origGlyphIndex + 1].getOffset(); + } + glyphLength = nextOffset - (int)mtxTab[origGlyphIndex].getOffset(); + + // Copy glyph + System.arraycopy( + in.getBytes((int)entry.getOffset() + (int)mtxTab[origGlyphIndex].getOffset(), + glyphLength), 0, + output, currentPos, + glyphLength); + + + // Update loca table + writeULong(locaOffset + i * 4, currentPos - start); + if ((currentPos - start + glyphLength) > endOffset) { + endOffset = (currentPos - start + glyphLength); + } + + currentPos += glyphLength; + realSize += glyphLength; + + } + + size = currentPos - start; + + int checksum = getCheckSum(start, size); + writeULong(glyfDirOffset, checksum); + writeULong(glyfDirOffset + 4, start); + writeULong(glyfDirOffset + 8, size); + currentPos += 12; + realSize += 12; + + // Update loca checksum and last loca index + writeULong(locaOffset + glyphs.size() * 4, endOffset); + + checksum = getCheckSum(locaOffset, glyphs.size() * 4 + 4); + writeULong(locaDirOffset, checksum); + } else { + throw new IOException("Can't find glyf table"); + } + } + + + /** + * Create the hmtx table by copying metrics from original + * font to subset font. The glyphs Map contains an + * Integer key and Integer value that maps the original + * metric (key) to the subset metric (value) + */ + private void createHmtx(FontFileReader in, + Map glyphs) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("hmtx"); + + int longHorMetricSize = glyphs.size() * 2; + int leftSideBearingSize = glyphs.size() * 2; + int hmtxSize = longHorMetricSize + leftSideBearingSize; + + if (entry != null) { + pad4(); + //int offset = (int)entry.offset; + Iterator e = glyphs.keySet().iterator(); + while (e.hasNext()) { + Integer origIndex = (Integer)e.next(); + Integer subsetIndex = (Integer)glyphs.get(origIndex); + + writeUShort(currentPos + subsetIndex.intValue() * 4, + mtxTab[origIndex.intValue()].getWx()); + writeUShort(currentPos + subsetIndex.intValue() * 4 + 2, + mtxTab[origIndex.intValue()].getLsb()); + } + + int checksum = getCheckSum(currentPos, hmtxSize); + writeULong(hmtxDirOffset, checksum); + writeULong(hmtxDirOffset + 4, currentPos); + writeULong(hmtxDirOffset + 8, hmtxSize); + currentPos += hmtxSize; + realSize += hmtxSize; + } else { + throw new IOException("Can't find hmtx table"); + } + } + + /** + * Returns a List containing the glyph itself plus all glyphs + * that this composite glyph uses + */ + private List getIncludedGlyphs(FontFileReader in, int glyphOffset, + Integer glyphIdx) throws IOException { + List ret = new java.util.ArrayList(); + ret.add(glyphIdx); + int offset = glyphOffset + (int)mtxTab[glyphIdx.intValue()].getOffset() + 10; + Integer compositeIdx = null; + int flags = 0; + boolean moreComposites = true; + while (moreComposites) { + flags = in.readTTFUShort(offset); + compositeIdx = new Integer(in.readTTFUShort(offset + 2)); + ret.add(compositeIdx); + + offset += 4; + if ((flags & 1) > 0) { + // ARG_1_AND_ARG_2_ARE_WORDS + offset += 4; + } else { + offset += 2; + } + + if ((flags & 8) > 0) { + offset += 2; // WE_HAVE_A_SCALE + } else if ((flags & 64) > 0) { + offset += 4; // WE_HAVE_AN_X_AND_Y_SCALE + } else if ((flags & 128) > 0) { + offset += 8; // WE_HAVE_A_TWO_BY_TWO + } + + if ((flags & 32) > 0) { + moreComposites = true; + } else { + moreComposites = false; + } + } + + return ret; + } + + + /** + * Rewrite all compositepointers in glyphindex glyphIdx + * + */ + private void remapComposite(FontFileReader in, Map glyphs, + int glyphOffset, + Integer glyphIdx) throws IOException { + int offset = glyphOffset + (int)mtxTab[glyphIdx.intValue()].getOffset() + + 10; + + Integer compositeIdx = null; + int flags = 0; + boolean moreComposites = true; + + while (moreComposites) { + flags = in.readTTFUShort(offset); + compositeIdx = new Integer(in.readTTFUShort(offset + 2)); + Integer newIdx = (Integer)glyphs.get(compositeIdx); + if (newIdx == null) { + // This errormessage would look much better + // if the fontname was printed to + //log.error("An embedded font " + // + "contains bad glyph data. " + // + "Characters might not display " + // + "correctly."); + moreComposites = false; + continue; + } + + in.writeTTFUShort(offset + 2, newIdx.intValue()); + + offset += 4; + + if ((flags & 1) > 0) { + // ARG_1_AND_ARG_2_ARE_WORDS + offset += 4; + } else { + offset += 2; + } + + if ((flags & 8) > 0) { + offset += 2; // WE_HAVE_A_SCALE + } else if ((flags & 64) > 0) { + offset += 4; // WE_HAVE_AN_X_AND_Y_SCALE + } else if ((flags & 128) > 0) { + offset += 8; // WE_HAVE_A_TWO_BY_TWO + } + + if ((flags & 32) > 0) { + moreComposites = true; + } else { + moreComposites = false; + } + } + } + + + /** + * Scan all the original glyphs for composite glyphs and add those glyphs + * to the glyphmapping also rewrite the composite glyph pointers to the new + * mapping + */ + private void scanGlyphs(FontFileReader in, + Map glyphs) throws IOException { + TTFDirTabEntry entry = (TTFDirTabEntry)dirTabs.get("glyf"); + Map newComposites = null; + Map allComposites = new java.util.HashMap(); + + int newIndex = glyphs.size(); + + if (entry != null) { + while (newComposites == null || newComposites.size() > 0) { + // Inefficient to iterate through all glyphs + newComposites = new java.util.HashMap(); + + Iterator e = glyphs.keySet().iterator(); + while (e.hasNext()) { + Integer origIndex = (Integer)e.next(); + + if (in.readTTFShort(entry.getOffset() + + mtxTab[origIndex.intValue()].getOffset()) < 0) { + // origIndex is a composite glyph + allComposites.put(origIndex, glyphs.get(origIndex)); + List composites = + getIncludedGlyphs(in, (int)entry.getOffset(), + origIndex); + + // Iterate through all composites pointed to + // by this composite and check if they exists + // in the glyphs map, add them if not. + Iterator cps = composites.iterator(); + while (cps.hasNext()) { + Integer cIdx = (Integer)cps.next(); + if (glyphs.get(cIdx) == null + && newComposites.get(cIdx) == null) { + newComposites.put(cIdx, + new Integer(newIndex)); + newIndex++; + } + } + } + } + + // Add composites to glyphs + Iterator m = newComposites.keySet().iterator(); + while (m.hasNext()) { + Integer im = (Integer)m.next(); + glyphs.put(im, newComposites.get(im)); + } + } + + // Iterate through all composites to remap their composite index + Iterator ce = allComposites.keySet().iterator(); + while (ce.hasNext()) { + remapComposite(in, glyphs, (int)entry.getOffset(), + (Integer)ce.next()); + } + + } else { + throw new IOException("Can't find glyf table"); + } + } + + + + /** + * Returns a subset of the original 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, + Map glyphs) 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"); + } + + output = new byte[in.getFileSize()]; + + readDirTabs(in); + readFontHeader(in); + getNumGlyphs(in); + readHorizontalHeader(in); + readHorizontalMetrics(in); + readIndexToLocation(in); + + scanGlyphs(in, glyphs); + + createDirectory(); // Create the TrueType header and directory + + createHead(in); + createHhea(in, glyphs.size()); // Create the hhea table + createHmtx(in, glyphs); // Create hmtx table + createMaxp(in, glyphs.size()); // copy the maxp table + + try { + createCvt(in); // copy the cvt table + } catch (IOException ex) { + // Cvt is optional (only required for OpenType (MS) fonts) + //log.error("TrueType warning: " + ex.getMessage()); + } + + try { + createFpgm(in); // copy fpgm table + } catch (IOException ex) { + // Fpgm is optional (only required for OpenType (MS) fonts) + //log.error("TrueType warning: " + ex.getMessage()); + } + + try { + createPrep(in); // copy prep table + } catch (IOException ex) { + // Prep is optional (only required for OpenType (MS) fonts) + //log.error("TrueType warning: " + ex.getMessage()); + } + + try { + createLoca(glyphs.size()); // create empty loca table + } catch (IOException ex) { + // Loca is optional (only required for OpenType (MS) fonts) + //log.error("TrueType warning: " + ex.getMessage()); + } + + try { + createGlyf(in, glyphs); + } catch (IOException ex) { + // Glyf is optional (only required for OpenType (MS) fonts) + //log.error("TrueType warning: " + ex.getMessage()); + } + + pad4(); + createCheckSumAdjustment(); + + byte[] ret = new byte[realSize]; + System.arraycopy(output, 0, ret, 0, realSize); + + return ret; + } + + /** + * writes a ISO-8859-1 string at the currentPosition + * updates currentPosition but not realSize + * @return number of bytes written + */ + private int writeString(String str) { + int length = 0; + try { + byte[] buf = str.getBytes("ISO-8859-1"); + System.arraycopy(buf, 0, output, currentPos, buf.length); + length = buf.length; + currentPos += length; + } catch (java.io.UnsupportedEncodingException e) { + // This should never happen! + } + + return length; + } + + /** + * Appends a byte to the output array, + * updates currentPost but not realSize + */ + private void writeByte(byte b) { + output[currentPos++] = b; + } + + /** + * Appends a USHORT to the output array, + * updates currentPost but not realSize + */ + private void writeUShort(int s) { + byte b1 = (byte)((s >> 8) & 0xff); + byte b2 = (byte)(s & 0xff); + writeByte(b1); + writeByte(b2); + } + + /** + * Appends a USHORT to the output array, + * at the given position without changing currentPos + */ + private void writeUShort(int pos, int s) { + byte b1 = (byte)((s >> 8) & 0xff); + byte b2 = (byte)(s & 0xff); + output[pos] = b1; + 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, + * at the given position without changing currentPos + */ + private void writeULong(int pos, 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); + output[pos] = b1; + output[pos + 1] = b2; + output[pos + 2] = b3; + output[pos + 3] = b4; + } + + /** + * Read a signed short value at given position + */ + private short readShort(int pos) { + int ret = readUShort(pos); + return (short)ret; + } + + /** + * Read a unsigned short value at given position + */ + private int readUShort(int pos) { + int ret = (int)output[pos]; + if (ret < 0) { + ret += 256; + } + ret = ret << 8; + if ((int)output[pos + 1] < 0) { + ret |= (int)output[pos + 1] + 256; + } else { + ret |= (int)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++; + } + } + + /** + * Returns the maximum power of 2 <= max + */ + private int maxPow2(int max) { + int i = 0; + while (Math.pow(2, (double)i) < max) { + i++; + } + + return (i - 1); + } + + private int log2(int num) { + return (int)(Math.log((double)num) / Math.log(2)); + } + + + private int getCheckSum(int start, int size) { + return (int)getLongCheckSum(start, size); + } + + private long getLongCheckSum(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; + if (remainder != 0) { + size += remainder; + } + + long sum = 0; + + for (int i = 0; i < size; i += 4) { + int l = (int)(output[start + i] << 24); + l += (int)(output[start + i + 1] << 16); + l += (int)(output[start + i + 2] << 16); + l += (int)(output[start + i + 3] << 16); + sum += l; + if (sum > 0xffffffff) { + sum = sum - 0xffffffff; + } + } + + return sum; + } + + private void createCheckSumAdjustment() { + long sum = getLongCheckSum(0, realSize); + int checksum = (int)(0xb1b0afba - sum); + writeULong(checkSumAdjustmentOffset, checksum); + } + +} + + +