KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > lowagie > text > pdf > TrueTypeFontSubSet


1 /*
2  * $Id: TrueTypeFontSubSet.java 2366 2006-09-14 23:10:58Z xlv $
3  * $Name$
4  *
5  * Copyright 2001, 2002 Paulo Soares
6  *
7  * The contents of this file are subject to the Mozilla Public License Version 1.1
8  * (the "License"); you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
10  *
11  * Software distributed under the License is distributed on an "AS IS" basis,
12  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13  * for the specific language governing rights and limitations under the License.
14  *
15  * The Original Code is 'iText, a free JAVA-PDF library'.
16  *
17  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
18  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
19  * All Rights Reserved.
20  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
21  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
22  *
23  * Contributor(s): all the names of the contributors are added in the source code
24  * where applicable.
25  *
26  * Alternatively, the contents of this file may be used under the terms of the
27  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
28  * provisions of LGPL are applicable instead of those above. If you wish to
29  * allow use of your version of this file only under the terms of the LGPL
30  * License and not to allow others to use your version of this file under
31  * the MPL, indicate your decision by deleting the provisions above and
32  * replace them with the notice and other provisions required by the LGPL.
33  * If you do not delete the provisions above, a recipient may use your version
34  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
35  *
36  * This library is free software; you can redistribute it and/or modify it
37  * under the terms of the MPL as stated above or under the terms of the GNU
38  * Library General Public License as published by the Free Software Foundation;
39  * either version 2 of the License, or any later version.
40  *
41  * This library is distributed in the hope that it will be useful, but WITHOUT
42  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
43  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
44  * details.
45  *
46  * If you didn't download this code from the following link, you should check if
47  * you aren't using an obsolete version:
48  * http://www.lowagie.com/iText/
49  */

50
51 package com.lowagie.text.pdf;
52
53 import java.io.IOException JavaDoc;
54 import java.util.ArrayList JavaDoc;
55 import java.util.Arrays JavaDoc;
56 import java.util.HashMap JavaDoc;
57
58 import com.lowagie.text.DocumentException;
59 import com.lowagie.text.ExceptionConverter;
60
61 /** Subsets a True Type font by removing the unneeded glyphs from
62  * the font.
63  *
64  * @author Paulo Soares (psoares@consiste.pt)
65  */

66 class TrueTypeFontSubSet {
67     static final String JavaDoc tableNamesSimple[] = {"cvt ", "fpgm", "glyf", "head",
68         "hhea", "hmtx", "loca", "maxp", "prep"};
69     static final String JavaDoc tableNamesCmap[] = {"cmap", "cvt ", "fpgm", "glyf", "head",
70         "hhea", "hmtx", "loca", "maxp", "prep"};
71     static final String JavaDoc tableNamesExtra[] = {"OS/2", "cmap", "cvt ", "fpgm", "glyf", "head",
72         "hhea", "hmtx", "loca", "maxp", "name, prep"};
73     static final int entrySelectors[] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4};
74     static final int TABLE_CHECKSUM = 0;
75     static final int TABLE_OFFSET = 1;
76     static final int TABLE_LENGTH = 2;
77     static final int HEAD_LOCA_FORMAT_OFFSET = 51;
78
79     static final int ARG_1_AND_2_ARE_WORDS = 1;
80     static final int WE_HAVE_A_SCALE = 8;
81     static final int MORE_COMPONENTS = 32;
82     static final int WE_HAVE_AN_X_AND_Y_SCALE = 64;
83     static final int WE_HAVE_A_TWO_BY_TWO = 128;
84     
85     
86     /** Contains the location of the several tables. The key is the name of
87      * the table and the value is an <CODE>int[3]</CODE> where position 0
88      * is the checksum, position 1 is the offset from the start of the file
89      * and position 2 is the length of the table.
90      */

91     protected HashMap JavaDoc tableDirectory;
92     /** The file in use.
93      */

94     protected RandomAccessFileOrArray rf;
95     /** The file name.
96      */

