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