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