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