97     protected String JavaDoc fileName;
98     protected boolean includeCmap;
99     protected boolean includeExtras;
100     protected boolean locaShortTable;
101     protected int locaTable[];
102     protected HashMap JavaDoc glyphsUsed;
103     protected ArrayList JavaDoc glyphsInList;
104     protected int tableGlyphOffset;
105     protected int newLocaTable[];
106     protected byte newLocaTableOut[];
107     protected byte newGlyfTable[];
108     protected int glyfTableRealSize;
109     protected int locaTableRealSize;
110     protected byte outFont[];
111     protected int fontPtr;
112     protected int directoryOffset;
113
114     /** Creates a new TrueTypeFontSubSet
115      * @param directoryOffset The offset from the start of the file to the table directory
116      * @param fileName the file name of the font
117      * @param glyphsUsed the glyphs used
118      * @param includeCmap <CODE>true</CODE> if the table cmap is to be included in the generated font
119      */

120     TrueTypeFontSubSet(String JavaDoc fileName, RandomAccessFileOrArray rf, HashMap JavaDoc glyphsUsed, int directoryOffset, boolean includeCmap, boolean includeExtras) {
121         this.fileName = fileName;
122         this.rf = rf;
123         this.glyphsUsed = glyphsUsed;
124         this.includeCmap = includeCmap;
125         this.includeExtras = includeExtras;
126         this.directoryOffset = directoryOffset;
127         glyphsInList = new ArrayList JavaDoc(glyphsUsed.keySet());
128     }
129     
130     /** Does the actual work of subsetting the font.
131      * @throws IOException on error
132      * @throws DocumentException on error
133      * @return the subset font
134      */

