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