001: /*
002: * $Id: TrueTypeFontSubSet.java 2366 2006-09-14 23:10:58Z xlv $
003: * $Name$
004: *
005: * Copyright 2001, 2002 Paulo Soares
006: *
007: * The contents of this file are subject to the Mozilla Public License Version 1.1
008: * (the "License"); you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the License.
014: *
015: * The Original Code is 'iText, a free JAVA-PDF library'.
016: *
017: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
018: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
019: * All Rights Reserved.
020: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
021: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
022: *
023: * Contributor(s): all the names of the contributors are added in the source code
024: * where applicable.
025: *
026: * Alternatively, the contents of this file may be used under the terms of the
027: * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
028: * provisions of LGPL are applicable instead of those above. If you wish to
029: * allow use of your version of this file only under the terms of the LGPL
030: * License and not to allow others to use your version of this file under
031: * the MPL, indicate your decision by deleting the provisions above and
032: * replace them with the notice and other provisions required by the LGPL.
033: * If you do not delete the provisions above, a recipient may use your version
034: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
035: *
036: * This library is free software; you can redistribute it and/or modify it
037: * under the terms of the MPL as stated above or under the terms of the GNU
038: * Library General Public License as published by the Free Software Foundation;
039: * either version 2 of the License, or any later version.
040: *
041: * This library is distributed in the hope that it will be useful, but WITHOUT
042: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
043: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
044: * details.
045: *
046: * If you didn't download this code from the following link, you should check if
047: * you aren't using an obsolete version:
048: * http://www.lowagie.com/iText/
049: */
050:
051: package com.lowagie.text.pdf;
052:
053: import java.io.IOException;
054: import java.util.ArrayList;
055: import java.util.Arrays;
056: import java.util.HashMap;
057:
058: import com.lowagie.text.DocumentException;
059: import com.lowagie.text.ExceptionConverter;
060:
061: /** Subsets a True Type font by removing the unneeded glyphs from
062: * the font.
063: *
064: * @author Paulo Soares (psoares@consiste.pt)
065: */
066: class TrueTypeFontSubSet {
067: static final String tableNamesSimple[] = { "cvt ", "fpgm", "glyf",
068: "head", "hhea", "hmtx", "loca", "maxp", "prep" };
069: static final String tableNamesCmap[] = { "cmap", "cvt ", "fpgm",
070: "glyf", "head", "hhea", "hmtx", "loca", "maxp", "prep" };
071: static final String tableNamesExtra[] = { "OS/2", "cmap", "cvt ",
072: "fpgm", "glyf", "head", "hhea", "hmtx", "loca", "maxp",
073: "name, prep" };
074: static final int entrySelectors[] = { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3,
075: 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4 };
076: static final int TABLE_CHECKSUM = 0;
077: static final int TABLE_OFFSET = 1;
078: static final int TABLE_LENGTH = 2;
079: static final int HEAD_LOCA_FORMAT_OFFSET = 51;
080:
081: static final int ARG_1_AND_2_ARE_WORDS = 1;
082: static final int WE_HAVE_A_SCALE = 8;
083: static final int MORE_COMPONENTS = 32;
084: static final int WE_HAVE_AN_X_AND_Y_SCALE = 64;
085: static final int WE_HAVE_A_TWO_BY_TWO = 128;
086:
087: /** Contains the location of the several tables. The key is the name of
088: * the table and the value is an <CODE>int[3]</CODE> where position 0
089: * is the checksum, position 1 is the offset from the start of the file
090: * and position 2 is the length of the table.
091: */
092: protected HashMap tableDirectory;
093: /** The file in use.
094: */
095: protected RandomAccessFileOrArray rf;
096: /** The file name.
097: */
098: protected String fileName;
099: protected boolean includeCmap;
100: protected boolean includeExtras;
101: protected boolean locaShortTable;
102: protected int locaTable[];
103: protected HashMap glyphsUsed;
104: protected ArrayList glyphsInList;
105: protected int tableGlyphOffset;
106: protected int newLocaTable[];
107: protected byte newLocaTableOut[];
108: protected byte newGlyfTable[];
109: protected int glyfTableRealSize;
110: protected int locaTableRealSize;
111: protected byte outFont[];
112: protected int fontPtr;
113: protected int directoryOffset;
114:
115: /** Creates a new TrueTypeFontSubSet
116: * @param directoryOffset The offset from the start of the file to the table directory
117: * @param fileName the file name of the font
118: * @param glyphsUsed the glyphs used
119: * @param includeCmap <CODE>true</CODE> if the table cmap is to be included in the generated font
120: */
121: TrueTypeFontSubSet(String fileName, RandomAccessFileOrArray rf,
122: HashMap glyphsUsed, int directoryOffset,
123: boolean includeCmap, boolean includeExtras) {
124: this .fileName = fileName;
125: this .rf = rf;
126: this .glyphsUsed = glyphsUsed;
127: this .includeCmap = includeCmap;
128: this .includeExtras = includeExtras;
129: this .directoryOffset = directoryOffset;
130: glyphsInList = new ArrayList(glyphsUsed.keySet());
131: }
132:
133: /** Does the actual work of subsetting the font.
134: * @throws IOException on error
135: * @throws DocumentException on error
136: * @return the subset font
137: */
138: byte[] process() throws IOException, DocumentException {
139: try {
140: rf.reOpen();
141: createTableDirectory();
142: readLoca();
143: flatGlyphs();
144: createNewGlyphTables();
145: locaTobytes();
146: assembleFont();
147: return outFont;
148: } finally {
149: try {
150: rf.close();
151: } catch (Exception e) {
152: // empty on purpose
153: }
154: }
155: }
156:
157: protected void assembleFont() throws IOException {
158: int tableLocation[];
159: int fullFontSize = 0;
160: String tableNames[];
161: if (includeExtras)
162: tableNames = tableNamesExtra;
163: else {
164: if (includeCmap)
165: tableNames = tableNamesCmap;
166: else
167: tableNames = tableNamesSimple;
168: }
169: int tablesUsed = 2;
170: int len = 0;
171: for (int k = 0; k < tableNames.length; ++k) {
172: String name = tableNames[k];
173: if (name.equals("glyf") || name.equals("loca"))
174: continue;
175: tableLocation = (int[]) tableDirectory.get(name);
176: if (tableLocation == null)
177: continue;
178: ++tablesUsed;
179: fullFontSize += (tableLocation[TABLE_LENGTH] + 3) & (~3);
180: }
181: fullFontSize += newLocaTableOut.length;
182: fullFontSize += newGlyfTable.length;
183: int ref = 16 * tablesUsed + 12;
184: fullFontSize += ref;
185: outFont = new byte[fullFontSize];
186: fontPtr = 0;
187: writeFontInt(0x00010000);
188: writeFontShort(tablesUsed);
189: int selector = entrySelectors[tablesUsed];
190: writeFontShort((1 << selector) * 16);
191: writeFontShort(selector);
192: writeFontShort((tablesUsed - (1 << selector)) * 16);
193: for (int k = 0; k < tableNames.length; ++k) {
194: String name = tableNames[k];
195: tableLocation = (int[]) tableDirectory.get(name);
196: if (tableLocation == null)
197: continue;
198: writeFontString(name);
199: if (name.equals("glyf")) {
200: writeFontInt(calculateChecksum(newGlyfTable));
201: len = glyfTableRealSize;
202: } else if (name.equals("loca")) {
203: writeFontInt(calculateChecksum(newLocaTableOut));
204: len = locaTableRealSize;
205: } else {
206: writeFontInt(tableLocation[TABLE_CHECKSUM]);
207: len = tableLocation[TABLE_LENGTH];
208: }
209: writeFontInt(ref);
210: writeFontInt(len);
211: ref += (len + 3) & (~3);
212: }
213: for (int k = 0; k < tableNames.length; ++k) {
214: String name = tableNames[k];
215: tableLocation = (int[]) tableDirectory.get(name);
216: if (tableLocation == null)
217: continue;
218: if (name.equals("glyf")) {
219: System.arraycopy(newGlyfTable, 0, outFont, fontPtr,
220: newGlyfTable.length);
221: fontPtr += newGlyfTable.length;
222: newGlyfTable = null;
223: } else if (name.equals("loca")) {
224: System.arraycopy(newLocaTableOut, 0, outFont, fontPtr,
225: newLocaTableOut.length);
226: fontPtr += newLocaTableOut.length;
227: newLocaTableOut = null;
228: } else {
229: rf.seek(tableLocation[TABLE_OFFSET]);
230: rf.readFully(outFont, fontPtr,
231: tableLocation[TABLE_LENGTH]);
232: fontPtr += (tableLocation[TABLE_LENGTH] + 3) & (~3);
233: }
234: }
235: }
236:
237: protected void createTableDirectory() throws IOException,
238: DocumentException {
239: tableDirectory = new HashMap();
240: rf.seek(directoryOffset);
241: int id = rf.readInt();
242: if (id != 0x00010000)
243: throw new DocumentException(fileName
244: + " is not a true type file.");
245: int num_tables = rf.readUnsignedShort();
246: rf.skipBytes(6);
247: for (int k = 0; k < num_tables; ++k) {
248: String tag = readStandardString(4);
249: int tableLocation[] = new int[3];
250: tableLocation[TABLE_CHECKSUM] = rf.readInt();
251: tableLocation[TABLE_OFFSET] = rf.readInt();
252: tableLocation[TABLE_LENGTH] = rf.readInt();
253: tableDirectory.put(tag, tableLocation);
254: }
255: }
256:
257: protected void readLoca() throws IOException, DocumentException {
258: int tableLocation[];
259: tableLocation = (int[]) tableDirectory.get("head");
260: if (tableLocation == null)
261: throw new DocumentException(
262: "Table 'head' does not exist in " + fileName);
263: rf.seek(tableLocation[TABLE_OFFSET] + HEAD_LOCA_FORMAT_OFFSET);
264: locaShortTable = (rf.readUnsignedShort() == 0);
265: tableLocation = (int[]) tableDirectory.get("loca");
266: if (tableLocation == null)
267: throw new DocumentException(
268: "Table 'loca' does not exist in " + fileName);
269: rf.seek(tableLocation[TABLE_OFFSET]);
270: if (locaShortTable) {
271: int entries = tableLocation[TABLE_LENGTH] / 2;
272: locaTable = new int[entries];
273: for (int k = 0; k < entries; ++k)
274: locaTable[k] = rf.readUnsignedShort() * 2;
275: } else {
276: int entries = tableLocation[TABLE_LENGTH] / 4;
277: locaTable = new int[entries];
278: for (int k = 0; k < entries; ++k)
279: locaTable[k] = rf.readInt();
280: }
281: }
282:
283: protected void createNewGlyphTables() throws IOException {
284: newLocaTable = new int[locaTable.length];
285: int activeGlyphs[] = new int[glyphsInList.size()];
286: for (int k = 0; k < activeGlyphs.length; ++k)
287: activeGlyphs[k] = ((Integer) glyphsInList.get(k))
288: .intValue();
289: Arrays.sort(activeGlyphs);
290: int glyfSize = 0;
291: for (int k = 0; k < activeGlyphs.length; ++k) {
292: int glyph = activeGlyphs[k];
293: glyfSize += locaTable[glyph + 1] - locaTable[glyph];
294: }
295: glyfTableRealSize = glyfSize;
296: glyfSize = (glyfSize + 3) & (~3);
297: newGlyfTable = new byte[glyfSize];
298: int glyfPtr = 0;
299: int listGlyf = 0;
300: for (int k = 0; k < newLocaTable.length; ++k) {
301: newLocaTable[k] = glyfPtr;
302: if (listGlyf < activeGlyphs.length
303: && activeGlyphs[listGlyf] == k) {
304: ++listGlyf;
305: newLocaTable[k] = glyfPtr;
306: int start = locaTable[k];
307: int len = locaTable[k + 1] - start;
308: if (len > 0) {
309: rf.seek(tableGlyphOffset + start);
310: rf.readFully(newGlyfTable, glyfPtr, len);
311: glyfPtr += len;
312: }
313: }
314: }
315: }
316:
317: protected void locaTobytes() {
318: if (locaShortTable)
319: locaTableRealSize = newLocaTable.length * 2;
320: else
321: locaTableRealSize = newLocaTable.length * 4;
322: newLocaTableOut = new byte[(locaTableRealSize + 3) & (~3)];
323: outFont = newLocaTableOut;
324: fontPtr = 0;
325: for (int k = 0; k < newLocaTable.length; ++k) {
326: if (locaShortTable)
327: writeFontShort(newLocaTable[k] / 2);
328: else
329: writeFontInt(newLocaTable[k]);
330: }
331:
332: }
333:
334: protected void flatGlyphs() throws IOException, DocumentException {
335: int tableLocation[];
336: tableLocation = (int[]) tableDirectory.get("glyf");
337: if (tableLocation == null)
338: throw new DocumentException(
339: "Table 'glyf' does not exist in " + fileName);
340: Integer glyph0 = new Integer(0);
341: if (!glyphsUsed.containsKey(glyph0)) {
342: glyphsUsed.put(glyph0, null);
343: glyphsInList.add(glyph0);
344: }
345: tableGlyphOffset = tableLocation[TABLE_OFFSET];
346: for (int k = 0; k < glyphsInList.size(); ++k) {
347: int glyph = ((Integer) glyphsInList.get(k)).intValue();
348: checkGlyphComposite(glyph);
349: }
350: }
351:
352: protected void checkGlyphComposite(int glyph) throws IOException {
353: int start = locaTable[glyph];
354: if (start == locaTable[glyph + 1]) // no contour
355: return;
356: rf.seek(tableGlyphOffset + start);
357: int numContours = rf.readShort();
358: if (numContours >= 0)
359: return;
360: rf.skipBytes(8);
361: for (;;) {
362: int flags = rf.readUnsignedShort();
363: Integer cGlyph = new Integer(rf.readUnsignedShort());
364: if (!glyphsUsed.containsKey(cGlyph)) {
365: glyphsUsed.put(cGlyph, null);
366: glyphsInList.add(cGlyph);
367: }
368: if ((flags & MORE_COMPONENTS) == 0)
369: return;
370: int skip;
371: if ((flags & ARG_1_AND_2_ARE_WORDS) != 0)
372: skip = 4;
373: else
374: skip = 2;
375: if ((flags & WE_HAVE_A_SCALE) != 0)
376: skip += 2;
377: else if ((flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0)
378: skip += 4;
379: if ((flags & WE_HAVE_A_TWO_BY_TWO) != 0)
380: skip += 8;
381: rf.skipBytes(skip);
382: }
383: }
384:
385: /** Reads a <CODE>String</CODE> from the font file as bytes using the Cp1252
386: * encoding.
387: * @param length the length of bytes to read
388: * @return the <CODE>String</CODE> read
389: * @throws IOException the font file could not be read
390: */
391: protected String readStandardString(int length) throws IOException {
392: byte buf[] = new byte[length];
393: rf.readFully(buf);
394: try {
395: return new String(buf, BaseFont.WINANSI);
396: } catch (Exception e) {
397: throw new ExceptionConverter(e);
398: }
399: }
400:
401: protected void writeFontShort(int n) {
402: outFont[fontPtr++] = (byte) (n >> 8);
403: outFont[fontPtr++] = (byte) (n);
404: }
405:
406: protected void writeFontInt(int n) {
407: outFont[fontPtr++] = (byte) (n >> 24);
408: outFont[fontPtr++] = (byte) (n >> 16);
409: outFont[fontPtr++] = (byte) (n >> 8);
410: outFont[fontPtr++] = (byte) (n);
411: }
412:
413: protected void writeFontString(String s) {
414: byte b[] = PdfEncodings.convertToBytes(s, BaseFont.WINANSI);
415: System.arraycopy(b, 0, outFont, fontPtr, b.length);
416: fontPtr += b.length;
417: }
418:
419: protected int calculateChecksum(byte b[]) {
420: int len = b.length / 4;
421: int v0 = 0;
422: int v1 = 0;
423: int v2 = 0;
424: int v3 = 0;
425: int ptr = 0;
426: for (int k = 0; k < len; ++k) {
427: v3 += (int) b[ptr++] & 0xff;
428: v2 += (int) b[ptr++] & 0xff;
429: v1 += (int) b[ptr++] & 0xff;
430: v0 += (int) b[ptr++] & 0xff;
431: }
432: return v0 + (v1 << 8) + (v2 << 16) + (v3 << 24);
433: }
434: }
|