0001: /*
0002: * $Id: TrueTypeFont.java 2423 2006-10-02 21:09:37Z xlv $
0003: * $Name$
0004: *
0005: * Copyright 2001-2006 Paulo Soares
0006: *
0007: * The contents of this file are subject to the Mozilla Public License Version 1.1
0008: * (the "License"); you may not use this file except in compliance with the License.
0009: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
0010: *
0011: * Software distributed under the License is distributed on an "AS IS" basis,
0012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
0013: * for the specific language governing rights and limitations under the License.
0014: *
0015: * The Original Code is 'iText, a free JAVA-PDF library'.
0016: *
0017: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
0018: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
0019: * All Rights Reserved.
0020: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
0021: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
0022: *
0023: * Contributor(s): all the names of the contributors are added in the source code
0024: * where applicable.
0025: *
0026: * Alternatively, the contents of this file may be used under the terms of the
0027: * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
0028: * provisions of LGPL are applicable instead of those above. If you wish to
0029: * allow use of your version of this file only under the terms of the LGPL
0030: * License and not to allow others to use your version of this file under
0031: * the MPL, indicate your decision by deleting the provisions above and
0032: * replace them with the notice and other provisions required by the LGPL.
0033: * If you do not delete the provisions above, a recipient may use your version
0034: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
0035: *
0036: * This library is free software; you can redistribute it and/or modify it
0037: * under the terms of the MPL as stated above or under the terms of the GNU
0038: * Library General Public License as published by the Free Software Foundation;
0039: * either version 2 of the License, or any later version.
0040: *
0041: * This library is distributed in the hope that it will be useful, but WITHOUT
0042: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
0043: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
0044: * details.
0045: *
0046: * If you didn't download this code from the following link, you should check if
0047: * you aren't using an obsolete version:
0048: * http://www.lowagie.com/iText/
0049: */
0050:
0051: package com.lowagie.text.pdf;
0052:
0053: import java.io.File;
0054: import java.io.IOException;
0055: import java.util.ArrayList;
0056: import java.util.HashMap;
0057: import java.util.Iterator;
0058: import java.util.Map;
0059:
0060: import com.lowagie.text.DocumentException;
0061: import com.lowagie.text.ExceptionConverter;
0062:
0063: /** Reads a Truetype font
0064: *
0065: * @author Paulo Soares (psoares@consiste.pt)
0066: */
0067: class TrueTypeFont extends BaseFont {
0068:
0069: /** The code pages possible for a True Type font.
0070: */
0071: static final String codePages[] = { "1252 Latin 1",
0072: "1250 Latin 2: Eastern Europe", "1251 Cyrillic",
0073: "1253 Greek", "1254 Turkish", "1255 Hebrew", "1256 Arabic",
0074: "1257 Windows Baltic", "1258 Vietnamese", null, null, null,
0075: null, null, null, null, "874 Thai", "932 JIS/Japan",
0076: "936 Chinese: Simplified chars--PRC and Singapore",
0077: "949 Korean Wansung",
0078: "950 Chinese: Traditional chars--Taiwan and Hong Kong",
0079: "1361 Korean Johab", null, null, null, null, null, null,
0080: null, "Macintosh Character Set (US Roman)",
0081: "OEM Character Set", "Symbol Character Set", null, null,
0082: null, null, null, null, null, null, null, null, null, null,
0083: null, null, null, null, "869 IBM Greek",
0084: "866 MS-DOS Russian", "865 MS-DOS Nordic", "864 Arabic",
0085: "863 MS-DOS Canadian French", "862 Hebrew",
0086: "861 MS-DOS Icelandic", "860 MS-DOS Portuguese",
0087: "857 IBM Turkish", "855 IBM Cyrillic; primarily Russian",
0088: "852 Latin 2", "775 MS-DOS Baltic",
0089: "737 Greek; former 437 G", "708 Arabic; ASMO 708",
0090: "850 WE/Latin 1", "437 US" };
0091:
0092: protected boolean justNames = false;
0093: /** Contains the location of the several tables. The key is the name of
0094: * the table and the value is an <CODE>int[2]</CODE> where position 0
0095: * is the offset from the start of the file and position 1 is the length
0096: * of the table.
0097: */
0098: protected HashMap tables;
0099: /** The file in use.
0100: */
0101: protected RandomAccessFileOrArray rf;
0102: /** The file name.
0103: */
0104: protected String fileName;
0105:
0106: protected boolean cff = false;
0107:
0108: protected int cffOffset;
0109:
0110: protected int cffLength;
0111:
0112: /** The offset from the start of the file to the table directory.
0113: * It is 0 for TTF and may vary for TTC depending on the chosen font.
0114: */
0115: protected int directoryOffset;
0116: /** The index for the TTC font. It is an empty <CODE>String</CODE> for a
0117: * TTF file.
0118: */
0119: protected String ttcIndex;
0120: /** The style modifier */
0121: protected String style = "";
0122: /** The content of table 'head'.
0123: */
0124: protected FontHeader head = new FontHeader();
0125: /** The content of table 'hhea'.
0126: */
0127: protected HorizontalHeader hhea = new HorizontalHeader();
0128: /** The content of table 'OS/2'.
0129: */
0130: protected WindowsMetrics os_2 = new WindowsMetrics();
0131: /** The width of the glyphs. This is essentially the content of table
0132: * 'hmtx' normalized to 1000 units.
0133: */
0134: protected int GlyphWidths[];
0135:
0136: protected int bboxes[][];
0137: /** The map containing the code information for the table 'cmap', encoding 1.0.
0138: * The key is the code and the value is an <CODE>int[2]</CODE> where position 0
0139: * is the glyph number and position 1 is the glyph width normalized to 1000
0140: * units.
0141: */
0142: protected HashMap cmap10;
0143: /** The map containing the code information for the table 'cmap', encoding 3.1
0144: * in Unicode.
0145: * <P>
0146: * The key is the code and the value is an <CODE>int</CODE>[2] where position 0
0147: * is the glyph number and position 1 is the glyph width normalized to 1000
0148: * units.
0149: */
0150: protected HashMap cmap31;
0151: /** The map containing the kerning information. It represents the content of
0152: * table 'kern'. The key is an <CODE>Integer</CODE> where the top 16 bits
0153: * are the glyph number for the first character and the lower 16 bits are the
0154: * glyph number for the second character. The value is the amount of kerning in
0155: * normalized 1000 units as an <CODE>Integer</CODE>. This value is usually negative.
0156: */
0157: protected IntHashtable kerning = new IntHashtable();
0158: /**
0159: * The font name.
0160: * This name is usually extracted from the table 'name' with
0161: * the 'Name ID' 6.
0162: */
0163: protected String fontName;
0164:
0165: /** The full name of the font
0166: */
0167: protected String fullName[][];
0168:
0169: /** The family name of the font
0170: */
0171: protected String familyName[][];
0172: /** The italic angle. It is usually extracted from the 'post' table or in it's
0173: * absence with the code:
0174: * <P>
0175: * <PRE>
0176: * -Math.atan2(hhea.caretSlopeRun, hhea.caretSlopeRise) * 180 / Math.PI
0177: * </PRE>
0178: */
0179: protected double italicAngle;
0180: /** <CODE>true</CODE> if all the glyphs have the same width.
0181: */
0182: protected boolean isFixedPitch = false;
0183:
0184: /** The components of table 'head'.
0185: */
0186: protected static class FontHeader {
0187: /** A variable. */
0188: int flags;
0189: /** A variable. */
0190: int unitsPerEm;
0191: /** A variable. */
0192: short xMin;
0193: /** A variable. */
0194: short yMin;
0195: /** A variable. */
0196: short xMax;
0197: /** A variable. */
0198: short yMax;
0199: /** A variable. */
0200: int macStyle;
0201: }
0202:
0203: /** The components of table 'hhea'.
0204: */
0205: protected static class HorizontalHeader {
0206: /** A variable. */
0207: short Ascender;
0208: /** A variable. */
0209: short Descender;
0210: /** A variable. */
0211: short LineGap;
0212: /** A variable. */
0213: int advanceWidthMax;
0214: /** A variable. */
0215: short minLeftSideBearing;
0216: /** A variable. */
0217: short minRightSideBearing;
0218: /** A variable. */
0219: short xMaxExtent;
0220: /** A variable. */
0221: short caretSlopeRise;
0222: /** A variable. */
0223: short caretSlopeRun;
0224: /** A variable. */
0225: int numberOfHMetrics;
0226: }
0227:
0228: /** The components of table 'OS/2'.
0229: */
0230: protected static class WindowsMetrics {
0231: /** A variable. */
0232: short xAvgCharWidth;
0233: /** A variable. */
0234: int usWeightClass;
0235: /** A variable. */
0236: int usWidthClass;
0237: /** A variable. */
0238: short fsType;
0239: /** A variable. */
0240: short ySubscriptXSize;
0241: /** A variable. */
0242: short ySubscriptYSize;
0243: /** A variable. */
0244: short ySubscriptXOffset;
0245: /** A variable. */
0246: short ySubscriptYOffset;
0247: /** A variable. */
0248: short ySuperscriptXSize;
0249: /** A variable. */
0250: short ySuperscriptYSize;
0251: /** A variable. */
0252: short ySuperscriptXOffset;
0253: /** A variable. */
0254: short ySuperscriptYOffset;
0255: /** A variable. */
0256: short yStrikeoutSize;
0257: /** A variable. */
0258: short yStrikeoutPosition;
0259: /** A variable. */
0260: short sFamilyClass;
0261: /** A variable. */
0262: byte panose[] = new byte[10];
0263: /** A variable. */
0264: byte achVendID[] = new byte[4];
0265: /** A variable. */
0266: int fsSelection;
0267: /** A variable. */
0268: int usFirstCharIndex;
0269: /** A variable. */
0270: int usLastCharIndex;
0271: /** A variable. */
0272: short sTypoAscender;
0273: /** A variable. */
0274: short sTypoDescender;
0275: /** A variable. */
0276: short sTypoLineGap;
0277: /** A variable. */
0278: int usWinAscent;
0279: /** A variable. */
0280: int usWinDescent;
0281: /** A variable. */
0282: int ulCodePageRange1;
0283: /** A variable. */
0284: int ulCodePageRange2;
0285: /** A variable. */
0286: int sCapHeight;
0287: }
0288:
0289: /** This constructor is present to allow extending the class.
0290: */
0291: protected TrueTypeFont() {
0292: }
0293:
0294: TrueTypeFont(String ttFile, String enc, boolean emb, byte ttfAfm[])
0295: throws DocumentException, IOException {
0296: this (ttFile, enc, emb, ttfAfm, false);
0297: }
0298:
0299: /** Creates a new TrueType font.
0300: * @param ttFile the location of the font on file. The file must end in '.ttf' or
0301: * '.ttc' but can have modifiers after the name
0302: * @param enc the encoding to be applied to this font
0303: * @param emb true if the font is to be embedded in the PDF
0304: * @param ttfAfm the font as a <CODE>byte</CODE> array
0305: * @throws DocumentException the font is invalid
0306: * @throws IOException the font file could not be read
0307: */
0308: TrueTypeFont(String ttFile, String enc, boolean emb, byte ttfAfm[],
0309: boolean justNames) throws DocumentException, IOException {
0310: this .justNames = justNames;
0311: String nameBase = getBaseName(ttFile);
0312: String ttcName = getTTCName(nameBase);
0313: if (nameBase.length() < ttFile.length()) {
0314: style = ttFile.substring(nameBase.length());
0315: }
0316: encoding = enc;
0317: embedded = emb;
0318: fileName = ttcName;
0319: fontType = FONT_TYPE_TT;
0320: ttcIndex = "";
0321: if (ttcName.length() < nameBase.length())
0322: ttcIndex = nameBase.substring(ttcName.length() + 1);
0323: if (fileName.toLowerCase().endsWith(".ttf")
0324: || fileName.toLowerCase().endsWith(".otf")
0325: || fileName.toLowerCase().endsWith(".ttc")) {
0326: process(ttfAfm);
0327: if (!justNames && embedded && os_2.fsType == 2)
0328: throw new DocumentException(
0329: fileName
0330: + style
0331: + " cannot be embedded due to licensing restrictions.");
0332: } else
0333: throw new DocumentException(fileName + style
0334: + " is not a TTF, OTF or TTC font file.");
0335: if (!encoding.startsWith("#"))
0336: PdfEncodings.convertToBytes(" ", enc); // check if the encoding exists
0337: createEncoding();
0338: }
0339:
0340: /** Gets the name from a composed TTC file name.
0341: * If I have for input "myfont.ttc,2" the return will
0342: * be "myfont.ttc".
0343: * @param name the full name
0344: * @return the simple file name
0345: */
0346: protected static String getTTCName(String name) {
0347: int idx = name.toLowerCase().indexOf(".ttc,");
0348: if (idx < 0)
0349: return name;
0350: else
0351: return name.substring(0, idx + 4);
0352: }
0353:
0354: /**
0355: * Reads the tables 'head', 'hhea', 'OS/2' and 'post' filling several variables.
0356: * @throws DocumentException the font is invalid
0357: * @throws IOException the font file could not be read
0358: */
0359: void fillTables() throws DocumentException, IOException {
0360: int table_location[];
0361: table_location = (int[]) tables.get("head");
0362: if (table_location == null)
0363: throw new DocumentException(
0364: "Table 'head' does not exist in " + fileName
0365: + style);
0366: rf.seek(table_location[0] + 16);
0367: head.flags = rf.readUnsignedShort();
0368: head.unitsPerEm = rf.readUnsignedShort();
0369: rf.skipBytes(16);
0370: head.xMin = rf.readShort();
0371: head.yMin = rf.readShort();
0372: head.xMax = rf.readShort();
0373: head.yMax = rf.readShort();
0374: head.macStyle = rf.readUnsignedShort();
0375:
0376: table_location = (int[]) tables.get("hhea");
0377: if (table_location == null)
0378: throw new DocumentException("Table 'hhea' does not exist "
0379: + fileName + style);
0380: rf.seek(table_location[0] + 4);
0381: hhea.Ascender = rf.readShort();
0382: hhea.Descender = rf.readShort();
0383: hhea.LineGap = rf.readShort();
0384: hhea.advanceWidthMax = rf.readUnsignedShort();
0385: hhea.minLeftSideBearing = rf.readShort();
0386: hhea.minRightSideBearing = rf.readShort();
0387: hhea.xMaxExtent = rf.readShort();
0388: hhea.caretSlopeRise = rf.readShort();
0389: hhea.caretSlopeRun = rf.readShort();
0390: rf.skipBytes(12);
0391: hhea.numberOfHMetrics = rf.readUnsignedShort();
0392:
0393: table_location = (int[]) tables.get("OS/2");
0394: if (table_location == null)
0395: throw new DocumentException(
0396: "Table 'OS/2' does not exist in " + fileName
0397: + style);
0398: rf.seek(table_location[0]);
0399: int version = rf.readUnsignedShort();
0400: os_2.xAvgCharWidth = rf.readShort();
0401: os_2.usWeightClass = rf.readUnsignedShort();
0402: os_2.usWidthClass = rf.readUnsignedShort();
0403: os_2.fsType = rf.readShort();
0404: os_2.ySubscriptXSize = rf.readShort();
0405: os_2.ySubscriptYSize = rf.readShort();
0406: os_2.ySubscriptXOffset = rf.readShort();
0407: os_2.ySubscriptYOffset = rf.readShort();
0408: os_2.ySuperscriptXSize = rf.readShort();
0409: os_2.ySuperscriptYSize = rf.readShort();
0410: os_2.ySuperscriptXOffset = rf.readShort();
0411: os_2.ySuperscriptYOffset = rf.readShort();
0412: os_2.yStrikeoutSize = rf.readShort();
0413: os_2.yStrikeoutPosition = rf.readShort();
0414: os_2.sFamilyClass = rf.readShort();
0415: rf.readFully(os_2.panose);
0416: rf.skipBytes(16);
0417: rf.readFully(os_2.achVendID);
0418: os_2.fsSelection = rf.readUnsignedShort();
0419: os_2.usFirstCharIndex = rf.readUnsignedShort();
0420: os_2.usLastCharIndex = rf.readUnsignedShort();
0421: os_2.sTypoAscender = rf.readShort();
0422: os_2.sTypoDescender = rf.readShort();
0423: if (os_2.sTypoDescender > 0)
0424: os_2.sTypoDescender = (short) (-os_2.sTypoDescender);
0425: os_2.sTypoLineGap = rf.readShort();
0426: os_2.usWinAscent = rf.readUnsignedShort();
0427: os_2.usWinDescent = rf.readUnsignedShort();
0428: os_2.ulCodePageRange1 = 0;
0429: os_2.ulCodePageRange2 = 0;
0430: if (version > 0) {
0431: os_2.ulCodePageRange1 = rf.readInt();
0432: os_2.ulCodePageRange2 = rf.readInt();
0433: }
0434: if (version > 1) {
0435: rf.skipBytes(2);
0436: os_2.sCapHeight = rf.readShort();
0437: } else
0438: os_2.sCapHeight = (int) (0.7 * head.unitsPerEm);
0439:
0440: table_location = (int[]) tables.get("post");
0441: if (table_location == null) {
0442: italicAngle = -Math.atan2(hhea.caretSlopeRun,
0443: hhea.caretSlopeRise)
0444: * 180 / Math.PI;
0445: return;
0446: }
0447: rf.seek(table_location[0] + 4);
0448: short mantissa = rf.readShort();
0449: int fraction = rf.readUnsignedShort();
0450: italicAngle = (double) mantissa + (double) fraction / 16384.0;
0451: rf.skipBytes(4);
0452: isFixedPitch = rf.readInt() != 0;
0453: }
0454:
0455: /**
0456: * Gets the Postscript font name.
0457: * @throws DocumentException the font is invalid
0458: * @throws IOException the font file could not be read
0459: * @return the Postscript font name
0460: */
0461: String getBaseFont() throws DocumentException, IOException {
0462: int table_location[];
0463: table_location = (int[]) tables.get("name");
0464: if (table_location == null)
0465: throw new DocumentException(
0466: "Table 'name' does not exist in " + fileName
0467: + style);
0468: rf.seek(table_location[0] + 2);
0469: int numRecords = rf.readUnsignedShort();
0470: int startOfStorage = rf.readUnsignedShort();
0471: for (int k = 0; k < numRecords; ++k) {
0472: int platformID = rf.readUnsignedShort();
0473: int platformEncodingID = rf.readUnsignedShort();
0474: int languageID = rf.readUnsignedShort();
0475: int nameID = rf.readUnsignedShort();
0476: int length = rf.readUnsignedShort();
0477: int offset = rf.readUnsignedShort();
0478: if (nameID == 6) {
0479: rf.seek(table_location[0] + startOfStorage + offset);
0480: if (platformID == 0 || platformID == 3)
0481: return readUnicodeString(length);
0482: else
0483: return readStandardString(length);
0484: }
0485: }
0486: File file = new File(fileName);
0487: return file.getName().replace(' ', '-');
0488: }
0489:
0490: /** Extracts the names of the font in all the languages available.
0491: * @param id the name id to retrieve
0492: * @throws DocumentException on error
0493: * @throws IOException on error
0494: */
0495: String[][] getNames(int id) throws DocumentException, IOException {
0496: int table_location[];
0497: table_location = (int[]) tables.get("name");
0498: if (table_location == null)
0499: throw new DocumentException(
0500: "Table 'name' does not exist in " + fileName
0501: + style);
0502: rf.seek(table_location[0] + 2);
0503: int numRecords = rf.readUnsignedShort();
0504: int startOfStorage = rf.readUnsignedShort();
0505: ArrayList names = new ArrayList();
0506: for (int k = 0; k < numRecords; ++k) {
0507: int platformID = rf.readUnsignedShort();
0508: int platformEncodingID = rf.readUnsignedShort();
0509: int languageID = rf.readUnsignedShort();
0510: int nameID = rf.readUnsignedShort();
0511: int length = rf.readUnsignedShort();
0512: int offset = rf.readUnsignedShort();
0513: if (nameID == id) {
0514: int pos = rf.getFilePointer();
0515: rf.seek(table_location[0] + startOfStorage + offset);
0516: String name;
0517: if (platformID == 0 || platformID == 3
0518: || (platformID == 2 && platformEncodingID == 1)) {
0519: name = readUnicodeString(length);
0520: } else {
0521: name = readStandardString(length);
0522: }
0523: names.add(new String[] { String.valueOf(platformID),
0524: String.valueOf(platformEncodingID),
0525: String.valueOf(languageID), name });
0526: rf.seek(pos);
0527: }
0528: }
0529: String this Name[][] = new String[names.size()][];
0530: for (int k = 0; k < names.size(); ++k)
0531: this Name[k] = (String[]) names.get(k);
0532: return this Name;
0533: }
0534:
0535: void checkCff() {
0536: int table_location[];
0537: table_location = (int[]) tables.get("CFF ");
0538: if (table_location != null) {
0539: cff = true;
0540: cffOffset = table_location[0];
0541: cffLength = table_location[1];
0542: }
0543: }
0544:
0545: /** Reads the font data.
0546: * @param ttfAfm the font as a <CODE>byte</CODE> array, possibly <CODE>null</CODE>
0547: * @throws DocumentException the font is invalid
0548: * @throws IOException the font file could not be read
0549: */
0550: void process(byte ttfAfm[]) throws DocumentException, IOException {
0551: tables = new HashMap();
0552:
0553: try {
0554: if (ttfAfm == null)
0555: rf = new RandomAccessFileOrArray(fileName);
0556: else
0557: rf = new RandomAccessFileOrArray(ttfAfm);
0558: if (ttcIndex.length() > 0) {
0559: int dirIdx = Integer.parseInt(ttcIndex);
0560: if (dirIdx < 0)
0561: throw new DocumentException("The font index for "
0562: + fileName + " must be positive.");
0563: String mainTag = readStandardString(4);
0564: if (!mainTag.equals("ttcf"))
0565: throw new DocumentException(fileName
0566: + " is not a valid TTC file.");
0567: rf.skipBytes(4);
0568: int dirCount = rf.readInt();
0569: if (dirIdx >= dirCount)
0570: throw new DocumentException("The font index for "
0571: + fileName + " must be between 0 and "
0572: + (dirCount - 1) + ". It was " + dirIdx
0573: + ".");
0574: rf.skipBytes(dirIdx * 4);
0575: directoryOffset = rf.readInt();
0576: }
0577: rf.seek(directoryOffset);
0578: int ttId = rf.readInt();
0579: if (ttId != 0x00010000 && ttId != 0x4F54544F)
0580: throw new DocumentException(fileName
0581: + " is not a valid TTF or OTF file.");
0582: int num_tables = rf.readUnsignedShort();
0583: rf.skipBytes(6);
0584: for (int k = 0; k < num_tables; ++k) {
0585: String tag = readStandardString(4);
0586: rf.skipBytes(4);
0587: int table_location[] = new int[2];
0588: table_location[0] = rf.readInt();
0589: table_location[1] = rf.readInt();
0590: tables.put(tag, table_location);
0591: }
0592: checkCff();
0593: fontName = getBaseFont();
0594: fullName = getNames(4); //full name
0595: familyName = getNames(1); //family name
0596: if (!justNames) {
0597: fillTables();
0598: readGlyphWidths();
0599: readCMaps();
0600: readKerning();
0601: readBbox();
0602: GlyphWidths = null;
0603: }
0604: } finally {
0605: if (rf != null) {
0606: rf.close();
0607: if (!embedded)
0608: rf = null;
0609: }
0610: }
0611: }
0612:
0613: /** Reads a <CODE>String</CODE> from the font file as bytes using the Cp1252
0614: * encoding.
0615: * @param length the length of bytes to read
0616: * @return the <CODE>String</CODE> read
0617: * @throws IOException the font file could not be read
0618: */
0619: protected String readStandardString(int length) throws IOException {
0620: byte buf[] = new byte[length];
0621: rf.readFully(buf);
0622: try {
0623: return new String(buf, WINANSI);
0624: } catch (Exception e) {
0625: throw new ExceptionConverter(e);
0626: }
0627: }
0628:
0629: /** Reads a Unicode <CODE>String</CODE> from the font file. Each character is
0630: * represented by two bytes.
0631: * @param length the length of bytes to read. The <CODE>String</CODE> will have <CODE>length</CODE>/2
0632: * characters
0633: * @return the <CODE>String</CODE> read
0634: * @throws IOException the font file could not be read
0635: */
0636: protected String readUnicodeString(int length) throws IOException {
0637: StringBuffer buf = new StringBuffer();
0638: length /= 2;
0639: for (int k = 0; k < length; ++k) {
0640: buf.append(rf.readChar());
0641: }
0642: return buf.toString();
0643: }
0644:
0645: /** Reads the glyphs widths. The widths are extracted from the table 'hmtx'.
0646: * The glyphs are normalized to 1000 units.
0647: * @throws DocumentException the font is invalid
0648: * @throws IOException the font file could not be read
0649: */
0650: protected void readGlyphWidths() throws DocumentException,
0651: IOException {
0652: int table_location[];
0653: table_location = (int[]) tables.get("hmtx");
0654: if (table_location == null)
0655: throw new DocumentException(
0656: "Table 'hmtx' does not exist in " + fileName
0657: + style);
0658: rf.seek(table_location[0]);
0659: GlyphWidths = new int[hhea.numberOfHMetrics];
0660: for (int k = 0; k < hhea.numberOfHMetrics; ++k) {
0661: GlyphWidths[k] = (rf.readUnsignedShort() * 1000)
0662: / head.unitsPerEm;
0663: rf.readUnsignedShort();
0664: }
0665: }
0666:
0667: /** Gets a glyph width.
0668: * @param glyph the glyph to get the width of
0669: * @return the width of the glyph in normalized 1000 units
0670: */
0671: protected int getGlyphWidth(int glyph) {
0672: if (glyph >= GlyphWidths.length)
0673: glyph = GlyphWidths.length - 1;
0674: return GlyphWidths[glyph];
0675: }
0676:
0677: private void readBbox() throws DocumentException, IOException {
0678: int tableLocation[];
0679: tableLocation = (int[]) tables.get("head");
0680: if (tableLocation == null)
0681: throw new DocumentException(
0682: "Table 'head' does not exist in " + fileName
0683: + style);
0684: rf.seek(tableLocation[0]
0685: + TrueTypeFontSubSet.HEAD_LOCA_FORMAT_OFFSET);
0686: boolean locaShortTable = (rf.readUnsignedShort() == 0);
0687: tableLocation = (int[]) tables.get("loca");
0688: if (tableLocation == null)
0689: return;
0690: rf.seek(tableLocation[0]);
0691: int locaTable[];
0692: if (locaShortTable) {
0693: int entries = tableLocation[1] / 2;
0694: locaTable = new int[entries];
0695: for (int k = 0; k < entries; ++k)
0696: locaTable[k] = rf.readUnsignedShort() * 2;
0697: } else {
0698: int entries = tableLocation[1] / 4;
0699: locaTable = new int[entries];
0700: for (int k = 0; k < entries; ++k)
0701: locaTable[k] = rf.readInt();
0702: }
0703: tableLocation = (int[]) tables.get("glyf");
0704: if (tableLocation == null)
0705: throw new DocumentException(
0706: "Table 'glyf' does not exist in " + fileName
0707: + style);
0708: int tableGlyphOffset = tableLocation[0];
0709: bboxes = new int[locaTable.length - 1][];
0710: for (int glyph = 0; glyph < locaTable.length - 1; ++glyph) {
0711: int start = locaTable[glyph];
0712: if (start != locaTable[glyph + 1]) {
0713: rf.seek(tableGlyphOffset + start + 2);
0714: bboxes[glyph] = new int[] {
0715: (rf.readShort() * 1000) / head.unitsPerEm,
0716: (rf.readShort() * 1000) / head.unitsPerEm,
0717: (rf.readShort() * 1000) / head.unitsPerEm,
0718: (rf.readShort() * 1000) / head.unitsPerEm };
0719: }
0720: }
0721: }
0722:
0723: /** Reads the several maps from the table 'cmap'. The maps of interest are 1.0 for symbolic
0724: * fonts and 3.1 for all others. A symbolic font is defined as having the map 3.0.
0725: * @throws DocumentException the font is invalid
0726: * @throws IOException the font file could not be read
0727: */
0728: void readCMaps() throws DocumentException, IOException {
0729: int table_location[];
0730: table_location = (int[]) tables.get("cmap");
0731: if (table_location == null)
0732: throw new DocumentException(
0733: "Table 'cmap' does not exist in " + fileName
0734: + style);
0735: rf.seek(table_location[0]);
0736: rf.skipBytes(2);
0737: int num_tables = rf.readUnsignedShort();
0738: fontSpecific = false;
0739: int map10 = 0;
0740: int map31 = 0;
0741: int map30 = 0;
0742: for (int k = 0; k < num_tables; ++k) {
0743: int platId = rf.readUnsignedShort();
0744: int platSpecId = rf.readUnsignedShort();
0745: int offset = rf.readInt();
0746: if (platId == 3 && platSpecId == 0) {
0747: fontSpecific = true;
0748: map30 = offset;
0749: } else if (platId == 3 && platSpecId == 1) {
0750: map31 = offset;
0751: }
0752: if (platId == 1 && platSpecId == 0) {
0753: map10 = offset;
0754: }
0755: }
0756: if (map10 > 0) {
0757: rf.seek(table_location[0] + map10);
0758: int format = rf.readUnsignedShort();
0759: switch (format) {
0760: case 0:
0761: cmap10 = readFormat0();
0762: break;
0763: case 4:
0764: cmap10 = readFormat4();
0765: break;
0766: case 6:
0767: cmap10 = readFormat6();
0768: break;
0769: }
0770: }
0771: if (map31 > 0) {
0772: rf.seek(table_location[0] + map31);
0773: int format = rf.readUnsignedShort();
0774: if (format == 4) {
0775: cmap31 = readFormat4();
0776: }
0777: }
0778: if (map30 > 0) {
0779: rf.seek(table_location[0] + map30);
0780: int format = rf.readUnsignedShort();
0781: if (format == 4) {
0782: cmap10 = readFormat4();
0783: }
0784: }
0785: }
0786:
0787: /** The information in the maps of the table 'cmap' is coded in several formats.
0788: * Format 0 is the Apple standard character to glyph index mapping table.
0789: * @return a <CODE>HashMap</CODE> representing this map
0790: * @throws IOException the font file could not be read
0791: */
0792: HashMap readFormat0() throws IOException {
0793: HashMap h = new HashMap();
0794: rf.skipBytes(4);
0795: for (int k = 0; k < 256; ++k) {
0796: int r[] = new int[2];
0797: r[0] = rf.readUnsignedByte();
0798: r[1] = getGlyphWidth(r[0]);
0799: h.put(new Integer(k), r);
0800: }
0801: return h;
0802: }
0803:
0804: /** The information in the maps of the table 'cmap' is coded in several formats.
0805: * Format 4 is the Microsoft standard character to glyph index mapping table.
0806: * @return a <CODE>HashMap</CODE> representing this map
0807: * @throws IOException the font file could not be read
0808: */
0809: HashMap readFormat4() throws IOException {
0810: HashMap h = new HashMap();
0811: int table_lenght = rf.readUnsignedShort();
0812: rf.skipBytes(2);
0813: int segCount = rf.readUnsignedShort() / 2;
0814: rf.skipBytes(6);
0815: int endCount[] = new int[segCount];
0816: for (int k = 0; k < segCount; ++k) {
0817: endCount[k] = rf.readUnsignedShort();
0818: }
0819: rf.skipBytes(2);
0820: int startCount[] = new int[segCount];
0821: for (int k = 0; k < segCount; ++k) {
0822: startCount[k] = rf.readUnsignedShort();
0823: }
0824: int idDelta[] = new int[segCount];
0825: for (int k = 0; k < segCount; ++k) {
0826: idDelta[k] = rf.readUnsignedShort();
0827: }
0828: int idRO[] = new int[segCount];
0829: for (int k = 0; k < segCount; ++k) {
0830: idRO[k] = rf.readUnsignedShort();
0831: }
0832: int glyphId[] = new int[table_lenght / 2 - 8 - segCount * 4];
0833: for (int k = 0; k < glyphId.length; ++k) {
0834: glyphId[k] = rf.readUnsignedShort();
0835: }
0836: for (int k = 0; k < segCount; ++k) {
0837: int glyph;
0838: for (int j = startCount[k]; j <= endCount[k] && j != 0xFFFF; ++j) {
0839: if (idRO[k] == 0) {
0840: glyph = (j + idDelta[k]) & 0xFFFF;
0841: } else {
0842: int idx = k + idRO[k] / 2 - segCount + j
0843: - startCount[k];
0844: if (idx >= glyphId.length)
0845: continue;
0846: glyph = (glyphId[idx] + idDelta[k]) & 0xFFFF;
0847: }
0848: int r[] = new int[2];
0849: r[0] = glyph;
0850: r[1] = getGlyphWidth(r[0]);
0851: h
0852: .put(
0853: new Integer(
0854: fontSpecific ? ((j & 0xff00) == 0xf000 ? j & 0xff
0855: : j)
0856: : j), r);
0857: }
0858: }
0859: return h;
0860: }
0861:
0862: /** The information in the maps of the table 'cmap' is coded in several formats.
0863: * Format 6 is a trimmed table mapping. It is similar to format 0 but can have
0864: * less than 256 entries.
0865: * @return a <CODE>HashMap</CODE> representing this map
0866: * @throws IOException the font file could not be read
0867: */
0868: HashMap readFormat6() throws IOException {
0869: HashMap h = new HashMap();
0870: rf.skipBytes(4);
0871: int start_code = rf.readUnsignedShort();
0872: int code_count = rf.readUnsignedShort();
0873: for (int k = 0; k < code_count; ++k) {
0874: int r[] = new int[2];
0875: r[0] = rf.readUnsignedShort();
0876: r[1] = getGlyphWidth(r[0]);
0877: h.put(new Integer(k + start_code), r);
0878: }
0879: return h;
0880: }
0881:
0882: /** Reads the kerning information from the 'kern' table.
0883: * @throws IOException the font file could not be read
0884: */
0885: void readKerning() throws IOException {
0886: int table_location[];
0887: table_location = (int[]) tables.get("kern");
0888: if (table_location == null)
0889: return;
0890: rf.seek(table_location[0] + 2);
0891: int nTables = rf.readUnsignedShort();
0892: int checkpoint = table_location[0] + 4;
0893: int length = 0;
0894: for (int k = 0; k < nTables; ++k) {
0895: checkpoint += length;
0896: rf.seek(checkpoint);
0897: rf.skipBytes(2);
0898: length = rf.readUnsignedShort();
0899: int coverage = rf.readUnsignedShort();
0900: if ((coverage & 0xfff7) == 0x0001) {
0901: int nPairs = rf.readUnsignedShort();
0902: rf.skipBytes(6);
0903: for (int j = 0; j < nPairs; ++j) {
0904: int pair = rf.readInt();
0905: int value = ((int) rf.readShort() * 1000)
0906: / head.unitsPerEm;
0907: kerning.put(pair, value);
0908: }
0909: }
0910: }
0911: }
0912:
0913: /** Gets the kerning between two Unicode chars.
0914: * @param char1 the first char
0915: * @param char2 the second char
0916: * @return the kerning to be applied
0917: */
0918: public int getKerning(char char1, char char2) {
0919: int metrics[] = getMetricsTT(char1);
0920: if (metrics == null)
0921: return 0;
0922: int c1 = metrics[0];
0923: metrics = getMetricsTT(char2);
0924: if (metrics == null)
0925: return 0;
0926: int c2 = metrics[0];
0927: return kerning.get((c1 << 16) + c2);
0928: }
0929:
0930: /** Gets the width from the font according to the unicode char <CODE>c</CODE>.
0931: * If the <CODE>name</CODE> is null it's a symbolic font.
0932: * @param c the unicode char
0933: * @param name the glyph name
0934: * @return the width of the char
0935: */
0936: int getRawWidth(int c, String name) {
0937: HashMap map = null;
0938: if (name == null || cmap31 == null)
0939: map = cmap10;
0940: else
0941: map = cmap31;
0942: if (map == null)
0943: return 0;
0944: int metric[] = (int[]) map.get(new Integer(c));
0945: if (metric == null)
0946: return 0;
0947: return metric[1];
0948: }
0949:
0950: /** Generates the font descriptor for this font.
0951: * @return the PdfDictionary containing the font descriptor or <CODE>null</CODE>
0952: * @param subsetPrefix the subset prefix
0953: * @param fontStream the indirect reference to a PdfStream containing the font or <CODE>null</CODE>
0954: * @throws DocumentException if there is an error
0955: */
0956: protected PdfDictionary getFontDescriptor(
0957: PdfIndirectReference fontStream, String subsetPrefix) {
0958: PdfDictionary dic = new PdfDictionary(PdfName.FONTDESCRIPTOR);
0959: dic.put(PdfName.ASCENT, new PdfNumber((int) os_2.sTypoAscender
0960: * 1000 / head.unitsPerEm));
0961: dic.put(PdfName.CAPHEIGHT, new PdfNumber((int) os_2.sCapHeight
0962: * 1000 / head.unitsPerEm));
0963: dic.put(PdfName.DESCENT, new PdfNumber(
0964: (int) os_2.sTypoDescender * 1000 / head.unitsPerEm));
0965: dic.put(PdfName.FONTBBOX, new PdfRectangle((int) head.xMin
0966: * 1000 / head.unitsPerEm, (int) head.yMin * 1000
0967: / head.unitsPerEm, (int) head.xMax * 1000
0968: / head.unitsPerEm, (int) head.yMax * 1000
0969: / head.unitsPerEm));
0970: if (cff) {
0971: if (encoding.startsWith("Identity-"))
0972: dic.put(PdfName.FONTNAME, new PdfName(subsetPrefix
0973: + fontName + "-" + encoding));
0974: else
0975: dic.put(PdfName.FONTNAME, new PdfName(subsetPrefix
0976: + fontName + style));
0977: } else
0978: dic.put(PdfName.FONTNAME, new PdfName(subsetPrefix
0979: + fontName + style));
0980: dic.put(PdfName.ITALICANGLE, new PdfNumber(italicAngle));
0981: dic.put(PdfName.STEMV, new PdfNumber(80));
0982: if (fontStream != null) {
0983: if (cff)
0984: dic.put(PdfName.FONTFILE3, fontStream);
0985: else
0986: dic.put(PdfName.FONTFILE2, fontStream);
0987: }
0988: int flags = 0;
0989: if (isFixedPitch)
0990: flags |= 1;
0991: flags |= fontSpecific ? 4 : 32;
0992: if ((head.macStyle & 2) != 0)
0993: flags |= 64;
0994: if ((head.macStyle & 1) != 0)
0995: flags |= 262144;
0996: dic.put(PdfName.FLAGS, new PdfNumber(flags));
0997:
0998: return dic;
0999: }
1000:
1001: /** Generates the font dictionary for this font.
1002: * @return the PdfDictionary containing the font dictionary
1003: * @param subsetPrefix the subset prefx
1004: * @param firstChar the first valid character
1005: * @param lastChar the last valid character
1006: * @param shortTag a 256 bytes long <CODE>byte</CODE> array where each unused byte is represented by 0
1007: * @param fontDescriptor the indirect reference to a PdfDictionary containing the font descriptor or <CODE>null</CODE>
1008: * @throws DocumentException if there is an error
1009: */
1010: protected PdfDictionary getFontBaseType(
1011: PdfIndirectReference fontDescriptor, String subsetPrefix,
1012: int firstChar, int lastChar, byte shortTag[]) {
1013: PdfDictionary dic = new PdfDictionary(PdfName.FONT);
1014: if (cff) {
1015: dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
1016: dic.put(PdfName.BASEFONT, new PdfName(fontName + style));
1017: } else {
1018: dic.put(PdfName.SUBTYPE, PdfName.TRUETYPE);
1019: dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix
1020: + fontName + style));
1021: }
1022: dic.put(PdfName.BASEFONT, new PdfName(subsetPrefix + fontName
1023: + style));
1024: if (!fontSpecific) {
1025: for (int k = firstChar; k <= lastChar; ++k) {
1026: if (!differences[k].equals(notdef)) {
1027: firstChar = k;
1028: break;
1029: }
1030: }
1031: if (encoding.equals("Cp1252")
1032: || encoding.equals("MacRoman"))
1033: dic
1034: .put(
1035: PdfName.ENCODING,
1036: encoding.equals("Cp1252") ? PdfName.WIN_ANSI_ENCODING
1037: : PdfName.MAC_ROMAN_ENCODING);
1038: else {
1039: PdfDictionary enc = new PdfDictionary(PdfName.ENCODING);
1040: PdfArray dif = new PdfArray();
1041: boolean gap = true;
1042: for (int k = firstChar; k <= lastChar; ++k) {
1043: if (shortTag[k] != 0) {
1044: if (gap) {
1045: dif.add(new PdfNumber(k));
1046: gap = false;
1047: }
1048: dif.add(new PdfName(differences[k]));
1049: } else
1050: gap = true;
1051: }
1052: enc.put(PdfName.DIFFERENCES, dif);
1053: dic.put(PdfName.ENCODING, enc);
1054: }
1055: }
1056: dic.put(PdfName.FIRSTCHAR, new PdfNumber(firstChar));
1057: dic.put(PdfName.LASTCHAR, new PdfNumber(lastChar));
1058: PdfArray wd = new PdfArray();
1059: for (int k = firstChar; k <= lastChar; ++k) {
1060: if (shortTag[k] == 0)
1061: wd.add(new PdfNumber(0));
1062: else
1063: wd.add(new PdfNumber(widths[k]));
1064: }
1065: dic.put(PdfName.WIDTHS, wd);
1066: if (fontDescriptor != null)
1067: dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor);
1068: return dic;
1069: }
1070:
1071: protected byte[] getFullFont() throws IOException {
1072: RandomAccessFileOrArray rf2 = null;
1073: try {
1074: rf2 = new RandomAccessFileOrArray(rf);
1075: rf2.reOpen();
1076: byte b[] = new byte[rf2.length()];
1077: rf2.readFully(b);
1078: return b;
1079: } finally {
1080: try {
1081: if (rf2 != null) {
1082: rf2.close();
1083: }
1084: } catch (Exception e) {
1085: }
1086: }
1087: }
1088:
1089: protected static int[] compactRanges(ArrayList ranges) {
1090: ArrayList simp = new ArrayList();
1091: for (int k = 0; k < ranges.size(); ++k) {
1092: int[] r = (int[]) ranges.get(k);
1093: for (int j = 0; j < r.length; j += 2) {
1094: simp.add(new int[] {
1095: Math.max(0, Math.min(r[j], r[j + 1])),
1096: Math.min(0xffff, Math.max(r[j], r[j + 1])) });
1097: }
1098: }
1099: for (int k1 = 0; k1 < simp.size() - 1; ++k1) {
1100: for (int k2 = k1 + 1; k2 < simp.size(); ++k2) {
1101: int[] r1 = (int[]) simp.get(k1);
1102: int[] r2 = (int[]) simp.get(k2);
1103: if ((r1[0] >= r2[0] && r1[0] <= r2[1])
1104: || (r1[1] >= r2[0] && r1[0] <= r2[1])) {
1105: r1[0] = Math.min(r1[0], r2[0]);
1106: r1[1] = Math.max(r1[1], r2[1]);
1107: simp.remove(k2);
1108: --k2;
1109: }
1110: }
1111: }
1112: int[] s = new int[simp.size() * 2];
1113: for (int k = 0; k < simp.size(); ++k) {
1114: int[] r = (int[]) simp.get(k);
1115: s[k * 2] = r[0];
1116: s[k * 2 + 1] = r[1];
1117: }
1118: return s;
1119: }
1120:
1121: protected void addRangeUni(HashMap longTag, boolean includeMetrics,
1122: boolean subsetp) {
1123: if (!subsetp && (subsetRanges != null || directoryOffset > 0)) {
1124: int[] rg = (subsetRanges == null && directoryOffset > 0) ? new int[] {
1125: 0, 0xffff }
1126: : compactRanges(subsetRanges);
1127: HashMap usemap;
1128: if (!fontSpecific && cmap31 != null)
1129: usemap = cmap31;
1130: else if (fontSpecific && cmap10 != null)
1131: usemap = cmap10;
1132: else if (cmap31 != null)
1133: usemap = cmap31;
1134: else
1135: usemap = cmap10;
1136: for (Iterator it = usemap.entrySet().iterator(); it
1137: .hasNext();) {
1138: Map.Entry e = (Map.Entry) it.next();
1139: int[] v = (int[]) e.getValue();
1140: Integer gi = new Integer(v[0]);
1141: if (longTag.containsKey(gi))
1142: continue;
1143: int c = ((Integer) e.getKey()).intValue();
1144: boolean skip = true;
1145: for (int k = 0; k < rg.length; k += 2) {
1146: if (c >= rg[k] && c <= rg[k + 1]) {
1147: skip = false;
1148: break;
1149: }
1150: }
1151: if (!skip)
1152: longTag.put(gi, includeMetrics ? new int[] { v[0],
1153: v[1], c } : null);
1154: }
1155: }
1156: }
1157:
1158: /** Outputs to the writer the font dictionaries and streams.
1159: * @param writer the writer for this document
1160: * @param ref the font indirect reference
1161: * @param params several parameters that depend on the font type
1162: * @throws IOException on error
1163: * @throws DocumentException error in generating the object
1164: */
1165: void writeFont(PdfWriter writer, PdfIndirectReference ref,
1166: Object params[]) throws DocumentException, IOException {
1167: int firstChar = ((Integer) params[0]).intValue();
1168: int lastChar = ((Integer) params[1]).intValue();
1169: byte shortTag[] = (byte[]) params[2];
1170: boolean subsetp = ((Boolean) params[3]).booleanValue()
1171: && subset;
1172:
1173: if (!subsetp) {
1174: firstChar = 0;
1175: lastChar = shortTag.length - 1;
1176: for (int k = 0; k < shortTag.length; ++k)
1177: shortTag[k] = 1;
1178: }
1179: PdfIndirectReference ind_font = null;
1180: PdfObject pobj = null;
1181: PdfIndirectObject obj = null;
1182: String subsetPrefix = "";
1183: if (embedded) {
1184: if (cff) {
1185: RandomAccessFileOrArray rf2 = new RandomAccessFileOrArray(
1186: rf);
1187: byte b[] = new byte[cffLength];
1188: try {
1189: rf2.reOpen();
1190: rf2.seek(cffOffset);
1191: rf2.readFully(b);
1192: } finally {
1193: try {
1194: rf2.close();
1195: } catch (Exception e) {
1196: // empty on purpose
1197: }
1198: }
1199: pobj = new StreamFont(b, "Type1C");
1200: obj = writer.addToBody(pobj);
1201: ind_font = obj.getIndirectReference();
1202: } else {
1203: if (subsetp)
1204: subsetPrefix = createSubsetPrefix();
1205: HashMap glyphs = new HashMap();
1206: for (int k = firstChar; k <= lastChar; ++k) {
1207: if (shortTag[k] != 0) {
1208: int[] metrics = null;
1209: if (specialMap != null) {
1210: int[] cd = GlyphList
1211: .nameToUnicode(differences[k]);
1212: if (cd != null)
1213: metrics = getMetricsTT(cd[0]);
1214: } else {
1215: if (fontSpecific)
1216: metrics = getMetricsTT(k);
1217: else
1218: metrics = getMetricsTT(unicodeDifferences[k]);
1219: }
1220: if (metrics != null)
1221: glyphs.put(new Integer(metrics[0]), null);
1222: }
1223: }
1224: addRangeUni(glyphs, false, subsetp);
1225: byte[] b = null;
1226: if (subsetp || directoryOffset != 0
1227: || subsetRanges != null) {
1228: TrueTypeFontSubSet sb = new TrueTypeFontSubSet(
1229: fileName, new RandomAccessFileOrArray(rf),
1230: glyphs, directoryOffset, true, !subsetp);
1231: b = sb.process();
1232: } else {
1233: b = getFullFont();
1234: }
1235: int lengths[] = new int[] { b.length };
1236: pobj = new StreamFont(b, lengths);
1237: obj = writer.addToBody(pobj);
1238: ind_font = obj.getIndirectReference();
1239: }
1240: }
1241: pobj = getFontDescriptor(ind_font, subsetPrefix);
1242: if (pobj != null) {
1243: obj = writer.addToBody(pobj);
1244: ind_font = obj.getIndirectReference();
1245: }
1246: pobj = getFontBaseType(ind_font, subsetPrefix, firstChar,
1247: lastChar, shortTag);
1248: writer.addToBody(pobj, ref);
1249: }
1250:
1251: /** Gets the font parameter identified by <CODE>key</CODE>. Valid values
1252: * for <CODE>key</CODE> are <CODE>ASCENT</CODE>, <CODE>CAPHEIGHT</CODE>, <CODE>DESCENT</CODE>
1253: * and <CODE>ITALICANGLE</CODE>.
1254: * @param key the parameter to be extracted
1255: * @param fontSize the font size in points
1256: * @return the parameter in points
1257: */
1258: public float getFontDescriptor(int key, float fontSize) {
1259: switch (key) {
1260: case ASCENT:
1261: return (float) os_2.sTypoAscender * fontSize
1262: / (float) head.unitsPerEm;
1263: case CAPHEIGHT:
1264: return (float) os_2.sCapHeight * fontSize
1265: / (float) head.unitsPerEm;
1266: case DESCENT:
1267: return (float) os_2.sTypoDescender * fontSize
1268: / (float) head.unitsPerEm;
1269: case ITALICANGLE:
1270: return (float) italicAngle;
1271: case BBOXLLX:
1272: return fontSize * (int) head.xMin / head.unitsPerEm;
1273: case BBOXLLY:
1274: return fontSize * (int) head.yMin / head.unitsPerEm;
1275: case BBOXURX:
1276: return fontSize * (int) head.xMax / head.unitsPerEm;
1277: case BBOXURY:
1278: return fontSize * (int) head.yMax / head.unitsPerEm;
1279: case AWT_ASCENT:
1280: return fontSize * (int) hhea.Ascender / head.unitsPerEm;
1281: case AWT_DESCENT:
1282: return fontSize * (int) hhea.Descender / head.unitsPerEm;
1283: case AWT_LEADING:
1284: return fontSize * (int) hhea.LineGap / head.unitsPerEm;
1285: case AWT_MAXADVANCE:
1286: return fontSize * (int) hhea.advanceWidthMax
1287: / head.unitsPerEm;
1288: }
1289: return 0;
1290: }
1291:
1292: /** Gets the glyph index and metrics for a character.
1293: * @param c the character
1294: * @return an <CODE>int</CODE> array with {glyph index, width}
1295: */
1296: public int[] getMetricsTT(int c) {
1297: if (!fontSpecific && cmap31 != null)
1298: return (int[]) cmap31.get(new Integer(c));
1299: if (fontSpecific && cmap10 != null)
1300: return (int[]) cmap10.get(new Integer(c));
1301: if (cmap31 != null)
1302: return (int[]) cmap31.get(new Integer(c));
1303: if (cmap10 != null)
1304: return (int[]) cmap10.get(new Integer(c));
1305: return null;
1306: }
1307:
1308: /** Gets the postscript font name.
1309: * @return the postscript font name
1310: */
1311: public String getPostscriptFontName() {
1312: return fontName;
1313: }
1314:
1315: /** Gets the code pages supported by the font.
1316: * @return the code pages supported by the font
1317: */
1318: public String[] getCodePagesSupported() {
1319: long cp = (((long) os_2.ulCodePageRange2) << 32)
1320: + ((long) os_2.ulCodePageRange1 & 0xffffffffL);
1321: int count = 0;
1322: long bit = 1;
1323: for (int k = 0; k < 64; ++k) {
1324: if ((cp & bit) != 0 && codePages[k] != null)
1325: ++count;
1326: bit <<= 1;
1327: }
1328: String ret[] = new String[count];
1329: count = 0;
1330: bit = 1;
1331: for (int k = 0; k < 64; ++k) {
1332: if ((cp & bit) != 0 && codePages[k] != null)
1333: ret[count++] = codePages[k];
1334: bit <<= 1;
1335: }
1336: return ret;
1337: }
1338:
1339: /** Gets the full name of the font. If it is a True Type font
1340: * each array element will have {Platform ID, Platform Encoding ID,
1341: * Language ID, font name}. The interpretation of this values can be
1342: * found in the Open Type specification, chapter 2, in the 'name' table.<br>
1343: * For the other fonts the array has a single element with {"", "", "",
1344: * font name}.
1345: * @return the full name of the font
1346: */
1347: public String[][] getFullFontName() {
1348: return fullName;
1349: }
1350:
1351: /** Gets the family name of the font. If it is a True Type font
1352: * each array element will have {Platform ID, Platform Encoding ID,
1353: * Language ID, font name}. The interpretation of this values can be
1354: * found in the Open Type specification, chapter 2, in the 'name' table.<br>
1355: * For the other fonts the array has a single element with {"", "", "",
1356: * font name}.
1357: * @return the family name of the font
1358: */
1359: public String[][] getFamilyFontName() {
1360: return familyName;
1361: }
1362:
1363: /** Checks if the font has any kerning pairs.
1364: * @return <CODE>true</CODE> if the font has any kerning pairs
1365: */
1366: public boolean hasKernPairs() {
1367: return kerning.size() > 0;
1368: }
1369:
1370: /**
1371: * Sets the font name that will appear in the pdf font dictionary.
1372: * Use with care as it can easily make a font unreadable if not embedded.
1373: * @param name the new font name
1374: */
1375: public void setPostscriptFontName(String name) {
1376: fontName = name;
1377: }
1378:
1379: /**
1380: * Sets the kerning between two Unicode chars.
1381: * @param char1 the first char
1382: * @param char2 the second char
1383: * @param kern the kerning to apply in normalized 1000 units
1384: * @return <code>true</code> if the kerning was applied, <code>false</code> otherwise
1385: */
1386: public boolean setKerning(char char1, char char2, int kern) {
1387: int metrics[] = getMetricsTT(char1);
1388: if (metrics == null)
1389: return false;
1390: int c1 = metrics[0];
1391: metrics = getMetricsTT(char2);
1392: if (metrics == null)
1393: return false;
1394: int c2 = metrics[0];
1395: kerning.put((c1 << 16) + c2, kern);
1396: return true;
1397: }
1398:
1399: protected int[] getRawCharBBox(int c, String name) {
1400: HashMap map = null;
1401: if (name == null || cmap31 == null)
1402: map = cmap10;
1403: else
1404: map = cmap31;
1405: if (map == null)
1406: return null;
1407: int metric[] = (int[]) map.get(new Integer(c));
1408: if (metric == null || bboxes == null)
1409: return null;
1410: return bboxes[metric[0]];
1411: }
1412: }
|