• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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