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