001: /*
002: * $Id: NativeFont.java,v 1.2 2007/12/20 18:33:31 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;
023:
024: import java.awt.Font;
025: import java.awt.FontFormatException;
026: import java.awt.font.FontRenderContext;
027: import java.awt.font.GlyphVector;
028: import java.awt.font.OpenType;
029: import java.awt.geom.AffineTransform;
030: import java.awt.geom.GeneralPath;
031: import java.io.ByteArrayInputStream;
032: import java.io.IOException;
033: import java.nio.ByteBuffer;
034:
035: import com.sun.pdfview.PDFObject;
036: import com.sun.pdfview.PDFParseException;
037: import com.sun.pdfview.font.ttf.CMap;
038: import com.sun.pdfview.font.ttf.CMapFormat0;
039: import com.sun.pdfview.font.ttf.CMapFormat4;
040: import com.sun.pdfview.font.ttf.CmapTable;
041: import com.sun.pdfview.font.ttf.HeadTable;
042: import com.sun.pdfview.font.ttf.HmtxTable;
043: import com.sun.pdfview.font.ttf.NameTable;
044: import com.sun.pdfview.font.ttf.PostTable;
045: import com.sun.pdfview.font.ttf.TrueTypeFont;
046: import com.sun.pdfview.font.ttf.TrueTypeTable;
047:
048: /**
049: * a font object derived from a true type font.
050: *
051: * @author Mike Wessler
052: */
053: public class NativeFont extends OutlineFont {
054: /** Control characters to filter out of the underlying font */
055: protected static final char[] controlChars = { 0x9, 0xa, 0xd };
056:
057: /** the ids of our favorite CMaps */
058: protected static final short[] mapIDs = { 3, 1, /* Microsoft Unicode */
059: 0, 0, /* unicode default */
060: 0, 3, /* unicode 2.0 map */
061: 1, 0 /* macintosh */
062: };
063:
064: /** the actual font in use */
065: private Font f;
066:
067: /** the font render context */
068: private FontRenderContext basecontext = new FontRenderContext(
069: new AffineTransform(), true, true);
070:
071: /** the cmap table from a TrueType font */
072: private CmapTable cmapTable;
073:
074: /** the post table from a TrueType font */
075: private PostTable postTable;
076:
077: /** the number of font units in one em */
078: private int unitsPerEm;
079:
080: /** the hmtx table from the TrueType font */
081: private HmtxTable hmtxTable;
082:
083: /**
084: * create a new NativeFont object based on a description of the
085: * font from the PDF file. If the description happens to contain
086: * an in-line true-type font file (under key "FontFile2"), use the
087: * true type font. Otherwise, parse the description for key information
088: * and use that to generate an appropriate font.
089: */
090: public NativeFont(String baseFont, PDFObject fontObj,
091: PDFFontDescriptor descriptor) throws IOException {
092: super (baseFont, fontObj, descriptor);
093:
094: String fontName = descriptor.getFontName();
095:
096: PDFObject ttf = descriptor.getFontFile2();
097: if (ttf != null) {
098: byte[] fontdata = ttf.getStream();
099:
100: try {
101: setFont(fontdata);
102: } catch (FontFormatException ffe) {
103: throw new PDFParseException("Font format exception: "
104: + ffe);
105: }
106: } else {
107: int flags = descriptor.getFlags();
108: int style = ((flags & (1 << 18)) != 0) ? Font.BOLD
109: : Font.PLAIN;
110:
111: if (fontName.indexOf("Bold") > 0) {
112: style |= Font.BOLD;
113: }
114: if (descriptor.getItalicAngle() != 0) {
115: style |= Font.ITALIC;
116: }
117: if ((flags & 1) != 0) { // fixed width
118: setFont(new Font("Monospaced", style, 1));
119: } else if ((flags & 1 << 1) != 0) { // serif font
120: setFont(new Font("Serif", style, 1));
121: } else {
122: setFont(new Font("Sans-serif", style, 1));
123: }
124: }
125: }
126:
127: /**
128: * Get a glyph outline by name
129: *
130: * @param name the name of the desired glyph
131: * @return the glyph outline, or null if unavailable
132: */
133: protected GeneralPath getOutline(String name, float width) {
134: if (postTable != null && cmapTable != null) {
135: // map this character name to a glyph ID
136: short glyphID = postTable.getGlyphNameIndex(name);
137:
138: if (glyphID == 0) {
139: // no glyph -- try by index
140: return null;
141: }
142:
143: // the mapped character
144: char mappedChar = 0;
145:
146: for (int i = 0; i < mapIDs.length; i += 2) {
147: CMap map = cmapTable.getCMap(mapIDs[i], mapIDs[i + 1]);
148: if (map != null) {
149: mappedChar = map.reverseMap(glyphID);
150:
151: // we found a character
152: if (mappedChar != 0) {
153: break;
154: }
155: }
156: }
157:
158: return getOutline(mappedChar, width);
159: }
160:
161: // no maps found, hope the font can deal
162: return null;
163: }
164:
165: /**
166: * Get a glyph outline by character code
167: *
168: * Note this method must always return an outline
169: *
170: * @param src the character code of the desired glyph
171: * @return the glyph outline
172: */
173: protected GeneralPath getOutline(char src, float width) {
174: // some true type fonts put characters in the undefined
175: // region of Unicode instead of as normal characters.
176: if (!f.canDisplay(src) && f.canDisplay((char) (src + 0xf000))) {
177: src += 0xf000;
178: }
179:
180: // filter out control characters
181: for (int i = 0; i < controlChars.length; i++) {
182: if (controlChars[i] == src) {
183: src = (char) (0xf000 | src);
184: break;
185: }
186: }
187:
188: char[] glyph = new char[1];
189: glyph[0] = src;
190:
191: GlyphVector gv = f.createGlyphVector(basecontext, glyph);
192: GeneralPath gp = new GeneralPath(gv.getGlyphOutline(0));
193:
194: // this should be gv.getGlyphMetrics(0).getAdvance(), but that is
195: // broken on the Mac, so we need to read the advance from the
196: // hmtx table in the font
197: CMap map = cmapTable.getCMap(mapIDs[0], mapIDs[1]);
198: int glyphID = map.map(src);
199: float advance = (float) hmtxTable.getAdvance(glyphID)
200: / (float) unitsPerEm;
201:
202: float widthfactor = width / advance;
203: gp.transform(AffineTransform.getScaleInstance(widthfactor, -1));
204:
205: return gp;
206: }
207:
208: /**
209: * Set the font
210: *
211: * @param f the font to use
212: */
213: protected void setFont(Font f) {
214: this .f = f;
215:
216: // if it's an OpenType font, parse the relevant tables to get
217: // glyph name to code mappings
218: if (f instanceof OpenType) {
219: OpenType ot = (OpenType) f;
220:
221: byte[] cmapData = ot.getFontTable(OpenType.TAG_CMAP);
222: byte[] postData = ot.getFontTable(OpenType.TAG_POST);
223:
224: TrueTypeFont ttf = new TrueTypeFont(0x10000);
225:
226: cmapTable = (CmapTable) TrueTypeTable.createTable(ttf,
227: "cmap", ByteBuffer.wrap(cmapData));
228: ttf.addTable("cmap", cmapTable);
229:
230: postTable = (PostTable) TrueTypeTable.createTable(ttf,
231: "post", ByteBuffer.wrap(postData));
232: ttf.addTable("post", postTable);
233: }
234: }
235:
236: /**
237: * Set the font
238: *
239: * @param fontdata the font data as a byte array
240: */
241: protected void setFont(byte[] fontdata) throws FontFormatException,
242: IOException {
243:
244: // System.out.println("Loading " + getBaseFont());
245: // FileOutputStream fos = new FileOutputStream("/tmp/" + getBaseFont() + ".ttf");
246: // fos.write(fontdata);
247: // fos.close();
248:
249: try {
250: // read the true type information
251: TrueTypeFont ttf = TrueTypeFont.parseFont(fontdata);
252:
253: // System.out.println(ttf.toString());
254:
255: // get the cmap, post, and hmtx tables for later use
256: cmapTable = (CmapTable) ttf.getTable("cmap");
257: postTable = (PostTable) ttf.getTable("post");
258: hmtxTable = (HmtxTable) ttf.getTable("hmtx");
259:
260: // read the units per em from the head table
261: HeadTable headTable = (HeadTable) ttf.getTable("head");
262: unitsPerEm = headTable.getUnitsPerEm();
263:
264: /* Find out if we have the right info in our name table.
265: * This is a hack because Java can only deal with fonts that
266: * have a Microsoft encoded name in their name table (PlatformID 3).
267: * We'll 'adjust' the font to add it if not, and take our chances
268: * with our parsing, since it wasn't going to work anyway.
269: */
270: NameTable nameTable = null;
271:
272: try {
273: nameTable = (NameTable) ttf.getTable("name");
274: } catch (Exception ex) {
275: System.out.println("Error reading name table for font "
276: + getBaseFont() + ". Repairing!");
277: }
278:
279: boolean nameFixed = fixNameTable(ttf, nameTable);
280:
281: /* Figure out if we need to hack the CMap table. This might
282: * be the case if we use characters that Java considers control
283: * characters (0x9, 0xa and 0xd), that have to be re-mapped
284: */
285: boolean cmapFixed = fixCMapTable(ttf, cmapTable);
286:
287: // use the parsed font instead of the original
288: if (nameFixed || cmapFixed) {
289: // System.out.println("Using fixed font!");
290: // System.out.println(ttf.toString());
291: fontdata = ttf.writeFont();
292:
293: // FileOutputStream fos2 = new FileOutputStream("/tmp/" + getBaseFont() + ".fix");
294: // fos2.write(fontdata);
295: // fos2.close();
296: }
297: } catch (Exception ex) {
298: System.out.println("Error parsing font : " + getBaseFont());
299: ex.printStackTrace();
300: }
301:
302: ByteArrayInputStream bais = new ByteArrayInputStream(fontdata);
303: f = Font.createFont(Font.TRUETYPE_FONT, bais);
304: bais.close();
305: }
306:
307: /**
308: * Fix a broken font name table for a TrueType font. Some fonts do not
309: * have Microsoft-specific name information, but Java won't work without
310: * it (grrr.). This method takes a font and adds the Microsoft data into
311: * it.
312: *
313: * @param ttf the font
314: * @param name the font's name table
315: * @return true if the table was fixed, or false if it was left as is
316: */
317: private boolean fixNameTable(TrueTypeFont ttf, NameTable name) {
318: // if we didn't find the table, or there was an exception,
319: // just create a new one
320: if (name == null) {
321: name = (NameTable) TrueTypeTable.createTable(ttf, "name");
322: ttf.addTable("name", name);
323: }
324:
325: // first, figure out some info about the font
326: String fName = this .getBaseFont();
327: String style = "Regular";
328:
329: if (fName.indexOf("Italic") > -1
330: || fName.indexOf("italic") > -1) {
331: style = "Italic";
332: } else if (fName.indexOf("Bold") > -1
333: || fName.indexOf("bold") > -1) {
334: style = "Bold";
335: }
336:
337: if (fName.indexOf('-') > -1) {
338: fName = fName.substring(0, fName.indexOf('-'));
339: }
340:
341: short platID = NameTable.PLATFORMID_MICROSOFT;
342: short encID = 1;
343: short langID = 1033;
344:
345: short[] nameIDs = { NameTable.NAMEID_COPYRIGHT,
346: NameTable.NAMEID_FAMILY, NameTable.NAMEID_SUBFAMILY,
347: NameTable.NAMEID_SUBFAMILY_UNIQUE,
348: NameTable.NAMEID_FULL_NAME, NameTable.NAMEID_VERSION,
349: NameTable.NAMEID_POSTSCRIPT_NAME,
350: NameTable.NAMEID_TRADEMARK };
351:
352: String[] defaultValues = { "No copyright", fName, style,
353: fName + " " + style, fName + " " + style, "1.0 (Fake)",
354: fName, "No Trademark" };
355:
356: boolean changed = false;
357:
358: for (int i = 0; i < nameIDs.length; i++) {
359: if (name.getRecord(platID, encID, langID, nameIDs[i]) == null) {
360: name.addRecord(platID, encID, langID, nameIDs[i],
361: defaultValues[i]);
362: changed = true;
363: }
364: }
365:
366: return changed;
367: }
368:
369: /**
370: * Fix the CMap table. This can be necessary if characters are mapped to
371: * control characters (0x9, 0xa, 0xd) Java will not render them, even
372: * though they are valid.
373: *
374: * Also, Java tends to not like it when there is only a Format 0 CMap,
375: * which happens frequently when included Format 4 CMaps are broken.
376: * Since PDF prefers the Format 0 map, while Java prefers the Format 4 map,
377: * it is generally necessary to re-write the Format 0 map as a Format 4 map
378: * to make most PDFs work.
379: *
380: * @param ttf the font
381: * @param cmap the CMap table
382: * @return true if the font was changed, or false if it was left as-is
383: */
384: private boolean fixCMapTable(TrueTypeFont ttf, CmapTable cmap) {
385: CMapFormat4 fourMap = null;
386: CMapFormat0 zeroMap = null;
387:
388: for (int i = 0; i < mapIDs.length; i += 2) {
389: CMap map = cmapTable.getCMap(mapIDs[i], mapIDs[i + 1]);
390: if (map != null) {
391: if (fourMap == null && map instanceof CMapFormat4) {
392: fourMap = (CMapFormat4) map;
393: } else if (zeroMap == null
394: && map instanceof CMapFormat0) {
395: zeroMap = (CMapFormat0) map;
396: }
397: }
398: }
399:
400: // if there were no maps, we could have problems. Just try creating
401: // an identity map
402: if (zeroMap == null && fourMap == null) {
403: fourMap = (CMapFormat4) CMap
404: .createMap((short) 4, (short) 0);
405: fourMap.addSegment((short) getFirstChar(),
406: (short) getLastChar(), (short) 0);
407: }
408:
409: // create our map based on the type 0 map, since PDF seems
410: // to prefer a type 0 map (Java prefers a unicode map)
411: if (zeroMap != null) {
412: fourMap = (CMapFormat4) CMap
413: .createMap((short) 4, (short) 0);
414:
415: // add the mappings from 0 to null and 1 to notdef
416: fourMap.addSegment((short) 0, (short) 1, (short) 0);
417:
418: for (int i = getFirstChar(); i <= getLastChar(); i++) {
419: short value = (short) (zeroMap.map((byte) i) & 0xff);
420: if (value != 0) {
421: fourMap.addSegment((short) i, (short) i,
422: (short) (value - i));
423: }
424: }
425: }
426:
427: // now that we have a type four map, remap control characters
428: for (int i = 0; i < controlChars.length; i++) {
429: short idx = (short) (0xf000 | controlChars[i]);
430: short value = (short) fourMap.map(controlChars[i]);
431:
432: fourMap.addSegment(idx, idx, (short) (value - idx));
433: }
434:
435: // create a whole new table with just our map
436: cmap = (CmapTable) TrueTypeTable.createTable(ttf, "cmap");
437: cmap.addCMap((short) 3, (short) 1, fourMap);
438:
439: // replace the table in the font
440: ttf.addTable("cmap", cmap);
441:
442: // change the stored table
443: cmapTable = cmap;
444:
445: return true;
446: }
447: }
|