1 17 18 19 20 package org.apache.fop.fonts.truetype; 21 22 import java.io.IOException ; 23 import java.util.HashMap ; 24 import java.util.Iterator ; 25 import java.util.Map ; 26 import java.util.List ; 27 28 import org.apache.commons.logging.Log; 29 import org.apache.commons.logging.LogFactory; 30 import org.apache.fop.fonts.Glyphs; 31 32 37 public class TTFFile { 38 39 static final byte NTABS = 24; 40 static final int NMACGLYPHS = 258; 41 static final int MAX_CHAR_CODE = 255; 42 static final int ENC_BUF_SIZE = 1024; 43 44 45 public static final boolean TRACE_ENABLED = false; 46 47 private String encoding = "WinAnsiEncoding"; 49 private short firstChar = 0; 50 private boolean isEmbeddable = true; 51 private boolean hasSerifs = true; 52 55 protected Map dirTabs; 56 private Map kerningTab; private Map ansiKerningTab; private List cmaps; 59 private List unicodeMapping; 60 61 private int upem; private int nhmtx; private int postFormat; 64 private int locaFormat; 65 68 protected long lastLoca = 0; 69 private int numberOfGlyphs; private int nmGlyphs; 72 75 protected TTFMtxEntry[] mtxTab; private int[] mtxEncoded = null; 77 78 private String fontName = ""; 79 private String fullName = ""; 80 private String notice = ""; 81 private String familyName = ""; 82 private String subFamilyName = ""; 83 84 private long italicAngle = 0; 85 private long isFixedPitch = 0; 86 private int fontBBox1 = 0; 87 private int fontBBox2 = 0; 88 private int fontBBox3 = 0; 89 private int fontBBox4 = 0; 90 private int capHeight = 0; 91 private int underlinePosition = 0; 92 private int underlineThickness = 0; 93 private int xHeight = 0; 94 private int ascender = 0; 96 private int descender = 0; 97 private int hheaAscender = 0; 99 private int hheaDescender = 0; 100 private int os2Ascender = 0; 102 private int os2Descender = 0; 103 104 private short lastChar = 0; 105 106 private int[] ansiWidth; 107 private Map ansiIndex; 108 109 private Map glyphToUnicodeMap = new HashMap (); 112 private Map unicodeToGlyphMap = new HashMap (); 113 114 private TTFDirTabEntry currentDirTab; 115 116 119 protected Log log = LogFactory.getLog(TTFFile.class); 120 121 124 class UnicodeMapping { 125 126 private int unicodeIndex; 127 private int glyphIndex; 128 129 UnicodeMapping(int glyphIndex, int unicodeIndex) { 130 this.unicodeIndex = unicodeIndex; 131 this.glyphIndex = glyphIndex; 132 glyphToUnicodeMap.put(new Integer (glyphIndex),new Integer (unicodeIndex)); 133 unicodeToGlyphMap.put(new Integer (unicodeIndex),new Integer (glyphIndex)); 134 } 135 136 140 public int getGlyphIndex() { 141 return glyphIndex; 142 } 143 144 148 public int getUnicodeIndex() { 149 return unicodeIndex; 150 } 151 } 152 153 157 boolean seekTab(FontFileReader in, String name, 158 long offset) throws IOException { 159 TTFDirTabEntry dt = (TTFDirTabEntry)dirTabs.get(name); 160 if (dt == null) { 161 log.error("Dirtab " + name + " not found."); 162 return false; 163 } else { 164 in.seekSet(dt.getOffset() + offset); 165 this.currentDirTab = dt; 166 } 167 return true; 168 } 169 170 176 public int convertTTFUnit2PDFUnit(int n) { 177 int ret; 178 if (n < 0) { 179 long rest1 = n % upem; 180 long storrest = 1000 * rest1; 181 long ledd2 = (storrest != 0 ? rest1 / storrest : 0); 182 ret = -((-1000 * n) / upem - (int)ledd2); 183 } else { 184 ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem; 185 } 186 187 return ret; 188 } 189 190 197 private boolean readCMAP(FontFileReader in) throws IOException { 198 199 unicodeMapping = new java.util.ArrayList (); 200 201 int mtxPtr = 0; 203 204 seekTab(in, "cmap", 2); 205 int numCMap = in.readTTFUShort(); long cmapUniOffset = 0; 207 208 log.info(numCMap + " cmap tables"); 209 210 for (int i = 0; i < numCMap; i++) { 212 int cmapPID = in.readTTFUShort(); 213 int cmapEID = in.readTTFUShort(); 214 long cmapOffset = in.readTTFULong(); 215 216 log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID); 217 218 if (cmapPID == 3 && cmapEID == 1) { 219 cmapUniOffset = cmapOffset; 220 } 221 } 222 223 if (cmapUniOffset <= 0) { 224 log.fatal("Unicode cmap table not present"); 225 log.fatal("Unsupported format: Aborting"); 226 return false; 227 } 228 229 seekTab(in, "cmap", cmapUniOffset); 231 int cmapFormat = in.readTTFUShort(); 232 in.readTTFUShort(); 234 log.info("CMAP format: " + cmapFormat); 235 236 if (cmapFormat == 4) { 237 in.skip(2); int cmapSegCountX2 = in.readTTFUShort(); 239 int cmapSearchRange = in.readTTFUShort(); 240 int cmapEntrySelector = in.readTTFUShort(); 241 int cmapRangeShift = in.readTTFUShort(); 242 243 if (log.isDebugEnabled()) { 244 log.debug("segCountX2 : " + cmapSegCountX2); 245 log.debug("searchRange : " + cmapSearchRange); 246 log.debug("entrySelector: " + cmapEntrySelector); 247 log.debug("rangeShift : " + cmapRangeShift); 248 } 249 250 251 int[] cmapEndCounts = new int[cmapSegCountX2 / 2]; 252 int[] cmapStartCounts = new int[cmapSegCountX2 / 2]; 253 int[] cmapDeltas = new int[cmapSegCountX2 / 2]; 254 int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2]; 255 256 for (int i = 0; i < (cmapSegCountX2 / 2); i++) { 257 cmapEndCounts[i] = in.readTTFUShort(); 258 } 259 260 in.skip(2); 262 for (int i = 0; i < (cmapSegCountX2 / 2); i++) { 263 cmapStartCounts[i] = in.readTTFUShort(); 264 } 265 266 for (int i = 0; i < (cmapSegCountX2 / 2); i++) { 267 cmapDeltas[i] = in.readTTFShort(); 268 } 269 270 272 for (int i = 0; i < (cmapSegCountX2 / 2); i++) { 273 cmapRangeOffsets[i] = in.readTTFUShort(); 274 } 275 276 int glyphIdArrayOffset = in.getCurrentPos(); 277 278 281 for (int i = 0; i < cmapStartCounts.length; i++) { 282 283 if (log.isTraceEnabled()) { 284 log.trace(i + ": " + cmapStartCounts[i] 285 + " - " + cmapEndCounts[i]); 286 } 287 288 for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) { 289 290 if (j < 256 && j > lastChar) { 292 lastChar = (short)j; 293 } 294 295 if (mtxPtr < mtxTab.length) { 296 int glyphIdx; 297 if (cmapRangeOffsets[i] != 0 && j != 65535) { 300 int glyphOffset = glyphIdArrayOffset 301 + ((cmapRangeOffsets[i] / 2) 302 + (j - cmapStartCounts[i]) 303 + (i) 304 - cmapSegCountX2 / 2) * 2; 305 in.seekSet(glyphOffset); 306 glyphIdx = (in.readTTFUShort() + cmapDeltas[i]) 307 & 0xffff; 308 309 unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); 310 mtxTab[glyphIdx].getUnicodeIndex().add(new Integer (j)); 311 312 313 List v = (List )ansiIndex.get(new Integer (j)); 315 if (v != null) { 316 Iterator e = v.listIterator(); 317 while (e.hasNext()) { 318 Integer aIdx = (Integer )e.next(); 319 ansiWidth[aIdx.intValue()] 320 = mtxTab[glyphIdx].getWx(); 321 322 if (log.isTraceEnabled()) { 323 log.trace("Added width " 324 + mtxTab[glyphIdx].getWx() 325 + " uni: " + j 326 + " ansi: " + aIdx.intValue()); 327 } 328 } 329 } 330 331 if (log.isTraceEnabled()) { 332 log.trace("Idx: " 333 + glyphIdx 334 + " Delta: " + cmapDeltas[i] 335 + " Unicode: " + j 336 + " name: " + mtxTab[glyphIdx].getName()); 337 } 338 } else { 339 glyphIdx = (j + cmapDeltas[i]) & 0xffff; 340 341 if (glyphIdx < mtxTab.length) { 342 mtxTab[glyphIdx].getUnicodeIndex().add(new Integer (j)); 343 } else { 344 log.debug("Glyph " + glyphIdx 345 + " out of range: " 346 + mtxTab.length); 347 } 348 349 unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); 350 if (glyphIdx < mtxTab.length) { 351 mtxTab[glyphIdx].getUnicodeIndex().add(new Integer (j)); 352 } else { 353 log.debug("Glyph " + glyphIdx 354 + " out of range: " 355 + mtxTab.length); 356 } 357 358 List v = (List )ansiIndex.get(new Integer (j)); 360 if (v != null) { 361 Iterator e = v.listIterator(); 362 while (e.hasNext()) { 363 Integer aIdx = (Integer )e.next(); 364 ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); 365 } 366 } 367 368 375 } 376 if (glyphIdx < mtxTab.length) { 377 if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) { 378 mtxPtr++; 379 } 380 } 381 } 382 } 383 } 384 } 385 return true; 386 } 387 388 391 private void printMaxMin() { 392 int min = 255; 393 int max = 0; 394 for (int i = 0; i < mtxTab.length; i++) { 395 if (mtxTab[i].getIndex() < min) { 396 min = mtxTab[i].getIndex(); 397 } 398 if (mtxTab[i].getIndex() > max) { 399 max = mtxTab[i].getIndex(); 400 } 401 } 402 log.info("Min: " + min); 403 log.info("Max: " + max); 404 } 405 406 407 413 public void readFont(FontFileReader in) throws IOException { 414 readFont(in, (String )null); 415 } 416 417 421 private void initAnsiWidths() { 422 ansiWidth = new int[256]; 423 for (int i = 0; i < 256; i++) { 424 ansiWidth[i] = mtxTab[0].getWx(); 425 } 426 427 ansiIndex = new java.util.HashMap (); 431 for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { 432 Integer ansi = new Integer (i); 433 Integer uni = new Integer ((int)Glyphs.WINANSI_ENCODING[i]); 434 435 List v = (List )ansiIndex.get(uni); 436 if (v == null) { 437 v = new java.util.ArrayList (); 438 ansiIndex.put(uni, v); 439 } 440 v.add(ansi); 441 } 442 } 443 444 455 public boolean readFont(FontFileReader in, String name) throws IOException { 456 457 461 if (!checkTTC(in, name)) { 462 if (name == null) { 463 throw new IllegalArgumentException ( 464 "For TrueType collection you must specify which font " 465 + "to select (-ttcname)"); 466 } else { 467 throw new IOException ( 468 "Name does not exist in the TrueType collection: " + name); 469 } 470 } 471 472 readDirTabs(in); 473 readFontHeader(in); 474 getNumGlyphs(in); 475 log.info("Number of glyphs in font: " + numberOfGlyphs); 476 readHorizontalHeader(in); 477 readHorizontalMetrics(in); 478 initAnsiWidths(); 479 readPostScript(in); 480 readOS2(in); 481 determineAscDesc(); 482 readIndexToLocation(in); 483 readGlyf(in); 484 readName(in); 485 boolean pcltFound = readPCLT(in); 486 boolean valid = readCMAP(in); 488 if (!valid) { 489 return false; 490 } 491 createCMaps(); 493 495 readKerning(in); 496 guessVerticalMetricsFromGlyphBBox(); 497 return true; 498 } 499 500 private void createCMaps() { 501 cmaps = new java.util.ArrayList (); 502 TTFCmapEntry tce = new TTFCmapEntry(); 503 504 Iterator e = unicodeMapping.listIterator(); 505 UnicodeMapping um = (UnicodeMapping)e.next(); 506 UnicodeMapping lastMapping = um; 507 508 tce.setUnicodeStart(um.getUnicodeIndex()); 509 tce.setGlyphStartIndex(um.getGlyphIndex()); 510 511 while (e.hasNext()) { 512 um = (UnicodeMapping)e.next(); 513 if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex()) 514 || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) { 515 tce.setUnicodeEnd(lastMapping.getUnicodeIndex()); 516 cmaps.add(tce); 517 518 tce = new TTFCmapEntry(); 519 tce.setUnicodeStart(um.getUnicodeIndex()); 520 tce.setGlyphStartIndex(um.getGlyphIndex()); 521 } 522 lastMapping = um; 523 } 524 525 tce.setUnicodeEnd(um.getUnicodeIndex()); 526 cmaps.add(tce); 527 } 528 529 533 public String getWindowsName() { 534 return familyName + "," + subFamilyName; 535 } 536 537 541 public String getPostScriptName() { 542 if ("Regular".equals(subFamilyName) || "Roman".equals(subFamilyName)) { 543 return familyName; 544 } else { 545 return familyName + "," + subFamilyName; 546 } 547 } 548 549 553 public String getFamilyName() { 554 return familyName; 555 } 556 557 561 public String getCharSetName() { 562 return encoding; 563 } 564 565 569 public int getCapHeight() { 570 return (int)convertTTFUnit2PDFUnit(capHeight); 571 } 572 573 577 public int getXHeight() { 578 return (int)convertTTFUnit2PDFUnit(xHeight); 579 } 580 581 585 public int getFlags() { 586 int flags = 32; if (italicAngle != 0) { 588 flags = flags | 64; 589 } 590 if (isFixedPitch != 0) { 591 flags = flags | 2; 592 } 593 if (hasSerifs) { 594 flags = flags | 1; 595 } 596 return flags; 597 } 598 599 600 604 public String getStemV() { 605 return "0"; 606 } 607 608 612 public String getItalicAngle() { 613 String ia = Short.toString((short)(italicAngle / 0x10000)); 614 615 621 return ia; 622 } 623 624 628 public int[] getFontBBox() { 629 final int[] fbb = new int[4]; 630 fbb[0] = (int)convertTTFUnit2PDFUnit(fontBBox1); 631 fbb[1] = (int)convertTTFUnit2PDFUnit(fontBBox2); 632 fbb[2] = (int)convertTTFUnit2PDFUnit(fontBBox3); 633 fbb[3] = (int)convertTTFUnit2PDFUnit(fontBBox4); 634 635 return fbb; 636 } 637 638 642 public int getLowerCaseAscent() { 643 return (int)convertTTFUnit2PDFUnit(ascender); 644 } 645 646 650 public int getLowerCaseDescent() { 651 return (int)convertTTFUnit2PDFUnit(descender); 652 } 653 654 659 public short getLastChar() { 660 return lastChar; 661 } 662 663 667 public short getFirstChar() { 668 return firstChar; 669 } 670 671 675 public int[] getWidths() { 676 int[] wx = new int[mtxTab.length]; 677 for (int i = 0; i < wx.length; i++) { 678 wx[i] = (int)convertTTFUnit2PDFUnit(mtxTab[i].getWx()); 679 } 680 681 return wx; 682 } 683 684 689 public int getCharWidth(int idx) { 690 return (int)convertTTFUnit2PDFUnit(ansiWidth[idx]); 691 } 692 693 697 public Map getKerning() { 698 return kerningTab; 699 } 700 701 705 public Map getAnsiKerning() { 706 return ansiKerningTab; 707 } 708 709 713 public boolean isEmbeddable() { 714 return isEmbeddable; 715 } 716 717 718 726 protected void readDirTabs(FontFileReader in) throws IOException { 727 in.skip(4); int ntabs = in.readTTFUShort(); 729 in.skip(6); 731 dirTabs = new java.util.HashMap (); 732 TTFDirTabEntry[] pd = new TTFDirTabEntry[ntabs]; 733 log.debug("Reading " + ntabs + " dir tables"); 734 for (int i = 0; i < ntabs; i++) { 735 pd[i] = new TTFDirTabEntry(); 736 dirTabs.put(pd[i].read(in), pd[i]); 737 } 738 log.debug("dir tables: " + dirTabs.keySet()); 739 } 740 741 747 protected void readFontHeader(FontFileReader in) throws IOException { 748 seekTab(in, "head", 2 * 4 + 2 * 4 + 2); 749 upem = in.readTTFUShort(); 750 log.debug("unit per em: " + upem); 751 752 in.skip(16); 753 754 fontBBox1 = in.readTTFShort(); 755 fontBBox2 = in.readTTFShort(); 756 fontBBox3 = in.readTTFShort(); 757 fontBBox4 = in.readTTFShort(); 758 759 in.skip(2 + 2 + 2); 760 761 locaFormat = in.readTTFShort(); 762 } 763 764 769 protected void getNumGlyphs(FontFileReader in) throws IOException { 770 seekTab(in, "maxp", 4); 771 numberOfGlyphs = in.readTTFUShort(); 772 } 773 774 775 782 protected void readHorizontalHeader(FontFileReader in) 783 throws IOException { 784 seekTab(in, "hhea", 4); 785 hheaAscender = in.readTTFShort(); 786 log.debug("hhea.Ascender: " + hheaAscender + " " + convertTTFUnit2PDFUnit(hheaAscender)); 787 hheaDescender = in.readTTFShort(); 788 log.debug("hhea.Descender: " + hheaDescender + " " + convertTTFUnit2PDFUnit(hheaDescender)); 789 790 in.skip(2 + 2 + 3 * 2 + 8 * 2); 791 nhmtx = in.readTTFUShort(); 792 log.debug("Number of horizontal metrics: " + nhmtx); 793 794 795 } 796 797 805 protected void readHorizontalMetrics(FontFileReader in) 806 throws IOException { 807 seekTab(in, "hmtx", 0); 808 809 int mtxSize = Math.max(numberOfGlyphs, nhmtx); 810 mtxTab = new TTFMtxEntry[mtxSize]; 811 812 if (TRACE_ENABLED) { 813 log.debug("*** Widths array: \n"); 814 } 815 for (int i = 0; i < mtxSize; i++) { 816 mtxTab[i] = new TTFMtxEntry(); 817 } 818 for (int i = 0; i < nhmtx; i++) { 819 mtxTab[i].setWx(in.readTTFUShort()); 820 mtxTab[i].setLsb(in.readTTFUShort()); 821 822 if (TRACE_ENABLED) { 823 if (log.isDebugEnabled()) { 824 log.debug(" width[" + i + "] = " 825 + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";"); 826 } 827 } 828 } 829 830 if (nhmtx < mtxSize) { 831 int lastWidth = mtxTab[nhmtx - 1].getWx(); 833 for (int i = nhmtx; i < mtxSize; i++) { 834 mtxTab[i].setWx(lastWidth); 835 mtxTab[i].setLsb(in.readTTFUShort()); 836 } 837 } 838 } 839 840 841 845 private final void readPostScript(FontFileReader in) throws IOException { 846 seekTab(in, "post", 0); 847 postFormat = in.readTTFLong(); 848 italicAngle = in.readTTFULong(); 849 underlinePosition = in.readTTFShort(); 850 underlineThickness = in.readTTFShort(); 851 isFixedPitch = in.readTTFULong(); 852 853 in.skip(4 * 4); 855 856 log.debug("PostScript format: 0x" + Integer.toHexString(postFormat)); 857 switch (postFormat) { 858 case 0x00010000: 859 log.debug("PostScript format 1"); 860 for (int i = 0; i < Glyphs.MAC_GLYPH_NAMES.length; i++) { 861 mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[i]); 862 } 863 break; 864 case 0x00020000: 865 log.debug("PostScript format 2"); 866 int numGlyphStrings = 0; 867 868 int l = in.readTTFUShort(); 870 871 for (int i = 0; i < l; i++) { 873 mtxTab[i].setIndex(in.readTTFUShort()); 874 875 if (mtxTab[i].getIndex() > 257) { 876 numGlyphStrings++; 878 } 879 880 if (log.isTraceEnabled()) { 881 log.trace("PostScript index: " + mtxTab[i].getIndexAsString()); 882 } 883 } 884 885 String [] psGlyphsBuffer = new String [numGlyphStrings]; 887 if (log.isDebugEnabled()) { 888 log.debug("Reading " + numGlyphStrings 889 + " glyphnames, that are not in the standard Macintosh" 890 + " set. Total number of glyphs=" + l); 891 } 892 for (int i = 0; i < psGlyphsBuffer.length; i++) { 893 psGlyphsBuffer[i] = in.readTTFString(in.readTTFUByte()); 894 } 895 896 for (int i = 0; i < l; i++) { 898 if (mtxTab[i].getIndex() < NMACGLYPHS) { 899 mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[mtxTab[i].getIndex()]); 900 } else { 901 if (!mtxTab[i].isIndexReserved()) { 902 int k = mtxTab[i].getIndex() - NMACGLYPHS; 903 904 if (log.isTraceEnabled()) { 905 log.trace(k + " i=" + i + " mtx=" + mtxTab.length 906 + " ps=" + psGlyphsBuffer.length); 907 } 908 909 mtxTab[i].setName(psGlyphsBuffer[k]); 910 } 911 } 912 } 913 914 break; 915 case 0x00030000: 916 log.debug("PostScript format 3"); 918 break; 919 default: 920 log.error("Unknown PostScript format: " + postFormat); 921 } 922 } 923 924 925 928 private final void readOS2(FontFileReader in) throws IOException { 929 if (dirTabs.get("OS/2") != null) { 931 seekTab(in, "OS/2", 2 * 4); 932 int fsType = in.readTTFUShort(); 933 if (fsType == 2) { 934 isEmbeddable = false; 935 } else { 936 isEmbeddable = true; 937 } 938 in.skip(11 * 2); 939 in.skip(10); in.skip(4 * 4); in.skip(4); 942 in.skip(3 * 2); 943 int v; 944 os2Ascender = in.readTTFShort(); log.debug("sTypoAscender: " + os2Ascender 946 + " " + convertTTFUnit2PDFUnit(os2Ascender)); 947 os2Descender = in.readTTFShort(); log.debug("sTypoDescender: " + os2Descender 949 + " " + convertTTFUnit2PDFUnit(os2Descender)); 950 v = in.readTTFShort(); log.debug("sTypoLineGap: " + v); 952 v = in.readTTFUShort(); log.debug("usWinAscent: " + v + " " + convertTTFUnit2PDFUnit(v)); 954 v = in.readTTFUShort(); log.debug("usWinDescent: " + v + " " + convertTTFUnit2PDFUnit(v)); 956 in.skip(2 * 4); 957 v = in.readTTFShort(); log.debug("sxHeight: " + v); 959 v = in.readTTFShort(); log.debug("sCapHeight: " + v); 961 962 } else { 963 isEmbeddable = true; 964 } 965 } 966 967 972 protected final void readIndexToLocation(FontFileReader in) 973 throws IOException { 974 if (!seekTab(in, "loca", 0)) { 975 throw new IOException ("'loca' table not found, happens when the font file doesn't" 976 + " contain TrueType outlines (trying to read an OpenType CFF font maybe?)"); 977 } 978 for (int i = 0; i < numberOfGlyphs; i++) { 979 mtxTab[i].setOffset(locaFormat == 1 ? in.readTTFULong() 980 : (in.readTTFUShort() << 1)); 981 } 982 lastLoca = (locaFormat == 1 ? in.readTTFULong() 983 : (in.readTTFUShort() << 1)); 984 } 985 986 991 private final void readGlyf(FontFileReader in) throws IOException { 992 TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("glyf"); 993 if (dirTab == null) { 994 throw new IOException ("glyf table not found, cannot continue"); 995 } 996 for (int i = 0; i < (numberOfGlyphs - 1); i++) { 997 if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { 998 in.seekSet(dirTab.getOffset() + mtxTab[i].getOffset()); 999 in.skip(2); 1000 final int[] bbox = { 1001 in.readTTFShort(), 1002 in.readTTFShort(), 1003 in.readTTFShort(), 1004 in.readTTFShort()}; 1005 mtxTab[i].setBoundingBox(bbox); 1006 } else { 1007 mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox()); 1008 } 1009 } 1010 1011 1012 long n = ((TTFDirTabEntry)dirTabs.get("glyf")).getOffset(); 1013 for (int i = 0; i < numberOfGlyphs; i++) { 1014 if ((i + 1) >= mtxTab.length 1015 || mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { 1016 in.seekSet(n + mtxTab[i].getOffset()); 1017 in.skip(2); 1018 final int[] bbox = { 1019 in.readTTFShort(), 1020 in.readTTFShort(), 1021 in.readTTFShort(), 1022 in.readTTFShort()}; 1023 mtxTab[i].setBoundingBox(bbox); 1024 } else { 1025 1026 final int bbox0 = mtxTab[0].getBoundingBox()[0]; 1027 final int[] bbox = {bbox0, bbox0, bbox0, bbox0}; 1028 mtxTab[i].setBoundingBox(bbox); 1029 1034 } 1035 if (log.isTraceEnabled()) { 1036 log.trace(mtxTab[i].toString(this)); 1037 } 1038 } 1039 } 1040 1041 1046 private final void readName(FontFileReader in) throws IOException { 1047 seekTab(in, "name", 2); 1048 int i = in.getCurrentPos(); 1049 int n = in.readTTFUShort(); 1050 int j = in.readTTFUShort() + i - 2; 1051 i += 2 * 2; 1052 1053 while (n-- > 0) { 1054 in.seekSet(i); 1056 final int platformID = in.readTTFUShort(); 1057 final int encodingID = in.readTTFUShort(); 1058 final int languageID = in.readTTFUShort(); 1059 1060 int k = in.readTTFUShort(); 1061 int l = in.readTTFUShort(); 1062 1063 if (((platformID == 1 || platformID == 3) 1064 && (encodingID == 0 || encodingID == 1)) 1065 && (k == 1 || k == 2 || k == 0 || k == 4 || k == 6)) { 1066 in.seekSet(j + in.readTTFUShort()); 1067 String txt = in.readTTFString(l); 1068 1069 log.debug(platformID + " " 1070 + encodingID + " " 1071 + languageID + " " 1072 + k + " " + txt); 1073 switch (k) { 1074 case 0: 1075 notice = txt; 1076 break; 1077 case 1: 1078 familyName = txt; 1079 break; 1080 case 2: 1081 subFamilyName = txt; 1082 break; 1083 case 4: 1084 fullName = txt; 1085 break; 1086 case 6: 1087 fontName = txt; 1088 break; 1089 } 1090 if (!notice.equals("") 1091 && !fullName.equals("") 1092 && !fontName.equals("") 1093 && !familyName.equals("") 1094 && !subFamilyName.equals("")) { 1095 break; 1096 } 1097 } 1098 i += 6 * 2; 1099 } 1100 } 1101 1102 1107 private final boolean readPCLT(FontFileReader in) throws IOException { 1108 TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("PCLT"); 1109 if (dirTab != null) { 1110 in.seekSet(dirTab.getOffset() + 4 + 4 + 2); 1111 xHeight = in.readTTFUShort(); 1112 log.debug("xHeight from PCLT: " + xHeight 1113 + " " + convertTTFUnit2PDFUnit(xHeight)); 1114 in.skip(2 * 2); 1115 capHeight = in.readTTFUShort(); 1116 log.debug("capHeight from PCLT: " + capHeight 1117 + " " + convertTTFUnit2PDFUnit(capHeight)); 1118 in.skip(2 + 16 + 8 + 6 + 1 + 1); 1119 1120 int serifStyle = in.readTTFUByte(); 1121 serifStyle = serifStyle >> 6; 1122 serifStyle = serifStyle & 3; 1123 if (serifStyle == 1) { 1124 hasSerifs = false; 1125 } else { 1126 hasSerifs = true; 1127 } 1128 return true; 1129 } else { 1130 return false; 1131 } 1132 } 1133 1134 1143 private void determineAscDesc() { 1144 int hheaBoxHeight = hheaAscender - hheaDescender; 1145 int os2BoxHeight = os2Ascender - os2Descender; 1146 if (os2Ascender > 0 && os2BoxHeight <= upem) { 1147 ascender = os2Ascender; 1148 descender = os2Descender; 1149 } else if (hheaAscender > 0 && hheaBoxHeight <= upem) { 1150 ascender = hheaAscender; 1151 descender = hheaDescender; 1152 } else { 1153 if (os2Ascender > 0) { 1154 ascender = os2Ascender; 1156 descender = os2Descender; 1157 } else { 1158 ascender = hheaAscender; 1159 descender = hheaDescender; 1160 } 1161 } 1162 1163 log.debug("Font box height: " + (ascender - descender)); 1164 if (ascender - descender > upem) { 1165 log.warn("Ascender and descender together are larger than the em box." 1166 + " This could lead to a wrong baseline placement in Apache FOP."); 1167 } 1168 } 1169 1170 private void guessVerticalMetricsFromGlyphBBox() { 1171 int localCapHeight = 0; 1178 int localXHeight = 0; 1179 int localAscender = 0; 1180 int localDescender = 0; 1181 for (int i = 0; i < mtxTab.length; i++) { 1182 if ("H".equals(mtxTab[i].getName())) { 1183 localCapHeight = mtxTab[i].getBoundingBox()[3]; 1184 } else if ("x".equals(mtxTab[i].getName())) { 1185 localXHeight = mtxTab[i].getBoundingBox()[3]; 1186 } else if ("d".equals(mtxTab[i].getName())) { 1187 localAscender = mtxTab[i].getBoundingBox()[3]; 1188 } else if ("p".equals(mtxTab[i].getName())) { 1189 localDescender = mtxTab[i].getBoundingBox()[1]; 1190 } else { 1191 List unicodeIndex = mtxTab[i].getUnicodeIndex(); 1194 if (unicodeIndex.size() > 0) { 1195 char ch = (char)((Integer )unicodeIndex.get(0)).intValue(); 1197 if (ch == 'H') { 1198 localCapHeight = mtxTab[i].getBoundingBox()[3]; 1199 } else if (ch == 'x') { 1200 localXHeight = mtxTab[i].getBoundingBox()[3]; 1201 } else if (ch == 'd') { 1202 localAscender = mtxTab[i].getBoundingBox()[3]; 1203 } else if (ch == 'p') { 1204 localDescender = mtxTab[i].getBoundingBox()[1]; 1205 } 1206 } 1207 } 1208 } 1209 log.debug("Ascender from glyph 'd': " + localAscender 1210 + " " + convertTTFUnit2PDFUnit(localAscender)); 1211 log.debug("Descender from glyph 'p': " + localDescender 1212 + " " + convertTTFUnit2PDFUnit(localDescender)); 1213 if (ascender - descender > upem) { 1214 log.debug("Replacing specified ascender/descender with derived values to get values" 1215 + " which fit in the em box."); 1216 ascender = localAscender; 1217 descender = localDescender; 1218 } 1219 1220 log.debug("xHeight from glyph 'x': " + localXHeight 1221 + " " + convertTTFUnit2PDFUnit(localXHeight)); 1222 log.debug("CapHeight from glyph 'H': " + localCapHeight 1223 + " " + convertTTFUnit2PDFUnit(localCapHeight)); 1224 if (capHeight == 0) { 1225 capHeight = localCapHeight; 1226 if (localCapHeight == 0) { 1227 log.warn("capHeight value could not be determined." 1228 + " The font may not work as expected."); 1229 } 1230 } 1231 if (xHeight == 0) { 1232 xHeight = localXHeight; 1233 if (xHeight == 0) { 1234 log.warn("xHeight value could not be determined." 1235 + " The font may not work as expected."); 1236 } 1237 } 1238 } 1239 1240 1246 private final void readKerning(FontFileReader in) throws IOException { 1247 kerningTab = new java.util.HashMap (); 1249 ansiKerningTab = new java.util.HashMap (); 1250 TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("kern"); 1251 if (dirTab != null) { 1252 seekTab(in, "kern", 2); 1253 for (int n = in.readTTFUShort(); n > 0; n--) { 1254 in.skip(2 * 2); 1255 int k = in.readTTFUShort(); 1256 if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { 1257 return; 1258 } 1259 if ((k >> 8) != 0) { 1260 continue; 1261 } 1262 1263 k = in.readTTFUShort(); 1264 in.skip(3 * 2); 1265 while (k-- > 0) { 1266 int i = in.readTTFUShort(); 1267 int j = in.readTTFUShort(); 1268 int kpx = in.readTTFShort(); 1269 if (kpx != 0) { 1270 final Integer iObj = glyphToUnicode(i); 1272 final Integer u2 = glyphToUnicode(j); 1273 if(iObj==null) { 1274 log.warn("Unicode index (1) not found for glyph " + i); 1277 } else if(u2==null) { 1278 log.warn("Unicode index (2) not found for glyph " + i); 1279 } else { 1280 Map adjTab = (Map )kerningTab.get(iObj); 1281 if (adjTab == null) { 1282 adjTab = new java.util.HashMap (); 1283 } 1284 adjTab.put(u2,new Integer ((int)convertTTFUnit2PDFUnit(kpx))); 1285 kerningTab.put(iObj, adjTab); 1286 } 1287 } 1288 } 1289 } 1290 1291 Iterator ae = kerningTab.keySet().iterator(); 1294 while (ae.hasNext()) { 1295 Integer unicodeKey1 = (Integer )ae.next(); 1296 Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue()); 1297 Map akpx = new java.util.HashMap (); 1298 Map ckpx = (Map )kerningTab.get(unicodeKey1); 1299 1300 Iterator aee = ckpx.keySet().iterator(); 1301 while (aee.hasNext()) { 1302 Integer unicodeKey2 = (Integer )aee.next(); 1303 Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue()); 1304 Integer kern = (Integer )ckpx.get(unicodeKey2); 1305 1306 Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator(); 1307 while (uniMap.hasNext()) { 1308 Integer unicodeKey = (Integer )uniMap.next(); 1309 Integer [] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); 1310 for (int u = 0; u < ansiKeys.length; u++) { 1311 akpx.put(ansiKeys[u], kern); 1312 } 1313 } 1314 } 1315 1316 if (akpx.size() > 0) { 1317 Iterator uniMap = mtxTab[cidKey1.intValue()].getUnicodeIndex().listIterator(); 1318 while (uniMap.hasNext()) { 1319 Integer unicodeKey = (Integer )uniMap.next(); 1320 Integer [] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); 1321 for (int u = 0; u < ansiKeys.length; u++) { 1322 ansiKerningTab.put(ansiKeys[u], akpx); 1323 } 1324 } 1325 } 1326 } 1327 } 1328 } 1329 1330 1334 public List getCMaps() { 1335 return cmaps; 1336 } 1337 1338 1348 protected final boolean checkTTC(FontFileReader in, String name) throws IOException { 1349 String tag = in.readTTFString(4); 1350 1351 if ("ttcf".equals(tag)) { 1352 in.skip(4); 1354 1355 int numDirectories = (int)in.readTTFULong(); 1357 long[] dirOffsets = new long[numDirectories]; 1359 for (int i = 0; i < numDirectories; i++) { 1360 dirOffsets[i] = in.readTTFULong(); 1361 } 1362 1363 log.info("This is a TrueType collection file with " 1364 + numDirectories + " fonts"); 1365 log.info("Containing the following fonts: "); 1366 boolean found = false; 1369 1370 long dirTabOffset = 0; 1373 for (int i = 0; (i < numDirectories); i++) { 1374 in.seekSet(dirOffsets[i]); 1375 readDirTabs(in); 1376 1377 readName(in); 1378 1379 if (fullName.equals(name)) { 1380 found = true; 1381 dirTabOffset = dirOffsets[i]; 1382 log.info(fullName + " <-- selected"); 1383 } else { 1384 log.info(fullName); 1385 } 1386 1387 notice = ""; 1389 fullName = ""; 1390 familyName = ""; 1391 fontName = ""; 1392 subFamilyName = ""; 1393 } 1394 1395 in.seekSet(dirTabOffset); 1396 return found; 1397 } else { 1398 in.seekSet(0); 1399 return true; 1400 } 1401 } 1402 1403 1407 private Integer [] unicodeToWinAnsi(int unicode) { 1408 List ret = new java.util.ArrayList (); 1409 for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { 1410 if (unicode == Glyphs.WINANSI_ENCODING[i]) { 1411 ret.add(new Integer (i)); 1412 } 1413 } 1414 return (Integer [])ret.toArray(new Integer [0]); 1415 } 1416 1417 1420 public void printStuff() { 1421 System.out.println("Font name: " + fontName); 1422 System.out.println("Full name: " + fullName); 1423 System.out.println("Family name: " + familyName); 1424 System.out.println("Subfamily name: " + subFamilyName); 1425 System.out.println("Notice: " + notice); 1426 System.out.println("xHeight: " + (int)convertTTFUnit2PDFUnit(xHeight)); 1427 System.out.println("capheight: " + (int)convertTTFUnit2PDFUnit(capHeight)); 1428 1429 int italic = (int)(italicAngle >> 16); 1430 System.out.println("Italic: " + italic); 1431 System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000)); 1432 if ((italicAngle % 0x10000) > 0) { 1433 System.out.print("." 1434 + (short)((italicAngle % 0x10000) * 1000) 1435 / 0x10000); 1436 } 1437 System.out.println(); 1438 System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender)); 1439 System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender)); 1440 System.out.println("FontBBox: [" + (int)convertTTFUnit2PDFUnit(fontBBox1) 1441 + " " + (int)convertTTFUnit2PDFUnit(fontBBox2) + " " 1442 + (int)convertTTFUnit2PDFUnit(fontBBox3) + " " 1443 + (int)convertTTFUnit2PDFUnit(fontBBox4) + "]"); 1444 } 1445 1446 1453 private Integer glyphToUnicode(int glyphIndex) throws IOException { 1454 return (Integer ) glyphToUnicodeMap.get(new Integer (glyphIndex)); 1455 } 1456 1457 1464 private Integer unicodeToGlyph(int unicodeIndex) throws IOException { 1465 final Integer result = 1466 (Integer ) unicodeToGlyphMap.get(new Integer (unicodeIndex)); 1467 if (result == null) { 1468 throw new IOException ( 1469 "Glyph index not found for unicode value " + unicodeIndex); 1470 } 1471 return result; 1472 } 1473 1474 1478 public static void main(String [] args) { 1479 try { 1480 TTFFile ttfFile = new TTFFile(); 1481 1482 FontFileReader reader = new FontFileReader(args[0]); 1483 1484 String name = null; 1485 if (args.length >= 2) { 1486 name = args[1]; 1487 } 1488 1489 ttfFile.readFont(reader, name); 1490 ttfFile.printStuff(); 1491 1492 } catch (IOException ioe) { 1493 System.err.println("Problem reading font: " + ioe.toString()); 1494 ioe.printStackTrace(System.err); 1495 } 1496 } 1497} | Popular Tags |