001: /*
002: * $Id: TrueTypeFont.java,v 1.3 2007/12/20 18:33:30 rbair Exp $
003: *
004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005: * Santa Clara, California 95054, U.S.A. All rights reserved.
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020: */
021:
022: package com.sun.pdfview.font.ttf;
023:
024: import java.awt.Font;
025: import java.io.ByteArrayInputStream;
026: import java.io.InputStream;
027: import java.io.RandomAccessFile;
028: import java.nio.ByteBuffer;
029: import java.util.Collections;
030: import java.util.Iterator;
031: import java.util.Map;
032: import java.util.SortedMap;
033: import java.util.TreeMap;
034:
035: /**
036: *
037: * @author jkaplan
038: */
039: public class TrueTypeFont {
040:
041: private int type;
042: private SortedMap tables;
043:
044: /** Creates a new instance of TrueTypeParser */
045: public TrueTypeFont(int type) {
046: this .type = type;
047:
048: tables = Collections.synchronizedSortedMap(new TreeMap());
049: }
050:
051: /**
052: * Parses a TrueType font from a byte array
053: */
054: public static TrueTypeFont parseFont(byte[] orig) {
055: ByteBuffer inBuf = ByteBuffer.wrap(orig);
056: return parseFont(inBuf);
057: }
058:
059: /**
060: * Parses a TrueType font from a byte buffer
061: */
062: public static TrueTypeFont parseFont(ByteBuffer inBuf) {
063: int type = inBuf.getInt();
064: short numTables = inBuf.getShort();
065: short searchRange = inBuf.getShort();
066: short entrySelector = inBuf.getShort();
067: short rangeShift = inBuf.getShort();
068:
069: TrueTypeFont font = new TrueTypeFont(type);
070: parseDirectories(inBuf, numTables, font);
071:
072: return font;
073: }
074:
075: /**
076: * Get the type of this font
077: */
078: public int getType() {
079: return type;
080: }
081:
082: /**
083: * Add a table to the font
084: *
085: * @param tagString the name of this table, as a 4 character string
086: * (i.e. cmap or head)
087: * @param data the data for this table, as a byte buffer
088: */
089: public void addTable(String tagString, ByteBuffer data) {
090: tables.put(tagString, data);
091: }
092:
093: /**
094: * Add a table to the font
095: *
096: * @param tagString the name of this table, as a 4 character string
097: * (i.e. cmap or head)
098: * @param table the table
099: */
100: public void addTable(String tagString, TrueTypeTable table) {
101: tables.put(tagString, table);
102: }
103:
104: /**
105: * Get a table by name. This command causes the table in question
106: * to be parsed, if it has not already been parsed.
107: *
108: * @param tagString the name of this table, as a 4 character string
109: * (i.e. cmap or head)
110: */
111: public TrueTypeTable getTable(String tagString) {
112: Object tableObj = tables.get(tagString);
113:
114: TrueTypeTable table = null;
115:
116: if (tableObj instanceof ByteBuffer) {
117: // the table has not yet been parsed. Parse it, and add the
118: // parsed version to the map of tables.
119: ByteBuffer data = (ByteBuffer) tableObj;
120:
121: table = TrueTypeTable.createTable(this , tagString, data);
122: addTable(tagString, table);
123: } else {
124: table = (TrueTypeTable) tableObj;
125: }
126:
127: return table;
128: }
129:
130: /**
131: * Remove a table by name
132: *
133: * @param tagString the name of this table, as a 4 character string
134: * (i.e. cmap or head)
135: */
136: public void removeTable(String tagString) {
137: tables.remove(tagString);
138: }
139:
140: /**
141: * Get the number of tables
142: */
143: public short getNumTables() {
144: return (short) tables.size();
145: }
146:
147: /**
148: * Get the search range
149: */
150: public short getSearchRange() {
151: double pow2 = Math
152: .floor(Math.log(getNumTables()) / Math.log(2));
153: double maxPower = Math.pow(2, pow2);
154:
155: return (short) (16 * maxPower);
156: }
157:
158: /**
159: * Get the entry selector
160: */
161: public short getEntrySelector() {
162: double pow2 = Math
163: .floor(Math.log(getNumTables()) / Math.log(2));
164: double maxPower = Math.pow(2, pow2);
165:
166: return (short) (Math.log(maxPower) / Math.log(2));
167: }
168:
169: /**
170: * Get the range shift
171: */
172: public short getRangeShift() {
173: double pow2 = Math
174: .floor(Math.log(getNumTables()) / Math.log(2));
175: double maxPower = Math.pow(2, pow2);
176:
177: return (short) ((maxPower * 16) - getSearchRange());
178: }
179:
180: /**
181: * Write a font given the type and an array of Table Directory Entries
182: */
183: public byte[] writeFont() {
184: // allocate a buffer to hold the font
185: ByteBuffer buf = ByteBuffer.allocate(getLength());
186:
187: // write the font header
188: buf.putInt(getType());
189: buf.putShort(getNumTables());
190: buf.putShort(getSearchRange());
191: buf.putShort(getEntrySelector());
192: buf.putShort(getRangeShift());
193:
194: // first offset is the end of the table directory entries
195: int curOffset = 12 + (getNumTables() * 16);
196:
197: // write the tables
198: for (Iterator i = tables.keySet().iterator(); i.hasNext();) {
199: String tagString = (String) i.next();
200: int tag = TrueTypeTable.stringToTag(tagString);
201:
202: ByteBuffer data = null;
203:
204: Object tableObj = tables.get(tagString);
205: if (tableObj instanceof TrueTypeTable) {
206: data = ((TrueTypeTable) tableObj).getData();
207: } else {
208: data = (ByteBuffer) tableObj;
209: }
210:
211: int dataLen = data.remaining();
212:
213: // write the table directory entry
214: buf.putInt(tag);
215: buf.putInt(calculateChecksum(tagString, data));
216: buf.putInt(curOffset);
217: buf.putInt(dataLen);
218:
219: // save the current position
220: buf.mark();
221:
222: // move to the current offset and write the data
223: buf.position(curOffset);
224: buf.put(data);
225:
226: // reset the data start pointer
227: data.flip();
228:
229: // return to the table directory entry
230: buf.reset();
231:
232: // udate the offset
233: curOffset += dataLen;
234:
235: // don't forget the padding
236: while ((curOffset % 4) > 0) {
237: curOffset++;
238: }
239: }
240:
241: buf.position(curOffset);
242: buf.flip();
243:
244: // adjust the checksum
245: updateChecksumAdj(buf);
246:
247: return buf.array();
248: }
249:
250: /**
251: * Calculate the checksum for a given table
252: *
253: * @param tagString the name of the data
254: * @param data the data in the table
255: */
256: private static int calculateChecksum(String tagString,
257: ByteBuffer data) {
258: int sum = 0;
259:
260: data.mark();
261:
262: // special adjustment for head table
263: if (tagString.equals("head")) {
264: data.putInt(8, 0);
265: }
266:
267: int nlongs = (data.remaining() + 3) / 4;
268:
269: while (nlongs-- > 0) {
270: if (data.remaining() > 3) {
271: sum += data.getInt();
272: } else {
273: byte b0 = (data.remaining() > 0) ? data.get() : 0;
274: byte b1 = (data.remaining() > 0) ? data.get() : 0;
275: byte b2 = (data.remaining() > 0) ? data.get() : 0;
276:
277: sum += ((0xff & b0) << 24) | ((0xff & b1) << 16)
278: | ((0xff & b2) << 8);
279: }
280: }
281:
282: data.reset();
283:
284: return sum;
285: }
286:
287: /**
288: * Get directory entries from a font
289: */
290: private static void parseDirectories(ByteBuffer data,
291: int numTables, TrueTypeFont ttf) {
292: for (int i = 0; i < numTables; i++) {
293: int tag = data.getInt();
294: String tagString = TrueTypeTable.tagToString(tag);
295: // System.out.println("TTFFont.parseDirectories: " + tagString);
296: int checksum = data.getInt();
297: int offset = data.getInt();
298: int length = data.getInt();
299:
300: // read the data
301: data.mark();
302: data.position(offset);
303:
304: ByteBuffer tableData = data.slice();
305: tableData.limit(length);
306:
307: // String tagString = TrueTypeTable.tagToString(tag);
308:
309: int calcChecksum = calculateChecksum(tagString, tableData);
310:
311: if (calcChecksum == checksum) {
312: ttf.addTable(tagString, tableData);
313: } else {
314: /* System.out.println("Mismatched checksums on table " +
315: tagString + ": " + calcChecksum + " != " +
316: checksum);*/
317:
318: ttf.addTable(tagString, tableData);
319:
320: }
321: data.reset();
322: }
323: }
324:
325: /**
326: * Get the length of the font
327: *
328: * @return the length of the entire font, in bytes
329: */
330: private int getLength() {
331: // the size of all the table directory entries
332: int length = 12 + (getNumTables() * 16);
333:
334: // for each directory entry, get the size,
335: // and don't forget the padding!
336: for (Iterator i = tables.values().iterator(); i.hasNext();) {
337: Object tableObj = i.next();
338:
339: // add the length of the entry
340: if (tableObj instanceof TrueTypeTable) {
341: length += ((TrueTypeTable) tableObj).getLength();
342: } else {
343: length += ((ByteBuffer) tableObj).remaining();
344: }
345:
346: // pad
347: if ((length % 4) != 0) {
348: length += (4 - (length % 4));
349: }
350: }
351:
352: return length;
353: }
354:
355: /**
356: * Update the checksumAdj field in the head table
357: */
358: private void updateChecksumAdj(ByteBuffer fontData) {
359: int checksum = calculateChecksum("", fontData);
360: int checksumAdj = 0xb1b0afba - checksum;
361:
362: // find the head table
363: int offset = 12 + (getNumTables() * 16);
364:
365: // find the head table
366: for (Iterator i = tables.keySet().iterator(); i.hasNext();) {
367: String tagString = (String) i.next();
368:
369: // adjust the checksum
370: if (tagString.equals("head")) {
371: fontData.putInt(offset + 8, checksumAdj);
372: return;
373: }
374:
375: // add the length of the entry
376: Object tableObj = tables.get(tagString);
377: if (tableObj instanceof TrueTypeTable) {
378: offset += ((TrueTypeTable) tableObj).getLength();
379: } else {
380: offset += ((ByteBuffer) tableObj).remaining();
381: }
382:
383: // pad
384: if ((offset % 4) != 0) {
385: offset += (4 - (offset % 4));
386: }
387: }
388: }
389:
390: /**
391: * Write the font to a pretty string
392: */
393: public String toString() {
394: StringBuffer buf = new StringBuffer();
395:
396: System.out.println("Type : " + getType());
397: System.out.println("NumTables : " + getNumTables());
398: System.out.println("SearchRange : " + getSearchRange());
399: System.out.println("EntrySelector: " + getEntrySelector());
400: System.out.println("RangeShift : " + getRangeShift());
401:
402: for (Iterator i = tables.entrySet().iterator(); i.hasNext();) {
403: Map.Entry e = (Map.Entry) i.next();
404:
405: TrueTypeTable table = null;
406: if (e.getValue() instanceof ByteBuffer) {
407: table = getTable((String) e.getKey());
408: } else {
409: table = (TrueTypeTable) e.getValue();
410: }
411:
412: System.out.println(table);
413: }
414:
415: return buf.toString();
416: }
417:
418: /**
419: * @param args the command line arguments
420: */
421: public static void main(String[] args) {
422: if (args.length != 1) {
423: System.out.println("Usage: ");
424: System.out.println(" TrueTypeParser <filename>");
425: System.exit(-1);
426: }
427:
428: try {
429: RandomAccessFile raf = new RandomAccessFile(args[0], "r");
430:
431: int size = (int) raf.length();
432: byte[] data = new byte[size];
433:
434: raf.readFully(data);
435:
436: TrueTypeFont ttp = TrueTypeFont.parseFont(data);
437:
438: System.out.println(ttp);
439:
440: InputStream fontStream = new ByteArrayInputStream(ttp
441: .writeFont());
442:
443: Font f = Font.createFont(Font.TRUETYPE_FONT, fontStream);
444: } catch (Exception e) {
445: e.printStackTrace();
446: }
447: }
448: }
|