0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: /* $Id: TTFFile.java 563951 2007-08-08 17:37:33Z vhennebert $ */
0019:
0020: package org.apache.fop.fonts.truetype;
0021:
0022: import java.io.IOException;
0023: import java.util.Iterator;
0024: import java.util.Map;
0025: import java.util.List;
0026:
0027: import org.apache.commons.logging.Log;
0028: import org.apache.commons.logging.LogFactory;
0029: import org.apache.fop.fonts.Glyphs;
0030:
0031: /**
0032: * Reads a TrueType file or a TrueType Collection.
0033: * The TrueType spec can be found at the Microsoft.
0034: * Typography site: http://www.microsoft.com/truetype/
0035: */
0036: public class TTFFile {
0037:
0038: static final byte NTABS = 24;
0039: static final int NMACGLYPHS = 258;
0040: static final int MAX_CHAR_CODE = 255;
0041: static final int ENC_BUF_SIZE = 1024;
0042:
0043: /** Set to true to get even more debug output than with level DEBUG */
0044: public static final boolean TRACE_ENABLED = false;
0045:
0046: private String encoding = "WinAnsiEncoding"; // Default encoding
0047:
0048: private short firstChar = 0;
0049: private boolean isEmbeddable = true;
0050: private boolean hasSerifs = true;
0051: /**
0052: * Table directory
0053: */
0054: protected Map dirTabs;
0055: private Map kerningTab; // for CIDs
0056: private Map ansiKerningTab; // For winAnsiEncoding
0057: private List cmaps;
0058: private List unicodeMapping;
0059:
0060: private int upem; // unitsPerEm from "head" table
0061: private int nhmtx; // Number of horizontal metrics
0062: private int postFormat;
0063: private int locaFormat;
0064: /**
0065: * Offset to last loca
0066: */
0067: protected long lastLoca = 0;
0068: private int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table)
0069: private int nmGlyphs; // Used in fixWidths - remove?
0070:
0071: /**
0072: * Contains glyph data
0073: */
0074: protected TTFMtxEntry[] mtxTab; // Contains glyph data
0075: private int[] mtxEncoded = null;
0076:
0077: private String fontName = "";
0078: private String fullName = "";
0079: private String notice = "";
0080: private String familyName = "";
0081: private String subFamilyName = "";
0082:
0083: private long italicAngle = 0;
0084: private long isFixedPitch = 0;
0085: private int fontBBox1 = 0;
0086: private int fontBBox2 = 0;
0087: private int fontBBox3 = 0;
0088: private int fontBBox4 = 0;
0089: private int capHeight = 0;
0090: private int os2CapHeight = 0;
0091: private int underlinePosition = 0;
0092: private int underlineThickness = 0;
0093: private int xHeight = 0;
0094: private int os2xHeight = 0;
0095: //Effective ascender/descender
0096: private int ascender = 0;
0097: private int descender = 0;
0098: //Ascender/descender from hhea table
0099: private int hheaAscender = 0;
0100: private int hheaDescender = 0;
0101: //Ascender/descender from OS/2 table
0102: private int os2Ascender = 0;
0103: private int os2Descender = 0;
0104:
0105: private short lastChar = 0;
0106:
0107: private int[] ansiWidth;
0108: private Map ansiIndex;
0109:
0110: // internal mapping of glyph indexes to unicode indexes
0111: // used for quick mappings in this class
0112: private Map glyphToUnicodeMap = new java.util.HashMap();
0113: private Map unicodeToGlyphMap = new java.util.HashMap();
0114:
0115: private TTFDirTabEntry currentDirTab;
0116:
0117: private boolean isCFF;
0118:
0119: /**
0120: * logging instance
0121: */
0122: protected Log log = LogFactory.getLog(TTFFile.class);
0123:
0124: /**
0125: * Key-value helper class
0126: */
0127: class UnicodeMapping {
0128:
0129: private int unicodeIndex;
0130: private int glyphIndex;
0131:
0132: UnicodeMapping(int glyphIndex, int unicodeIndex) {
0133: this .unicodeIndex = unicodeIndex;
0134: this .glyphIndex = glyphIndex;
0135: glyphToUnicodeMap.put(new Integer(glyphIndex), new Integer(
0136: unicodeIndex));
0137: unicodeToGlyphMap.put(new Integer(unicodeIndex),
0138: new Integer(glyphIndex));
0139: }
0140:
0141: /**
0142: * Returns the glyphIndex.
0143: * @return the glyph index
0144: */
0145: public int getGlyphIndex() {
0146: return glyphIndex;
0147: }
0148:
0149: /**
0150: * Returns the unicodeIndex.
0151: * @return the Unicode index
0152: */
0153: public int getUnicodeIndex() {
0154: return unicodeIndex;
0155: }
0156: }
0157:
0158: /**
0159: * Position inputstream to position indicated
0160: * in the dirtab offset + offset
0161: */
0162: boolean seekTab(FontFileReader in, String name, long offset)
0163: throws IOException {
0164: TTFDirTabEntry dt = (TTFDirTabEntry) dirTabs.get(name);
0165: if (dt == null) {
0166: log.error("Dirtab " + name + " not found.");
0167: return false;
0168: } else {
0169: in.seekSet(dt.getOffset() + offset);
0170: this .currentDirTab = dt;
0171: }
0172: return true;
0173: }
0174:
0175: /**
0176: * Convert from truetype unit to pdf unit based on the
0177: * unitsPerEm field in the "head" table
0178: * @param n truetype unit
0179: * @return pdf unit
0180: */
0181: public int convertTTFUnit2PDFUnit(int n) {
0182: int ret;
0183: if (n < 0) {
0184: long rest1 = n % upem;
0185: long storrest = 1000 * rest1;
0186: long ledd2 = (storrest != 0 ? rest1 / storrest : 0);
0187: ret = -((-1000 * n) / upem - (int) ledd2);
0188: } else {
0189: ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem;
0190: }
0191:
0192: return ret;
0193: }
0194:
0195: /**
0196: * Read the cmap table,
0197: * return false if the table is not present or only unsupported
0198: * tables are present. Currently only unicode cmaps are supported.
0199: * Set the unicodeIndex in the TTFMtxEntries and fills in the
0200: * cmaps vector.
0201: */
0202: private boolean readCMAP(FontFileReader in) throws IOException {
0203:
0204: unicodeMapping = new java.util.ArrayList();
0205:
0206: //Read CMAP table and correct mtxTab.index
0207: int mtxPtr = 0;
0208:
0209: seekTab(in, "cmap", 2);
0210: int numCMap = in.readTTFUShort(); // Number of cmap subtables
0211: long cmapUniOffset = 0;
0212:
0213: if (log.isDebugEnabled()) {
0214: log.debug(numCMap + " cmap tables");
0215: }
0216:
0217: //Read offset for all tables. We are only interested in the unicode table
0218: for (int i = 0; i < numCMap; i++) {
0219: int cmapPID = in.readTTFUShort();
0220: int cmapEID = in.readTTFUShort();
0221: long cmapOffset = in.readTTFULong();
0222:
0223: log.debug("Platform ID: " + cmapPID + " Encoding: "
0224: + cmapEID);
0225:
0226: if (cmapPID == 3 && cmapEID == 1) {
0227: cmapUniOffset = cmapOffset;
0228: }
0229: }
0230:
0231: if (cmapUniOffset <= 0) {
0232: log.fatal("Unicode cmap table not present");
0233: log.fatal("Unsupported format: Aborting");
0234: return false;
0235: }
0236:
0237: // Read unicode cmap
0238: seekTab(in, "cmap", cmapUniOffset);
0239: int cmapFormat = in.readTTFUShort();
0240: /*int cmap_length =*/in.readTTFUShort(); //skip cmap length
0241:
0242: if (log.isDebugEnabled()) {
0243: log.debug("CMAP format: " + cmapFormat);
0244: }
0245:
0246: if (cmapFormat == 4) {
0247: in.skip(2); // Skip version number
0248: int cmapSegCountX2 = in.readTTFUShort();
0249: int cmapSearchRange = in.readTTFUShort();
0250: int cmapEntrySelector = in.readTTFUShort();
0251: int cmapRangeShift = in.readTTFUShort();
0252:
0253: if (log.isDebugEnabled()) {
0254: log.debug("segCountX2 : " + cmapSegCountX2);
0255: log.debug("searchRange : " + cmapSearchRange);
0256: log.debug("entrySelector: " + cmapEntrySelector);
0257: log.debug("rangeShift : " + cmapRangeShift);
0258: }
0259:
0260: int[] cmapEndCounts = new int[cmapSegCountX2 / 2];
0261: int[] cmapStartCounts = new int[cmapSegCountX2 / 2];
0262: int[] cmapDeltas = new int[cmapSegCountX2 / 2];
0263: int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2];
0264:
0265: for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
0266: cmapEndCounts[i] = in.readTTFUShort();
0267: }
0268:
0269: in.skip(2); // Skip reservedPad
0270:
0271: for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
0272: cmapStartCounts[i] = in.readTTFUShort();
0273: }
0274:
0275: for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
0276: cmapDeltas[i] = in.readTTFShort();
0277: }
0278:
0279: //int startRangeOffset = in.getCurrentPos();
0280:
0281: for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
0282: cmapRangeOffsets[i] = in.readTTFUShort();
0283: }
0284:
0285: int glyphIdArrayOffset = in.getCurrentPos();
0286:
0287: // Insert the unicode id for the glyphs in mtxTab
0288: // and fill in the cmaps ArrayList
0289:
0290: for (int i = 0; i < cmapStartCounts.length; i++) {
0291:
0292: if (log.isTraceEnabled()) {
0293: log.trace(i + ": " + cmapStartCounts[i] + " - "
0294: + cmapEndCounts[i]);
0295: }
0296:
0297: for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) {
0298:
0299: // Update lastChar
0300: if (j < 256 && j > lastChar) {
0301: lastChar = (short) j;
0302: }
0303:
0304: if (mtxPtr < mtxTab.length) {
0305: int glyphIdx;
0306: // the last character 65535 = .notdef
0307: // may have a range offset
0308: if (cmapRangeOffsets[i] != 0 && j != 65535) {
0309: int glyphOffset = glyphIdArrayOffset
0310: + ((cmapRangeOffsets[i] / 2)
0311: + (j - cmapStartCounts[i])
0312: + (i) - cmapSegCountX2 / 2)
0313: * 2;
0314: in.seekSet(glyphOffset);
0315: glyphIdx = (in.readTTFUShort() + cmapDeltas[i]) & 0xffff;
0316:
0317: unicodeMapping.add(new UnicodeMapping(
0318: glyphIdx, j));
0319: mtxTab[glyphIdx].getUnicodeIndex().add(
0320: new Integer(j));
0321:
0322: // Also add winAnsiWidth
0323: List v = (List) ansiIndex
0324: .get(new Integer(j));
0325: if (v != null) {
0326: Iterator e = v.listIterator();
0327: while (e.hasNext()) {
0328: Integer aIdx = (Integer) e.next();
0329: ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx]
0330: .getWx();
0331:
0332: if (log.isTraceEnabled()) {
0333: log.trace("Added width "
0334: + mtxTab[glyphIdx]
0335: .getWx()
0336: + " uni: " + j
0337: + " ansi: "
0338: + aIdx.intValue());
0339: }
0340: }
0341: }
0342:
0343: if (log.isTraceEnabled()) {
0344: log.trace("Idx: " + glyphIdx
0345: + " Delta: " + cmapDeltas[i]
0346: + " Unicode: " + j + " name: "
0347: + mtxTab[glyphIdx].getName());
0348: }
0349: } else {
0350: glyphIdx = (j + cmapDeltas[i]) & 0xffff;
0351:
0352: if (glyphIdx < mtxTab.length) {
0353: mtxTab[glyphIdx].getUnicodeIndex().add(
0354: new Integer(j));
0355: } else {
0356: log.debug("Glyph " + glyphIdx
0357: + " out of range: "
0358: + mtxTab.length);
0359: }
0360:
0361: unicodeMapping.add(new UnicodeMapping(
0362: glyphIdx, j));
0363: if (glyphIdx < mtxTab.length) {
0364: mtxTab[glyphIdx].getUnicodeIndex().add(
0365: new Integer(j));
0366: } else {
0367: log.debug("Glyph " + glyphIdx
0368: + " out of range: "
0369: + mtxTab.length);
0370: }
0371:
0372: // Also add winAnsiWidth
0373: List v = (List) ansiIndex
0374: .get(new Integer(j));
0375: if (v != null) {
0376: Iterator e = v.listIterator();
0377: while (e.hasNext()) {
0378: Integer aIdx = (Integer) e.next();
0379: ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx]
0380: .getWx();
0381: }
0382: }
0383:
0384: //getLogger().debug("IIdx: " +
0385: // mtxPtr +
0386: // " Delta: " + cmap_deltas[i] +
0387: // " Unicode: " + j +
0388: // " name: " +
0389: // mtxTab[(j+cmap_deltas[i]) & 0xffff].name);
0390:
0391: }
0392: if (glyphIdx < mtxTab.length) {
0393: if (mtxTab[glyphIdx].getUnicodeIndex()
0394: .size() < 2) {
0395: mtxPtr++;
0396: }
0397: }
0398: }
0399: }
0400: }
0401: }
0402: return true;
0403: }
0404:
0405: /**
0406: * Print first char/last char
0407: */
0408: private void printMaxMin() {
0409: int min = 255;
0410: int max = 0;
0411: for (int i = 0; i < mtxTab.length; i++) {
0412: if (mtxTab[i].getIndex() < min) {
0413: min = mtxTab[i].getIndex();
0414: }
0415: if (mtxTab[i].getIndex() > max) {
0416: max = mtxTab[i].getIndex();
0417: }
0418: }
0419: log.info("Min: " + min);
0420: log.info("Max: " + max);
0421: }
0422:
0423: /**
0424: * Reads the font using a FontFileReader.
0425: *
0426: * @param in The FontFileReader to use
0427: * @throws IOException In case of an I/O problem
0428: */
0429: public void readFont(FontFileReader in) throws IOException {
0430: readFont(in, (String) null);
0431: }
0432:
0433: /**
0434: * initialize the ansiWidths array (for winAnsiEncoding)
0435: * and fill with the missingwidth
0436: */
0437: private void initAnsiWidths() {
0438: ansiWidth = new int[256];
0439: for (int i = 0; i < 256; i++) {
0440: ansiWidth[i] = mtxTab[0].getWx();
0441: }
0442:
0443: // Create an index hash to the ansiWidth
0444: // Can't just index the winAnsiEncoding when inserting widths
0445: // same char (eg bullet) is repeated more than one place
0446: ansiIndex = new java.util.HashMap();
0447: for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) {
0448: Integer ansi = new Integer(i);
0449: Integer uni = new Integer((int) Glyphs.WINANSI_ENCODING[i]);
0450:
0451: List v = (List) ansiIndex.get(uni);
0452: if (v == null) {
0453: v = new java.util.ArrayList();
0454: ansiIndex.put(uni, v);
0455: }
0456: v.add(ansi);
0457: }
0458: }
0459:
0460: /**
0461: * Read the font data.
0462: * If the fontfile is a TrueType Collection (.ttc file)
0463: * the name of the font to read data for must be supplied,
0464: * else the name is ignored.
0465: *
0466: * @param in The FontFileReader to use
0467: * @param name The name of the font
0468: * @return boolean Returns true if the font is valid
0469: * @throws IOException In case of an I/O problem
0470: */
0471: public boolean readFont(FontFileReader in, String name)
0472: throws IOException {
0473:
0474: /*
0475: * Check if TrueType collection, and that the name
0476: * exists in the collection
0477: */
0478: if (!checkTTC(in, name)) {
0479: if (name == null) {
0480: throw new IllegalArgumentException(
0481: "For TrueType collection you must specify which font "
0482: + "to select (-ttcname)");
0483: } else {
0484: throw new IOException(
0485: "Name does not exist in the TrueType collection: "
0486: + name);
0487: }
0488: }
0489:
0490: readDirTabs(in);
0491: readFontHeader(in);
0492: getNumGlyphs(in);
0493: if (log.isDebugEnabled()) {
0494: log.debug("Number of glyphs in font: " + numberOfGlyphs);
0495: }
0496: readHorizontalHeader(in);
0497: readHorizontalMetrics(in);
0498: initAnsiWidths();
0499: readPostScript(in);
0500: readOS2(in);
0501: determineAscDesc();
0502: if (!isCFF) {
0503: readIndexToLocation(in);
0504: readGlyf(in);
0505: }
0506: readName(in);
0507: boolean pcltFound = readPCLT(in);
0508: // Read cmap table and fill in ansiwidths
0509: boolean valid = readCMAP(in);
0510: if (!valid) {
0511: return false;
0512: }
0513: // Create cmaps for bfentries
0514: createCMaps();
0515: // print_max_min();
0516:
0517: readKerning(in);
0518: guessVerticalMetricsFromGlyphBBox();
0519: return true;
0520: }
0521:
0522: private void createCMaps() {
0523: cmaps = new java.util.ArrayList();
0524: TTFCmapEntry tce = new TTFCmapEntry();
0525:
0526: Iterator e = unicodeMapping.listIterator();
0527: UnicodeMapping um = (UnicodeMapping) e.next();
0528: UnicodeMapping lastMapping = um;
0529:
0530: tce.setUnicodeStart(um.getUnicodeIndex());
0531: tce.setGlyphStartIndex(um.getGlyphIndex());
0532:
0533: while (e.hasNext()) {
0534: um = (UnicodeMapping) e.next();
0535: if (((lastMapping.getUnicodeIndex() + 1) != um
0536: .getUnicodeIndex())
0537: || ((lastMapping.getGlyphIndex() + 1) != um
0538: .getGlyphIndex())) {
0539: tce.setUnicodeEnd(lastMapping.getUnicodeIndex());
0540: cmaps.add(tce);
0541:
0542: tce = new TTFCmapEntry();
0543: tce.setUnicodeStart(um.getUnicodeIndex());
0544: tce.setGlyphStartIndex(um.getGlyphIndex());
0545: }
0546: lastMapping = um;
0547: }
0548:
0549: tce.setUnicodeEnd(um.getUnicodeIndex());
0550: cmaps.add(tce);
0551: }
0552:
0553: /**
0554: * Returns the Windows name of the font.
0555: * @return String The Windows name
0556: */
0557: public String getWindowsName() {
0558: return familyName + "," + subFamilyName;
0559: }
0560:
0561: /**
0562: * Returns the PostScript name of the font.
0563: * @return String The PostScript name
0564: */
0565: public String getPostScriptName() {
0566: if ("Regular".equals(subFamilyName)
0567: || "Roman".equals(subFamilyName)) {
0568: return familyName;
0569: } else {
0570: return familyName + "," + subFamilyName;
0571: }
0572: }
0573:
0574: /**
0575: * Returns the font family name of the font.
0576: * @return String The family name
0577: */
0578: public String getFamilyName() {
0579: return familyName;
0580: }
0581:
0582: /**
0583: * Returns the font sub family name of the font.
0584: * @return String The sub family name
0585: */
0586: public String getSubFamilyName() {
0587: return subFamilyName;
0588: }
0589:
0590: /**
0591: * Returns the name of the character set used.
0592: * @return String The caracter set
0593: */
0594: public String getCharSetName() {
0595: return encoding;
0596: }
0597:
0598: /**
0599: * Returns the CapHeight attribute of the font.
0600: * @return int The CapHeight
0601: */
0602: public int getCapHeight() {
0603: return (int) convertTTFUnit2PDFUnit(capHeight);
0604: }
0605:
0606: /**
0607: * Returns the XHeight attribute of the font.
0608: * @return int The XHeight
0609: */
0610: public int getXHeight() {
0611: return (int) convertTTFUnit2PDFUnit(xHeight);
0612: }
0613:
0614: /**
0615: * Returns the Flags attribute of the font.
0616: * @return int The Flags
0617: */
0618: public int getFlags() {
0619: int flags = 32; // Use Adobe Standard charset
0620: if (italicAngle != 0) {
0621: flags = flags | 64;
0622: }
0623: if (isFixedPitch != 0) {
0624: flags = flags | 2;
0625: }
0626: if (hasSerifs) {
0627: flags = flags | 1;
0628: }
0629: return flags;
0630: }
0631:
0632: /**
0633: * Returns the StemV attribute of the font.
0634: * @return String The StemV
0635: */
0636: public String getStemV() {
0637: return "0";
0638: }
0639:
0640: /**
0641: * Returns the ItalicAngle attribute of the font.
0642: * @return String The ItalicAngle
0643: */
0644: public String getItalicAngle() {
0645: String ia = Short.toString((short) (italicAngle / 0x10000));
0646:
0647: // This is the correct italic angle, however only int italic
0648: // angles are supported at the moment so this is commented out.
0649: /*
0650: * if ((italicAngle % 0x10000) > 0 )
0651: * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000)));
0652: */
0653: return ia;
0654: }
0655:
0656: /**
0657: * Returns the font bounding box.
0658: * @return int[] The font bbox
0659: */
0660: public int[] getFontBBox() {
0661: final int[] fbb = new int[4];
0662: fbb[0] = (int) convertTTFUnit2PDFUnit(fontBBox1);
0663: fbb[1] = (int) convertTTFUnit2PDFUnit(fontBBox2);
0664: fbb[2] = (int) convertTTFUnit2PDFUnit(fontBBox3);
0665: fbb[3] = (int) convertTTFUnit2PDFUnit(fontBBox4);
0666:
0667: return fbb;
0668: }
0669:
0670: /**
0671: * Returns the LowerCaseAscent attribute of the font.
0672: * @return int The LowerCaseAscent
0673: */
0674: public int getLowerCaseAscent() {
0675: return (int) convertTTFUnit2PDFUnit(ascender);
0676: }
0677:
0678: /**
0679: * Returns the LowerCaseDescent attribute of the font.
0680: * @return int The LowerCaseDescent
0681: */
0682: public int getLowerCaseDescent() {
0683: return (int) convertTTFUnit2PDFUnit(descender);
0684: }
0685:
0686: /**
0687: * Returns the index of the last character, but this is for WinAnsiEncoding
0688: * only, so the last char is < 256.
0689: * @return short Index of the last character (<256)
0690: */
0691: public short getLastChar() {
0692: return lastChar;
0693: }
0694:
0695: /**
0696: * Returns the index of the first character.
0697: * @return short Index of the first character
0698: */
0699: public short getFirstChar() {
0700: return firstChar;
0701: }
0702:
0703: /**
0704: * Returns an array of character widths.
0705: * @return int[] The character widths
0706: */
0707: public int[] getWidths() {
0708: int[] wx = new int[mtxTab.length];
0709: for (int i = 0; i < wx.length; i++) {
0710: wx[i] = (int) convertTTFUnit2PDFUnit(mtxTab[i].getWx());
0711: }
0712:
0713: return wx;
0714: }
0715:
0716: /**
0717: * Returns the width of a given character.
0718: * @param idx Index of the character
0719: * @return int Standard width
0720: */
0721: public int getCharWidth(int idx) {
0722: return (int) convertTTFUnit2PDFUnit(ansiWidth[idx]);
0723: }
0724:
0725: /**
0726: * Returns the kerning table.
0727: * @return Map The kerning table
0728: */
0729: public Map getKerning() {
0730: return kerningTab;
0731: }
0732:
0733: /**
0734: * Returns the ANSI kerning table.
0735: * @return Map The ANSI kerning table
0736: */
0737: public Map getAnsiKerning() {
0738: return ansiKerningTab;
0739: }
0740:
0741: /**
0742: * Indicates if the font may be embedded.
0743: * @return boolean True if it may be embedded
0744: */
0745: public boolean isEmbeddable() {
0746: return isEmbeddable;
0747: }
0748:
0749: /**
0750: * Indicates whether or not the font is an OpenType
0751: * CFF font (rather than a TrueType font).
0752: * @return true if the font is in OpenType CFF format.
0753: */
0754: public boolean isCFF() {
0755: return this .isCFF;
0756: }
0757:
0758: /**
0759: * Read Table Directory from the current position in the
0760: * FontFileReader and fill the global HashMap dirTabs
0761: * with the table name (String) as key and a TTFDirTabEntry
0762: * as value.
0763: * @param in FontFileReader to read the table directory from
0764: * @throws IOException in case of an I/O problem
0765: */
0766: protected void readDirTabs(FontFileReader in) throws IOException {
0767: int sfntVersion = in.readTTFLong(); // TTF_FIXED_SIZE (4 bytes)
0768: switch (sfntVersion) {
0769: case 0x10000:
0770: log.debug("sfnt version: OpenType 1.0");
0771: break;
0772: case 0x4F54544F: //"OTTO"
0773: this .isCFF = true;
0774: log.debug("sfnt version: OpenType with CFF data");
0775: break;
0776: case 0x74727565: //"true"
0777: log.debug("sfnt version: Apple TrueType");
0778: break;
0779: case 0x74797031: //"typ1"
0780: log
0781: .debug("sfnt version: Apple Type 1 housed in sfnt wrapper");
0782: break;
0783: default:
0784: log.debug("Unknown sfnt version: "
0785: + Integer.toHexString(sfntVersion));
0786: break;
0787: }
0788: int ntabs = in.readTTFUShort();
0789: in.skip(6); // 3xTTF_USHORT_SIZE
0790:
0791: dirTabs = new java.util.HashMap();
0792: TTFDirTabEntry[] pd = new TTFDirTabEntry[ntabs];
0793: log.debug("Reading " + ntabs + " dir tables");
0794: for (int i = 0; i < ntabs; i++) {
0795: pd[i] = new TTFDirTabEntry();
0796: dirTabs.put(pd[i].read(in), pd[i]);
0797: }
0798: log.debug("dir tables: " + dirTabs.keySet());
0799: }
0800:
0801: /**
0802: * Read the "head" table, this reads the bounding box and
0803: * sets the upem (unitsPerEM) variable
0804: * @param in FontFileReader to read the header from
0805: * @throws IOException in case of an I/O problem
0806: */
0807: protected void readFontHeader(FontFileReader in) throws IOException {
0808: seekTab(in, "head", 2 * 4 + 2 * 4 + 2);
0809: upem = in.readTTFUShort();
0810: log.debug("unit per em: " + upem);
0811:
0812: in.skip(16);
0813:
0814: fontBBox1 = in.readTTFShort();
0815: fontBBox2 = in.readTTFShort();
0816: fontBBox3 = in.readTTFShort();
0817: fontBBox4 = in.readTTFShort();
0818:
0819: in.skip(2 + 2 + 2);
0820:
0821: locaFormat = in.readTTFShort();
0822: }
0823:
0824: /**
0825: * Read the number of glyphs from the "maxp" table
0826: * @param in FontFileReader to read the number of glyphs from
0827: * @throws IOException in case of an I/O problem
0828: */
0829: protected void getNumGlyphs(FontFileReader in) throws IOException {
0830: seekTab(in, "maxp", 4);
0831: numberOfGlyphs = in.readTTFUShort();
0832: }
0833:
0834: /**
0835: * Read the "hhea" table to find the ascender and descender and
0836: * size of "hmtx" table, as a fixed size font might have only
0837: * one width.
0838: * @param in FontFileReader to read the hhea table from
0839: * @throws IOException in case of an I/O problem
0840: */
0841: protected void readHorizontalHeader(FontFileReader in)
0842: throws IOException {
0843: seekTab(in, "hhea", 4);
0844: hheaAscender = in.readTTFShort();
0845: log.debug("hhea.Ascender: " + hheaAscender + " "
0846: + convertTTFUnit2PDFUnit(hheaAscender));
0847: hheaDescender = in.readTTFShort();
0848: log.debug("hhea.Descender: " + hheaDescender + " "
0849: + convertTTFUnit2PDFUnit(hheaDescender));
0850:
0851: in.skip(2 + 2 + 3 * 2 + 8 * 2);
0852: nhmtx = in.readTTFUShort();
0853: log.debug("Number of horizontal metrics: " + nhmtx);
0854:
0855: }
0856:
0857: /**
0858: * Read "hmtx" table and put the horizontal metrics
0859: * in the mtxTab array. If the number of metrics is less
0860: * than the number of glyphs (eg fixed size fonts), extend
0861: * the mtxTab array and fill in the missing widths
0862: * @param in FontFileReader to read the hmtx table from
0863: * @throws IOException in case of an I/O problem
0864: */
0865: protected void readHorizontalMetrics(FontFileReader in)
0866: throws IOException {
0867: seekTab(in, "hmtx", 0);
0868:
0869: int mtxSize = Math.max(numberOfGlyphs, nhmtx);
0870: mtxTab = new TTFMtxEntry[mtxSize];
0871:
0872: if (TRACE_ENABLED) {
0873: log.debug("*** Widths array: \n");
0874: }
0875: for (int i = 0; i < mtxSize; i++) {
0876: mtxTab[i] = new TTFMtxEntry();
0877: }
0878: for (int i = 0; i < nhmtx; i++) {
0879: mtxTab[i].setWx(in.readTTFUShort());
0880: mtxTab[i].setLsb(in.readTTFUShort());
0881:
0882: if (TRACE_ENABLED) {
0883: if (log.isDebugEnabled()) {
0884: log.debug(" width[" + i + "] = "
0885: + convertTTFUnit2PDFUnit(mtxTab[i].getWx())
0886: + ";");
0887: }
0888: }
0889: }
0890:
0891: if (nhmtx < mtxSize) {
0892: // Fill in the missing widths
0893: int lastWidth = mtxTab[nhmtx - 1].getWx();
0894: for (int i = nhmtx; i < mtxSize; i++) {
0895: mtxTab[i].setWx(lastWidth);
0896: mtxTab[i].setLsb(in.readTTFUShort());
0897: }
0898: }
0899: }
0900:
0901: /**
0902: * Read the "post" table
0903: * containing the PostScript names of the glyphs.
0904: */
0905: private final void readPostScript(FontFileReader in)
0906: throws IOException {
0907: seekTab(in, "post", 0);
0908: postFormat = in.readTTFLong();
0909: italicAngle = in.readTTFULong();
0910: underlinePosition = in.readTTFShort();
0911: underlineThickness = in.readTTFShort();
0912: isFixedPitch = in.readTTFULong();
0913:
0914: //Skip memory usage values
0915: in.skip(4 * 4);
0916:
0917: log.debug("PostScript format: 0x"
0918: + Integer.toHexString(postFormat));
0919: switch (postFormat) {
0920: case 0x00010000:
0921: log.debug("PostScript format 1");
0922: for (int i = 0; i < Glyphs.MAC_GLYPH_NAMES.length; i++) {
0923: mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[i]);
0924: }
0925: break;
0926: case 0x00020000:
0927: log.debug("PostScript format 2");
0928: int numGlyphStrings = 0;
0929:
0930: // Read Number of Glyphs
0931: int l = in.readTTFUShort();
0932:
0933: // Read indexes
0934: for (int i = 0; i < l; i++) {
0935: mtxTab[i].setIndex(in.readTTFUShort());
0936:
0937: if (mtxTab[i].getIndex() > 257) {
0938: //Index is not in the Macintosh standard set
0939: numGlyphStrings++;
0940: }
0941:
0942: if (log.isTraceEnabled()) {
0943: log.trace("PostScript index: "
0944: + mtxTab[i].getIndexAsString());
0945: }
0946: }
0947:
0948: // firstChar=minIndex;
0949: String[] psGlyphsBuffer = new String[numGlyphStrings];
0950: if (log.isDebugEnabled()) {
0951: log
0952: .debug("Reading "
0953: + numGlyphStrings
0954: + " glyphnames, that are not in the standard Macintosh"
0955: + " set. Total number of glyphs=" + l);
0956: }
0957: for (int i = 0; i < psGlyphsBuffer.length; i++) {
0958: psGlyphsBuffer[i] = in.readTTFString(in.readTTFUByte());
0959: }
0960:
0961: //Set glyph names
0962: for (int i = 0; i < l; i++) {
0963: if (mtxTab[i].getIndex() < NMACGLYPHS) {
0964: mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[mtxTab[i]
0965: .getIndex()]);
0966: } else {
0967: if (!mtxTab[i].isIndexReserved()) {
0968: int k = mtxTab[i].getIndex() - NMACGLYPHS;
0969:
0970: if (log.isTraceEnabled()) {
0971: log.trace(k + " i=" + i + " mtx="
0972: + mtxTab.length + " ps="
0973: + psGlyphsBuffer.length);
0974: }
0975:
0976: mtxTab[i].setName(psGlyphsBuffer[k]);
0977: }
0978: }
0979: }
0980:
0981: break;
0982: case 0x00030000:
0983: // PostScript format 3 contains no glyph names
0984: log.debug("PostScript format 3");
0985: break;
0986: default:
0987: log.error("Unknown PostScript format: " + postFormat);
0988: }
0989: }
0990:
0991: /**
0992: * Read the "OS/2" table
0993: */
0994: private final void readOS2(FontFileReader in) throws IOException {
0995: // Check if font is embeddable
0996: if (dirTabs.get("OS/2") != null) {
0997: seekTab(in, "OS/2", 2 * 4);
0998: int fsType = in.readTTFUShort();
0999: if (fsType == 2) {
1000: isEmbeddable = false;
1001: } else {
1002: isEmbeddable = true;
1003: }
1004: in.skip(11 * 2);
1005: in.skip(10); //panose array
1006: in.skip(4 * 4); //unicode ranges
1007: in.skip(4);
1008: in.skip(3 * 2);
1009: int v;
1010: os2Ascender = in.readTTFShort(); //sTypoAscender
1011: log.debug("sTypoAscender: " + os2Ascender + " "
1012: + convertTTFUnit2PDFUnit(os2Ascender));
1013: os2Descender = in.readTTFShort(); //sTypoDescender
1014: log.debug("sTypoDescender: " + os2Descender + " "
1015: + convertTTFUnit2PDFUnit(os2Descender));
1016: v = in.readTTFShort(); //sTypoLineGap
1017: log.debug("sTypoLineGap: " + v);
1018: v = in.readTTFUShort(); //usWinAscent
1019: log.debug("usWinAscent: " + v + " "
1020: + convertTTFUnit2PDFUnit(v));
1021: v = in.readTTFUShort(); //usWinDescent
1022: log.debug("usWinDescent: " + v + " "
1023: + convertTTFUnit2PDFUnit(v));
1024: in.skip(2 * 4);
1025: this .os2xHeight = in.readTTFShort(); //sxHeight
1026: log.debug("sxHeight: " + this .os2xHeight);
1027: this .os2CapHeight = in.readTTFShort(); //sCapHeight
1028: log.debug("sCapHeight: " + this .os2CapHeight);
1029:
1030: } else {
1031: isEmbeddable = true;
1032: }
1033: }
1034:
1035: /**
1036: * Read the "loca" table.
1037: * @param in FontFileReader to read from
1038: * @throws IOException In case of a I/O problem
1039: */
1040: protected final void readIndexToLocation(FontFileReader in)
1041: throws IOException {
1042: if (!seekTab(in, "loca", 0)) {
1043: throw new IOException(
1044: "'loca' table not found, happens when the font file doesn't"
1045: + " contain TrueType outlines (trying to read an OpenType CFF font maybe?)");
1046: }
1047: for (int i = 0; i < numberOfGlyphs; i++) {
1048: mtxTab[i].setOffset(locaFormat == 1 ? in.readTTFULong()
1049: : (in.readTTFUShort() << 1));
1050: }
1051: lastLoca = (locaFormat == 1 ? in.readTTFULong() : (in
1052: .readTTFUShort() << 1));
1053: }
1054:
1055: /**
1056: * Read the "glyf" table to find the bounding boxes.
1057: * @param in FontFileReader to read from
1058: * @throws IOException In case of a I/O problem
1059: */
1060: private final void readGlyf(FontFileReader in) throws IOException {
1061: TTFDirTabEntry dirTab = (TTFDirTabEntry) dirTabs.get("glyf");
1062: if (dirTab == null) {
1063: throw new IOException(
1064: "glyf table not found, cannot continue");
1065: }
1066: for (int i = 0; i < (numberOfGlyphs - 1); i++) {
1067: if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) {
1068: in.seekSet(dirTab.getOffset() + mtxTab[i].getOffset());
1069: in.skip(2);
1070: final int[] bbox = { in.readTTFShort(),
1071: in.readTTFShort(), in.readTTFShort(),
1072: in.readTTFShort() };
1073: mtxTab[i].setBoundingBox(bbox);
1074: } else {
1075: mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox());
1076: }
1077: }
1078:
1079: long n = ((TTFDirTabEntry) dirTabs.get("glyf")).getOffset();
1080: for (int i = 0; i < numberOfGlyphs; i++) {
1081: if ((i + 1) >= mtxTab.length
1082: || mtxTab[i].getOffset() != mtxTab[i + 1]
1083: .getOffset()) {
1084: in.seekSet(n + mtxTab[i].getOffset());
1085: in.skip(2);
1086: final int[] bbox = { in.readTTFShort(),
1087: in.readTTFShort(), in.readTTFShort(),
1088: in.readTTFShort() };
1089: mtxTab[i].setBoundingBox(bbox);
1090: } else {
1091: /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/
1092: final int bbox0 = mtxTab[0].getBoundingBox()[0];
1093: final int[] bbox = { bbox0, bbox0, bbox0, bbox0 };
1094: mtxTab[i].setBoundingBox(bbox);
1095: /* Original code
1096: mtxTab[i].bbox[0] = mtxTab[0].bbox[0];
1097: mtxTab[i].bbox[1] = mtxTab[0].bbox[0];
1098: mtxTab[i].bbox[2] = mtxTab[0].bbox[0];
1099: mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */
1100: }
1101: if (log.isTraceEnabled()) {
1102: log.trace(mtxTab[i].toString(this ));
1103: }
1104: }
1105: }
1106:
1107: /**
1108: * Read the "name" table.
1109: * @param in FontFileReader to read from
1110: * @throws IOException In case of a I/O problem
1111: */
1112: private final void readName(FontFileReader in) throws IOException {
1113: seekTab(in, "name", 2);
1114: int i = in.getCurrentPos();
1115: int n = in.readTTFUShort();
1116: int j = in.readTTFUShort() + i - 2;
1117: i += 2 * 2;
1118:
1119: while (n-- > 0) {
1120: // getLogger().debug("Iteration: " + n);
1121: in.seekSet(i);
1122: final int platformID = in.readTTFUShort();
1123: final int encodingID = in.readTTFUShort();
1124: final int languageID = in.readTTFUShort();
1125:
1126: int k = in.readTTFUShort();
1127: int l = in.readTTFUShort();
1128:
1129: if (((platformID == 1 || platformID == 3) && (encodingID == 0 || encodingID == 1))
1130: && (k == 1 || k == 2 || k == 0 || k == 4 || k == 6)) {
1131: in.seekSet(j + in.readTTFUShort());
1132: String txt = in.readTTFString(l);
1133:
1134: log.debug(platformID + " " + encodingID + " "
1135: + languageID + " " + k + " " + txt);
1136: switch (k) {
1137: case 0:
1138: notice = txt;
1139: break;
1140: case 1:
1141: familyName = txt;
1142: break;
1143: case 2:
1144: subFamilyName = txt;
1145: break;
1146: case 4:
1147: fullName = txt;
1148: break;
1149: case 6:
1150: fontName = txt;
1151: break;
1152: }
1153: if (!notice.equals("") && !fullName.equals("")
1154: && !fontName.equals("")
1155: && !familyName.equals("")
1156: && !subFamilyName.equals("")) {
1157: break;
1158: }
1159: }
1160: i += 6 * 2;
1161: }
1162: }
1163:
1164: /**
1165: * Read the "PCLT" table to find xHeight and capHeight.
1166: * @param in FontFileReader to read from
1167: * @throws IOException In case of a I/O problem
1168: */
1169: private final boolean readPCLT(FontFileReader in)
1170: throws IOException {
1171: TTFDirTabEntry dirTab = (TTFDirTabEntry) dirTabs.get("PCLT");
1172: if (dirTab != null) {
1173: in.seekSet(dirTab.getOffset() + 4 + 4 + 2);
1174: xHeight = in.readTTFUShort();
1175: log.debug("xHeight from PCLT: " + xHeight + " "
1176: + convertTTFUnit2PDFUnit(xHeight));
1177: in.skip(2 * 2);
1178: capHeight = in.readTTFUShort();
1179: log.debug("capHeight from PCLT: " + capHeight + " "
1180: + convertTTFUnit2PDFUnit(capHeight));
1181: in.skip(2 + 16 + 8 + 6 + 1 + 1);
1182:
1183: int serifStyle = in.readTTFUByte();
1184: serifStyle = serifStyle >> 6;
1185: serifStyle = serifStyle & 3;
1186: if (serifStyle == 1) {
1187: hasSerifs = false;
1188: } else {
1189: hasSerifs = true;
1190: }
1191: return true;
1192: } else {
1193: return false;
1194: }
1195: }
1196:
1197: /**
1198: * Determines the right source for the ascender and descender values. The problem here is
1199: * that the interpretation of these values is not the same for every font. There doesn't seem
1200: * to be a uniform definition of an ascender and a descender. In some fonts
1201: * the hhea values are defined after the Apple interpretation, but not in every font. The
1202: * same problem is in the OS/2 table. FOP needs the ascender and descender to determine the
1203: * baseline so we need values which add up more or less to the "em box". However, due to
1204: * accent modifiers a character can grow beyond the em box.
1205: */
1206: private void determineAscDesc() {
1207: int hheaBoxHeight = hheaAscender - hheaDescender;
1208: int os2BoxHeight = os2Ascender - os2Descender;
1209: if (os2Ascender > 0 && os2BoxHeight <= upem) {
1210: ascender = os2Ascender;
1211: descender = os2Descender;
1212: } else if (hheaAscender > 0 && hheaBoxHeight <= upem) {
1213: ascender = hheaAscender;
1214: descender = hheaDescender;
1215: } else {
1216: if (os2Ascender > 0) {
1217: //Fall back to info from OS/2 if possible
1218: ascender = os2Ascender;
1219: descender = os2Descender;
1220: } else {
1221: ascender = hheaAscender;
1222: descender = hheaDescender;
1223: }
1224: }
1225:
1226: log.debug("Font box height: " + (ascender - descender));
1227: if (ascender - descender > upem) {
1228: log
1229: .warn("Ascender and descender together are larger than the em box."
1230: + " This could lead to a wrong baseline placement in Apache FOP.");
1231: }
1232: }
1233:
1234: private void guessVerticalMetricsFromGlyphBBox() {
1235: // Approximate capHeight from height of "H"
1236: // It's most unlikely that a font misses the PCLT table
1237: // This also assumes that postscriptnames exists ("H")
1238: // Should look it up int the cmap (that wouldn't help
1239: // for charsets without H anyway...)
1240: // Same for xHeight with the letter "x"
1241: int localCapHeight = 0;
1242: int localXHeight = 0;
1243: int localAscender = 0;
1244: int localDescender = 0;
1245: for (int i = 0; i < mtxTab.length; i++) {
1246: if ("H".equals(mtxTab[i].getName())) {
1247: localCapHeight = mtxTab[i].getBoundingBox()[3];
1248: } else if ("x".equals(mtxTab[i].getName())) {
1249: localXHeight = mtxTab[i].getBoundingBox()[3];
1250: } else if ("d".equals(mtxTab[i].getName())) {
1251: localAscender = mtxTab[i].getBoundingBox()[3];
1252: } else if ("p".equals(mtxTab[i].getName())) {
1253: localDescender = mtxTab[i].getBoundingBox()[1];
1254: } else {
1255: // OpenType Fonts with a version 3.0 "post" table don't have glyph names.
1256: // Use Unicode indices instead.
1257: List unicodeIndex = mtxTab[i].getUnicodeIndex();
1258: if (unicodeIndex.size() > 0) {
1259: //Only the first index is used
1260: char ch = (char) ((Integer) unicodeIndex.get(0))
1261: .intValue();
1262: if (ch == 'H') {
1263: localCapHeight = mtxTab[i].getBoundingBox()[3];
1264: } else if (ch == 'x') {
1265: localXHeight = mtxTab[i].getBoundingBox()[3];
1266: } else if (ch == 'd') {
1267: localAscender = mtxTab[i].getBoundingBox()[3];
1268: } else if (ch == 'p') {
1269: localDescender = mtxTab[i].getBoundingBox()[1];
1270: }
1271: }
1272: }
1273: }
1274: log.debug("Ascender from glyph 'd': " + localAscender + " "
1275: + convertTTFUnit2PDFUnit(localAscender));
1276: log.debug("Descender from glyph 'p': " + localDescender + " "
1277: + convertTTFUnit2PDFUnit(localDescender));
1278: if (ascender - descender > upem) {
1279: log
1280: .debug("Replacing specified ascender/descender with derived values to get values"
1281: + " which fit in the em box.");
1282: ascender = localAscender;
1283: descender = localDescender;
1284: }
1285:
1286: log.debug("xHeight from glyph 'x': " + localXHeight + " "
1287: + convertTTFUnit2PDFUnit(localXHeight));
1288: log.debug("CapHeight from glyph 'H': " + localCapHeight + " "
1289: + convertTTFUnit2PDFUnit(localCapHeight));
1290: if (capHeight == 0) {
1291: capHeight = localCapHeight;
1292: if (capHeight == 0) {
1293: capHeight = os2CapHeight;
1294: }
1295: if (capHeight == 0) {
1296: log.warn("capHeight value could not be determined."
1297: + " The font may not work as expected.");
1298: }
1299: }
1300: if (xHeight == 0) {
1301: xHeight = localXHeight;
1302: if (xHeight == 0) {
1303: xHeight = os2xHeight;
1304: }
1305: if (xHeight == 0) {
1306: log.warn("xHeight value could not be determined."
1307: + " The font may not work as expected.");
1308: }
1309: }
1310: }
1311:
1312: /**
1313: * Read the kerning table, create a table for both CIDs and
1314: * winAnsiEncoding.
1315: * @param in FontFileReader to read from
1316: * @throws IOException In case of a I/O problem
1317: */
1318: private final void readKerning(FontFileReader in)
1319: throws IOException {
1320: // Read kerning
1321: kerningTab = new java.util.HashMap();
1322: ansiKerningTab = new java.util.HashMap();
1323: TTFDirTabEntry dirTab = (TTFDirTabEntry) dirTabs.get("kern");
1324: if (dirTab != null) {
1325: seekTab(in, "kern", 2);
1326: for (int n = in.readTTFUShort(); n > 0; n--) {
1327: in.skip(2 * 2);
1328: int k = in.readTTFUShort();
1329: if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) {
1330: return;
1331: }
1332: if ((k >> 8) != 0) {
1333: continue;
1334: }
1335:
1336: k = in.readTTFUShort();
1337: in.skip(3 * 2);
1338: while (k-- > 0) {
1339: int i = in.readTTFUShort();
1340: int j = in.readTTFUShort();
1341: int kpx = in.readTTFShort();
1342: if (kpx != 0) {
1343: // CID kerning table entry, using unicode indexes
1344: final Integer iObj = glyphToUnicode(i);
1345: final Integer u2 = glyphToUnicode(j);
1346: if (iObj == null) {
1347: // happens for many fonts (Ubuntu font set),
1348: // stray entries in the kerning table??
1349: log
1350: .warn("Unicode index (1) not found for glyph "
1351: + i);
1352: } else if (u2 == null) {
1353: log
1354: .warn("Unicode index (2) not found for glyph "
1355: + i);
1356: } else {
1357: Map adjTab = (Map) kerningTab.get(iObj);
1358: if (adjTab == null) {
1359: adjTab = new java.util.HashMap();
1360: }
1361: adjTab.put(u2, new Integer(
1362: (int) convertTTFUnit2PDFUnit(kpx)));
1363: kerningTab.put(iObj, adjTab);
1364: }
1365: }
1366: }
1367: }
1368:
1369: // Create winAnsiEncoded kerning table from kerningTab
1370: // (could probably be simplified, for now we remap back to CID indexes and
1371: // then to winAnsi)
1372: Iterator ae = kerningTab.keySet().iterator();
1373: while (ae.hasNext()) {
1374: Integer unicodeKey1 = (Integer) ae.next();
1375: Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue());
1376: Map akpx = new java.util.HashMap();
1377: Map ckpx = (Map) kerningTab.get(unicodeKey1);
1378:
1379: Iterator aee = ckpx.keySet().iterator();
1380: while (aee.hasNext()) {
1381: Integer unicodeKey2 = (Integer) aee.next();
1382: Integer cidKey2 = unicodeToGlyph(unicodeKey2
1383: .intValue());
1384: Integer kern = (Integer) ckpx.get(unicodeKey2);
1385:
1386: Iterator uniMap = mtxTab[cidKey2.intValue()]
1387: .getUnicodeIndex().listIterator();
1388: while (uniMap.hasNext()) {
1389: Integer unicodeKey = (Integer) uniMap.next();
1390: Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey
1391: .intValue());
1392: for (int u = 0; u < ansiKeys.length; u++) {
1393: akpx.put(ansiKeys[u], kern);
1394: }
1395: }
1396: }
1397:
1398: if (akpx.size() > 0) {
1399: Iterator uniMap = mtxTab[cidKey1.intValue()]
1400: .getUnicodeIndex().listIterator();
1401: while (uniMap.hasNext()) {
1402: Integer unicodeKey = (Integer) uniMap.next();
1403: Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey
1404: .intValue());
1405: for (int u = 0; u < ansiKeys.length; u++) {
1406: ansiKerningTab.put(ansiKeys[u], akpx);
1407: }
1408: }
1409: }
1410: }
1411: }
1412: }
1413:
1414: /**
1415: * Return a List with TTFCmapEntry.
1416: * @return A list of TTFCmapEntry objects
1417: */
1418: public List getCMaps() {
1419: return cmaps;
1420: }
1421:
1422: /**
1423: * Check if this is a TrueType collection and that the given
1424: * name exists in the collection.
1425: * If it does, set offset in fontfile to the beginning of
1426: * the Table Directory for that font.
1427: * @param in FontFileReader to read from
1428: * @param name The name to check
1429: * @return True if not collection or font name present, false otherwise
1430: * @throws IOException In case of an I/O problem
1431: */
1432: protected final boolean checkTTC(FontFileReader in, String name)
1433: throws IOException {
1434: String tag = in.readTTFString(4);
1435:
1436: if ("ttcf".equals(tag)) {
1437: // This is a TrueType Collection
1438: in.skip(4);
1439:
1440: // Read directory offsets
1441: int numDirectories = (int) in.readTTFULong();
1442: // int numDirectories=in.readTTFUShort();
1443: long[] dirOffsets = new long[numDirectories];
1444: for (int i = 0; i < numDirectories; i++) {
1445: dirOffsets[i] = in.readTTFULong();
1446: }
1447:
1448: log.info("This is a TrueType collection file with "
1449: + numDirectories + " fonts");
1450: log.info("Containing the following fonts: ");
1451: // Read all the directories and name tables to check
1452: // If the font exists - this is a bit ugly, but...
1453: boolean found = false;
1454:
1455: // Iterate through all name tables even if font
1456: // Is found, just to show all the names
1457: long dirTabOffset = 0;
1458: for (int i = 0; (i < numDirectories); i++) {
1459: in.seekSet(dirOffsets[i]);
1460: readDirTabs(in);
1461:
1462: readName(in);
1463:
1464: if (fullName.equals(name)) {
1465: found = true;
1466: dirTabOffset = dirOffsets[i];
1467: log.info(fullName + " <-- selected");
1468: } else {
1469: log.info(fullName);
1470: }
1471:
1472: // Reset names
1473: notice = "";
1474: fullName = "";
1475: familyName = "";
1476: fontName = "";
1477: subFamilyName = "";
1478: }
1479:
1480: in.seekSet(dirTabOffset);
1481: return found;
1482: } else {
1483: in.seekSet(0);
1484: return true;
1485: }
1486: }
1487:
1488: /*
1489: * Helper classes, they are not very efficient, but that really
1490: * doesn't matter...
1491: */
1492: private Integer[] unicodeToWinAnsi(int unicode) {
1493: List ret = new java.util.ArrayList();
1494: for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) {
1495: if (unicode == Glyphs.WINANSI_ENCODING[i]) {
1496: ret.add(new Integer(i));
1497: }
1498: }
1499: return (Integer[]) ret.toArray(new Integer[0]);
1500: }
1501:
1502: /**
1503: * Dumps a few informational values to System.out.
1504: */
1505: public void printStuff() {
1506: System.out.println("Font name: " + fontName);
1507: System.out.println("Full name: " + fullName);
1508: System.out.println("Family name: " + familyName);
1509: System.out.println("Subfamily name: " + subFamilyName);
1510: System.out.println("Notice: " + notice);
1511: System.out.println("xHeight: "
1512: + (int) convertTTFUnit2PDFUnit(xHeight));
1513: System.out.println("capheight: "
1514: + (int) convertTTFUnit2PDFUnit(capHeight));
1515:
1516: int italic = (int) (italicAngle >> 16);
1517: System.out.println("Italic: " + italic);
1518: System.out.print("ItalicAngle: "
1519: + (short) (italicAngle / 0x10000));
1520: if ((italicAngle % 0x10000) > 0) {
1521: System.out.print("."
1522: + (short) ((italicAngle % 0x10000) * 1000)
1523: / 0x10000);
1524: }
1525: System.out.println();
1526: System.out.println("Ascender: "
1527: + convertTTFUnit2PDFUnit(ascender));
1528: System.out.println("Descender: "
1529: + convertTTFUnit2PDFUnit(descender));
1530: System.out.println("FontBBox: ["
1531: + (int) convertTTFUnit2PDFUnit(fontBBox1) + " "
1532: + (int) convertTTFUnit2PDFUnit(fontBBox2) + " "
1533: + (int) convertTTFUnit2PDFUnit(fontBBox3) + " "
1534: + (int) convertTTFUnit2PDFUnit(fontBBox4) + "]");
1535: }
1536:
1537: /**
1538: * Map a glyph index to the corresponding unicode code point
1539: *
1540: * @param glyphIndex
1541: * @return unicode code point
1542: * @throws IOException if glyphIndex not found
1543: */
1544: private Integer glyphToUnicode(int glyphIndex) throws IOException {
1545: return (Integer) glyphToUnicodeMap.get(new Integer(glyphIndex));
1546: }
1547:
1548: /**
1549: * Map a unicode code point to the corresponding glyph index
1550: *
1551: * @param unicodeIndex unicode code point
1552: * @return glyph index
1553: * @throws IOException if unicodeIndex not found
1554: */
1555: private Integer unicodeToGlyph(int unicodeIndex) throws IOException {
1556: final Integer result = (Integer) unicodeToGlyphMap
1557: .get(new Integer(unicodeIndex));
1558: if (result == null) {
1559: throw new IOException(
1560: "Glyph index not found for unicode value "
1561: + unicodeIndex);
1562: }
1563: return result;
1564: }
1565:
1566: /**
1567: * Static main method to get info about a TrueType font.
1568: * @param args The command line arguments
1569: */
1570: public static void main(String[] args) {
1571: try {
1572: TTFFile ttfFile = new TTFFile();
1573:
1574: FontFileReader reader = new FontFileReader(args[0]);
1575:
1576: String name = null;
1577: if (args.length >= 2) {
1578: name = args[1];
1579: }
1580:
1581: ttfFile.readFont(reader, name);
1582: ttfFile.printStuff();
1583:
1584: } catch (IOException ioe) {
1585: System.err.println("Problem reading font: "
1586: + ioe.toString());
1587: ioe.printStackTrace(System.err);
1588: }
1589: }
1590: }
|