subset.py revision 83f1f5c438e415b6dbaef22ddc6e98bfcc1174a2
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  class _NonrecursingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1369
1370    def __init__(self, localSubrs, globalSubrs):
1371      fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1372                                                               localSubrs,
1373                                                               globalSubrs)
1374
1375    def op_callsubr(self, index):
1376      self.pop()
1377
1378    def op_callgsubr(self, index):
1379      self.pop()
1380
1381  for fontname in cff.keys():
1382    font = cff[fontname]
1383    cs = font.CharStrings
1384
1385    # Drop unused FontDictionaries
1386    if hasattr(font, "FDSelect"):
1387      sel = font.FDSelect
1388      indices = _uniq_sort(sel.gidArray)
1389      sel.gidArray = [indices.index (ss) for ss in sel.gidArray]
1390      arr = font.FDArray
1391      arr.items = [arr[i] for i in indices]
1392      arr.count = len(arr.items)
1393      del arr.file, arr.offsets
1394
1395    # Mark all used subroutines
1396    for g in font.charset:
1397      c,sel = cs.getItemAndSelector(g)
1398      subrs = getattr(c.private, "Subrs", [])
1399      decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs)
1400      decompiler.execute(c)
1401
1402    # Renumber subroutines to remove unused ones
1403    all_subrs = [font.GlobalSubrs]
1404    all_subrs.append(font.Private.Subrs)
1405    if hasattr(font, 'FDSelect'):
1406      all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs'))
1407    # Prepare
1408    for subrs in all_subrs:
1409      if not subrs: continue
1410      if not hasattr(subrs, '_used'):
1411        subrs._used = set()
1412      subrs._used = _uniq_sort(subrs._used)
1413      subrs._old_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs)
1414      subrs._new_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs._used)
1415    # Renumber glyph charstrings
1416    for g in font.charset:
1417      c,sel = cs.getItemAndSelector(g)
1418      subrs = getattr(c.private, "Subrs", [])
1419      c.subset_subroutines (subrs, font.GlobalSubrs)
1420    # Renumber subroutines themselves
1421    for subrs in all_subrs:
1422      if not subrs: continue
1423      decompiler = _NonrecursingT2Decompiler(subrs, font.GlobalSubrs)
1424      for i in xrange (subrs.count):
1425        if i not in subrs._used: continue
1426        decompiler.reset()
1427        decompiler.execute(subrs[i])
1428        if subrs == font.GlobalSubrs:
1429          if not hasattr(font, 'FDSelect'):
1430            local_subrs = font.Private.Subrs
1431          else:
1432            local_subrs = []
1433        else:
1434          local_subrs = subrs
1435        subrs[i].subset_subroutines (local_subrs, font.GlobalSubrs)
1436    # Cleanup
1437    for subrs in all_subrs:
1438      if not subrs: continue
1439      subrs.items = [subrs.items[i] for i in subrs._used]
1440      del subrs.file, subrs.offsets
1441      del subrs._used, subrs._old_bias, subrs._new_bias
1442
1443    if not options.hinting:
1444      pass  # TODO(behdad) Drop hints
1445
1446  return True
1447
1448@_add_method(fontTools.ttLib.getTableClass('cmap'))
1449def closure_glyphs(self, s):
1450  tables = [t for t in self.tables
1451            if t.platformID == 3 and t.platEncID in [1, 10]]
1452  for u in s.unicodes_requested:
1453    found = False
1454    for table in tables:
1455      if u in table.cmap:
1456        s.glyphs.add(table.cmap[u])
1457        found = True
1458        break
1459    if not found:
1460      s.log("No glyph for Unicode value %s; skipping." % u)
1461
1462@_add_method(fontTools.ttLib.getTableClass('cmap'))
1463def prune_pre_subset(self, options):
1464  if not options.legacy_cmap:
1465    # Drop non-Unicode / non-Symbol cmaps
1466    self.tables = [t for t in self.tables
1467                   if t.platformID == 3 and t.platEncID in [0, 1, 10]]
1468  if not options.symbol_cmap:
1469    self.tables = [t for t in self.tables
1470                   if t.platformID == 3 and t.platEncID in [1, 10]]
1471  # TODO(behdad) Only keep one subtable?
1472  # For now, drop format=0 which can't be subset_glyphs easily?
1473  self.tables = [t for t in self.tables if t.format != 0]
1474  return bool(self.tables)
1475
1476@_add_method(fontTools.ttLib.getTableClass('cmap'))
1477def subset_glyphs(self, s):
1478  s.glyphs = s.glyphs_cmaped
1479  for t in self.tables:
1480    # For reasons I don't understand I need this here
1481    # to force decompilation of the cmap format 14.
1482    try:
1483      getattr(t, "asdf")
1484    except AttributeError:
1485      pass
1486    if t.format == 14:
1487      # TODO(behdad) XXX We drop all the default-UVS mappings(g==None).
1488      t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs])
1489                       for v,l in t.uvsDict.iteritems())
1490      t.uvsDict = dict((v,l) for v,l in t.uvsDict.iteritems() if l)
1491    else:
1492      t.cmap = dict((u,g) for u,g in t.cmap.iteritems()
1493                    if g in s.glyphs_requested or u in s.unicodes_requested)
1494  self.tables = [t for t in self.tables
1495                 if (t.cmap if t.format != 14 else t.uvsDict)]
1496  # TODO(behdad) Convert formats when needed.
1497  # In particular, if we have a format=12 without non-BMP
1498  # characters, either drop format=12 one or convert it
1499  # to format=4 if there's not one.
1500  return bool(self.tables)
1501
1502@_add_method(fontTools.ttLib.getTableClass('name'))
1503def prune_pre_subset(self, options):
1504  if '*' not in options.name_IDs:
1505    self.names = [n for n in self.names if n.nameID in options.name_IDs]
1506  if not options.name_legacy:
1507    self.names = [n for n in self.names
1508                  if n.platformID == 3 and n.platEncID == 1]
1509  if '*' not in options.name_languages:
1510    self.names = [n for n in self.names if n.langID in options.name_languages]
1511  return True  # Retain even if empty
1512
1513
1514# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange?
1515# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
1516# TODO(behdad) Drop empty GSUB/GPOS, and GDEF if no GSUB/GPOS left
1517# TODO(behdad) Drop GDEF subitems if unused by lookups
1518# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF)
1519# TODO(behdad) Text direction considerations.
1520# TODO(behdad) Text script / language considerations.
1521
1522
1523class Options(object):
1524
1525  class UnknownOptionError(Exception):
1526    pass
1527
1528  _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ',
1529                          'PCLT', 'LTSH']
1530  _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill']  # Graphite
1531  _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL']  # Color
1532  _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1533                               'loca', 'name', 'cvt ', 'fpgm', 'prep']
1534  _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
1535
1536  # Based on HarfBuzz shapers
1537  _layout_features_groups = {
1538    # Default shaper
1539    'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1540    'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
1541    'vertical':  ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
1542    'ltr': ['ltra', 'ltrm'],
1543    'rtl': ['rtla', 'rtlm'],
1544    # Complex shapers
1545    'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1546               'cswh', 'mset'],
1547    'hangul': ['ljmo', 'vjmo', 'tjmo'],
1548    'tibetal': ['abvs', 'blws', 'abvm', 'blwm'],
1549    'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1550              'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1551              'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
1552  }
1553  _layout_features_default = _uniq_sort(sum(
1554      _layout_features_groups.itervalues(), []))
1555
1556  drop_tables = _drop_tables_default
1557  no_subset_tables = _no_subset_tables_default
1558  hinting_tables = _hinting_tables_default
1559  layout_features = _layout_features_default
1560  hinting = False
1561  glyph_names = False
1562  legacy_cmap = False
1563  symbol_cmap = False
1564  name_IDs = [1, 2]  # Family and Style
1565  name_legacy = False
1566  name_languages = [0x0409]  # English
1567  notdef_glyph = True # gid0 for TrueType / .notdef for CFF
1568  notdef_outline = False # No need for notdef to have an outline really
1569  recommended_glyphs = False  # gid1, gid2, gid3 for TrueType
1570  recalc_bounds = False # Recalculate font bounding boxes
1571  canonical_order = False # Order tables as recommended
1572  flavor = None # May be 'woff'
1573
1574  def __init__(self, **kwargs):
1575
1576    self.set(**kwargs)
1577
1578  def set(self, **kwargs):
1579    for k,v in kwargs.iteritems():
1580      if not hasattr(self, k):
1581        raise self.UnknownOptionError("Unknown option '%s'" % a)
1582      setattr(self, k, v)
1583
1584  def parse_opts(self, argv, ignore_unknown=False):
1585    ret = []
1586    opts = {}
1587    for a in argv:
1588      orig_a = a
1589      if not a.startswith('--'):
1590        ret.append(a)
1591        continue
1592      a = a[2:]
1593      i = a.find('=')
1594      op = '='
1595      if i == -1:
1596        if a.startswith("no-"):
1597          k = a[3:]
1598          v = False
1599        else:
1600          k = a
1601          v = True
1602      else:
1603        k = a[:i]
1604        if k[-1] in "-+":
1605          op = k[-1]+'='  # Ops is '-=' or '+=' now.
1606          k = k[:-1]
1607        v = a[i+1:]
1608      k = k.replace('-', '_')
1609      if not hasattr(self, k):
1610        if ignore_unknown == True or k in ignore_unknown:
1611          ret.append(orig_a)
1612          continue
1613        else:
1614          raise self.UnknownOptionError("Unknown option '%s'" % a)
1615
1616      ov = getattr(self, k)
1617      if isinstance(ov, bool):
1618        v = bool(v)
1619      elif isinstance(ov, int):
1620        v = int(v)
1621      elif isinstance(ov, list):
1622        vv = v.split(',')
1623        if vv == ['']:
1624          vv = []
1625        vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
1626        if op == '=':
1627          v = vv
1628        elif op == '+=':
1629          v = ov
1630          v.extend(vv)
1631        elif op == '-=':
1632          v = ov
1633          for x in vv:
1634            if x in v:
1635              v.remove(x)
1636        else:
1637          assert 0
1638
1639      opts[k] = v
1640    self.set(**opts)
1641
1642    return ret
1643
1644
1645class Subsetter(object):
1646
1647  def __init__(self, options=None, log=None):
1648
1649    if not log:
1650      log = Logger()
1651    if not options:
1652      options = Options()
1653
1654    self.options = options
1655    self.log = log
1656    self.unicodes_requested = set()
1657    self.glyphs_requested = set()
1658    self.glyphs = set()
1659
1660  def populate(self, glyphs=[], unicodes=[], text=""):
1661    self.unicodes_requested.update(unicodes)
1662    if isinstance(text, str):
1663      text = text.decode("utf8")
1664    for u in text:
1665      self.unicodes_requested.add(ord(u))
1666    self.glyphs_requested.update(glyphs)
1667    self.glyphs.update(glyphs)
1668
1669  def _prune_pre_subset(self, font):
1670
1671    for tag in font.keys():
1672      if tag == 'GlyphOrder': continue
1673
1674      if(tag in self.options.drop_tables or
1675         (tag in self.options.hinting_tables and not self.options.hinting)):
1676        self.log(tag, "dropped")
1677        del font[tag]
1678        continue
1679
1680      clazz = fontTools.ttLib.getTableClass(tag)
1681
1682      if hasattr(clazz, 'prune_pre_subset'):
1683        table = font[tag]
1684        retain = table.prune_pre_subset(self.options)
1685        self.log.lapse("prune  '%s'" % tag)
1686        if not retain:
1687          self.log(tag, "pruned to empty; dropped")
1688          del font[tag]
1689          continue
1690        else:
1691          self.log(tag, "pruned")
1692
1693  def _closure_glyphs(self, font):
1694
1695    self.glyphs = self.glyphs_requested.copy()
1696
1697    if 'cmap' in font:
1698      font['cmap'].closure_glyphs(self)
1699    self.glyphs_cmaped = self.glyphs
1700
1701    if self.options.notdef_glyph:
1702      if 'glyf' in font:
1703        self.glyphs.add(font.getGlyphName(0))
1704        self.log("Added gid0 to subset")
1705      else:
1706        self.glyphs.add('.notdef')
1707        self.log("Added .notdef to subset")
1708    if self.options.recommended_glyphs:
1709      if 'glyf' in font:
1710        for i in range(4):
1711          self.glyphs.add(font.getGlyphName(i))
1712        self.log("Added first four glyphs to subset")
1713
1714    if 'GSUB' in font:
1715      self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1716                len(self.glyphs))
1717      self.log.glyphs(self.glyphs, font=font)
1718      font['GSUB'].closure_glyphs(self)
1719      self.log("Closed  glyph list over 'GSUB': %d glyphs after" %
1720                len(self.glyphs))
1721      self.log.glyphs(self.glyphs, font=font)
1722      self.log.lapse("close glyph list over 'GSUB'")
1723    self.glyphs_gsubed = self.glyphs.copy()
1724
1725    if 'glyf' in font:
1726      self.log("Closing glyph list over 'glyf': %d glyphs before" %
1727                len(self.glyphs))
1728      self.log.glyphs(self.glyphs, font=font)
1729      font['glyf'].closure_glyphs(self)
1730      self.log("Closed  glyph list over 'glyf': %d glyphs after" %
1731                len(self.glyphs))
1732      self.log.glyphs(self.glyphs, font=font)
1733      self.log.lapse("close glyph list over 'glyf'")
1734    self.glyphs_glyfed = self.glyphs.copy()
1735
1736    self.glyphs_all = self.glyphs.copy()
1737
1738    self.log("Retaining %d glyphs: " % len(self.glyphs_all))
1739
1740  def _subset_glyphs(self, font):
1741    for tag in font.keys():
1742      if tag == 'GlyphOrder': continue
1743      clazz = fontTools.ttLib.getTableClass(tag)
1744
1745      if tag in self.options.no_subset_tables:
1746        self.log(tag, "subsetting not needed")
1747      elif hasattr(clazz, 'subset_glyphs'):
1748        table = font[tag]
1749        self.glyphs = self.glyphs_all
1750        retain = table.subset_glyphs(self)
1751        self.glyphs = self.glyphs_all
1752        self.log.lapse("subset '%s'" % tag)
1753        if not retain:
1754          self.log(tag, "subsetted to empty; dropped")
1755          del font[tag]
1756        else:
1757          self.log(tag, "subsetted")
1758      else:
1759        self.log(tag, "NOT subset; don't know how to subset; dropped")
1760        del font[tag]
1761
1762    glyphOrder = font.getGlyphOrder()
1763    glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
1764    font.setGlyphOrder(glyphOrder)
1765    font._buildReverseGlyphOrderDict()
1766    self.log.lapse("subset GlyphOrder")
1767
1768  def _prune_post_subset(self, font):
1769    for tag in font.keys():
1770      if tag == 'GlyphOrder': continue
1771      clazz = fontTools.ttLib.getTableClass(tag)
1772      if hasattr(clazz, 'prune_post_subset'):
1773        table = font[tag]
1774        retain = table.prune_post_subset(self.options)
1775        self.log.lapse("prune  '%s'" % tag)
1776        if not retain:
1777          self.log(tag, "pruned to empty; dropped")
1778          del font[tag]
1779        else:
1780          self.log(tag, "pruned")
1781
1782  def subset(self, font):
1783
1784    self._prune_pre_subset(font)
1785    self._closure_glyphs(font)
1786    self._subset_glyphs(font)
1787    self._prune_post_subset(font)
1788
1789
1790class Logger(object):
1791
1792  def __init__(self, verbose=False, xml=False, timing=False):
1793    self.verbose = verbose
1794    self.xml = xml
1795    self.timing = timing
1796    self.last_time = self.start_time = time.time()
1797
1798  def parse_opts(self, argv):
1799    argv = argv[:]
1800    for v in ['verbose', 'xml', 'timing']:
1801      if "--"+v in argv:
1802        setattr(self, v, True)
1803        argv.remove("--"+v)
1804    return argv
1805
1806  def __call__(self, *things):
1807    if not self.verbose:
1808      return
1809    print ' '.join(str(x) for x in things)
1810
1811  def lapse(self, *things):
1812    if not self.timing:
1813      return
1814    new_time = time.time()
1815    print "Took %0.3fs to %s" %(new_time - self.last_time,
1816                                 ' '.join(str(x) for x in things))
1817    self.last_time = new_time
1818
1819  def glyphs(self, glyphs, font=None):
1820    self("Names: ", sorted(glyphs))
1821    if font:
1822      reverseGlyphMap = font.getReverseGlyphMap()
1823      self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
1824
1825  def font(self, font, file=sys.stdout):
1826    if not self.xml:
1827      return
1828    import xmlWriter
1829    writer = xmlWriter.XMLWriter(file)
1830    font.disassembleInstructions = False  # Work around ttLib bug
1831    for tag in font.keys():
1832      writer.begintag(tag)
1833      writer.newline()
1834      font[tag].toXML(writer, font)
1835      writer.endtag(tag)
1836      writer.newline()
1837
1838
1839def load_font(fontFile,
1840              options,
1841              checkChecksums=False,
1842              dontLoadGlyphNames=False):
1843
1844  font = fontTools.ttLib.TTFont(fontFile,
1845                                checkChecksums=checkChecksums,
1846                                recalcBBoxes=options.recalc_bounds)
1847
1848  # Hack:
1849  #
1850  # If we don't need glyph names, change 'post' class to not try to
1851  # load them.  It avoid lots of headache with broken fonts as well
1852  # as loading time.
1853  #
1854  # Ideally ttLib should provide a way to ask it to skip loading
1855  # glyph names.  But it currently doesn't provide such a thing.
1856  #
1857  if dontLoadGlyphNames:
1858    post = fontTools.ttLib.getTableClass('post')
1859    saved = post.decode_format_2_0
1860    post.decode_format_2_0 = post.decode_format_3_0
1861    f = font['post']
1862    if f.formatType == 2.0:
1863      f.formatType = 3.0
1864    post.decode_format_2_0 = saved
1865
1866  return font
1867
1868def save_font(font, outfile, options):
1869  if options.flavor and not hasattr(font, 'flavor'):
1870    raise Exception("fonttools version does not support flavors.")
1871  font.flavor = options.flavor
1872  font.save(outfile, reorderTables=options.canonical_order)
1873
1874def main(args):
1875
1876  log = Logger()
1877  args = log.parse_opts(args)
1878
1879  options = Options()
1880  args = options.parse_opts(args, ignore_unknown=['text'])
1881
1882  if len(args) < 2:
1883    print >>sys.stderr, "usage: pyftsubset font-file glyph... [--text=ABC]... [--option=value]..."
1884    sys.exit(1)
1885
1886  fontfile = args[0]
1887  args = args[1:]
1888
1889  dontLoadGlyphNames =(not options.glyph_names and
1890         all(any(g.startswith(p)
1891             for p in ['gid', 'glyph', 'uni', 'U+'])
1892              for g in args))
1893
1894  font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
1895  subsetter = Subsetter(options=options, log=log)
1896  log.lapse("load font")
1897
1898  names = font.getGlyphNames()
1899  log.lapse("loading glyph names")
1900
1901  glyphs = []
1902  unicodes = []
1903  text = ""
1904  for g in args:
1905    if g in names:
1906      glyphs.append(g)
1907      continue
1908    if g.startswith('--text='):
1909      text += g[7:]
1910      continue
1911    if g.startswith('uni') or g.startswith('U+'):
1912      if g.startswith('uni') and len(g) > 3:
1913        g = g[3:]
1914      elif g.startswith('U+') and len(g) > 2:
1915        g = g[2:]
1916      u = int(g, 16)
1917      unicodes.append(u)
1918      continue
1919    if g.startswith('gid') or g.startswith('glyph'):
1920      if g.startswith('gid') and len(g) > 3:
1921        g = g[3:]
1922      elif g.startswith('glyph') and len(g) > 5:
1923        g = g[5:]
1924      try:
1925        glyphs.append(font.getGlyphName(int(g), requireReal=1))
1926      except ValueError:
1927        raise Exception("Invalid glyph identifier: %s" % g)
1928      continue
1929    raise Exception("Invalid glyph identifier: %s" % g)
1930  log.lapse("compile glyph list")
1931  log("Unicodes:", unicodes)
1932  log("Glyphs:", glyphs)
1933
1934  subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
1935  subsetter.subset(font)
1936
1937  outfile = fontfile + '.subset'
1938
1939  save_font (font, outfile, options)
1940  log.lapse("compile and save font")
1941
1942  log.last_time = log.start_time
1943  log.lapse("make one with everything(TOTAL TIME)")
1944
1945  if log.verbose:
1946    import os
1947    log("Input  font: %d bytes" % os.path.getsize(fontfile))
1948    log("Subset font: %d bytes" % os.path.getsize(outfile))
1949
1950  log.font(font)
1951
1952  font.close()
1953
1954
1955__all__ = [
1956  'Options',
1957  'Subsetter',
1958  'Logger',
1959  'load_font',
1960  'save_font',
1961  'main'
1962]
1963
1964if __name__ == '__main__':
1965  main(sys.argv[1:])
1966