/** * create a new TrueTypeFont object based on a description of the font from the PDF file. If the * description happens to contain an in-line true-type font file (under key "FontFile2"), use the * true type font. Otherwise, parse the description for key information and use that to generate * an appropriate font. */ public TTFFont(String baseFont, PDFObject fontObj, PDFFontDescriptor descriptor) throws IOException { super(baseFont, fontObj, descriptor); String fontName = descriptor.getFontName(); PDFObject ttfObj = descriptor.getFontFile2(); // try { // byte[] fontData = ttfObj.getStream(); // java.io.FileOutputStream fis = new java.io.FileOutputStream("/tmp/" + fontName + ".ttf"); // fis.write(fontData); // fis.flush(); // fis.close(); // } catch (Exception ex) { // ex.printStackTrace(); // } if (ttfObj != null) { this.font = TrueTypeFont.parseFont(ttfObj.getStreamBuffer()); // read the units per em from the head table HeadTable head = (HeadTable) this.font.getTable("head"); this.unitsPerEm = head.getUnitsPerEm(); } else { this.font = null; } // System.out.println ("TTFFont: ttfObj: " + ttfObj + ", fontName: " + fontName); }
/** * Fix a broken font name table for a TrueType font. Some fonts do not have Microsoft-specific * name information, but Java won't work without it (grrr.). This method takes a font and adds the * Microsoft data into it. * * @param ttf the font * @param name the font's name table * @return true if the table was fixed, or false if it was left as is */ private boolean fixNameTable(TrueTypeFont ttf, NameTable name) { // if we didn't find the table, or there was an exception, // just create a new one if (name == null) { name = (NameTable) TrueTypeTable.createTable(ttf, "name"); ttf.addTable("name", name); } // first, figure out some info about the font String fName = this.getBaseFont(); String style = "Regular"; if (fName.indexOf("Italic") > -1 || fName.indexOf("italic") > -1) { style = "Italic"; } else if (fName.indexOf("Bold") > -1 || fName.indexOf("bold") > -1) { style = "Bold"; } if (fName.indexOf('-') > -1) { fName = fName.substring(0, fName.indexOf('-')); } short platID = NameTable.PLATFORMID_MICROSOFT; short encID = 1; short langID = 1033; short[] nameIDs = { NameTable.NAMEID_COPYRIGHT, NameTable.NAMEID_FAMILY, NameTable.NAMEID_SUBFAMILY, NameTable.NAMEID_SUBFAMILY_UNIQUE, NameTable.NAMEID_FULL_NAME, NameTable.NAMEID_VERSION, NameTable.NAMEID_POSTSCRIPT_NAME, NameTable.NAMEID_TRADEMARK }; String[] defaultValues = { "No copyright", fName, style, fName + " " + style, fName + " " + style, "1.0 (Fake)", fName, "No Trademark" }; boolean changed = false; for (int i = 0; i < nameIDs.length; i++) { if (name.getRecord(platID, encID, langID, nameIDs[i]) == null) { name.addRecord(platID, encID, langID, nameIDs[i], defaultValues[i]); changed = true; } } return changed; }
/** * Fix the CMap table. This can be necessary if characters are mapped to control characters (0x9, * 0xa, 0xd) Java will not render them, even though they are valid. * * <p>Also, Java tends to not like it when there is only a Format 0 CMap, which happens frequently * when included Format 4 CMaps are broken. Since PDF prefers the Format 0 map, while Java prefers * the Format 4 map, it is generally necessary to re-write the Format 0 map as a Format 4 map to * make most PDFs work. * * @param ttf the font * @param cmap the CMap table * @return true if the font was changed, or false if it was left as-is */ private boolean fixCMapTable(TrueTypeFont ttf, CmapTable cmap) { CMapFormat4 fourMap = null; CMapFormat0 zeroMap = null; for (int i = 0; i < mapIDs.length; i += 2) { CMap map = this.cmapTable.getCMap(mapIDs[i], mapIDs[i + 1]); if (map != null) { if (fourMap == null && map instanceof CMapFormat4) { fourMap = (CMapFormat4) map; } else if (zeroMap == null && map instanceof CMapFormat0) { zeroMap = (CMapFormat0) map; } } } // if there were no maps, we could have problems. Just try creating // an identity map if (zeroMap == null && fourMap == null) { fourMap = (CMapFormat4) CMap.createMap((short) 4, (short) 0); fourMap.addSegment((short) getFirstChar(), (short) getLastChar(), (short) 0); } // create our map based on the type 0 map, since PDF seems // to prefer a type 0 map (Java prefers a unicode map) if (zeroMap != null) { fourMap = (CMapFormat4) CMap.createMap((short) 4, (short) 0); // add the mappings from 0 to null and 1 to notdef fourMap.addSegment((short) 0, (short) 1, (short) 0); for (int i = getFirstChar(); i <= getLastChar(); i++) { short value = (short) (zeroMap.map((byte) i) & 0xff); if (value != 0) { fourMap.addSegment((short) i, (short) i, (short) (value - i)); } } } // now that we have a type four map, remap control characters for (int i = 0; i < controlChars.length; i++) { short idx = (short) (0xf000 | controlChars[i]); short value = (short) fourMap.map(controlChars[i]); fourMap.addSegment(idx, idx, (short) (value - idx)); } // create a whole new table with just our map cmap = (CmapTable) TrueTypeTable.createTable(ttf, "cmap"); cmap.addCMap((short) 3, (short) 1, fourMap); // replace the table in the font ttf.addTable("cmap", cmap); // change the stored table this.cmapTable = cmap; return true; }
/** * Set the font * * @param f the font to use */ protected void setFont(Font f) { this.f = f; // if it's an OpenType font, parse the relevant tables to get // glyph name to code mappings if (f instanceof OpenType) { OpenType ot = (OpenType) f; byte[] cmapData = ot.getFontTable(OpenType.TAG_CMAP); byte[] postData = ot.getFontTable(OpenType.TAG_POST); TrueTypeFont ttf = new TrueTypeFont(0x10000); this.cmapTable = (CmapTable) TrueTypeTable.createTable(ttf, "cmap", ByteBuffer.wrap(cmapData)); ttf.addTable("cmap", this.cmapTable); this.postTable = (PostTable) TrueTypeTable.createTable(ttf, "post", ByteBuffer.wrap(postData)); ttf.addTable("post", this.postTable); } }
/** * Set the font * * @param fontdata the font data as a byte array */ protected void setFont(byte[] fontdata) throws FontFormatException, IOException { // System.out.println("Loading " + getBaseFont()); // FileOutputStream fos = new FileOutputStream("/tmp/" + getBaseFont() + ".ttf"); // fos.write(fontdata); // fos.close(); try { // read the true type information TrueTypeFont ttf = TrueTypeFont.parseFont(fontdata); // System.out.println(ttf.toString()); // get the cmap, post, and hmtx tables for later use this.cmapTable = (CmapTable) ttf.getTable("cmap"); this.postTable = (PostTable) ttf.getTable("post"); this.hmtxTable = (HmtxTable) ttf.getTable("hmtx"); // read the units per em from the head table HeadTable headTable = (HeadTable) ttf.getTable("head"); this.unitsPerEm = headTable.getUnitsPerEm(); /* Find out if we have the right info in our name table. * This is a hack because Java can only deal with fonts that * have a Microsoft encoded name in their name table (PlatformID 3). * We'll 'adjust' the font to add it if not, and take our chances * with our parsing, since it wasn't going to work anyway. */ NameTable nameTable = null; try { nameTable = (NameTable) ttf.getTable("name"); } catch (Exception ex) { System.out.println("Error reading name table for font " + getBaseFont() + ". Repairing!"); } boolean nameFixed = fixNameTable(ttf, nameTable); /* Figure out if we need to hack the CMap table. This might * be the case if we use characters that Java considers control * characters (0x9, 0xa and 0xd), that have to be re-mapped */ boolean cmapFixed = fixCMapTable(ttf, this.cmapTable); // use the parsed font instead of the original if (nameFixed || cmapFixed) { // System.out.println("Using fixed font!"); // System.out.println(ttf.toString()); fontdata = ttf.writeFont(); // FileOutputStream fos2 = new FileOutputStream("/tmp/" + getBaseFont() + ".fix"); // fos2.write(fontdata); // fos2.close(); } } catch (Exception ex) { System.out.println("Error parsing font : " + getBaseFont()); ex.printStackTrace(); } ByteArrayInputStream bais = new ByteArrayInputStream(fontdata); this.f = Font.createFont(Font.TRUETYPE_FONT, bais); bais.close(); }