/** * Get a glyph outline by character code * * <p>Note this method must always return an outline * * @param src the character code of the desired glyph * @return the glyph outline */ @Override protected GeneralPath getOutline(char src, float width) { // some true type fonts put characters in the undefined // region of Unicode instead of as normal characters. if (!this.f.canDisplay(src) && this.f.canDisplay((char) (src + 0xf000))) { src += 0xf000; } // filter out control characters for (int i = 0; i < controlChars.length; i++) { if (controlChars[i] == src) { src = (char) (0xf000 | src); break; } } char[] glyph = new char[1]; glyph[0] = src; GlyphVector gv = this.f.createGlyphVector(this.basecontext, glyph); GeneralPath gp = new GeneralPath(gv.getGlyphOutline(0)); // this should be gv.getGlyphMetrics(0).getAdvance(), but that is // broken on the Mac, so we need to read the advance from the // hmtx table in the font CMap map = this.cmapTable.getCMap(mapIDs[0], mapIDs[1]); int glyphID = map.map(src); float advance = (float) this.hmtxTable.getAdvance(glyphID) / (float) this.unitsPerEm; float widthfactor = width / advance; gp.transform(AffineTransform.getScaleInstance(widthfactor, -1)); return gp; }
/** * Get a glyph outline by name * * @param name the name of the desired glyph * @return the glyph outline, or null if unavailable */ @Override protected GeneralPath getOutline(String name, float width) { if (this.postTable != null && this.cmapTable != null) { // map this character name to a glyph ID short glyphID = this.postTable.getGlyphNameIndex(name); if (glyphID == 0) { // no glyph -- try by index return null; } // the mapped character char mappedChar = 0; for (int i = 0; i < mapIDs.length; i += 2) { CMap map = this.cmapTable.getCMap(mapIDs[i], mapIDs[i + 1]); if (map != null) { mappedChar = map.reverseMap(glyphID); // we found a character if (mappedChar != 0) { break; } } } return getOutline(mappedChar, width); } // no maps found, hope the font can deal return null; }
/** * 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; }
/** * lookup the outline using the CMAPs, as specified in 32000-1:2008, 9.6.6.4, when an Encoding is * specified. * * @param val * @param width * @return GeneralPath */ protected synchronized GeneralPath getOutlineFromCMaps(char val, float width) { // find the cmaps CmapTable cmap = (CmapTable) this.font.getTable("cmap"); if (cmap == null) { return null; } // try maps in required order of (3, 1), (1, 0) CMap map = cmap.getCMap((short) 3, (short) 1); if (map == null) { map = cmap.getCMap((short) 1, (short) 0); } int idx = map.map(val); if (idx != 0) { return getOutline(idx, width); } return null; }