135     byte[] process() throws IOException JavaDoc, DocumentException {
136         try {
137             rf.reOpen();
138             createTableDirectory();
139             readLoca();
140             flatGlyphs();
141             createNewGlyphTables();
142             locaTobytes();
143             assembleFont();
144             return outFont;
145         }
146         finally {
147             try {
148                 rf.close();
149             }
150             catch (Exception JavaDoc e) {
151                 // empty on purpose
152
}
153         }
154     }
155     
156     protected void assembleFont() throws IOException JavaDoc {
157         int tableLocation[];
158         int fullFontSize = 0;
159         String JavaDoc tableNames[];
160         if (includeExtras)
161             tableNames = tableNamesExtra;
162         else {
163             if (includeCmap)
164                 tableNames = tableNamesCmap;
165             else
166                 tableNames = tableNamesSimple;
167         }
168         int tablesUsed = 2;
169         int len = 0;
170         for (int k = 0; k < tableNames.length; ++k) {
171             String JavaDoc name = tableNames[k];
172             if (name.equals("glyf") || name.equals("loca"))
173                 continue;
174             tableLocation = (int[])tableDirectory.get(name);
175             if (tableLocation == null)
176                 continue;
177             ++tablesUsed;
178             fullFontSize += (tableLocation[TABLE_LENGTH] + 3) & (~3);
179         }
180         fullFontSize += newLocaTableOut.length;
181         fullFontSize += newGlyfTable.length;
182         int ref = 16 * tablesUsed + 12;
183         fullFontSize += ref;
184         outFont = new byte[fullFontSize];
185         fontPtr = 0;
186         writeFontInt(0x00010000);
187         writeFontShort(tablesUsed);
188         int selector = entrySelectors[tablesUsed];
189         writeFontShort((1 << selector) * 16);
190         writeFontShort(selector);
191         writeFontShort((tablesUsed - (1 << selector)) * 16);
192         for (int k = 0; k < tableNames.length; ++k) {
193             String JavaDoc name = tableNames[k];
194             tableLocation = (int[])tableDirectory.get(name);
195             if (tableLocation == null)
196                 continue;
197             writeFontString(name);
198             if (name.equals("glyf")) {
199                 writeFontInt(calculateChecksum(newGlyfTable));
200                 len = glyfTableRealSize;
201             }
202             else if (name.equals("loca")) {
203                 writeFontInt(calculateChecksum(newLocaTableOut));
204                 len = locaTableRealSize;
205             }
206             else {
207                 writeFontInt(tableLocation[TABLE_CHECKSUM]);
208                 len = tableLocation[TABLE_LENGTH];
209             }
210             writeFontInt(ref);
211             writeFontInt(len);
212             ref += (len + 3) & (~3);
213         }
214         for (int k = 0; k < tableNames.length; ++k) {
215             String JavaDoc name = tableNames[k];
216             tableLocation = (int[])tableDirectory.get(name);
217             if (tableLocation == null)
218                 continue;
219             if (name.equals("glyf")) {
220                 System.arraycopy(newGlyfTable, 0, outFont, fontPtr, newGlyfTable.length);
221                 fontPtr += newGlyfTable.length;
222                 newGlyfTable = null;
223             }
224             else if (name.equals("loca")) {
225                 System.arraycopy(newLocaTableOut, 0, outFont, fontPtr, newLocaTableOut.length);
226                 fontPtr += newLocaTableOut.length;
227                 newLocaTableOut = null;
228             }
229             else {
230                 rf.seek(tableLocation[TABLE_OFFSET]);
231                 rf.readFully(outFont, fontPtr, tableLocation[TABLE_LENGTH]);
232                 fontPtr += (tableLocation[TABLE_LENGTH] + 3) & (~3);
233             }
234         }
235     }
236     
237     protected void createTableDirectory() throws IOException JavaDoc, DocumentException {
238         tableDirectory = new HashMap JavaDoc();
239         rf.seek(directoryOffset);
240         int id = rf.readInt();
241         if (id != 0x00010000)
242             throw new DocumentException(fileName + " is not a true type file.");
243         int num_tables = rf.readUnsignedShort();
244         rf.skipBytes(6);
245         for (int k = 0; k < num_tables; ++k) {
246             String JavaDoc tag = readStandardString(4);
247             int tableLocation[] = new int[3];
248             tableLocation[TABLE_CHECKSUM] = rf.readInt();
249             tableLocation[TABLE_OFFSET] = rf.readInt();
250             tableLocation[TABLE_LENGTH] = rf.readInt();
251             tableDirectory.put(tag, tableLocation);
252         }
253     }
254     
255     protected void readLoca() throws IOException JavaDoc, DocumentException {
256         int tableLocation[];
257         tableLocation = (int[])tableDirectory.get("head");
258         if (tableLocation == null)
259             throw new DocumentException("Table 'head' does not exist in " + fileName);
260         rf.seek(tableLocation[TABLE_OFFSET] + HEAD_LOCA_FORMAT_OFFSET);
261         locaShortTable = (rf.readUnsignedShort() == 0);
262         tableLocation = (int[])tableDirectory.get("loca");
263         if (tableLocation == null)
264             throw new DocumentException("Table 'loca' does not exist in " + fileName);
265         rf.seek(tableLocation[TABLE_OFFSET]);
266         if (locaShortTable) {
267             int entries = tableLocation[TABLE_LENGTH] / 2;
268             locaTable = new int[entries];
269             for (int k = 0; k < entries; ++k)
270                 locaTable[k] = rf.readUnsignedShort() * 2;
271         }
272         else {
273             int entries = tableLocation[TABLE_LENGTH] / 4;
274             locaTable = new int[entries];
275             for (int k = 0; k < entries; ++k)
276                 locaTable[k] = rf.readInt();
277         }
278     }
279     
280     protected void createNewGlyphTables() throws IOException JavaDoc {
281         newLocaTable = new int[locaTable.length];
282         int activeGlyphs[] = new int[glyphsInList.size()];
283         for (int k = 0; k < activeGlyphs.length; ++k)
284             activeGlyphs[k] = ((Integer JavaDoc)glyphsInList.get(k)).intValue();
285         Arrays.sort(activeGlyphs);
286         int glyfSize = 0;
287         for (int k = 0; k < activeGlyphs.length; ++k) {
288             int glyph = activeGlyphs[k];
289             glyfSize += locaTable[glyph + 1] - locaTable[glyph];
290         }
291         glyfTableRealSize = glyfSize;
292         glyfSize = (glyfSize + 3) & (~3);
293         newGlyfTable = new byte[glyfSize];
294         int glyfPtr = 0;
295         int listGlyf = 0;
296         for (int k = 0; k < newLocaTable.length; ++k) {
297             newLocaTable[k] = glyfPtr;
298             if (listGlyf < activeGlyphs.length && activeGlyphs[listGlyf] == k) {
299                 ++listGlyf;
300                 newLocaTable[k] = glyfPtr;
301                 int start = locaTable[k];
302                 int len = locaTable[k + 1] - start;
303                 if (len > 0) {
304                     rf.seek(tableGlyphOffset + start);
305                     rf.readFully(newGlyfTable, glyfPtr, len);
306                     glyfPtr += len;
307                 }
308             }
309         }
310     }
311     
312     protected void locaTobytes() {
313         if (locaShortTable)
314             locaTableRealSize = newLocaTable.length * 2;
315         else
316             locaTableRealSize = newLocaTable.length * 4;
317         newLocaTableOut = new byte[(locaTableRealSize + 3) & (~3)];
318         outFont = newLocaTableOut;
319         fontPtr = 0;
320         for (int k = 0; k < newLocaTable.length; ++k) {
321             if (locaShortTable)
322                 writeFontShort(newLocaTable[k] / 2);
323             else
324                 writeFontInt(newLocaTable[k]);
325         }
326         
327     }
328     
329     protected void flatGlyphs() throws IOException JavaDoc, DocumentException {
330         int tableLocation[];
331         tableLocation = (int[])tableDirectory.get("glyf");
332         if (tableLocation == null)
333             throw new DocumentException("Table 'glyf' does not exist in " + fileName);
334         Integer JavaDoc glyph0 = new Integer JavaDoc(0);
335         if (!glyphsUsed.containsKey(glyph0)) {
336             glyphsUsed.put(glyph0, null);
337             glyphsInList.add(glyph0);
338         }
339         tableGlyphOffset = tableLocation[TABLE_OFFSET];
340         for (int k = 0; k < glyphsInList.size(); ++k) {
341             int glyph = ((Integer JavaDoc)glyphsInList.get(k)).intValue();
342             checkGlyphComposite(glyph);
343         }
344     }
345
346     protected void checkGlyphComposite(int glyph) throws IOException JavaDoc {
347         int start = locaTable[glyph];
348         if (start == locaTable[glyph + 1]) // no contour
349
return;
350         rf.seek(tableGlyphOffset + start);
351         int numContours = rf.readShort();
352         if (numContours >= 0)
353             return;
354         rf.skipBytes(8);
355         for(;;) {
356             int flags = rf.readUnsignedShort();
357             Integer JavaDoc cGlyph = new Integer JavaDoc(rf.readUnsignedShort());
358             if (!glyphsUsed.containsKey(cGlyph)) {
359                 glyphsUsed.put(cGlyph, null);
360                 glyphsInList.add(cGlyph);
361             }
362             if ((flags & MORE_COMPONENTS) == 0)
363                 return;
364             int skip;
365             if ((flags & ARG_1_AND_2_ARE_WORDS) != 0)
366                 skip = 4;
367             else
368                 skip = 2;
369             if ((flags & WE_HAVE_A_SCALE) != 0)
370                 skip += 2;
371             else if ((flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0)
372                 skip += 4;
373             if ((flags & WE_HAVE_A_TWO_BY_TWO) != 0)
374                 skip += 8;
375             rf.skipBytes(skip);
376         }
377     }
378     
379     /** Reads a <CODE>String</CODE> from the font file as bytes using the Cp1252
380      * encoding.
381      * @param length the length of bytes to read
382      * @return the <CODE>String</CODE> read
383      * @throws IOException the font file could not be read
384      */

385     protected String JavaDoc readStandardString(int length) throws IOException JavaDoc {
386         byte buf[] = new byte[length];
387         rf.readFully(buf);
388         try {
389             return new String JavaDoc(buf, BaseFont.WINANSI);
390         }
391         catch (Exception JavaDoc e) {
392             throw new ExceptionConverter(e);
393         }
394     }
395     
396     protected void writeFontShort(int n) {
397         outFont[fontPtr++] = (byte)(n >> 8);
398         outFont[fontPtr++] = (byte)(n);
399     }
400
401     protected void writeFontInt(int n) {
402         outFont[fontPtr++] = (byte)(n >> 24);
403         outFont[fontPtr++] = (byte)(n >> 16);
404         outFont[fontPtr++] = (byte)(n >> 8);
405         outFont[fontPtr++] = (byte)(n);
406     }
407
408     protected void writeFontString(String JavaDoc s) {
409         byte b[] = PdfEncodings.convertToBytes(s, BaseFont.WINANSI);
410         System.arraycopy(b, 0, outFont, fontPtr, b.length);
411         fontPtr += b.length;
412     }
413     
414     protected int calculateChecksum(byte b[]) {
415         int len = b.length / 4;
416         int v0 = 0;
417         int v1 = 0;
418         int v2 = 0;
419         int v3 = 0;
420         int ptr = 0;
421         for (int k = 0; k < len; ++k) {
422             v3 += (int)b[ptr++] & 0xff;
423             v2 += (int)b[ptr++] & 0xff;
424             v1 += (int)b[ptr++] & 0xff;
425             v0 += (int)b[ptr++] & 0xff;
426         }
427         return v0 + (v1 << 8) + (v2 << 16) + (v3 << 24);
428     }
429 }
430
Popular Tags