1 // Copyright 2012 Google Inc. All Rights Reserved. 2 3 package com.google.typography.font.tools.fontinfo; 4 5 import com.google.typography.font.sfntly.Font; 6 import com.google.typography.font.sfntly.Font.MacintoshEncodingId; 7 import com.google.typography.font.sfntly.Font.PlatformId; 8 import com.google.typography.font.sfntly.Font.UnicodeEncodingId; 9 import com.google.typography.font.sfntly.Font.WindowsEncodingId; 10 import com.google.typography.font.sfntly.Tag; 11 import com.google.typography.font.sfntly.math.Fixed1616; 12 import com.google.typography.font.sfntly.table.Table; 13 import com.google.typography.font.sfntly.table.core.CMap; 14 import com.google.typography.font.sfntly.table.core.CMapTable; 15 import com.google.typography.font.sfntly.table.core.FontHeaderTable; 16 import com.google.typography.font.sfntly.table.core.HorizontalHeaderTable; 17 import com.google.typography.font.sfntly.table.core.MaximumProfileTable; 18 import com.google.typography.font.sfntly.table.core.NameTable; 19 import com.google.typography.font.sfntly.table.core.NameTable.MacintoshLanguageId; 20 import com.google.typography.font.sfntly.table.core.NameTable.NameEntry; 21 import com.google.typography.font.sfntly.table.core.NameTable.NameId; 22 import com.google.typography.font.sfntly.table.core.NameTable.UnicodeLanguageId; 23 import com.google.typography.font.sfntly.table.core.NameTable.WindowsLanguageId; 24 import com.google.typography.font.sfntly.table.core.OS2Table; 25 import com.google.typography.font.sfntly.table.truetype.CompositeGlyph; 26 import com.google.typography.font.sfntly.table.truetype.Glyph; 27 import com.google.typography.font.sfntly.table.truetype.Glyph.GlyphType; 28 import com.google.typography.font.sfntly.table.truetype.GlyphTable; 29 import com.google.typography.font.sfntly.table.truetype.LocaTable; 30 import com.google.typography.font.tools.fontinfo.DataDisplayTable.Align; 31 32 import com.ibm.icu.impl.IllegalIcuArgumentException; 33 import com.ibm.icu.lang.UCharacter; 34 import com.ibm.icu.lang.UScript; 35 import com.ibm.icu.text.UnicodeSet; 36 37 import java.text.DecimalFormat; 38 import java.text.NumberFormat; 39 import java.util.Arrays; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.Iterator; 43 import java.util.Map; 44 import java.util.Set; 45 import java.util.TreeSet; 46 47 /** 48 * Class of static functions that return information about a given font 49 * 50 * @author Brian Stell, Han-Wen Yeh 51 * 52 */ 53 // TODO Make abstract FontInfo class with nonstatic functions and subclass this 54 // as TrueTypeFontInfo 55 public class FontInfo { 56 private static final int CHECKSUM_LENGTH = 8; 57 private static final DecimalFormat twoDecimalPlaces = new DecimalFormat("#.##"); 58 59 /** 60 * @param font 61 * the source font 62 * @return the sfnt version of the font 63 */ sfntVersion(Font font)64 public static String sfntVersion(Font font) { 65 double version = Fixed1616.doubleValue(font.sfntVersion()); 66 NumberFormat numberFormatter = NumberFormat.getInstance(); 67 numberFormatter.setMinimumFractionDigits(2); 68 numberFormatter.setGroupingUsed(false); 69 return numberFormatter.format(version); 70 } 71 72 /** 73 * Gets a list of information regarding various dimensions about the given 74 * font from the head, hhea, and OS/2 font tables 75 * 76 * @param font 77 * the source font 78 * @return a list of dimensional information about the font 79 */ listFontMetrics(Font font)80 public static DataDisplayTable listFontMetrics(Font font) { 81 String[] header = { "Name", "Value" }; 82 Align[] displayAlignment = { Align.Left, Align.Left }; 83 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 84 table.setAlignment(Arrays.asList(displayAlignment)); 85 86 // Retrieve necessary tables 87 FontHeaderTable headTable = (FontHeaderTable) FontUtils.getTable(font, Tag.head); 88 HorizontalHeaderTable hheaTable = (HorizontalHeaderTable) FontUtils.getTable(font, Tag.hhea); 89 OS2Table os2Table = (OS2Table) FontUtils.getTable(font, Tag.OS_2); 90 91 table.add(Arrays.asList( 92 new String[] { "Units per em", String.format("%d", headTable.unitsPerEm()) })); 93 table.add(Arrays.asList(new String[] { 94 "[xMin, xMax]", String.format("[%d, %d]", headTable.xMin(), headTable.xMax()) })); 95 table.add(Arrays.asList(new String[] { 96 "[yMin, yMax]", String.format("[%d, %d]", headTable.yMin(), headTable.yMax()) })); 97 table.add(Arrays.asList(new String[] { 98 "Smallest readable size (px per em)", String.format("%d", headTable.lowestRecPPEM()) })); 99 table.add( 100 Arrays.asList(new String[] { "hhea ascender", String.format("%d", hheaTable.ascender()) })); 101 table.add(Arrays.asList( 102 new String[] { "hhea descender", String.format("%d", hheaTable.descender()) })); 103 table.add(Arrays.asList( 104 new String[] { "hhea typographic line gap", String.format("%d", hheaTable.lineGap()) })); 105 table.add(Arrays.asList( 106 new String[] { "OS/2 Windows ascender", String.format("%d", os2Table.usWinAscent()) })); 107 table.add(Arrays.asList( 108 new String[] { "OS/2 Windows descender", String.format("%d", os2Table.usWinDescent()) })); 109 table.add(Arrays.asList(new String[] { 110 "OS/2 typographic ascender", String.format("%d", os2Table.sTypoAscender()) })); 111 table.add(Arrays.asList(new String[] { 112 "OS/2 typographic ascender", String.format("%d", os2Table.sTypoDescender()) })); 113 table.add(Arrays.asList(new String[] { 114 "OS/2 typographic line gap", String.format("%d", os2Table.sTypoLineGap()) })); 115 116 return table; 117 } 118 119 /** 120 * Gets a list of tables in the font as well as their sizes. 121 * 122 * This function returns a list of tables in the given font as well as their 123 * sizes, both in bytes and as fractions of the overall font size. The 124 * information for each font is contained in a TableInfo object. 125 * 126 * @param font 127 * the source font 128 * @return a list of information about tables in the font provided 129 */ listTables(Font font)130 public static DataDisplayTable listTables(Font font) { 131 String[] header = { "tag", "checksum", "length", "offset" }; 132 Align[] displayAlignment = { Align.Left, Align.Right, Align.Right, Align.Right }; 133 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 134 table.setAlignment(Arrays.asList(displayAlignment)); 135 136 // Total size of font 137 int fontSize = 0; 138 139 // Calculate font size 140 Iterator<? extends Table> fontTableIter = font.iterator(); 141 while (fontTableIter.hasNext()) { 142 Table fontTable = fontTableIter.next(); 143 fontSize += fontTable.headerLength(); 144 } 145 146 // Add table data to output string 147 fontTableIter = font.iterator(); 148 while (fontTableIter.hasNext()) { 149 Table fontTable = fontTableIter.next(); 150 String name = Tag.stringValue(fontTable.headerTag()); 151 String checksum = String.format("0x%0" + CHECKSUM_LENGTH + "X", fontTable.headerChecksum()); 152 int length = fontTable.headerLength(); 153 double lengthPercent = length * 100.0 / fontSize; 154 int offset = fontTable.headerOffset(); 155 156 // Add table data 157 String[] data = { name, checksum, 158 String.format("%s (%s%%)", length, twoDecimalPlaces.format(lengthPercent)), 159 String.format("%d", offset) }; 160 table.add(Arrays.asList(data)); 161 } 162 163 return table; 164 } 165 166 /** 167 * Gets a list of entries in the name table of a font. These entries contain 168 * information related to the font, such as the font name, style name, and 169 * copyright notices. 170 * 171 * @param font 172 * the source font 173 * @return a list of entries in the name table of the font 174 */ listNameEntries(Font font)175 public static DataDisplayTable listNameEntries(Font font) { 176 String[] header = { "Platform", "Encoding", "Language", "Name", "Value" }; 177 Align[] displayAlignment = { Align.Left, Align.Left, Align.Left, Align.Left, Align.Left }; 178 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 179 table.setAlignment(Arrays.asList(displayAlignment)); 180 181 NameTable nameTable = (NameTable) FontUtils.getTable(font, Tag.name); 182 for (NameEntry entry : nameTable) { 183 184 String eidEntry = ""; // Platform-specific encoding 185 String lidEntry = ""; // Language 186 187 switch (PlatformId.valueOf(entry.platformId())) { 188 case Unicode: 189 eidEntry = UnicodeEncodingId.valueOf(entry.encodingId()).toString(); 190 lidEntry = UnicodeLanguageId.valueOf(entry.languageId()).toString(); 191 break; 192 case Macintosh: 193 eidEntry = MacintoshEncodingId.valueOf(entry.encodingId()).toString(); 194 lidEntry = MacintoshLanguageId.valueOf(entry.languageId()).toString(); 195 break; 196 case Windows: 197 eidEntry = WindowsEncodingId.valueOf(entry.encodingId()).toString(); 198 lidEntry = WindowsLanguageId.valueOf(entry.languageId()).toString(); 199 break; 200 default: 201 break; 202 } 203 204 String[] data = { String.format( 205 "%s (id=%d)", PlatformId.valueOf(entry.platformId()).toString(), entry.platformId()), 206 String.format("%s (id=%d)", eidEntry, entry.encodingId()), 207 String.format("%s (id=%d)", lidEntry, entry.languageId()), 208 NameId.valueOf(entry.nameId()).toString(), entry.name() }; 209 table.add(Arrays.asList(data)); 210 } 211 212 return table; 213 } 214 215 /** 216 * Gets a list containing the platform ID, encoding ID, and format of all the 217 * cmaps in a font 218 * 219 * @param font 220 * the source font 221 * @return a list of information about the cmaps in the font 222 */ listCmaps(Font font)223 public static DataDisplayTable listCmaps(Font font) { 224 String[] header = { "Platform ID", "Encoding ID", "Format" }; 225 Align[] displayAlignment = { Align.Right, Align.Right, Align.Right }; 226 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 227 table.setAlignment(Arrays.asList(displayAlignment)); 228 229 // Add information about each individual cmap in the table 230 CMapTable cmapTable = FontUtils.getCMapTable(font); 231 for (CMap cmap : cmapTable) { 232 String[] data = { String.format("%d", cmap.platformId()), 233 String.format("%d", cmap.encodingId()), String.format("%d", cmap.format()) }; 234 table.add(Arrays.asList(data)); 235 } 236 237 return table; 238 } 239 240 /** 241 * Gets the number of valid characters in the given font 242 * 243 * @param font 244 * the source font 245 * @return the number of valid characters in the given font 246 * @throws UnsupportedOperationException 247 * if font does not contain a UCS-4 or UCS-2 cmap 248 */ numChars(Font font)249 public static int numChars(Font font) { 250 int numChars = 0; 251 CMap cmap = FontUtils.getUCSCMap(font); 252 253 // Find the number of characters that point to a valid glyph 254 for (int charId : cmap) { 255 if (cmap.glyphId(charId) != CMapTable.NOTDEF) { 256 // Valid glyph 257 numChars++; 258 } 259 } 260 261 return numChars; 262 } 263 264 /** 265 * Gets a list of code points of valid characters and their names in the given 266 * font. 267 * 268 * @param font 269 * the source font 270 * @return a list of code points of valid characters and their names in the 271 * given font. 272 * @throws UnsupportedOperationException 273 * if font does not contain a UCS-4 or UCS-2 cmap 274 */ listChars(Font font)275 public static DataDisplayTable listChars(Font font) { 276 String[] header = { "Code point", "Glyph ID", "Unicode-designated name for code point" }; 277 Align[] displayAlignment = { Align.Right, Align.Right, Align.Left }; 278 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 279 table.setAlignment(Arrays.asList(displayAlignment)); 280 281 // Iterate through all code points 282 CMap cmap = FontUtils.getUCSCMap(font); 283 for (int charId : cmap) { 284 int glyphId = cmap.glyphId(charId); 285 if (glyphId != CMapTable.NOTDEF) { 286 String[] data = { FontUtils.getFormattedCodePointString(charId), 287 String.format("%d", glyphId), UCharacter.getExtendedName(charId) }; 288 table.add(Arrays.asList(data)); 289 } 290 } 291 292 return table; 293 } 294 295 // Gets the code point and name of all the characters in the provided string 296 // for the font 297 // TODO public static DataDisplayTable listChars(Font font, String charString) 298 299 /** 300 * Gets a list of Unicode blocks covered by the font and the amount each block 301 * is covered. 302 * 303 * @param font 304 * the source font 305 * @return a list of Unicode blocks covered by the font 306 */ 307 // FIXME Find more elegant method of retrieving block data listCharBlockCoverage(Font font)308 public static DataDisplayTable listCharBlockCoverage(Font font) { 309 String[] header = { "Block", "Coverage" }; 310 Align[] displayAlignment = { Align.Left, Align.Right }; 311 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 312 table.setAlignment(Arrays.asList(displayAlignment)); 313 314 // Iterate through each block to check for coverage 315 CMap cmap = FontUtils.getUCSCMap(font); 316 int totalCount = 0; 317 for (int i = 0; i < UnicodeBlockData.numBlocks(); i++) { 318 String block = UnicodeBlockData.getBlockName(i); 319 UnicodeSet set = null; 320 try { 321 set = new UnicodeSet("[[:Block=" + block + ":]-[:gc=Unassigned:]-[:gc=Control:]]"); 322 } catch (IllegalIcuArgumentException e) { 323 continue; 324 } 325 int count = 0; 326 for (String charStr : set) { 327 if (cmap.glyphId(UCharacter.codePointAt(charStr, 0)) > 0) { 328 count++; 329 } 330 } 331 if (count > 0) { 332 table.add(Arrays.asList(new String[] { String.format( 333 "%s [%s, %s]", block, UnicodeBlockData.getBlockStartCode(i), 334 UnicodeBlockData.getBlockEndCode(i)), String.format("%d / %d", count, set.size()) })); 335 } 336 totalCount += count; 337 } 338 339 // Add control code points with valid glyphs to find the total number of 340 // unicode characters with valid glyphs 341 UnicodeSet controlSet = new UnicodeSet("[[:gc=Control:]]"); 342 for (String charStr : controlSet) { 343 if (cmap.glyphId(UCharacter.codePointAt(charStr, 0)) > 0) { 344 totalCount++; 345 } 346 } 347 int nonUnicodeCount = numChars(font) - totalCount; 348 if (nonUnicodeCount > 0) { 349 table.add(Arrays.asList(new String[] { "Unknown", String.format("%d", nonUnicodeCount) })); 350 } 351 352 return table; 353 } 354 355 /** 356 * Gets a list of scripts covered by the font and the amount each block is 357 * covered. 358 * 359 * @param font 360 * the source font 361 * @return a list of scripts covered by the font 362 */ listScriptCoverage(Font font)363 public static DataDisplayTable listScriptCoverage(Font font) { 364 String[] header = { "Script", "Coverage" }; 365 Align[] displayAlignment = { Align.Left, Align.Right }; 366 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 367 table.setAlignment(Arrays.asList(displayAlignment)); 368 HashMap<Integer, Integer> coveredScripts = new HashMap<Integer, Integer>(); 369 370 // Add to script count for the script each code point belongs to 371 CMap cmap = FontUtils.getUCSCMap(font); 372 for (int charId : cmap) { 373 if (cmap.glyphId(charId) != CMapTable.NOTDEF) { 374 int scriptCode = UScript.getScript(charId); 375 int scriptCount = 1; 376 if (coveredScripts.containsKey(scriptCode)) { 377 scriptCount += coveredScripts.get(scriptCode); 378 } 379 coveredScripts.put(scriptCode, scriptCount); 380 } 381 } 382 383 // For each covered script, find the total size of the script and add 384 // coverage to table 385 Set<Integer> sortedScripts = new TreeSet<Integer>(coveredScripts.keySet()); 386 int unknown = 0; 387 for (Integer scriptCode : sortedScripts) { 388 UnicodeSet scriptSet = null; 389 String scriptName = UScript.getName(scriptCode); 390 try { 391 scriptSet = new UnicodeSet("[[:" + scriptName + ":]]"); 392 } catch (IllegalIcuArgumentException e) { 393 unknown += coveredScripts.get(scriptCode); 394 continue; 395 } 396 397 table.add(Arrays.asList(new String[] { scriptName, 398 String.format("%d / %d", coveredScripts.get(scriptCode), scriptSet.size()) })); 399 } 400 if (unknown > 0) { 401 table.add(Arrays.asList(new String[] { "Unsupported script", String.format("%d", unknown) })); 402 } 403 404 return table; 405 } 406 407 /** 408 * Gets a list of characters needed to fully cover scripts partially covered 409 * by the font 410 * 411 * @param font 412 * the source font 413 * @return a list of characters needed to fully cover partially-covered 414 * scripts 415 */ listCharsNeededToCoverScript(Font font)416 public static DataDisplayTable listCharsNeededToCoverScript(Font font) { 417 String[] header = { "Script", "Code Point", "Name" }; 418 Align[] displayAlignment = { Align.Left, Align.Right, Align.Left }; 419 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 420 table.setAlignment(Arrays.asList(displayAlignment)); 421 HashMap<Integer, UnicodeSet> coveredScripts = new HashMap<Integer, UnicodeSet>(); 422 423 // Iterate through each set 424 CMap cmap = FontUtils.getUCSCMap(font); 425 for (int charId : cmap) { 426 if (cmap.glyphId(charId) != CMapTable.NOTDEF) { 427 int scriptCode = UScript.getScript(charId); 428 if (scriptCode == UScript.UNKNOWN) { 429 continue; 430 } 431 432 UnicodeSet scriptSet = null; 433 if (!coveredScripts.containsKey(scriptCode)) { 434 // New covered script found, create set 435 try { 436 scriptSet = new UnicodeSet( 437 "[[:" + UScript.getName(scriptCode) + ":]-[:gc=Unassigned:]-[:gc=Control:]]"); 438 } catch (IllegalIcuArgumentException e) { 439 continue; 440 } 441 coveredScripts.put(scriptCode, scriptSet); 442 } else { 443 // Set for script already exists, retrieve for character removal 444 scriptSet = coveredScripts.get(scriptCode); 445 } 446 scriptSet.remove(UCharacter.toString(charId)); 447 } 448 } 449 450 // Insert into table in order 451 Set<Integer> sortedScripts = new TreeSet<Integer>(coveredScripts.keySet()); 452 for (Integer scriptCode : sortedScripts) { 453 UnicodeSet uSet = coveredScripts.get(scriptCode); 454 for (String charStr : uSet) { 455 int codePoint = UCharacter.codePointAt(charStr, 0); 456 table.add(Arrays.asList(new String[] { String.format("%s", UScript.getName(scriptCode)), 457 FontUtils.getFormattedCodePointString(codePoint), 458 UCharacter.getExtendedName(codePoint) })); 459 } 460 } 461 462 return table; 463 } 464 465 /** 466 * Gets the number of glyphs in the given font 467 * 468 * @param font 469 * the source font 470 * @return the number of glyphs in the font 471 * @throws UnsupportedOperationException 472 * if font does not contain a valid glyf table 473 */ numGlyphs(Font font)474 public static int numGlyphs(Font font) { 475 return ((MaximumProfileTable) FontUtils.getTable(font, Tag.maxp)).numGlyphs(); 476 } 477 478 /** 479 * Gets a list of minimum and maximum x and y dimensions for the glyphs in the 480 * font. This is based on the reported min and max values for each glyph and 481 * not on the actual outline sizes. 482 * 483 * @param font 484 * the source font 485 * @return a list of glyph dimensions for the font 486 */ listGlyphDimensionBounds(Font font)487 public static DataDisplayTable listGlyphDimensionBounds(Font font) { 488 String[] header = { "Dimension", "Value" }; 489 Align[] displayAlignment = { Align.Left, Align.Right }; 490 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 491 table.setAlignment(Arrays.asList(displayAlignment)); 492 493 LocaTable locaTable = FontUtils.getLocaTable(font); 494 GlyphTable glyfTable = FontUtils.getGlyphTable(font); 495 496 // Initialise boundaries 497 int xMin = Integer.MAX_VALUE; 498 int yMin = Integer.MAX_VALUE; 499 int xMax = Integer.MIN_VALUE; 500 int yMax = Integer.MIN_VALUE; 501 502 // Find boundaries 503 for (int i = 0; i < locaTable.numGlyphs(); i++) { 504 Glyph glyph = glyfTable.glyph(locaTable.glyphOffset(i), locaTable.glyphLength(i)); 505 if (glyph.xMin() < xMin) { 506 xMin = glyph.xMin(); 507 } 508 if (glyph.yMin() < yMin) { 509 yMin = glyph.yMin(); 510 } 511 if (glyph.xMax() > xMax) { 512 xMax = glyph.xMax(); 513 } 514 if (glyph.yMax() > yMax) { 515 yMax = glyph.yMax(); 516 } 517 } 518 519 table.add(Arrays.asList(new String[] { "xMin", String.format("%d", xMin) })); 520 table.add(Arrays.asList(new String[] { "xMax", String.format("%d", xMax) })); 521 table.add(Arrays.asList(new String[] { "yMin", String.format("%d", yMin) })); 522 table.add(Arrays.asList(new String[] { "yMax", String.format("%d", yMax) })); 523 524 return table; 525 } 526 527 /** 528 * Gets the size of hinting instructions in the glyph table, both in bytes and 529 * as a fraction of the glyph table size. 530 * 531 * @param font 532 * the source font 533 * @return the amount of hinting that is contained in the font 534 */ hintingSize(Font font)535 public static String hintingSize(Font font) { 536 int instrSize = 0; 537 538 LocaTable locaTable = FontUtils.getLocaTable(font); 539 GlyphTable glyfTable = FontUtils.getGlyphTable(font); 540 541 // Get hinting information from each glyph 542 for (int i = 0; i < locaTable.numGlyphs(); i++) { 543 Glyph glyph = glyfTable.glyph(locaTable.glyphOffset(i), locaTable.glyphLength(i)); 544 instrSize += glyph.instructionSize(); 545 } 546 547 double percentage = instrSize * 100.0 / glyfTable.headerLength(); 548 return String.format( 549 "%d bytes (%s%% of glyf table)", instrSize, twoDecimalPlaces.format(percentage)); 550 } 551 552 /** 553 * Gets a list of glyphs in the font that are used as subglyphs and the number 554 * of times each subglyph is used as a subglyph 555 * 556 * @param font 557 * the source font 558 * @return the number of glyphs in the font that are used as subglyphs of 559 * other glyphs more than once 560 */ listSubglyphFrequency(Font font)561 public static DataDisplayTable listSubglyphFrequency(Font font) { 562 Map<Integer, Integer> subglyphFreq = new HashMap<Integer, Integer>(); 563 String[] header = { "Glyph ID", "Frequency" }; 564 Align[] displayAlignment = { Align.Right, Align.Right }; 565 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 566 table.setAlignment(Arrays.asList(displayAlignment)); 567 568 LocaTable locaTable = FontUtils.getLocaTable(font); 569 GlyphTable glyfTable = FontUtils.getGlyphTable(font); 570 571 // Add subglyphs of all composite glyphs to hashmap 572 for (int i = 0; i < locaTable.numGlyphs(); i++) { 573 Glyph glyph = glyfTable.glyph(locaTable.glyphOffset(i), locaTable.glyphLength(i)); 574 if (glyph.glyphType() == GlyphType.Composite) { 575 CompositeGlyph cGlyph = (CompositeGlyph) glyph; 576 577 // Add all subglyphs of this glyph to hashmap 578 for (int j = 0; j < cGlyph.numGlyphs(); j++) { 579 int subglyphId = cGlyph.glyphIndex(j); 580 int frequency = 1; 581 if (subglyphFreq.containsKey(subglyphId)) { 582 frequency += subglyphFreq.get(subglyphId); 583 } 584 subglyphFreq.put(subglyphId, frequency); 585 } 586 } 587 } 588 589 // Add frequency data to table 590 int numSubglyphs = 0; 591 Set<Integer> sortedKeySet = new TreeSet<Integer>(subglyphFreq.keySet()); 592 for (Integer key : sortedKeySet) { 593 String[] data = { key.toString(), subglyphFreq.get(key).toString() }; 594 table.add(Arrays.asList(data)); 595 } 596 597 return table; 598 } 599 600 /** 601 * Gets a list of IDs for glyphs that are not mapped by any cmap in the font 602 * 603 * @param font 604 * the source font 605 * @return a list of unmapped glyphs 606 */ listUnmappedGlyphs(Font font)607 public static DataDisplayTable listUnmappedGlyphs(Font font) { 608 String[] header = { "Glyph ID" }; 609 Align[] displayAlignment = { Align.Right }; 610 DataDisplayTable table = new DataDisplayTable(Arrays.asList(header)); 611 table.setAlignment(Arrays.asList(displayAlignment)); 612 613 // Get a set of all mapped glyph IDs 614 Set<Integer> mappedGlyphs = new HashSet<Integer>(); 615 CMapTable cmapTable = FontUtils.getCMapTable(font); 616 for (CMap cmap : cmapTable) { 617 for (Integer codePoint : cmap) { 618 mappedGlyphs.add(cmap.glyphId(codePoint)); 619 } 620 } 621 622 // Iterate through all glyph IDs and check if in the set 623 LocaTable locaTable = FontUtils.getLocaTable(font); 624 for (int i = 0; i < locaTable.numGlyphs(); i++) { 625 if (!mappedGlyphs.contains(i)) { 626 table.add(Arrays.asList(new String[] { String.format("%d", i) })); 627 } 628 } 629 630 return table; 631 } 632 633 // TODO Calculate savings of subglyphs 634 // public static int subglyphSavings(Font font) {} 635 636 // TODO Find the maximum glyph nesting depth in a font 637 // public static int glyphNestingDepth(Font font) {} 638 639 // TODO Find the maximum glyph nexting depth in a font using the maxp table 640 // public static int glyphNestingDepthMaxp(Font font) {} 641 642 // TODO Find number of code points that use simple glyphs and number of code 643 // points that use composite glyphs (and provide a list of code points for 644 // each one) 645 // public static int listSimpleGlyphs(Font font) {} 646 // public static int listCompositeGlyphs(Font font) {} 647 648 } 649