1 # Copyright 2013 Google, Inc. All Rights Reserved. 2 # 3 # Google Author(s): Behdad Esfahbod 4 5 """GUI font inspector. 6 """ 7 8 from __future__ import print_function, division, absolute_import 9 from fontTools.misc.py23 import * 10 from fontTools import misc, ttLib, cffLib 11 import pygtk 12 pygtk.require('2.0') 13 import gtk 14 import sys 15 16 17 18 class Row(object): 19 def __init__(self, parent, index, key, value, font): 20 self._parent = parent 21 self._index = index 22 self._key = key 23 self._value = value 24 self._font = font 25 26 if isinstance(value, ttLib.TTFont): 27 self._add_font(value) 28 return 29 30 if not isinstance(value, basestring): 31 # Try sequences 32 is_sequence = True 33 try: 34 len(value) 35 iter(value) 36 # It's hard to differentiate list-type sequences 37 # from dict-type ones. Try fetching item 0. 38 value[0] 39 except (TypeError, AttributeError, KeyError, IndexError): 40 is_sequence = False 41 if is_sequence: 42 self._add_list(key, value) 43 return 44 if hasattr(value, '__dict__'): 45 self._add_object(key, value) 46 return 47 if hasattr(value, 'items'): 48 self._add_dict(key, value) 49 return 50 51 if isinstance(value, basestring): 52 self._value_str = '"'+value+'"' 53 self._children = [] 54 return 55 56 # Everything else 57 self._children = [] 58 59 def _filter_items(self): 60 items = [] 61 for k,v in self._items: 62 if isinstance(v, ttLib.TTFont): 63 continue 64 if k in ['reader', 'file', 'tableTag', 'compileStatus', 'recurse']: 65 continue 66 if isinstance(k, basestring) and k[0] == '_': 67 continue 68 items.append((k,v)) 69 self._items = items 70 71 def _add_font(self, font): 72 self._items = [(tag,font[tag]) for tag in font.keys()] 73 74 def _add_object(self, key, value): 75 # Make sure item is decompiled 76 try: 77 value["asdf"] 78 except (AttributeError, KeyError, TypeError, ttLib.TTLibError): 79 pass 80 if isinstance(value, ttLib.getTableModule('glyf').Glyph): 81 # Glyph type needs explicit expanding to be useful 82 value.expand(self._font['glyf']) 83 if isinstance(value, misc.psCharStrings.T2CharString): 84 try: 85 value.decompile() 86 except TypeError: # Subroutines can't be decompiled 87 pass 88 if isinstance(value, cffLib.BaseDict): 89 for k in value.rawDict.keys(): 90 getattr(value, k) 91 if isinstance(value, cffLib.Index): 92 # Load all items 93 for i in range(len(value)): 94 value[i] 95 # Discard offsets as should not be needed anymore 96 if hasattr(value, 'offsets'): 97 del value.offsets 98 99 self._value_str = value.__class__.__name__ 100 if isinstance(value, ttLib.tables.DefaultTable.DefaultTable): 101 self._value_str += ' (%d Bytes)' % self._font.reader.tables[key].length 102 self._items = sorted(value.__dict__.items()) 103 self._filter_items() 104 105 def _add_dict(self, key, value): 106 self._value_str = '%s of %d items' % (value.__class__.__name__, len(value)) 107 self._items = sorted(value.items()) 108 109 def _add_list(self, key, value): 110 if len(value) and len(value) <= 32: 111 self._value_str = str(value) 112 else: 113 self._value_str = '%s of %d items' % (value.__class__.__name__, 114 len(value)) 115 self._items = list(enumerate(value)) 116 117 def __len__(self): 118 if hasattr(self, '_children'): 119 return len(self._children) 120 if hasattr(self, '_items'): 121 return len(self._items) 122 assert False 123 124 def _ensure_children(self): 125 if hasattr(self, '_children'): 126 return 127 children = [] 128 for i,(k,v) in enumerate(self._items): 129 children.append(Row(self, i, k, v, self._font)) 130 self._children = children 131 del self._items 132 133 def __getitem__(self, n): 134 if n >= len(self): 135 return None 136 if not hasattr(self, '_children'): 137 self._children = [None] * len(self) 138 c = self._children[n] 139 if c is None: 140 k,v = self._items[n] 141 c = self._children[n] = Row(self, n, k, v, self._font) 142 self._items[n] = None 143 return c 144 145 def get_parent(self): 146 return self._parent 147 148 def get_index(self): 149 return self._index 150 151 def get_key(self): 152 return self._key 153 154 def get_value(self): 155 return self._value 156 157 def get_value_str(self): 158 if hasattr(self,'_value_str'): 159 return self._value_str 160 return str(self._value) 161 162 class FontTreeModel(gtk.GenericTreeModel): 163 164 __gtype_name__ = 'FontTreeModel' 165 166 def __init__(self, font): 167 super(FontTreeModel, self).__init__() 168 self._columns = (str, str) 169 self.font = font 170 self._root = Row(None, 0, "font", font, font) 171 172 def on_get_flags(self): 173 return 0 174 175 def on_get_n_columns(self): 176 return len(self._columns) 177 178 def on_get_column_type(self, index): 179 return self._columns[index] 180 181 def on_get_iter(self, path): 182 rowref = self._root 183 while path: 184 rowref = rowref[path[0]] 185 path = path[1:] 186 return rowref 187 188 def on_get_path(self, rowref): 189 path = [] 190 while rowref != self._root: 191 path.append(rowref.get_index()) 192 rowref = rowref.get_parent() 193 path.reverse() 194 return tuple(path) 195 196 def on_get_value(self, rowref, column): 197 if column == 0: 198 return rowref.get_key() 199 else: 200 return rowref.get_value_str() 201 202 def on_iter_next(self, rowref): 203 return rowref.get_parent()[rowref.get_index() + 1] 204 205 def on_iter_children(self, rowref): 206 return rowref[0] 207 208 def on_iter_has_child(self, rowref): 209 return bool(len(rowref)) 210 211 def on_iter_n_children(self, rowref): 212 return len(rowref) 213 214 def on_iter_nth_child(self, rowref, n): 215 if not rowref: rowref = self._root 216 return rowref[n] 217 218 def on_iter_parent(self, rowref): 219 return rowref.get_parent() 220 221 class Inspect(object): 222 223 def _delete_event(self, widget, event, data=None): 224 gtk.main_quit() 225 return False 226 227 def __init__(self, fontfile): 228 229 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) 230 self.window.set_title("%s - pyftinspect" % fontfile) 231 self.window.connect("delete_event", self._delete_event) 232 self.window.set_size_request(400, 600) 233 234 self.scrolled_window = gtk.ScrolledWindow() 235 self.window.add(self.scrolled_window) 236 237 self.font = ttLib.TTFont(fontfile, lazy=True) 238 self.treemodel = FontTreeModel(self.font) 239 self.treeview = gtk.TreeView(self.treemodel) 240 #self.treeview.set_reorderable(True) 241 242 for i in range(2): 243 col_name = ('Key', 'Value')[i] 244 col = gtk.TreeViewColumn(col_name) 245 col.set_sort_column_id(-1) 246 self.treeview.append_column(col) 247 248 cell = gtk.CellRendererText() 249 col.pack_start(cell, True) 250 col.add_attribute(cell, 'text', i) 251 252 self.treeview.set_search_column(1) 253 self.scrolled_window.add(self.treeview) 254 self.window.show_all() 255 256 def main(args): 257 if len(args) < 1: 258 print("usage: pyftinspect font...", file=sys.stderr) 259 sys.exit(1) 260 for arg in args: 261 Inspect(arg) 262 gtk.main() 263 264 if __name__ == "__main__": 265 main(sys.argv[1:]) 266