subset.py revision bed5f61e138f50c40fe6dadff3fdf07c29010d2a
12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Copyright 2013 Google, Inc. All Rights Reserved. 22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Google Author(s): Behdad Esfahbod 42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)"""Python OpenType Layout Subsetter. 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)Later grown into full OpenType subsetter, supporting all standard tables. 8868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)""" 9868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import sys 112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import struct 122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import time 132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import array 142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)from fontTools import ttLib 162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)from fontTools.ttLib.tables import otTables 172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)from fontTools.misc import psCharStrings 182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)from fontTools.pens import basePen 192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def _add_method(*clazzes): 222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Returns a decorator function that adds a new method to one or 23868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) more classes.""" 242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def wrapper(method): 252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for clazz in clazzes: 26b2df76ea8fec9e32f6f3718986dba0d95315b29cTorne (Richard Coles) assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.' 27c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) assert not hasattr(clazz, method.func_name), \ 282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) "Oops, class '%s' has method '%s'." % (clazz.__name__, 292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) method.func_name) 302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) setattr(clazz, method.func_name, method) 312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return None 322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return wrapper 332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def _uniq_sort(l): 352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return sorted(set(l)) 362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def _set_update(s, *others): 382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Jython's set.update only takes one other argument. 392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Emulate real set.update... 402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for other in others: 412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) s.update(other) 422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 43868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 44868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)@_add_method(otTables.Coverage) 45868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)def intersect(self, glyphs): 46868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) "Returns ascending list of matching coverage values." 47868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) return [i for i,g in enumerate(self.glyphs) if g in glyphs] 482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.Coverage) 502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def intersect_glyphs(self, glyphs): 512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) "Returns set of intersecting glyphs." 522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return set(g for g in self.glyphs if g in glyphs) 532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.Coverage) 552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def subset(self, glyphs): 562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) "Returns ascending list of remaining coverage values." 572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) indices = self.intersect(glyphs) 582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.glyphs = [g for g in self.glyphs if g in glyphs] 592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return indices 602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.Coverage) 622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def remap(self, coverage_map): 632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) "Remaps coverage." 642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.glyphs = [self.glyphs[i] for i in coverage_map] 652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.ClassDef) 672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def intersect(self, glyphs): 682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) "Returns ascending list of matching class values." 692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return _uniq_sort( 702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ([0] if any(g not in self.classDefs for g in glyphs) else []) + 712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) [v for g,v in self.classDefs.iteritems() if g in glyphs]) 722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.ClassDef) 742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def intersect_class(self, glyphs, klass): 752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) "Returns set of glyphs matching class." 762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if klass == 0: 772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return set(g for g in glyphs if g not in self.classDefs) 782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return set(g for g,v in self.classDefs.iteritems() 792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if v == klass and g in glyphs) 802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.ClassDef) 822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def subset(self, glyphs, remap=False): 832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) "Returns ascending list of remaining classes." 842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.classDefs = dict((g,v) for g,v in self.classDefs.iteritems() if g in glyphs) 852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Note: while class 0 has the special meaning of "not matched", 862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # if no glyph will ever /not match/, we can optimize class 0 out too. 872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) indices = _uniq_sort( 882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ([0] if any(g not in self.classDefs for g in glyphs) else []) + 892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.classDefs.values()) 902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if remap: 91868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) self.remap(indices) 92868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) return indices 93868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 94868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)@_add_method(otTables.ClassDef) 95868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)def remap(self, class_map): 96868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) "Remaps classes." 972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.classDefs = dict((g,class_map.index(v)) 982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for g,v in self.classDefs.iteritems()) 99c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.SingleSubst) 1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def closure_glyphs(self, s, cur_glyphs=None): 1022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if cur_glyphs == None: cur_glyphs = s.glyphs 1032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format in [1, 2]: 1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) s.glyphs.update(v for g,v in self.mapping.iteritems() if g in cur_glyphs) 1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.SingleSubst) 1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def subset_glyphs(self, s): 1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format in [1, 2]: 1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.mapping = dict((g,v) for g,v in self.mapping.iteritems() 1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if g in s.glyphs and v in s.glyphs) 1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return bool(self.mapping) 1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.MultipleSubst) 118c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def closure_glyphs(self, s, cur_glyphs=None): 119c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if cur_glyphs == None: cur_glyphs = s.glyphs 120c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if self.Format == 1: 121c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) indices = self.Coverage.intersect(cur_glyphs) 122c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) _set_update(s.glyphs, *(self.Sequence[i].Substitute for i in indices)) 123c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) else: 1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.MultipleSubst) 1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def subset_glyphs(self, s): 1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format == 1: 1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) indices = self.Coverage.subset(s.glyphs) 1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.Sequence = [self.Sequence[i] for i in indices] 1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Now drop rules generating glyphs we don't want 1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) indices = [i for i,seq in enumerate(self.Sequence) 1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if all(sub in s.glyphs for sub in seq.Substitute)] 1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.Sequence = [self.Sequence[i] for i in indices] 1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.Coverage.remap(indices) 1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.SequenceCount = len(self.Sequence) 1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return bool(self.SequenceCount) 13890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) else: 1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.AlternateSubst) 1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def closure_glyphs(self, s, cur_glyphs=None): 143c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if cur_glyphs == None: cur_glyphs = s.glyphs 1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format == 1: 1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) _set_update(s.glyphs, *(vlist for g,vlist in self.alternates.iteritems() 1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if g in cur_glyphs)) 1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.AlternateSubst) 1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def subset_glyphs(self, s): 1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format == 1: 1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.alternates = dict((g,vlist) 1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for g,vlist in self.alternates.iteritems() 1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if g in s.glyphs and 1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) all(v in s.glyphs for v in vlist)) 1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return bool(self.alternates) 1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.LigatureSubst) 1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def closure_glyphs(self, s, cur_glyphs=None): 1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if cur_glyphs == None: cur_glyphs = s.glyphs 1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format == 1: 1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) _set_update(s.glyphs, *([seq.LigGlyph for seq in seqs 1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if all(c in s.glyphs for c in seq.Component)] 1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for g,seqs in self.ligatures.iteritems() 1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if g in cur_glyphs)) 1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.LigatureSubst) 1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def subset_glyphs(self, s): 1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format == 1: 1752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems() 1762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if g in s.glyphs) 1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.ligatures = dict((g,[seq for seq in seqs 1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if seq.LigGlyph in s.glyphs and 1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) all(c in s.glyphs for c in seq.Component)]) 1802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for g,seqs in self.ligatures.iteritems()) 181c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems() if v) 1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return bool(self.ligatures) 1832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.ReverseChainSingleSubst) 1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def closure_glyphs(self, s, cur_glyphs=None): 1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if cur_glyphs == None: cur_glyphs = s.glyphs 1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format == 1: 1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) indices = self.Coverage.intersect(cur_glyphs) 1912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if(not indices or 1922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) not all(c.intersect(s.glyphs) 1932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for c in self.LookAheadCoverage + self.BacktrackCoverage)): 1942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return 1952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) s.glyphs.update(self.Substitute[i] for i in indices) 1962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 1982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.ReverseChainSingleSubst) 2002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def subset_glyphs(self, s): 2012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format == 1: 2022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) indices = self.Coverage.subset(s.glyphs) 2032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.Substitute = [self.Substitute[i] for i in indices] 2042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Now drop rules generating glyphs we don't want 2052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) indices = [i for i,sub in enumerate(self.Substitute) 2062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if sub in s.glyphs] 2072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.Substitute = [self.Substitute[i] for i in indices] 2082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.Coverage.remap(indices) 2092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.GlyphCount = len(self.Substitute) 2102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return bool(self.GlyphCount and 2112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) all(c.subset(s.glyphs) 2122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for c in self.LookAheadCoverage+self.BacktrackCoverage)) 2132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 2142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 215c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 2162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.SinglePos) 2172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def subset_glyphs(self, s): 2182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format == 1: 2192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return len(self.Coverage.subset(s.glyphs)) 2202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) elif self.Format == 2: 2212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) indices = self.Coverage.subset(s.glyphs) 2222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.Value = [self.Value[i] for i in indices] 2232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.ValueCount = len(self.Value) 2242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return bool(self.ValueCount) 2252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 2262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 2272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.SinglePos) 2292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def prune_post_subset(self, options): 2302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if not options.hinting: 2312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Drop device tables 2322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.ValueFormat &= ~0x00F0 2332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return True 2342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@_add_method(otTables.PairPos) 2362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def subset_glyphs(self, s): 2372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.Format == 1: 2382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) indices = self.Coverage.subset(s.glyphs) 2392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.PairSet = [self.PairSet[i] for i in indices] 2402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for p in self.PairSet: 2412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) p.PairValueRecord = [r for r in p.PairValueRecord 242c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if r.SecondGlyph in s.glyphs] 243c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) p.PairValueCount = len(p.PairValueRecord) 244c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.PairSet = [p for p in self.PairSet if p.PairValueCount] 245c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.PairSetCount = len(self.PairSet) 246c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return bool(self.PairSetCount) 247c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) elif self.Format == 2: 248c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) class1_map = self.ClassDef1.subset(s.glyphs, remap=True) 249c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) class2_map = self.ClassDef2.subset(s.glyphs, remap=True) 250c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.Class1Record = [self.Class1Record[i] for i in class1_map] 251c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) for c in self.Class1Record: 252c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) c.Class2Record = [c.Class2Record[i] for i in class2_map] 253c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.Class1Count = len(class1_map) 254c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.Class2Count = len(class2_map) 255c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return bool(self.Class1Count and 256c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.Class2Count and 257c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.Coverage.subset(s.glyphs)) 258c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) else: 259c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 260c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 261c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)@_add_method(otTables.PairPos) 262c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def prune_post_subset(self, options): 263c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if not options.hinting: 264c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) # Drop device tables 265c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.ValueFormat1 &= ~0x00F0 266c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.ValueFormat2 &= ~0x00F0 267c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return True 268c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 269c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)@_add_method(otTables.CursivePos) 270c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def subset_glyphs(self, s): 271c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if self.Format == 1: 272c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) indices = self.Coverage.subset(s.glyphs) 273c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices] 274c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.EntryExitCount = len(self.EntryExitRecord) 275c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) return bool(self.EntryExitCount) 276c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) else: 277c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) assert 0, "unknown format: %s" % self.Format 278c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 279c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)@_add_method(otTables.Anchor) 280c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)def prune_hints(self): 281c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) # Drop device tables / contour anchor point 282c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) self.Format = 1 283c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 284868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)@_add_method(otTables.CursivePos) 285868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)def prune_post_subset(self, options): 286868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if not options.hinting: 287868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) for rec in self.EntryExitRecord: 288868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if rec.EntryAnchor: rec.EntryAnchor.prune_hints() 289868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if rec.ExitAnchor: rec.ExitAnchor.prune_hints() 290868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) return True 291868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 292868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)@_add_method(otTables.MarkBasePos) 293868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)def subset_glyphs(self, s): 294 if self.Format == 1: 295 mark_indices = self.MarkCoverage.subset(s.glyphs) 296 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i] 297 for i in mark_indices] 298 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord) 299 base_indices = self.BaseCoverage.subset(s.glyphs) 300 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i] 301 for i in base_indices] 302 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord) 303 # Prune empty classes 304 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord) 305 self.ClassCount = len(class_indices) 306 for m in self.MarkArray.MarkRecord: 307 m.Class = class_indices.index(m.Class) 308 for b in self.BaseArray.BaseRecord: 309 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices] 310 return bool(self.ClassCount and 311 self.MarkArray.MarkCount and 312 self.BaseArray.BaseCount) 313 else: 314 assert 0, "unknown format: %s" % self.Format 315 316@_add_method(otTables.MarkBasePos) 317def prune_post_subset(self, options): 318 if not options.hinting: 319 for m in self.MarkArray.MarkRecord: 320 m.MarkAnchor.prune_hints() 321 for b in self.BaseArray.BaseRecord: 322 for a in b.BaseAnchor: 323 a.prune_hints() 324 return True 325 326@_add_method(otTables.MarkLigPos) 327def subset_glyphs(self, s): 328 if self.Format == 1: 329 mark_indices = self.MarkCoverage.subset(s.glyphs) 330 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i] 331 for i in mark_indices] 332 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord) 333 ligature_indices = self.LigatureCoverage.subset(s.glyphs) 334 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i] 335 for i in ligature_indices] 336 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach) 337 # Prune empty classes 338 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord) 339 self.ClassCount = len(class_indices) 340 for m in self.MarkArray.MarkRecord: 341 m.Class = class_indices.index(m.Class) 342 for l in self.LigatureArray.LigatureAttach: 343 for c in l.ComponentRecord: 344 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices] 345 return bool(self.ClassCount and 346 self.MarkArray.MarkCount and 347 self.LigatureArray.LigatureCount) 348 else: 349 assert 0, "unknown format: %s" % self.Format 350 351@_add_method(otTables.MarkLigPos) 352def prune_post_subset(self, options): 353 if not options.hinting: 354 for m in self.MarkArray.MarkRecord: 355 m.MarkAnchor.prune_hints() 356 for l in self.LigatureArray.LigatureAttach: 357 for c in l.ComponentRecord: 358 for a in c.LigatureAnchor: 359 a.prune_hints() 360 return True 361 362@_add_method(otTables.MarkMarkPos) 363def subset_glyphs(self, s): 364 if self.Format == 1: 365 mark1_indices = self.Mark1Coverage.subset(s.glyphs) 366 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i] 367 for i in mark1_indices] 368 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord) 369 mark2_indices = self.Mark2Coverage.subset(s.glyphs) 370 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i] 371 for i in mark2_indices] 372 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record) 373 # Prune empty classes 374 class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord) 375 self.ClassCount = len(class_indices) 376 for m in self.Mark1Array.MarkRecord: 377 m.Class = class_indices.index(m.Class) 378 for b in self.Mark2Array.Mark2Record: 379 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices] 380 return bool(self.ClassCount and 381 self.Mark1Array.MarkCount and 382 self.Mark2Array.MarkCount) 383 else: 384 assert 0, "unknown format: %s" % self.Format 385 386@_add_method(otTables.MarkMarkPos) 387def prune_post_subset(self, options): 388 if not options.hinting: 389 # Drop device tables or contour anchor point 390 for m in self.Mark1Array.MarkRecord: 391 m.MarkAnchor.prune_hints() 392 for b in self.Mark2Array.Mark2Record: 393 for m in b.Mark2Anchor: 394 m.prune_hints() 395 return True 396 397@_add_method(otTables.SingleSubst, 398 otTables.MultipleSubst, 399 otTables.AlternateSubst, 400 otTables.LigatureSubst, 401 otTables.ReverseChainSingleSubst, 402 otTables.SinglePos, 403 otTables.PairPos, 404 otTables.CursivePos, 405 otTables.MarkBasePos, 406 otTables.MarkLigPos, 407 otTables.MarkMarkPos) 408def subset_lookups(self, lookup_indices): 409 pass 410 411@_add_method(otTables.SingleSubst, 412 otTables.MultipleSubst, 413 otTables.AlternateSubst, 414 otTables.LigatureSubst, 415 otTables.ReverseChainSingleSubst, 416 otTables.SinglePos, 417 otTables.PairPos, 418 otTables.CursivePos, 419 otTables.MarkBasePos, 420 otTables.MarkLigPos, 421 otTables.MarkMarkPos) 422def collect_lookups(self): 423 return [] 424 425@_add_method(otTables.SingleSubst, 426 otTables.MultipleSubst, 427 otTables.AlternateSubst, 428 otTables.LigatureSubst, 429 otTables.ContextSubst, 430 otTables.ChainContextSubst, 431 otTables.ReverseChainSingleSubst, 432 otTables.SinglePos, 433 otTables.PairPos, 434 otTables.CursivePos, 435 otTables.MarkBasePos, 436 otTables.MarkLigPos, 437 otTables.MarkMarkPos, 438 otTables.ContextPos, 439 otTables.ChainContextPos) 440def prune_pre_subset(self, options): 441 return True 442 443@_add_method(otTables.SingleSubst, 444 otTables.MultipleSubst, 445 otTables.AlternateSubst, 446 otTables.LigatureSubst, 447 otTables.ReverseChainSingleSubst, 448 otTables.ContextSubst, 449 otTables.ChainContextSubst, 450 otTables.ContextPos, 451 otTables.ChainContextPos) 452def prune_post_subset(self, options): 453 return True 454 455@_add_method(otTables.SingleSubst, 456 otTables.AlternateSubst, 457 otTables.ReverseChainSingleSubst) 458def may_have_non_1to1(self): 459 return False 460 461@_add_method(otTables.MultipleSubst, 462 otTables.LigatureSubst, 463 otTables.ContextSubst, 464 otTables.ChainContextSubst) 465def may_have_non_1to1(self): 466 return True 467 468@_add_method(otTables.ContextSubst, 469 otTables.ChainContextSubst, 470 otTables.ContextPos, 471 otTables.ChainContextPos) 472def __classify_context(self): 473 474 class ContextHelper(object): 475 def __init__(self, klass, Format): 476 if klass.__name__.endswith('Subst'): 477 Typ = 'Sub' 478 Type = 'Subst' 479 else: 480 Typ = 'Pos' 481 Type = 'Pos' 482 if klass.__name__.startswith('Chain'): 483 Chain = 'Chain' 484 else: 485 Chain = '' 486 ChainTyp = Chain+Typ 487 488 self.Typ = Typ 489 self.Type = Type 490 self.Chain = Chain 491 self.ChainTyp = ChainTyp 492 493 self.LookupRecord = Type+'LookupRecord' 494 495 if Format == 1: 496 Coverage = lambda r: r.Coverage 497 ChainCoverage = lambda r: r.Coverage 498 ContextData = lambda r:(None,) 499 ChainContextData = lambda r:(None, None, None) 500 RuleData = lambda r:(r.Input,) 501 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead) 502 SetRuleData = None 503 ChainSetRuleData = None 504 elif Format == 2: 505 Coverage = lambda r: r.Coverage 506 ChainCoverage = lambda r: r.Coverage 507 ContextData = lambda r:(r.ClassDef,) 508 ChainContextData = lambda r:(r.LookAheadClassDef, 509 r.InputClassDef, 510 r.BacktrackClassDef) 511 RuleData = lambda r:(r.Class,) 512 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack) 513 def SetRuleData(r, d):(r.Class,) = d 514 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d 515 elif Format == 3: 516 Coverage = lambda r: r.Coverage[0] 517 ChainCoverage = lambda r: r.InputCoverage[0] 518 ContextData = None 519 ChainContextData = None 520 RuleData = lambda r: r.Coverage 521 ChainRuleData = lambda r:(r.LookAheadCoverage + 522 r.InputCoverage + 523 r.BacktrackCoverage) 524 SetRuleData = None 525 ChainSetRuleData = None 526 else: 527 assert 0, "unknown format: %s" % Format 528 529 if Chain: 530 self.Coverage = ChainCoverage 531 self.ContextData = ChainContextData 532 self.RuleData = ChainRuleData 533 self.SetRuleData = ChainSetRuleData 534 else: 535 self.Coverage = Coverage 536 self.ContextData = ContextData 537 self.RuleData = RuleData 538 self.SetRuleData = SetRuleData 539 540 if Format == 1: 541 self.Rule = ChainTyp+'Rule' 542 self.RuleCount = ChainTyp+'RuleCount' 543 self.RuleSet = ChainTyp+'RuleSet' 544 self.RuleSetCount = ChainTyp+'RuleSetCount' 545 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else [] 546 elif Format == 2: 547 self.Rule = ChainTyp+'ClassRule' 548 self.RuleCount = ChainTyp+'ClassRuleCount' 549 self.RuleSet = ChainTyp+'ClassSet' 550 self.RuleSetCount = ChainTyp+'ClassSetCount' 551 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r) 552 553 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef' 554 self.Input = 'Input' if Chain else 'Class' 555 556 if self.Format not in [1, 2, 3]: 557 return None # Don't shoot the messenger; let it go 558 if not hasattr(self.__class__, "__ContextHelpers"): 559 self.__class__.__ContextHelpers = {} 560 if self.Format not in self.__class__.__ContextHelpers: 561 helper = ContextHelper(self.__class__, self.Format) 562 self.__class__.__ContextHelpers[self.Format] = helper 563 return self.__class__.__ContextHelpers[self.Format] 564 565@_add_method(otTables.ContextSubst, 566 otTables.ChainContextSubst) 567def closure_glyphs(self, s, cur_glyphs=None): 568 if cur_glyphs == None: cur_glyphs = s.glyphs 569 c = self.__classify_context() 570 571 indices = c.Coverage(self).intersect(s.glyphs) 572 if not indices: 573 return [] 574 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs); 575 576 if self.Format == 1: 577 ContextData = c.ContextData(self) 578 rss = getattr(self, c.RuleSet) 579 for i in indices: 580 if not rss[i]: continue 581 for r in getattr(rss[i], c.Rule): 582 if not r: continue 583 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist) 584 for cd,klist in zip(ContextData, c.RuleData(r))): 585 chaos = False 586 for ll in getattr(r, c.LookupRecord): 587 if not ll: continue 588 seqi = ll.SequenceIndex 589 if chaos: 590 pos_glyphs = s.glyphs 591 else: 592 if seqi == 0: 593 pos_glyphs = set([c.Coverage(self).glyphs[i]]) 594 else: 595 pos_glyphs = set([r.Input[seqi - 1]]) 596 lookup = s.table.LookupList.Lookup[ll.LookupListIndex] 597 chaos = chaos or lookup.may_have_non_1to1() 598 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs) 599 elif self.Format == 2: 600 ClassDef = getattr(self, c.ClassDef) 601 indices = ClassDef.intersect(cur_glyphs) 602 ContextData = c.ContextData(self) 603 rss = getattr(self, c.RuleSet) 604 for i in indices: 605 if not rss[i]: continue 606 for r in getattr(rss[i], c.Rule): 607 if not r: continue 608 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist) 609 for cd,klist in zip(ContextData, c.RuleData(r))): 610 chaos = False 611 for ll in getattr(r, c.LookupRecord): 612 if not ll: continue 613 seqi = ll.SequenceIndex 614 if chaos: 615 pos_glyphs = s.glyphs 616 else: 617 if seqi == 0: 618 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i) 619 else: 620 pos_glyphs = ClassDef.intersect_class(s.glyphs, 621 getattr(r, c.Input)[seqi - 1]) 622 lookup = s.table.LookupList.Lookup[ll.LookupListIndex] 623 chaos = chaos or lookup.may_have_non_1to1() 624 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs) 625 elif self.Format == 3: 626 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)): 627 return [] 628 r = self 629 chaos = False 630 for ll in getattr(r, c.LookupRecord): 631 if not ll: continue 632 seqi = ll.SequenceIndex 633 if chaos: 634 pos_glyphs = s.glyphs 635 else: 636 if seqi == 0: 637 pos_glyphs = cur_glyphs 638 else: 639 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs) 640 lookup = s.table.LookupList.Lookup[ll.LookupListIndex] 641 chaos = chaos or lookup.may_have_non_1to1() 642 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs) 643 else: 644 assert 0, "unknown format: %s" % self.Format 645 646@_add_method(otTables.ContextSubst, 647 otTables.ContextPos, 648 otTables.ChainContextSubst, 649 otTables.ChainContextPos) 650def subset_glyphs(self, s): 651 c = self.__classify_context() 652 653 if self.Format == 1: 654 indices = self.Coverage.subset(s.glyphs) 655 rss = getattr(self, c.RuleSet) 656 rss = [rss[i] for i in indices] 657 for rs in rss: 658 if not rs: continue 659 ss = getattr(rs, c.Rule) 660 ss = [r for r in ss 661 if r and all(all(g in s.glyphs for g in glist) 662 for glist in c.RuleData(r))] 663 setattr(rs, c.Rule, ss) 664 setattr(rs, c.RuleCount, len(ss)) 665 # Prune empty subrulesets 666 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)] 667 setattr(self, c.RuleSet, rss) 668 setattr(self, c.RuleSetCount, len(rss)) 669 return bool(rss) 670 elif self.Format == 2: 671 if not self.Coverage.subset(s.glyphs): 672 return False 673 indices = getattr(self, c.ClassDef).subset(self.Coverage.glyphs, 674 remap=False) 675 rss = getattr(self, c.RuleSet) 676 rss = [rss[i] for i in indices] 677 ContextData = c.ContextData(self) 678 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData] 679 for rs in rss: 680 if not rs: continue 681 ss = getattr(rs, c.Rule) 682 ss = [r for r in ss 683 if r and all(all(k in klass_map for k in klist) 684 for klass_map,klist in zip(klass_maps, c.RuleData(r)))] 685 setattr(rs, c.Rule, ss) 686 setattr(rs, c.RuleCount, len(ss)) 687 688 # Remap rule classes 689 for r in ss: 690 c.SetRuleData(r, [[klass_map.index(k) for k in klist] 691 for klass_map,klist in zip(klass_maps, c.RuleData(r))]) 692 # Prune empty subrulesets 693 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)] 694 setattr(self, c.RuleSet, rss) 695 setattr(self, c.RuleSetCount, len(rss)) 696 return bool(rss) 697 elif self.Format == 3: 698 return all(x.subset(s.glyphs) for x in c.RuleData(self)) 699 else: 700 assert 0, "unknown format: %s" % self.Format 701 702@_add_method(otTables.ContextSubst, 703 otTables.ChainContextSubst, 704 otTables.ContextPos, 705 otTables.ChainContextPos) 706def subset_lookups(self, lookup_indices): 707 c = self.__classify_context() 708 709 if self.Format in [1, 2]: 710 for rs in getattr(self, c.RuleSet): 711 if not rs: continue 712 for r in getattr(rs, c.Rule): 713 if not r: continue 714 setattr(r, c.LookupRecord, 715 [ll for ll in getattr(r, c.LookupRecord) 716 if ll and ll.LookupListIndex in lookup_indices]) 717 for ll in getattr(r, c.LookupRecord): 718 if not ll: continue 719 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex) 720 elif self.Format == 3: 721 setattr(self, c.LookupRecord, 722 [ll for ll in getattr(self, c.LookupRecord) 723 if ll and ll.LookupListIndex in lookup_indices]) 724 for ll in getattr(self, c.LookupRecord): 725 if not ll: continue 726 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex) 727 else: 728 assert 0, "unknown format: %s" % self.Format 729 730@_add_method(otTables.ContextSubst, 731 otTables.ChainContextSubst, 732 otTables.ContextPos, 733 otTables.ChainContextPos) 734def collect_lookups(self): 735 c = self.__classify_context() 736 737 if self.Format in [1, 2]: 738 return [ll.LookupListIndex 739 for rs in getattr(self, c.RuleSet) if rs 740 for r in getattr(rs, c.Rule) if r 741 for ll in getattr(r, c.LookupRecord) if ll] 742 elif self.Format == 3: 743 return [ll.LookupListIndex 744 for ll in getattr(self, c.LookupRecord) if ll] 745 else: 746 assert 0, "unknown format: %s" % self.Format 747 748@_add_method(otTables.ExtensionSubst) 749def closure_glyphs(self, s, cur_glyphs=None): 750 if self.Format == 1: 751 self.ExtSubTable.closure_glyphs(s, cur_glyphs) 752 else: 753 assert 0, "unknown format: %s" % self.Format 754 755@_add_method(otTables.ExtensionSubst) 756def may_have_non_1to1(self): 757 if self.Format == 1: 758 return self.ExtSubTable.may_have_non_1to1() 759 else: 760 assert 0, "unknown format: %s" % self.Format 761 762@_add_method(otTables.ExtensionSubst, 763 otTables.ExtensionPos) 764def prune_pre_subset(self, options): 765 if self.Format == 1: 766 return self.ExtSubTable.prune_pre_subset(options) 767 else: 768 assert 0, "unknown format: %s" % self.Format 769 770@_add_method(otTables.ExtensionSubst, 771 otTables.ExtensionPos) 772def subset_glyphs(self, s): 773 if self.Format == 1: 774 return self.ExtSubTable.subset_glyphs(s) 775 else: 776 assert 0, "unknown format: %s" % self.Format 777 778@_add_method(otTables.ExtensionSubst, 779 otTables.ExtensionPos) 780def prune_post_subset(self, options): 781 if self.Format == 1: 782 return self.ExtSubTable.prune_post_subset(options) 783 else: 784 assert 0, "unknown format: %s" % self.Format 785 786@_add_method(otTables.ExtensionSubst, 787 otTables.ExtensionPos) 788def subset_lookups(self, lookup_indices): 789 if self.Format == 1: 790 return self.ExtSubTable.subset_lookups(lookup_indices) 791 else: 792 assert 0, "unknown format: %s" % self.Format 793 794@_add_method(otTables.ExtensionSubst, 795 otTables.ExtensionPos) 796def collect_lookups(self): 797 if self.Format == 1: 798 return self.ExtSubTable.collect_lookups() 799 else: 800 assert 0, "unknown format: %s" % self.Format 801 802@_add_method(otTables.Lookup) 803def closure_glyphs(self, s, cur_glyphs=None): 804 for st in self.SubTable: 805 if not st: continue 806 st.closure_glyphs(s, cur_glyphs) 807 808@_add_method(otTables.Lookup) 809def prune_pre_subset(self, options): 810 ret = False 811 for st in self.SubTable: 812 if not st: continue 813 if st.prune_pre_subset(options): ret = True 814 return ret 815 816@_add_method(otTables.Lookup) 817def subset_glyphs(self, s): 818 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)] 819 self.SubTableCount = len(self.SubTable) 820 return bool(self.SubTableCount) 821 822@_add_method(otTables.Lookup) 823def prune_post_subset(self, options): 824 ret = False 825 for st in self.SubTable: 826 if not st: continue 827 if st.prune_post_subset(options): ret = True 828 return ret 829 830@_add_method(otTables.Lookup) 831def subset_lookups(self, lookup_indices): 832 for s in self.SubTable: 833 s.subset_lookups(lookup_indices) 834 835@_add_method(otTables.Lookup) 836def collect_lookups(self): 837 return _uniq_sort(sum((st.collect_lookups() for st in self.SubTable 838 if st), [])) 839 840@_add_method(otTables.Lookup) 841def may_have_non_1to1(self): 842 return any(st.may_have_non_1to1() for st in self.SubTable if st) 843 844@_add_method(otTables.LookupList) 845def prune_pre_subset(self, options): 846 ret = False 847 for l in self.Lookup: 848 if not l: continue 849 if l.prune_pre_subset(options): ret = True 850 return ret 851 852@_add_method(otTables.LookupList) 853def subset_glyphs(self, s): 854 "Returns the indices of nonempty lookups." 855 return [i for i,l in enumerate(self.Lookup) if l and l.subset_glyphs(s)] 856 857@_add_method(otTables.LookupList) 858def prune_post_subset(self, options): 859 ret = False 860 for l in self.Lookup: 861 if not l: continue 862 if l.prune_post_subset(options): ret = True 863 return ret 864 865@_add_method(otTables.LookupList) 866def subset_lookups(self, lookup_indices): 867 self.Lookup = [self.Lookup[i] for i in lookup_indices 868 if i < self.LookupCount] 869 self.LookupCount = len(self.Lookup) 870 for l in self.Lookup: 871 l.subset_lookups(lookup_indices) 872 873@_add_method(otTables.LookupList) 874def closure_lookups(self, lookup_indices): 875 lookup_indices = _uniq_sort(lookup_indices) 876 recurse = lookup_indices 877 while True: 878 recurse_lookups = sum((self.Lookup[i].collect_lookups() 879 for i in recurse if i < self.LookupCount), []) 880 recurse_lookups = [l for l in recurse_lookups 881 if l not in lookup_indices and l < self.LookupCount] 882 if not recurse_lookups: 883 return _uniq_sort(lookup_indices) 884 recurse_lookups = _uniq_sort(recurse_lookups) 885 lookup_indices.extend(recurse_lookups) 886 recurse = recurse_lookups 887 888@_add_method(otTables.Feature) 889def subset_lookups(self, lookup_indices): 890 self.LookupListIndex = [l for l in self.LookupListIndex 891 if l in lookup_indices] 892 # Now map them. 893 self.LookupListIndex = [lookup_indices.index(l) 894 for l in self.LookupListIndex] 895 self.LookupCount = len(self.LookupListIndex) 896 return self.LookupCount 897 898@_add_method(otTables.Feature) 899def collect_lookups(self): 900 return self.LookupListIndex[:] 901 902@_add_method(otTables.FeatureList) 903def subset_lookups(self, lookup_indices): 904 "Returns the indices of nonempty features." 905 feature_indices = [i for i,f in enumerate(self.FeatureRecord) 906 if f.Feature.subset_lookups(lookup_indices)] 907 self.subset_features(feature_indices) 908 return feature_indices 909 910@_add_method(otTables.FeatureList) 911def collect_lookups(self, feature_indices): 912 return _uniq_sort(sum((self.FeatureRecord[i].Feature.collect_lookups() 913 for i in feature_indices 914 if i < self.FeatureCount), [])) 915 916@_add_method(otTables.FeatureList) 917def subset_features(self, feature_indices): 918 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices] 919 self.FeatureCount = len(self.FeatureRecord) 920 return bool(self.FeatureCount) 921 922@_add_method(otTables.DefaultLangSys, 923 otTables.LangSys) 924def subset_features(self, feature_indices): 925 if self.ReqFeatureIndex in feature_indices: 926 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex) 927 else: 928 self.ReqFeatureIndex = 65535 929 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices] 930 # Now map them. 931 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex 932 if f in feature_indices] 933 self.FeatureCount = len(self.FeatureIndex) 934 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535) 935 936@_add_method(otTables.DefaultLangSys, 937 otTables.LangSys) 938def collect_features(self): 939 feature_indices = self.FeatureIndex[:] 940 if self.ReqFeatureIndex != 65535: 941 feature_indices.append(self.ReqFeatureIndex) 942 return _uniq_sort(feature_indices) 943 944@_add_method(otTables.Script) 945def subset_features(self, feature_indices): 946 if(self.DefaultLangSys and 947 not self.DefaultLangSys.subset_features(feature_indices)): 948 self.DefaultLangSys = None 949 self.LangSysRecord = [l for l in self.LangSysRecord 950 if l.LangSys.subset_features(feature_indices)] 951 self.LangSysCount = len(self.LangSysRecord) 952 return bool(self.LangSysCount or self.DefaultLangSys) 953 954@_add_method(otTables.Script) 955def collect_features(self): 956 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord] 957 if self.DefaultLangSys: 958 feature_indices.append(self.DefaultLangSys.collect_features()) 959 return _uniq_sort(sum(feature_indices, [])) 960 961@_add_method(otTables.ScriptList) 962def subset_features(self, feature_indices): 963 self.ScriptRecord = [s for s in self.ScriptRecord 964 if s.Script.subset_features(feature_indices)] 965 self.ScriptCount = len(self.ScriptRecord) 966 return bool(self.ScriptCount) 967 968@_add_method(otTables.ScriptList) 969def collect_features(self): 970 return _uniq_sort(sum((s.Script.collect_features() 971 for s in self.ScriptRecord), [])) 972 973@_add_method(ttLib.getTableClass('GSUB')) 974def closure_glyphs(self, s): 975 s.table = self.table 976 feature_indices = self.table.ScriptList.collect_features() 977 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices) 978 while True: 979 orig_glyphs = s.glyphs.copy() 980 for i in lookup_indices: 981 if i >= self.table.LookupList.LookupCount: continue 982 if not self.table.LookupList.Lookup[i]: continue 983 self.table.LookupList.Lookup[i].closure_glyphs(s) 984 if orig_glyphs == s.glyphs: 985 break 986 del s.table 987 988@_add_method(ttLib.getTableClass('GSUB'), 989 ttLib.getTableClass('GPOS')) 990def subset_glyphs(self, s): 991 s.glyphs = s.glyphs_gsubed 992 lookup_indices = self.table.LookupList.subset_glyphs(s) 993 self.subset_lookups(lookup_indices) 994 self.prune_lookups() 995 return True 996 997@_add_method(ttLib.getTableClass('GSUB'), 998 ttLib.getTableClass('GPOS')) 999def subset_lookups(self, lookup_indices): 1000 """Retrains specified lookups, then removes empty features, language 1001 systems, and scripts.""" 1002 self.table.LookupList.subset_lookups(lookup_indices) 1003 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices) 1004 self.table.ScriptList.subset_features(feature_indices) 1005 1006@_add_method(ttLib.getTableClass('GSUB'), 1007 ttLib.getTableClass('GPOS')) 1008def prune_lookups(self): 1009 "Remove unreferenced lookups" 1010 feature_indices = self.table.ScriptList.collect_features() 1011 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices) 1012 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices) 1013 self.subset_lookups(lookup_indices) 1014 1015@_add_method(ttLib.getTableClass('GSUB'), 1016 ttLib.getTableClass('GPOS')) 1017def subset_feature_tags(self, feature_tags): 1018 feature_indices = [i for i,f in 1019 enumerate(self.table.FeatureList.FeatureRecord) 1020 if f.FeatureTag in feature_tags] 1021 self.table.FeatureList.subset_features(feature_indices) 1022 self.table.ScriptList.subset_features(feature_indices) 1023 1024@_add_method(ttLib.getTableClass('GSUB'), 1025 ttLib.getTableClass('GPOS')) 1026def prune_pre_subset(self, options): 1027 if '*' not in options.layout_features: 1028 self.subset_feature_tags(options.layout_features) 1029 self.prune_lookups() 1030 self.table.LookupList.prune_pre_subset(options); 1031 return True 1032 1033@_add_method(ttLib.getTableClass('GSUB'), 1034 ttLib.getTableClass('GPOS')) 1035def prune_post_subset(self, options): 1036 self.table.LookupList.prune_post_subset(options); 1037 return True 1038 1039@_add_method(ttLib.getTableClass('GDEF')) 1040def subset_glyphs(self, s): 1041 glyphs = s.glyphs_gsubed 1042 table = self.table 1043 if table.LigCaretList: 1044 indices = table.LigCaretList.Coverage.subset(glyphs) 1045 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i] 1046 for i in indices] 1047 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph) 1048 if not table.LigCaretList.LigGlyphCount: 1049 table.LigCaretList = None 1050 if table.MarkAttachClassDef: 1051 table.MarkAttachClassDef.classDefs = dict((g,v) for g,v in 1052 table.MarkAttachClassDef. 1053 classDefs.iteritems() 1054 if g in glyphs) 1055 if not table.MarkAttachClassDef.classDefs: 1056 table.MarkAttachClassDef = None 1057 if table.GlyphClassDef: 1058 table.GlyphClassDef.classDefs = dict((g,v) for g,v in 1059 table.GlyphClassDef. 1060 classDefs.iteritems() 1061 if g in glyphs) 1062 if not table.GlyphClassDef.classDefs: 1063 table.GlyphClassDef = None 1064 if table.AttachList: 1065 indices = table.AttachList.Coverage.subset(glyphs) 1066 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i] 1067 for i in indices] 1068 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint) 1069 if not table.AttachList.GlyphCount: 1070 table.AttachList = None 1071 return bool(table.LigCaretList or 1072 table.MarkAttachClassDef or 1073 table.GlyphClassDef or 1074 table.AttachList) 1075 1076@_add_method(ttLib.getTableClass('kern')) 1077def prune_pre_subset(self, options): 1078 # Prune unknown kern table types 1079 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')] 1080 return bool(self.kernTables) 1081 1082@_add_method(ttLib.getTableClass('kern')) 1083def subset_glyphs(self, s): 1084 glyphs = s.glyphs_gsubed 1085 for t in self.kernTables: 1086 t.kernTable = dict(((a,b),v) for (a,b),v in t.kernTable.iteritems() 1087 if a in glyphs and b in glyphs) 1088 self.kernTables = [t for t in self.kernTables if t.kernTable] 1089 return bool(self.kernTables) 1090 1091@_add_method(ttLib.getTableClass('vmtx'), 1092 ttLib.getTableClass('hmtx')) 1093def subset_glyphs(self, s): 1094 self.metrics = dict((g,v) for g,v in self.metrics.iteritems() if g in s.glyphs) 1095 return bool(self.metrics) 1096 1097@_add_method(ttLib.getTableClass('hdmx')) 1098def subset_glyphs(self, s): 1099 self.hdmx = dict((sz,dict((g,v) for g,v in l.iteritems() if g in s.glyphs)) 1100 for sz,l in self.hdmx.iteritems()) 1101 return bool(self.hdmx) 1102 1103@_add_method(ttLib.getTableClass('VORG')) 1104def subset_glyphs(self, s): 1105 self.VOriginRecords = dict((g,v) for g,v in self.VOriginRecords.iteritems() 1106 if g in s.glyphs) 1107 self.numVertOriginYMetrics = len(self.VOriginRecords) 1108 return True # Never drop; has default metrics 1109 1110@_add_method(ttLib.getTableClass('post')) 1111def prune_pre_subset(self, options): 1112 if not options.glyph_names: 1113 self.formatType = 3.0 1114 return True 1115 1116@_add_method(ttLib.getTableClass('post')) 1117def subset_glyphs(self, s): 1118 self.extraNames = [] # This seems to do it 1119 return True 1120 1121@_add_method(ttLib.getTableModule('glyf').Glyph) 1122def remapComponentsFast(self, indices): 1123 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0: 1124 return # Not composite 1125 data = array.array("B", self.data) 1126 i = 10 1127 more = 1 1128 while more: 1129 flags =(data[i] << 8) | data[i+1] 1130 glyphID =(data[i+2] << 8) | data[i+3] 1131 # Remap 1132 glyphID = indices.index(glyphID) 1133 data[i+2] = glyphID >> 8 1134 data[i+3] = glyphID & 0xFF 1135 i += 4 1136 flags = int(flags) 1137 1138 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS 1139 else: i += 2 1140 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE 1141 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE 1142 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO 1143 more = flags & 0x0020 # MORE_COMPONENTS 1144 1145 self.data = data.tostring() 1146 1147@_add_method(ttLib.getTableClass('glyf')) 1148def closure_glyphs(self, s): 1149 decompose = s.glyphs 1150 while True: 1151 components = set() 1152 for g in decompose: 1153 if g not in self.glyphs: 1154 continue 1155 gl = self.glyphs[g] 1156 for c in gl.getComponentNames(self): 1157 if c not in s.glyphs: 1158 components.add(c) 1159 components = set(c for c in components if c not in s.glyphs) 1160 if not components: 1161 break 1162 decompose = components 1163 s.glyphs.update(components) 1164 1165@_add_method(ttLib.getTableClass('glyf')) 1166def prune_pre_subset(self, options): 1167 if options.notdef_glyph and not options.notdef_outline: 1168 g = self[self.glyphOrder[0]] 1169 # Yay, easy! 1170 g.__dict__.clear() 1171 g.data = "" 1172 return True 1173 1174@_add_method(ttLib.getTableClass('glyf')) 1175def subset_glyphs(self, s): 1176 self.glyphs = dict((g,v) for g,v in self.glyphs.iteritems() if g in s.glyphs) 1177 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs] 1178 for v in self.glyphs.itervalues(): 1179 if hasattr(v, "data"): 1180 v.remapComponentsFast(indices) 1181 else: 1182 pass # No need 1183 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs] 1184 # Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset. 1185 return True 1186 1187@_add_method(ttLib.getTableClass('glyf')) 1188def prune_post_subset(self, options): 1189 if not options.hinting: 1190 for v in self.glyphs.itervalues(): 1191 v.removeHinting() 1192 return True 1193 1194@_add_method(ttLib.getTableClass('CFF ')) 1195def prune_pre_subset(self, options): 1196 cff = self.cff 1197 # CFF table must have one font only 1198 cff.fontNames = cff.fontNames[:1] 1199 1200 if options.notdef_glyph and not options.notdef_outline: 1201 for fontname in cff.keys(): 1202 font = cff[fontname] 1203 c,_ = font.CharStrings.getItemAndSelector('.notdef') 1204 # XXX we should preserve the glyph width 1205 c.bytecode = '\x0e' # endchar 1206 c.program = None 1207 1208 return True # bool(cff.fontNames) 1209 1210@_add_method(ttLib.getTableClass('CFF ')) 1211def subset_glyphs(self, s): 1212 cff = self.cff 1213 for fontname in cff.keys(): 1214 font = cff[fontname] 1215 cs = font.CharStrings 1216 1217 # Load all glyphs 1218 for g in font.charset: 1219 if g not in s.glyphs: continue 1220 c,sel = cs.getItemAndSelector(g) 1221 1222 if cs.charStringsAreIndexed: 1223 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs] 1224 csi = cs.charStringsIndex 1225 csi.items = [csi.items[i] for i in indices] 1226 csi.count = len(csi.items) 1227 del csi.file, csi.offsets 1228 if hasattr(font, "FDSelect"): 1229 sel = font.FDSelect 1230 sel.format = None 1231 sel.gidArray = [sel.gidArray[i] for i in indices] 1232 cs.charStrings = dict((g,indices.index(v)) 1233 for g,v in cs.charStrings.iteritems() 1234 if g in s.glyphs) 1235 else: 1236 cs.charStrings = dict((g,v) 1237 for g,v in cs.charStrings.iteritems() 1238 if g in s.glyphs) 1239 font.charset = [g for g in font.charset if g in s.glyphs] 1240 font.numGlyphs = len(font.charset) 1241 1242 return True # any(cff[fontname].numGlyphs for fontname in cff.keys()) 1243 1244@_add_method(psCharStrings.T2CharString) 1245def subset_subroutines(self, subrs, gsubrs): 1246 p = self.program 1247 assert len(p) 1248 for i in xrange(1, len(p)): 1249 if p[i] == 'callsubr': 1250 assert type(p[i-1]) is int 1251 p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias 1252 elif p[i] == 'callgsubr': 1253 assert type(p[i-1]) is int 1254 p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias 1255 1256@_add_method(psCharStrings.T2CharString) 1257def drop_hints(self): 1258 hints = self._hints 1259 1260 if hints.has_hint: 1261 self.program = self.program[hints.last_hint:] 1262 if hasattr(self, 'width'): 1263 # Insert width back if needed 1264 if self.width != self.private.defaultWidthX: 1265 self.program.insert(0, self.width - self.private.nominalWidthX) 1266 1267 if hints.has_hintmask: 1268 i = 0 1269 p = self.program 1270 while i < len(p): 1271 if p[i] in ['hintmask', 'cntrmask']: 1272 assert i + 1 <= len(p) 1273 del p[i:i+2] 1274 continue 1275 i += 1 1276 1277 assert len(self.program) 1278 1279 del self._hints 1280 1281class _MarkingT2Decompiler(psCharStrings.SimpleT2Decompiler): 1282 1283 def __init__(self, localSubrs, globalSubrs): 1284 psCharStrings.SimpleT2Decompiler.__init__(self, 1285 localSubrs, 1286 globalSubrs) 1287 for subrs in [localSubrs, globalSubrs]: 1288 if subrs and not hasattr(subrs, "_used"): 1289 subrs._used = set() 1290 1291 def op_callsubr(self, index): 1292 self.localSubrs._used.add(self.operandStack[-1]+self.localBias) 1293 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index) 1294 1295 def op_callgsubr(self, index): 1296 self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias) 1297 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index) 1298 1299class _DehintingT2Decompiler(psCharStrings.SimpleT2Decompiler): 1300 1301 class Hints: 1302 def __init__(self): 1303 # Whether calling this charstring produces any hint stems 1304 self.has_hint = False 1305 # Index to start at to drop all hints 1306 self.last_hint = 0 1307 # Index up to which we know more hints are possible. Only 1308 # relevant if status is 0 or 1. 1309 self.last_checked = 0 1310 # The status means: 1311 # 0: after dropping hints, this charstring is empty 1312 # 1: after dropping hints, there may be more hints continuing after this 1313 # 2: no more hints possible after this charstring 1314 self.status = 0 1315 # Has hintmask instructions; not recursive 1316 self.has_hintmask = False 1317 pass 1318 1319 def __init__(self, css, localSubrs, globalSubrs): 1320 self._css = css 1321 psCharStrings.SimpleT2Decompiler.__init__(self, 1322 localSubrs, 1323 globalSubrs) 1324 1325 def execute(self, charString): 1326 old_hints = charString._hints if hasattr(charString, '_hints') else None 1327 charString._hints = self.Hints() 1328 1329 psCharStrings.SimpleT2Decompiler.execute(self, charString) 1330 1331 hints = charString._hints 1332 1333 if hints.has_hint or hints.has_hintmask: 1334 self._css.add(charString) 1335 1336 if hints.status != 2: 1337 # Check from last_check, make sure we didn't have any operators. 1338 for i in xrange(hints.last_checked, len(charString.program) - 1): 1339 if type(charString.program[i]) == str: 1340 hints.status = 2 1341 break; 1342 else: 1343 hints.status = 1 # There's *something* here 1344 hints.last_checked = len(charString.program) 1345 1346 if old_hints: 1347 assert hints.__dict__ == old_hints.__dict__ 1348 1349 def op_callsubr(self, index): 1350 subr = self.localSubrs[self.operandStack[-1]+self.localBias] 1351 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index) 1352 self.processSubr(index, subr) 1353 1354 def op_callgsubr(self, index): 1355 subr = self.globalSubrs[self.operandStack[-1]+self.globalBias] 1356 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index) 1357 self.processSubr(index, subr) 1358 1359 def op_hstem(self, index): 1360 psCharStrings.SimpleT2Decompiler.op_hstem(self, index) 1361 self.processHint(index) 1362 def op_vstem(self, index): 1363 psCharStrings.SimpleT2Decompiler.op_vstem(self, index) 1364 self.processHint(index) 1365 def op_hstemhm(self, index): 1366 psCharStrings.SimpleT2Decompiler.op_hstemhm(self, index) 1367 self.processHint(index) 1368 def op_vstemhm(self, index): 1369 psCharStrings.SimpleT2Decompiler.op_vstemhm(self, index) 1370 self.processHint(index) 1371 def op_hintmask(self, index): 1372 psCharStrings.SimpleT2Decompiler.op_hintmask(self, index) 1373 self.processHintmask(index) 1374 def op_cntrmask(self, index): 1375 psCharStrings.SimpleT2Decompiler.op_cntrmask(self, index) 1376 self.processHintmask(index) 1377 1378 def processHintmask(self, index): 1379 cs = self.callingStack[-1] 1380 hints = cs._hints 1381 hints.has_hintmask = True 1382 if hints.status != 2 and hints.has_hint: 1383 # Check from last_check, see if we may be an implicit vstem 1384 for i in xrange(hints.last_checked, index - 1): 1385 if type(cs.program[i]) == str: 1386 hints.status = 2 1387 break; 1388 if hints.status != 2: 1389 # We are an implicit vstem 1390 hints.last_hint = index + 1 1391 hints.status = 0 1392 hints.last_checked = index + 1 1393 1394 def processHint(self, index): 1395 cs = self.callingStack[-1] 1396 hints = cs._hints 1397 hints.has_hint = True 1398 hints.last_hint = index 1399 hints.last_checked = index 1400 1401 def processSubr(self, index, subr): 1402 cs = self.callingStack[-1] 1403 hints = cs._hints 1404 subr_hints = subr._hints 1405 1406 if subr_hints.has_hint: 1407 if hints.status != 2: 1408 hints.has_hint = True 1409 hints.last_checked = index 1410 hints.status = subr_hints.status 1411 # Decide where to chop off from 1412 if subr_hints.status == 0: 1413 hints.last_hint = index 1414 else: 1415 hints.last_hint = index - 2 # Leave the subr call in 1416 else: 1417 # In my understanding, this is a font bug. Ie. it has hint stems 1418 # *after* path construction. I've seen this in widespread fonts. 1419 # Best to ignore the hints I suppose... 1420 pass 1421 #assert 0 1422 else: 1423 hints.status = max(hints.status, subr_hints.status) 1424 if hints.status != 2: 1425 # Check from last_check, make sure we didn't have 1426 # any operators. 1427 for i in xrange(hints.last_checked, index - 1): 1428 if type(cs.program[i]) == str: 1429 hints.status = 2 1430 break; 1431 hints.last_checked = index 1432 1433@_add_method(ttLib.getTableClass('CFF ')) 1434def prune_post_subset(self, options): 1435 cff = self.cff 1436 for fontname in cff.keys(): 1437 font = cff[fontname] 1438 cs = font.CharStrings 1439 1440 1441 # 1442 # Drop unused FontDictionaries 1443 # 1444 if hasattr(font, "FDSelect"): 1445 sel = font.FDSelect 1446 indices = _uniq_sort(sel.gidArray) 1447 sel.gidArray = [indices.index (ss) for ss in sel.gidArray] 1448 arr = font.FDArray 1449 arr.items = [arr[i] for i in indices] 1450 arr.count = len(arr.items) 1451 del arr.file, arr.offsets 1452 1453 1454 # 1455 # Drop hints if not needed 1456 # 1457 if not options.hinting: 1458 1459 # 1460 # This can be tricky, but doesn't have to. What we do is: 1461 # 1462 # - Run all used glyph charstrings and recurse into subroutines, 1463 # - For each charstring (including subroutines), if it has any 1464 # of the hint stem operators, we mark it as such. Upon returning, 1465 # for each charstring we note all the subroutine calls it makes 1466 # that (recursively) contain a stem, 1467 # - Dropping hinting then consists of the following two ops: 1468 # * Drop the piece of the program in each charstring before the 1469 # last call to a stem op or a stem-calling subroutine, 1470 # * Drop all hintmask operations. 1471 # - It's trickier... A hintmask right after hints and a few numbers 1472 # will act as an implicit vstemhm. As such, we track whether 1473 # we have seen any non-hint operators so far and do the right 1474 # thing, recursively... Good luck understanding that :( 1475 # 1476 css = set() 1477 for g in font.charset: 1478 c,sel = cs.getItemAndSelector(g) 1479 # Make sure it's decompiled. We want our "decompiler" to walk 1480 # the program, not the bytecode. 1481 c.draw(basePen.NullPen()) 1482 subrs = getattr(c.private, "Subrs", []) 1483 decompiler = _DehintingT2Decompiler(css, subrs, c.globalSubrs) 1484 decompiler.execute(c) 1485 for charstring in css: 1486 charstring.drop_hints() 1487 1488 # Drop font-wide hinting values 1489 all_privs = [] 1490 if hasattr(font, 'FDSelect'): 1491 all_privs.extend(fd.Private for fd in font.FDArray) 1492 else: 1493 all_privs.append(font.Private) 1494 for priv in all_privs: 1495 priv.BlueValues = [] 1496 for k in ['OtherBlues', 'StemSnapH', 'StemSnapV', 'StdHW', 'StdVW', \ 1497 'FamilyBlues', 'FamilyOtherBlues']: 1498 if hasattr(priv, k): 1499 setattr(priv, k, None) 1500 1501 1502 # 1503 # Renumber subroutines to remove unused ones 1504 # 1505 1506 # Mark all used subroutines 1507 for g in font.charset: 1508 c,sel = cs.getItemAndSelector(g) 1509 subrs = getattr(c.private, "Subrs", []) 1510 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs) 1511 decompiler.execute(c) 1512 1513 all_subrs = [font.GlobalSubrs] 1514 if hasattr(font, 'FDSelect'): 1515 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs') and fd.Private.Subrs) 1516 elif hasattr(font.Private, 'Subrs') and font.Private.Subrs: 1517 all_subrs.append(font.Private.Subrs) 1518 1519 subrs = set(subrs) # Remove duplicates 1520 1521 # Prepare 1522 for subrs in all_subrs: 1523 if not hasattr(subrs, '_used'): 1524 subrs._used = set() 1525 subrs._used = _uniq_sort(subrs._used) 1526 subrs._old_bias = psCharStrings.calcSubrBias(subrs) 1527 subrs._new_bias = psCharStrings.calcSubrBias(subrs._used) 1528 1529 # Renumber glyph charstrings 1530 for g in font.charset: 1531 c,sel = cs.getItemAndSelector(g) 1532 subrs = getattr(c.private, "Subrs", []) 1533 c.subset_subroutines (subrs, font.GlobalSubrs) 1534 1535 # Renumber subroutines themselves 1536 for subrs in all_subrs: 1537 1538 if subrs == font.GlobalSubrs: 1539 if not hasattr(font, 'FDSelect') and hasattr(font.Private, 'Subrs'): 1540 local_subrs = font.Private.Subrs 1541 else: 1542 local_subrs = [] 1543 else: 1544 local_subrs = subrs 1545 1546 subrs.items = [subrs.items[i] for i in subrs._used] 1547 subrs.count = len(subrs.items) 1548 del subrs.file 1549 if hasattr(subrs, 'offsets'): 1550 del subrs.offsets 1551 1552 for i in xrange (subrs.count): 1553 subrs[i].subset_subroutines (local_subrs, font.GlobalSubrs) 1554 1555 # Cleanup 1556 for subrs in all_subrs: 1557 del subrs._used, subrs._old_bias, subrs._new_bias 1558 1559 return True 1560 1561@_add_method(ttLib.getTableClass('cmap')) 1562def closure_glyphs(self, s): 1563 tables = [t for t in self.tables 1564 if t.platformID == 3 and t.platEncID in [1, 10]] 1565 for u in s.unicodes_requested: 1566 found = False 1567 for table in tables: 1568 if u in table.cmap: 1569 s.glyphs.add(table.cmap[u]) 1570 found = True 1571 break 1572 if not found: 1573 s.log("No glyph for Unicode value %s; skipping." % u) 1574 1575@_add_method(ttLib.getTableClass('cmap')) 1576def prune_pre_subset(self, options): 1577 if not options.legacy_cmap: 1578 # Drop non-Unicode / non-Symbol cmaps 1579 self.tables = [t for t in self.tables 1580 if t.platformID == 3 and t.platEncID in [0, 1, 10]] 1581 if not options.symbol_cmap: 1582 self.tables = [t for t in self.tables 1583 if t.platformID == 3 and t.platEncID in [1, 10]] 1584 # TODO(behdad) Only keep one subtable? 1585 # For now, drop format=0 which can't be subset_glyphs easily? 1586 self.tables = [t for t in self.tables if t.format != 0] 1587 self.numSubTables = len(self.tables) 1588 return bool(self.tables) 1589 1590@_add_method(ttLib.getTableClass('cmap')) 1591def subset_glyphs(self, s): 1592 s.glyphs = s.glyphs_cmaped 1593 for t in self.tables: 1594 # For reasons I don't understand I need this here 1595 # to force decompilation of the cmap format 14. 1596 try: 1597 getattr(t, "asdf") 1598 except AttributeError: 1599 pass 1600 if t.format == 14: 1601 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None). 1602 t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs]) 1603 for v,l in t.uvsDict.iteritems()) 1604 t.uvsDict = dict((v,l) for v,l in t.uvsDict.iteritems() if l) 1605 else: 1606 t.cmap = dict((u,g) for u,g in t.cmap.iteritems() 1607 if g in s.glyphs_requested or u in s.unicodes_requested) 1608 self.tables = [t for t in self.tables 1609 if (t.cmap if t.format != 14 else t.uvsDict)] 1610 self.numSubTables = len(self.tables) 1611 # TODO(behdad) Convert formats when needed. 1612 # In particular, if we have a format=12 without non-BMP 1613 # characters, either drop format=12 one or convert it 1614 # to format=4 if there's not one. 1615 return bool(self.tables) 1616 1617@_add_method(ttLib.getTableClass('name')) 1618def prune_pre_subset(self, options): 1619 if '*' not in options.name_IDs: 1620 self.names = [n for n in self.names if n.nameID in options.name_IDs] 1621 if not options.name_legacy: 1622 self.names = [n for n in self.names 1623 if n.platformID == 3 and n.platEncID == 1] 1624 if '*' not in options.name_languages: 1625 self.names = [n for n in self.names if n.langID in options.name_languages] 1626 return True # Retain even if empty 1627 1628 1629# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange? 1630# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries. 1631# TODO(behdad) Drop empty GSUB/GPOS, and GDEF if no GSUB/GPOS left 1632# TODO(behdad) Drop GDEF subitems if unused by lookups 1633# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF) 1634# TODO(behdad) Text direction considerations. 1635# TODO(behdad) Text script / language considerations. 1636 1637class Options(object): 1638 1639 class UnknownOptionError(Exception): 1640 pass 1641 1642 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ', 1643 'PCLT', 'LTSH'] 1644 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite 1645 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color 1646 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2', 1647 'loca', 'name', 'cvt ', 'fpgm', 'prep'] 1648 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX'] 1649 1650 # Based on HarfBuzz shapers 1651 _layout_features_groups = { 1652 # Default shaper 1653 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'], 1654 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'], 1655 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'], 1656 'ltr': ['ltra', 'ltrm'], 1657 'rtl': ['rtla', 'rtlm'], 1658 # Complex shapers 1659 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3', 1660 'cswh', 'mset'], 1661 'hangul': ['ljmo', 'vjmo', 'tjmo'], 1662 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'], 1663 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half', 1664 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres', 1665 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'], 1666 } 1667 _layout_features_default = _uniq_sort(sum( 1668 _layout_features_groups.itervalues(), [])) 1669 1670 drop_tables = _drop_tables_default 1671 no_subset_tables = _no_subset_tables_default 1672 hinting_tables = _hinting_tables_default 1673 layout_features = _layout_features_default 1674 hinting = False 1675 glyph_names = False 1676 legacy_cmap = False 1677 symbol_cmap = False 1678 name_IDs = [1, 2] # Family and Style 1679 name_legacy = False 1680 name_languages = [0x0409] # English 1681 notdef_glyph = True # gid0 for TrueType / .notdef for CFF 1682 notdef_outline = False # No need for notdef to have an outline really 1683 recommended_glyphs = False # gid1, gid2, gid3 for TrueType 1684 recalc_bounds = False # Recalculate font bounding boxes 1685 canonical_order = False # Order tables as recommended 1686 flavor = None # May be 'woff' 1687 1688 def __init__(self, **kwargs): 1689 1690 self.set(**kwargs) 1691 1692 def set(self, **kwargs): 1693 for k,v in kwargs.iteritems(): 1694 if not hasattr(self, k): 1695 raise self.UnknownOptionError("Unknown option '%s'" % k) 1696 setattr(self, k, v) 1697 1698 def parse_opts(self, argv, ignore_unknown=False): 1699 ret = [] 1700 opts = {} 1701 for a in argv: 1702 orig_a = a 1703 if not a.startswith('--'): 1704 ret.append(a) 1705 continue 1706 a = a[2:] 1707 i = a.find('=') 1708 op = '=' 1709 if i == -1: 1710 if a.startswith("no-"): 1711 k = a[3:] 1712 v = False 1713 else: 1714 k = a 1715 v = True 1716 else: 1717 k = a[:i] 1718 if k[-1] in "-+": 1719 op = k[-1]+'=' # Ops is '-=' or '+=' now. 1720 k = k[:-1] 1721 v = a[i+1:] 1722 k = k.replace('-', '_') 1723 if not hasattr(self, k): 1724 if ignore_unknown == True or k in ignore_unknown: 1725 ret.append(orig_a) 1726 continue 1727 else: 1728 raise self.UnknownOptionError("Unknown option '%s'" % a) 1729 1730 ov = getattr(self, k) 1731 if isinstance(ov, bool): 1732 v = bool(v) 1733 elif isinstance(ov, int): 1734 v = int(v) 1735 elif isinstance(ov, list): 1736 vv = v.split(',') 1737 if vv == ['']: 1738 vv = [] 1739 vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv] 1740 if op == '=': 1741 v = vv 1742 elif op == '+=': 1743 v = ov 1744 v.extend(vv) 1745 elif op == '-=': 1746 v = ov 1747 for x in vv: 1748 if x in v: 1749 v.remove(x) 1750 else: 1751 assert 0 1752 1753 opts[k] = v 1754 self.set(**opts) 1755 1756 return ret 1757 1758 1759class Subsetter(object): 1760 1761 def __init__(self, options=None, log=None): 1762 1763 if not log: 1764 log = Logger() 1765 if not options: 1766 options = Options() 1767 1768 self.options = options 1769 self.log = log 1770 self.unicodes_requested = set() 1771 self.glyphs_requested = set() 1772 self.glyphs = set() 1773 1774 def populate(self, glyphs=[], unicodes=[], text=""): 1775 self.unicodes_requested.update(unicodes) 1776 if isinstance(text, str): 1777 text = text.decode("utf8") 1778 for u in text: 1779 self.unicodes_requested.add(ord(u)) 1780 self.glyphs_requested.update(glyphs) 1781 self.glyphs.update(glyphs) 1782 1783 def _prune_pre_subset(self, font): 1784 1785 for tag in font.keys(): 1786 if tag == 'GlyphOrder': continue 1787 1788 if(tag in self.options.drop_tables or 1789 (tag in self.options.hinting_tables and not self.options.hinting)): 1790 self.log(tag, "dropped") 1791 del font[tag] 1792 continue 1793 1794 clazz = ttLib.getTableClass(tag) 1795 1796 if hasattr(clazz, 'prune_pre_subset'): 1797 table = font[tag] 1798 self.log.lapse("load '%s'" % tag) 1799 retain = table.prune_pre_subset(self.options) 1800 self.log.lapse("prune '%s'" % tag) 1801 if not retain: 1802 self.log(tag, "pruned to empty; dropped") 1803 del font[tag] 1804 continue 1805 else: 1806 self.log(tag, "pruned") 1807 1808 def _closure_glyphs(self, font): 1809 1810 self.glyphs = self.glyphs_requested.copy() 1811 1812 if 'cmap' in font: 1813 font['cmap'].closure_glyphs(self) 1814 self.glyphs_cmaped = self.glyphs 1815 1816 if self.options.notdef_glyph: 1817 if 'glyf' in font: 1818 self.glyphs.add(font.getGlyphName(0)) 1819 self.log("Added gid0 to subset") 1820 else: 1821 self.glyphs.add('.notdef') 1822 self.log("Added .notdef to subset") 1823 if self.options.recommended_glyphs: 1824 if 'glyf' in font: 1825 for i in range(4): 1826 self.glyphs.add(font.getGlyphName(i)) 1827 self.log("Added first four glyphs to subset") 1828 1829 if 'GSUB' in font: 1830 self.log("Closing glyph list over 'GSUB': %d glyphs before" % 1831 len(self.glyphs)) 1832 self.log.glyphs(self.glyphs, font=font) 1833 font['GSUB'].closure_glyphs(self) 1834 self.log("Closed glyph list over 'GSUB': %d glyphs after" % 1835 len(self.glyphs)) 1836 self.log.glyphs(self.glyphs, font=font) 1837 self.log.lapse("close glyph list over 'GSUB'") 1838 self.glyphs_gsubed = self.glyphs.copy() 1839 1840 if 'glyf' in font: 1841 self.log("Closing glyph list over 'glyf': %d glyphs before" % 1842 len(self.glyphs)) 1843 self.log.glyphs(self.glyphs, font=font) 1844 font['glyf'].closure_glyphs(self) 1845 self.log("Closed glyph list over 'glyf': %d glyphs after" % 1846 len(self.glyphs)) 1847 self.log.glyphs(self.glyphs, font=font) 1848 self.log.lapse("close glyph list over 'glyf'") 1849 self.glyphs_glyfed = self.glyphs.copy() 1850 1851 self.glyphs_all = self.glyphs.copy() 1852 1853 self.log("Retaining %d glyphs: " % len(self.glyphs_all)) 1854 1855 def _subset_glyphs(self, font): 1856 for tag in font.keys(): 1857 if tag == 'GlyphOrder': continue 1858 clazz = ttLib.getTableClass(tag) 1859 1860 if tag in self.options.no_subset_tables: 1861 self.log(tag, "subsetting not needed") 1862 elif hasattr(clazz, 'subset_glyphs'): 1863 table = font[tag] 1864 self.glyphs = self.glyphs_all 1865 retain = table.subset_glyphs(self) 1866 self.glyphs = self.glyphs_all 1867 self.log.lapse("subset '%s'" % tag) 1868 if not retain: 1869 self.log(tag, "subsetted to empty; dropped") 1870 del font[tag] 1871 else: 1872 self.log(tag, "subsetted") 1873 else: 1874 self.log(tag, "NOT subset; don't know how to subset; dropped") 1875 del font[tag] 1876 1877 glyphOrder = font.getGlyphOrder() 1878 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all] 1879 font.setGlyphOrder(glyphOrder) 1880 font._buildReverseGlyphOrderDict() 1881 self.log.lapse("subset GlyphOrder") 1882 1883 def _prune_post_subset(self, font): 1884 for tag in font.keys(): 1885 if tag == 'GlyphOrder': continue 1886 clazz = ttLib.getTableClass(tag) 1887 if hasattr(clazz, 'prune_post_subset'): 1888 table = font[tag] 1889 retain = table.prune_post_subset(self.options) 1890 self.log.lapse("prune '%s'" % tag) 1891 if not retain: 1892 self.log(tag, "pruned to empty; dropped") 1893 del font[tag] 1894 else: 1895 self.log(tag, "pruned") 1896 1897 def subset(self, font): 1898 1899 self._prune_pre_subset(font) 1900 self._closure_glyphs(font) 1901 self._subset_glyphs(font) 1902 self._prune_post_subset(font) 1903 1904 1905class Logger(object): 1906 1907 def __init__(self, verbose=False, xml=False, timing=False): 1908 self.verbose = verbose 1909 self.xml = xml 1910 self.timing = timing 1911 self.last_time = self.start_time = time.time() 1912 1913 def parse_opts(self, argv): 1914 argv = argv[:] 1915 for v in ['verbose', 'xml', 'timing']: 1916 if "--"+v in argv: 1917 setattr(self, v, True) 1918 argv.remove("--"+v) 1919 return argv 1920 1921 def __call__(self, *things): 1922 if not self.verbose: 1923 return 1924 print ' '.join(str(x) for x in things) 1925 1926 def lapse(self, *things): 1927 if not self.timing: 1928 return 1929 new_time = time.time() 1930 print "Took %0.3fs to %s" %(new_time - self.last_time, 1931 ' '.join(str(x) for x in things)) 1932 self.last_time = new_time 1933 1934 def glyphs(self, glyphs, font=None): 1935 self("Names: ", sorted(glyphs)) 1936 if font: 1937 reverseGlyphMap = font.getReverseGlyphMap() 1938 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs)) 1939 1940 def font(self, font, file=sys.stdout): 1941 if not self.xml: 1942 return 1943 from fontTools.misc import xmlWriter 1944 writer = xmlWriter.XMLWriter(file) 1945 font.disassembleInstructions = False # Work around ttLib bug 1946 for tag in font.keys(): 1947 writer.begintag(tag) 1948 writer.newline() 1949 font[tag].toXML(writer, font) 1950 writer.endtag(tag) 1951 writer.newline() 1952 1953 1954def load_font(fontFile, 1955 options, 1956 checkChecksums=False, 1957 dontLoadGlyphNames=False): 1958 1959 font = ttLib.TTFont(fontFile, 1960 checkChecksums=checkChecksums, 1961 recalcBBoxes=options.recalc_bounds) 1962 1963 # Hack: 1964 # 1965 # If we don't need glyph names, change 'post' class to not try to 1966 # load them. It avoid lots of headache with broken fonts as well 1967 # as loading time. 1968 # 1969 # Ideally ttLib should provide a way to ask it to skip loading 1970 # glyph names. But it currently doesn't provide such a thing. 1971 # 1972 if dontLoadGlyphNames: 1973 post = ttLib.getTableClass('post') 1974 saved = post.decode_format_2_0 1975 post.decode_format_2_0 = post.decode_format_3_0 1976 f = font['post'] 1977 if f.formatType == 2.0: 1978 f.formatType = 3.0 1979 post.decode_format_2_0 = saved 1980 1981 return font 1982 1983def save_font(font, outfile, options): 1984 if options.flavor and not hasattr(font, 'flavor'): 1985 raise Exception("fonttools version does not support flavors.") 1986 font.flavor = options.flavor 1987 font.save(outfile, reorderTables=options.canonical_order) 1988 1989def main(args): 1990 1991 log = Logger() 1992 args = log.parse_opts(args) 1993 1994 options = Options() 1995 args = options.parse_opts(args, ignore_unknown=['text']) 1996 1997 if len(args) < 2: 1998 print >>sys.stderr, "usage: pyftsubset font-file glyph... [--text=ABC]... [--option=value]..." 1999 sys.exit(1) 2000 2001 fontfile = args[0] 2002 args = args[1:] 2003 2004 dontLoadGlyphNames =(not options.glyph_names and 2005 all(any(g.startswith(p) 2006 for p in ['gid', 'glyph', 'uni', 'U+']) 2007 for g in args)) 2008 2009 font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames) 2010 subsetter = Subsetter(options=options, log=log) 2011 log.lapse("load font") 2012 2013 names = font.getGlyphNames() 2014 log.lapse("loading glyph names") 2015 2016 glyphs = [] 2017 unicodes = [] 2018 text = "" 2019 for g in args: 2020 if g == '*': 2021 glyphs.extend(font.getGlyphOrder()) 2022 continue 2023 if g in names: 2024 glyphs.append(g) 2025 continue 2026 if g.startswith('--text='): 2027 text += g[7:] 2028 continue 2029 if g.startswith('uni') or g.startswith('U+'): 2030 if g.startswith('uni') and len(g) > 3: 2031 g = g[3:] 2032 elif g.startswith('U+') and len(g) > 2: 2033 g = g[2:] 2034 u = int(g, 16) 2035 unicodes.append(u) 2036 continue 2037 if g.startswith('gid') or g.startswith('glyph'): 2038 if g.startswith('gid') and len(g) > 3: 2039 g = g[3:] 2040 elif g.startswith('glyph') and len(g) > 5: 2041 g = g[5:] 2042 try: 2043 glyphs.append(font.getGlyphName(int(g), requireReal=1)) 2044 except ValueError: 2045 raise Exception("Invalid glyph identifier: %s" % g) 2046 continue 2047 raise Exception("Invalid glyph identifier: %s" % g) 2048 log.lapse("compile glyph list") 2049 log("Unicodes:", unicodes) 2050 log("Glyphs:", glyphs) 2051 2052 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text) 2053 subsetter.subset(font) 2054 2055 outfile = fontfile + '.subset' 2056 2057 save_font (font, outfile, options) 2058 log.lapse("compile and save font") 2059 2060 log.last_time = log.start_time 2061 log.lapse("make one with everything(TOTAL TIME)") 2062 2063 if log.verbose: 2064 import os 2065 log("Input font: %d bytes" % os.path.getsize(fontfile)) 2066 log("Subset font: %d bytes" % os.path.getsize(outfile)) 2067 2068 log.font(font) 2069 2070 font.close() 2071 2072 2073__all__ = [ 2074 'Options', 2075 'Subsetter', 2076 'Logger', 2077 'load_font', 2078 'save_font', 2079 'main' 2080] 2081 2082if __name__ == '__main__': 2083 main(sys.argv[1:]) 2084