blob: 88cad2e6fd6ed12684da7db1d7511fa864b9261e [file] [log] [blame]
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04001# Copyright 2013 Google, Inc. All Rights Reserved.
2#
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04003# Google Author(s): Behdad Esfahbod
Behdad Esfahbod616d36e2013-08-13 20:02:59 -04004
5"""Python OpenType Layout Subsetter.
6
7Later grown into full OpenType subsetter, supporting all standard tables.
8"""
9
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040010import sys
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -040011import struct
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040012import time
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -040013import array
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040014
Behdad Esfahbod45a84602013-08-19 14:44:49 -040015import fontTools.ttLib
16import fontTools.ttLib.tables
17import fontTools.ttLib.tables.otTables
18import fontTools.cffLib
19import fontTools.misc.psCharStrings
Behdad Esfahbod285d7b82013-09-10 20:30:47 -040020import fontTools.pens.basePen
Behdad Esfahbod54660612013-07-21 18:16:55 -040021
Behdad Esfahbod54660612013-07-21 18:16:55 -040022
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040023def _add_method(*clazzes):
Behdad Esfahbod616d36e2013-08-13 20:02:59 -040024 """Returns a decorator function that adds a new method to one or
25 more classes."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040026 def wrapper(method):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040027 for clazz in clazzes:
28 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
Behdad Esfahbode7a0d562013-08-16 10:56:30 -040029 assert not hasattr(clazz, method.func_name), \
Behdad Esfahbodd77f1572013-08-15 19:24:36 -040030 "Oops, class '%s' has method '%s'." % (clazz.__name__,
31 method.func_name)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040032 setattr(clazz, method.func_name, method)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040033 return None
34 return wrapper
Behdad Esfahbod54660612013-07-21 18:16:55 -040035
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040036def _uniq_sort(l):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040037 return sorted(set(l))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040038
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -040039def _set_update(s, *others):
40 # Jython's set.update only takes one other argument.
41 # Emulate real set.update...
42 for other in others:
43 s.update(other)
44
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040045
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040046@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040047def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040048 "Returns ascending list of matching coverage values."
Behdad Esfahbod4734be52013-08-14 19:47:42 -040049 return [i for i,g in enumerate(self.glyphs) if g in glyphs]
Behdad Esfahbod610b0552013-07-23 14:52:18 -040050
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040051@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040052def intersect_glyphs(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040053 "Returns set of intersecting glyphs."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040054 return set(g for g in self.glyphs if g in glyphs)
Behdad Esfahbod849d25c2013-08-12 19:24:24 -040055
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040056@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040057def subset(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040058 "Returns ascending list of remaining coverage values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040059 indices = self.intersect(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040060 self.glyphs = [g for g in self.glyphs if g in glyphs]
61 return indices
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040062
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040063@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040064def remap(self, coverage_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040065 "Remaps coverage."
66 self.glyphs = [self.glyphs[i] for i in coverage_map]
Behdad Esfahbod14374262013-08-08 22:26:49 -040067
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040068@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040069def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040070 "Returns ascending list of matching class values."
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040071 return _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040072 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040073 [v for g,v in self.classDefs.iteritems() if g in glyphs])
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040074
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040075@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040076def intersect_class(self, glyphs, klass):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040077 "Returns set of glyphs matching class."
78 if klass == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040079 return set(g for g in glyphs if g not in self.classDefs)
80 return set(g for g,v in self.classDefs.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040081 if v == klass and g in glyphs)
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040082
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040083@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040084def subset(self, glyphs, remap=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040085 "Returns ascending list of remaining classes."
Behdad Esfahbodd73f2252013-08-16 10:58:25 -040086 self.classDefs = dict((g,v) for g,v in self.classDefs.iteritems() if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040087 # Note: while class 0 has the special meaning of "not matched",
88 # if no glyph will ever /not match/, we can optimize class 0 out too.
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040089 indices = _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040090 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod4e5d9672013-08-14 19:49:53 -040091 self.classDefs.values())
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040092 if remap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040093 self.remap(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040094 return indices
Behdad Esfahbod4aa6ce32013-07-22 12:15:36 -040095
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040096@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040097def remap(self, class_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040098 "Remaps classes."
Behdad Esfahbodd73f2252013-08-16 10:58:25 -040099 self.classDefs = dict((g,class_map.index(v))
100 for g,v in self.classDefs.iteritems())
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400101
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400102@_add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400103def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400104 if cur_glyphs == None: cur_glyphs = s.glyphs
105 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400106 s.glyphs.update(v for g,v in self.mapping.iteritems() if g in cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400107 else:
108 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400109
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400110@_add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400111def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400112 if self.Format in [1, 2]:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400113 self.mapping = dict((g,v) for g,v in self.mapping.iteritems()
114 if g in s.glyphs and v in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400115 return bool(self.mapping)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400116 else:
117 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400118
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400119@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400120def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400121 if cur_glyphs == None: cur_glyphs = s.glyphs
122 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400123 indices = self.Coverage.intersect(cur_glyphs)
Behdad Esfahboda9bfec12013-08-16 16:21:25 -0400124 _set_update(s.glyphs, *(self.Sequence[i].Substitute for i in indices))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400125 else:
126 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400127
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400128@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400129def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400130 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400131 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400132 self.Sequence = [self.Sequence[i] for i in indices]
133 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400134 indices = [i for i,seq in enumerate(self.Sequence)
135 if all(sub in s.glyphs for sub in seq.Substitute)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400136 self.Sequence = [self.Sequence[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400137 self.Coverage.remap(indices)
138 self.SequenceCount = len(self.Sequence)
139 return bool(self.SequenceCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400140 else:
141 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400142
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400143@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400144def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400145 if cur_glyphs == None: cur_glyphs = s.glyphs
146 if self.Format == 1:
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -0400147 _set_update(s.glyphs, *(vlist for g,vlist in self.alternates.iteritems()
148 if g in cur_glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400149 else:
150 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400151
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400152@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400153def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400154 if self.Format == 1:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400155 self.alternates = dict((g,vlist)
156 for g,vlist in self.alternates.iteritems()
157 if g in s.glyphs and
158 all(v in s.glyphs for v in vlist))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400159 return bool(self.alternates)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400160 else:
161 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400162
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400163@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400164def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400165 if cur_glyphs == None: cur_glyphs = s.glyphs
166 if self.Format == 1:
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -0400167 _set_update(s.glyphs, *([seq.LigGlyph for seq in seqs
168 if all(c in s.glyphs for c in seq.Component)]
169 for g,seqs in self.ligatures.iteritems()
170 if g in cur_glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400171 else:
172 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400173
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400174@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400175def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400176 if self.Format == 1:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400177 self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems()
178 if g in s.glyphs)
179 self.ligatures = dict((g,[seq for seq in seqs
180 if seq.LigGlyph in s.glyphs and
181 all(c in s.glyphs for c in seq.Component)])
182 for g,seqs in self.ligatures.iteritems())
183 self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems() if v)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400184 return bool(self.ligatures)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400185 else:
186 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400187
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400188@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400189def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400190 if cur_glyphs == None: cur_glyphs = s.glyphs
191 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400192 indices = self.Coverage.intersect(cur_glyphs)
193 if(not indices or
194 not all(c.intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400195 for c in self.LookAheadCoverage + self.BacktrackCoverage)):
196 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400197 s.glyphs.update(self.Substitute[i] for i in indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400198 else:
199 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400200
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400201@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400202def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400203 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400204 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400205 self.Substitute = [self.Substitute[i] for i in indices]
206 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400207 indices = [i for i,sub in enumerate(self.Substitute)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400208 if sub in s.glyphs]
209 self.Substitute = [self.Substitute[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400210 self.Coverage.remap(indices)
211 self.GlyphCount = len(self.Substitute)
212 return bool(self.GlyphCount and
213 all(c.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400214 for c in self.LookAheadCoverage+self.BacktrackCoverage))
215 else:
216 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400217
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400218@_add_method(fontTools.ttLib.tables.otTables.SinglePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400219def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400220 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400221 return len(self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400222 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400223 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400224 self.Value = [self.Value[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400225 self.ValueCount = len(self.Value)
226 return bool(self.ValueCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400227 else:
228 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400229
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400230@_add_method(fontTools.ttLib.tables.otTables.SinglePos)
231def prune_post_subset(self, options):
232 if not options.hinting:
233 # Drop device tables
234 self.ValueFormat &= ~0x00F0
235 return True
236
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400237@_add_method(fontTools.ttLib.tables.otTables.PairPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400238def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400239 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400240 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400241 self.PairSet = [self.PairSet[i] for i in indices]
242 for p in self.PairSet:
243 p.PairValueRecord = [r for r in p.PairValueRecord
244 if r.SecondGlyph in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400245 p.PairValueCount = len(p.PairValueRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400246 self.PairSet = [p for p in self.PairSet if p.PairValueCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400247 self.PairSetCount = len(self.PairSet)
248 return bool(self.PairSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400249 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400250 class1_map = self.ClassDef1.subset(s.glyphs, remap=True)
251 class2_map = self.ClassDef2.subset(s.glyphs, remap=True)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400252 self.Class1Record = [self.Class1Record[i] for i in class1_map]
253 for c in self.Class1Record:
254 c.Class2Record = [c.Class2Record[i] for i in class2_map]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400255 self.Class1Count = len(class1_map)
256 self.Class2Count = len(class2_map)
257 return bool(self.Class1Count and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400258 self.Class2Count and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400259 self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400260 else:
261 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400262
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400263@_add_method(fontTools.ttLib.tables.otTables.PairPos)
264def prune_post_subset(self, options):
265 if not options.hinting:
266 # Drop device tables
267 self.ValueFormat1 &= ~0x00F0
268 self.ValueFormat2 &= ~0x00F0
269 return True
270
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400271@_add_method(fontTools.ttLib.tables.otTables.CursivePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400272def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400273 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400274 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400275 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400276 self.EntryExitCount = len(self.EntryExitRecord)
277 return bool(self.EntryExitCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400278 else:
279 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400280
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400281@_add_method(fontTools.ttLib.tables.otTables.Anchor)
282def prune_hints(self):
283 # Drop device tables / contour anchor point
284 self.Format = 1
285
286@_add_method(fontTools.ttLib.tables.otTables.CursivePos)
287def prune_post_subset(self, options):
288 if not options.hinting:
289 for rec in self.EntryExitRecord:
290 if rec.EntryAnchor: rec.EntryAnchor.prune_hints()
291 if rec.ExitAnchor: rec.ExitAnchor.prune_hints()
292 return True
293
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400294@_add_method(fontTools.ttLib.tables.otTables.MarkBasePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400295def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400296 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400297 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400298 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
299 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400300 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
301 base_indices = self.BaseCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400302 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i]
303 for i in base_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400304 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400305 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400306 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400307 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400308 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400309 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400310 for b in self.BaseArray.BaseRecord:
311 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400312 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400313 self.MarkArray.MarkCount and
314 self.BaseArray.BaseCount)
315 else:
316 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400317
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400318@_add_method(fontTools.ttLib.tables.otTables.MarkBasePos)
319def prune_post_subset(self, options):
320 if not options.hinting:
321 for m in self.MarkArray.MarkRecord:
322 m.MarkAnchor.prune_hints()
323 for b in self.BaseArray.BaseRecord:
324 for a in b.BaseAnchor:
325 a.prune_hints()
326 return True
327
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400328@_add_method(fontTools.ttLib.tables.otTables.MarkLigPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400329def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400330 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400331 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400332 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
333 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400334 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
335 ligature_indices = self.LigatureCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400336 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i]
337 for i in ligature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400338 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400339 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400340 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400341 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400342 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400343 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400344 for l in self.LigatureArray.LigatureAttach:
345 for c in l.ComponentRecord:
346 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400347 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400348 self.MarkArray.MarkCount and
349 self.LigatureArray.LigatureCount)
350 else:
351 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400352
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400353@_add_method(fontTools.ttLib.tables.otTables.MarkLigPos)
354def prune_post_subset(self, options):
355 if not options.hinting:
356 for m in self.MarkArray.MarkRecord:
357 m.MarkAnchor.prune_hints()
358 for l in self.LigatureArray.LigatureAttach:
359 for c in l.ComponentRecord:
360 for a in c.LigatureAnchor:
361 a.prune_hints()
362 return True
363
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400364@_add_method(fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400365def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400366 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400367 mark1_indices = self.Mark1Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400368 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i]
369 for i in mark1_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400370 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
371 mark2_indices = self.Mark2Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400372 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i]
373 for i in mark2_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400374 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400375 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400376 class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400377 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400378 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400379 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400380 for b in self.Mark2Array.Mark2Record:
381 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400382 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400383 self.Mark1Array.MarkCount and
384 self.Mark2Array.MarkCount)
385 else:
386 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400387
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400388@_add_method(fontTools.ttLib.tables.otTables.MarkMarkPos)
389def prune_post_subset(self, options):
390 if not options.hinting:
391 # Drop device tables or contour anchor point
392 for m in self.Mark1Array.MarkRecord:
393 m.MarkAnchor.prune_hints()
394 for b in self.Mark2Array.Mark2Record:
Behdad Esfahbod0ec17d92013-09-15 18:30:41 -0400395 for m in b.Mark2Anchor:
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400396 m.prune_hints()
397 return True
398
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400399@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
400 fontTools.ttLib.tables.otTables.MultipleSubst,
401 fontTools.ttLib.tables.otTables.AlternateSubst,
402 fontTools.ttLib.tables.otTables.LigatureSubst,
403 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
404 fontTools.ttLib.tables.otTables.SinglePos,
405 fontTools.ttLib.tables.otTables.PairPos,
406 fontTools.ttLib.tables.otTables.CursivePos,
407 fontTools.ttLib.tables.otTables.MarkBasePos,
408 fontTools.ttLib.tables.otTables.MarkLigPos,
409 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400410def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400411 pass
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400412
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400413@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
414 fontTools.ttLib.tables.otTables.MultipleSubst,
415 fontTools.ttLib.tables.otTables.AlternateSubst,
416 fontTools.ttLib.tables.otTables.LigatureSubst,
417 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
418 fontTools.ttLib.tables.otTables.SinglePos,
419 fontTools.ttLib.tables.otTables.PairPos,
420 fontTools.ttLib.tables.otTables.CursivePos,
421 fontTools.ttLib.tables.otTables.MarkBasePos,
422 fontTools.ttLib.tables.otTables.MarkLigPos,
423 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400424def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400425 return []
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400426
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400427@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400428 fontTools.ttLib.tables.otTables.MultipleSubst,
429 fontTools.ttLib.tables.otTables.AlternateSubst,
430 fontTools.ttLib.tables.otTables.LigatureSubst,
431 fontTools.ttLib.tables.otTables.ContextSubst,
432 fontTools.ttLib.tables.otTables.ChainContextSubst,
433 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
434 fontTools.ttLib.tables.otTables.SinglePos,
435 fontTools.ttLib.tables.otTables.PairPos,
436 fontTools.ttLib.tables.otTables.CursivePos,
437 fontTools.ttLib.tables.otTables.MarkBasePos,
438 fontTools.ttLib.tables.otTables.MarkLigPos,
439 fontTools.ttLib.tables.otTables.MarkMarkPos,
440 fontTools.ttLib.tables.otTables.ContextPos,
441 fontTools.ttLib.tables.otTables.ChainContextPos)
442def prune_pre_subset(self, options):
443 return True
444
445@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
446 fontTools.ttLib.tables.otTables.MultipleSubst,
447 fontTools.ttLib.tables.otTables.AlternateSubst,
448 fontTools.ttLib.tables.otTables.LigatureSubst,
449 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
450 fontTools.ttLib.tables.otTables.ContextSubst,
451 fontTools.ttLib.tables.otTables.ChainContextSubst,
452 fontTools.ttLib.tables.otTables.ContextPos,
453 fontTools.ttLib.tables.otTables.ChainContextPos)
454def prune_post_subset(self, options):
455 return True
456
457@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400458 fontTools.ttLib.tables.otTables.AlternateSubst,
459 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400460def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400461 return False
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400462
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400463@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst,
464 fontTools.ttLib.tables.otTables.LigatureSubst,
465 fontTools.ttLib.tables.otTables.ContextSubst,
466 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400467def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400468 return True
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400469
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400470@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
471 fontTools.ttLib.tables.otTables.ChainContextSubst,
472 fontTools.ttLib.tables.otTables.ContextPos,
473 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400474def __classify_context(self):
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400475
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -0400476 class ContextHelper(object):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400477 def __init__(self, klass, Format):
478 if klass.__name__.endswith('Subst'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400479 Typ = 'Sub'
480 Type = 'Subst'
481 else:
482 Typ = 'Pos'
483 Type = 'Pos'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400484 if klass.__name__.startswith('Chain'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400485 Chain = 'Chain'
486 else:
487 Chain = ''
488 ChainTyp = Chain+Typ
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400489
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400490 self.Typ = Typ
491 self.Type = Type
492 self.Chain = Chain
493 self.ChainTyp = ChainTyp
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400494
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400495 self.LookupRecord = Type+'LookupRecord'
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400496
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400497 if Format == 1:
498 Coverage = lambda r: r.Coverage
499 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400500 ContextData = lambda r:(None,)
501 ChainContextData = lambda r:(None, None, None)
502 RuleData = lambda r:(r.Input,)
503 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400504 SetRuleData = None
505 ChainSetRuleData = None
506 elif Format == 2:
507 Coverage = lambda r: r.Coverage
508 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400509 ContextData = lambda r:(r.ClassDef,)
510 ChainContextData = lambda r:(r.LookAheadClassDef,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400511 r.InputClassDef,
512 r.BacktrackClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400513 RuleData = lambda r:(r.Class,)
514 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack)
515 def SetRuleData(r, d):(r.Class,) = d
516 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400517 elif Format == 3:
518 Coverage = lambda r: r.Coverage[0]
519 ChainCoverage = lambda r: r.InputCoverage[0]
520 ContextData = None
521 ChainContextData = None
522 RuleData = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400523 ChainRuleData = lambda r:(r.LookAheadCoverage +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400524 r.InputCoverage +
525 r.BacktrackCoverage)
526 SetRuleData = None
527 ChainSetRuleData = None
528 else:
529 assert 0, "unknown format: %s" % Format
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400530
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400531 if Chain:
532 self.Coverage = ChainCoverage
533 self.ContextData = ChainContextData
534 self.RuleData = ChainRuleData
535 self.SetRuleData = ChainSetRuleData
536 else:
537 self.Coverage = Coverage
538 self.ContextData = ContextData
539 self.RuleData = RuleData
540 self.SetRuleData = SetRuleData
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400541
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400542 if Format == 1:
543 self.Rule = ChainTyp+'Rule'
544 self.RuleCount = ChainTyp+'RuleCount'
545 self.RuleSet = ChainTyp+'RuleSet'
546 self.RuleSetCount = ChainTyp+'RuleSetCount'
547 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
548 elif Format == 2:
549 self.Rule = ChainTyp+'ClassRule'
550 self.RuleCount = ChainTyp+'ClassRuleCount'
551 self.RuleSet = ChainTyp+'ClassSet'
552 self.RuleSetCount = ChainTyp+'ClassSetCount'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400553 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r)
Behdad Esfahbod89987002013-07-23 23:07:42 -0400554
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400555 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
Behdad Esfahbod11763302013-08-14 15:33:08 -0400556 self.Input = 'Input' if Chain else 'Class'
Behdad Esfahbod27108392013-07-23 16:40:47 -0400557
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400558 if self.Format not in [1, 2, 3]:
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400559 return None # Don't shoot the messenger; let it go
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400560 if not hasattr(self.__class__, "__ContextHelpers"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400561 self.__class__.__ContextHelpers = {}
562 if self.Format not in self.__class__.__ContextHelpers:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400563 helper = ContextHelper(self.__class__, self.Format)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400564 self.__class__.__ContextHelpers[self.Format] = helper
565 return self.__class__.__ContextHelpers[self.Format]
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400566
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400567@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
568 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400569def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400570 if cur_glyphs == None: cur_glyphs = s.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400571 c = self.__classify_context()
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400572
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400573 indices = c.Coverage(self).intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400574 if not indices:
575 return []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400576 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs);
Behdad Esfahbod1d4fa132013-08-08 22:59:32 -0400577
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400578 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400579 ContextData = c.ContextData(self)
580 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400581 for i in indices:
582 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400583 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400584 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400585 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
586 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400587 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400588 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400589 if not ll: continue
590 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400591 if chaos:
592 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400593 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400594 if seqi == 0:
595 pos_glyphs = set([c.Coverage(self).glyphs[i]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400596 else:
Behdad Esfahbodd3fdcc72013-08-14 17:59:31 -0400597 pos_glyphs = set([r.Input[seqi - 1]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400598 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400599 chaos = chaos or lookup.may_have_non_1to1()
600 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400601 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400602 ClassDef = getattr(self, c.ClassDef)
603 indices = ClassDef.intersect(cur_glyphs)
604 ContextData = c.ContextData(self)
605 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400606 for i in indices:
607 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400608 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400609 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400610 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
611 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400612 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400613 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400614 if not ll: continue
615 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400616 if chaos:
617 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400618 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400619 if seqi == 0:
620 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400621 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400622 pos_glyphs = ClassDef.intersect_class(s.glyphs,
Behdad Esfahbod11763302013-08-14 15:33:08 -0400623 getattr(r, c.Input)[seqi - 1])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400624 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400625 chaos = chaos or lookup.may_have_non_1to1()
626 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400627 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400628 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400629 return []
630 r = self
631 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400632 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400633 if not ll: continue
634 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400635 if chaos:
636 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400637 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400638 if seqi == 0:
639 pos_glyphs = cur_glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400640 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400641 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400642 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400643 chaos = chaos or lookup.may_have_non_1to1()
644 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400645 else:
646 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod00776972013-07-23 15:33:00 -0400647
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400648@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
649 fontTools.ttLib.tables.otTables.ContextPos,
650 fontTools.ttLib.tables.otTables.ChainContextSubst,
651 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400652def subset_glyphs(self, s):
653 c = self.__classify_context()
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400654
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400655 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400656 indices = self.Coverage.subset(s.glyphs)
657 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400658 rss = [rss[i] for i in indices]
659 for rs in rss:
660 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400661 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400662 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400663 if r and all(all(g in s.glyphs for g in glist)
664 for glist in c.RuleData(r))]
665 setattr(rs, c.Rule, ss)
666 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400667 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400668 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
669 setattr(self, c.RuleSet, rss)
670 setattr(self, c.RuleSetCount, len(rss))
671 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400672 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400673 if not self.Coverage.subset(s.glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400674 return False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400675 indices = getattr(self, c.ClassDef).subset(self.Coverage.glyphs,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400676 remap=False)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400677 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400678 rss = [rss[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400679 ContextData = c.ContextData(self)
680 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400681 for rs in rss:
682 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400683 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400684 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400685 if r and all(all(k in klass_map for k in klist)
686 for klass_map,klist in zip(klass_maps, c.RuleData(r)))]
687 setattr(rs, c.Rule, ss)
688 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400689
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400690 # Remap rule classes
691 for r in ss:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400692 c.SetRuleData(r, [[klass_map.index(k) for k in klist]
693 for klass_map,klist in zip(klass_maps, c.RuleData(r))])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400694 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400695 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
696 setattr(self, c.RuleSet, rss)
697 setattr(self, c.RuleSetCount, len(rss))
698 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400699 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400700 return all(x.subset(s.glyphs) for x in c.RuleData(self))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400701 else:
702 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400703
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400704@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
705 fontTools.ttLib.tables.otTables.ChainContextSubst,
706 fontTools.ttLib.tables.otTables.ContextPos,
707 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400708def subset_lookups(self, lookup_indices):
709 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400710
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400711 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400712 for rs in getattr(self, c.RuleSet):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400713 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400714 for r in getattr(rs, c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400715 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400716 setattr(r, c.LookupRecord,
717 [ll for ll in getattr(r, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400718 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400719 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400720 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400721 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400722 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400723 setattr(self, c.LookupRecord,
724 [ll for ll in getattr(self, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400725 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400726 for ll in getattr(self, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400727 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400728 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400729 else:
730 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400731
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400732@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
733 fontTools.ttLib.tables.otTables.ChainContextSubst,
734 fontTools.ttLib.tables.otTables.ContextPos,
735 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400736def collect_lookups(self):
737 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400738
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400739 if self.Format in [1, 2]:
740 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400741 for rs in getattr(self, c.RuleSet) if rs
742 for r in getattr(rs, c.Rule) if r
743 for ll in getattr(r, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400744 elif self.Format == 3:
745 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400746 for ll in getattr(self, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400747 else:
748 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400749
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400750@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400751def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400752 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400753 self.ExtSubTable.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400754 else:
755 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400756
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400757@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400758def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400759 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400760 return self.ExtSubTable.may_have_non_1to1()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400761 else:
762 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400763
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400764@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
765 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400766def prune_pre_subset(self, options):
767 if self.Format == 1:
768 return self.ExtSubTable.prune_pre_subset(options)
769 else:
770 assert 0, "unknown format: %s" % self.Format
771
772@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
773 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400774def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400775 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400776 return self.ExtSubTable.subset_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400777 else:
778 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400779
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400780@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
781 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400782def prune_post_subset(self, options):
783 if self.Format == 1:
784 return self.ExtSubTable.prune_post_subset(options)
785 else:
786 assert 0, "unknown format: %s" % self.Format
787
788@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
789 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400790def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400791 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400792 return self.ExtSubTable.subset_lookups(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400793 else:
794 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400795
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400796@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
797 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400798def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400799 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400800 return self.ExtSubTable.collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400801 else:
802 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400803
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400804@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400805def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400806 for st in self.SubTable:
807 if not st: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400808 st.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400809
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400810@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400811def prune_pre_subset(self, options):
812 ret = False
813 for st in self.SubTable:
814 if not st: continue
815 if st.prune_pre_subset(options): ret = True
816 return ret
817
818@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400819def subset_glyphs(self, s):
820 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)]
821 self.SubTableCount = len(self.SubTable)
822 return bool(self.SubTableCount)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400823
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400824@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400825def prune_post_subset(self, options):
826 ret = False
827 for st in self.SubTable:
828 if not st: continue
829 if st.prune_post_subset(options): ret = True
830 return ret
831
832@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400833def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400834 for s in self.SubTable:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400835 s.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400836
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400837@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400838def collect_lookups(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400839 return _uniq_sort(sum((st.collect_lookups() for st in self.SubTable
840 if st), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400841
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400842@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400843def may_have_non_1to1(self):
844 return any(st.may_have_non_1to1() for st in self.SubTable if st)
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400845
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400846@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400847def prune_pre_subset(self, options):
848 ret = False
849 for l in self.Lookup:
850 if not l: continue
851 if l.prune_pre_subset(options): ret = True
852 return ret
853
854@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400855def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400856 "Returns the indices of nonempty lookups."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400857 return [i for i,l in enumerate(self.Lookup) if l and l.subset_glyphs(s)]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400858
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400859@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400860def prune_post_subset(self, options):
861 ret = False
862 for l in self.Lookup:
863 if not l: continue
864 if l.prune_post_subset(options): ret = True
865 return ret
866
867@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400868def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400869 self.Lookup = [self.Lookup[i] for i in lookup_indices
870 if i < self.LookupCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400871 self.LookupCount = len(self.Lookup)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400872 for l in self.Lookup:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400873 l.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400874
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400875@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400876def closure_lookups(self, lookup_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400877 lookup_indices = _uniq_sort(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400878 recurse = lookup_indices
879 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400880 recurse_lookups = sum((self.Lookup[i].collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400881 for i in recurse if i < self.LookupCount), [])
882 recurse_lookups = [l for l in recurse_lookups
883 if l not in lookup_indices and l < self.LookupCount]
884 if not recurse_lookups:
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400885 return _uniq_sort(lookup_indices)
886 recurse_lookups = _uniq_sort(recurse_lookups)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400887 lookup_indices.extend(recurse_lookups)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400888 recurse = recurse_lookups
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400889
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400890@_add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400891def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400892 self.LookupListIndex = [l for l in self.LookupListIndex
893 if l in lookup_indices]
894 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400895 self.LookupListIndex = [lookup_indices.index(l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400896 for l in self.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400897 self.LookupCount = len(self.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400898 return self.LookupCount
Behdad Esfahbod54660612013-07-21 18:16:55 -0400899
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400900@_add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400901def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400902 return self.LookupListIndex[:]
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400903
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400904@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400905def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400906 "Returns the indices of nonempty features."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400907 feature_indices = [i for i,f in enumerate(self.FeatureRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400908 if f.Feature.subset_lookups(lookup_indices)]
909 self.subset_features(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400910 return feature_indices
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400911
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400912@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400913def collect_lookups(self, feature_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400914 return _uniq_sort(sum((self.FeatureRecord[i].Feature.collect_lookups()
915 for i in feature_indices
Behdad Esfahbod1ee298d2013-08-13 20:07:09 -0400916 if i < self.FeatureCount), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400917
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400918@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400919def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400920 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400921 self.FeatureCount = len(self.FeatureRecord)
922 return bool(self.FeatureCount)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400923
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400924@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
925 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400926def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400927 if self.ReqFeatureIndex in feature_indices:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400928 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400929 else:
930 self.ReqFeatureIndex = 65535
931 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
932 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400933 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400934 if f in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400935 self.FeatureCount = len(self.FeatureIndex)
936 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400937
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400938@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
939 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400940def collect_features(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400941 feature_indices = self.FeatureIndex[:]
942 if self.ReqFeatureIndex != 65535:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400943 feature_indices.append(self.ReqFeatureIndex)
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400944 return _uniq_sort(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400945
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400946@_add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400947def subset_features(self, feature_indices):
948 if(self.DefaultLangSys and
949 not self.DefaultLangSys.subset_features(feature_indices)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400950 self.DefaultLangSys = None
951 self.LangSysRecord = [l for l in self.LangSysRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400952 if l.LangSys.subset_features(feature_indices)]
953 self.LangSysCount = len(self.LangSysRecord)
954 return bool(self.LangSysCount or self.DefaultLangSys)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400955
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400956@_add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400957def collect_features(self):
958 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400959 if self.DefaultLangSys:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400960 feature_indices.append(self.DefaultLangSys.collect_features())
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400961 return _uniq_sort(sum(feature_indices, []))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400962
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400963@_add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400964def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400965 self.ScriptRecord = [s for s in self.ScriptRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400966 if s.Script.subset_features(feature_indices)]
967 self.ScriptCount = len(self.ScriptRecord)
968 return bool(self.ScriptCount)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400969
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400970@_add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400971def collect_features(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400972 return _uniq_sort(sum((s.Script.collect_features()
973 for s in self.ScriptRecord), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400974
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400975@_add_method(fontTools.ttLib.getTableClass('GSUB'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400976def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400977 s.table = self.table
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400978 feature_indices = self.table.ScriptList.collect_features()
979 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400980 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400981 orig_glyphs = s.glyphs.copy()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400982 for i in lookup_indices:
983 if i >= self.table.LookupList.LookupCount: continue
984 if not self.table.LookupList.Lookup[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400985 self.table.LookupList.Lookup[i].closure_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400986 if orig_glyphs == s.glyphs:
987 break
988 del s.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400989
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400990@_add_method(fontTools.ttLib.getTableClass('GSUB'),
991 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400992def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400993 s.glyphs = s.glyphs_gsubed
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400994 lookup_indices = self.table.LookupList.subset_glyphs(s)
995 self.subset_lookups(lookup_indices)
996 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400997 return True
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400998
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400999@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1000 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001001def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001002 """Retrains specified lookups, then removes empty features, language
1003 systems, and scripts."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001004 self.table.LookupList.subset_lookups(lookup_indices)
1005 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices)
1006 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -04001007
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001008@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1009 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001010def prune_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001011 "Remove unreferenced lookups"
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001012 feature_indices = self.table.ScriptList.collect_features()
1013 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
1014 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices)
1015 self.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -04001016
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001017@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1018 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001019def subset_feature_tags(self, feature_tags):
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001020 feature_indices = [i for i,f in
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001021 enumerate(self.table.FeatureList.FeatureRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001022 if f.FeatureTag in feature_tags]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001023 self.table.FeatureList.subset_features(feature_indices)
1024 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001025
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001026@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1027 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001028def prune_pre_subset(self, options):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001029 if '*' not in options.layout_features:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001030 self.subset_feature_tags(options.layout_features)
1031 self.prune_lookups()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -04001032 self.table.LookupList.prune_pre_subset(options);
1033 return True
1034
1035@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1036 fontTools.ttLib.getTableClass('GPOS'))
1037def prune_post_subset(self, options):
1038 self.table.LookupList.prune_post_subset(options);
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001039 return True
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001040
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001041@_add_method(fontTools.ttLib.getTableClass('GDEF'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001042def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001043 glyphs = s.glyphs_gsubed
1044 table = self.table
1045 if table.LigCaretList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001046 indices = table.LigCaretList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001047 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i]
1048 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001049 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001050 if not table.LigCaretList.LigGlyphCount:
1051 table.LigCaretList = None
1052 if table.MarkAttachClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001053 table.MarkAttachClassDef.classDefs = dict((g,v) for g,v in
1054 table.MarkAttachClassDef.
1055 classDefs.iteritems()
1056 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001057 if not table.MarkAttachClassDef.classDefs:
1058 table.MarkAttachClassDef = None
1059 if table.GlyphClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001060 table.GlyphClassDef.classDefs = dict((g,v) for g,v in
1061 table.GlyphClassDef.
1062 classDefs.iteritems()
1063 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001064 if not table.GlyphClassDef.classDefs:
1065 table.GlyphClassDef = None
1066 if table.AttachList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001067 indices = table.AttachList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001068 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i]
1069 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001070 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001071 if not table.AttachList.GlyphCount:
1072 table.AttachList = None
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001073 return bool(table.LigCaretList or
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001074 table.MarkAttachClassDef or
1075 table.GlyphClassDef or
1076 table.AttachList)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001077
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001078@_add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001079def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001080 # Prune unknown kern table types
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001081 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')]
1082 return bool(self.kernTables)
Behdad Esfahbodd4e33a72013-07-24 18:51:05 -04001083
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001084@_add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001085def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001086 glyphs = s.glyphs_gsubed
1087 for t in self.kernTables:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001088 t.kernTable = dict(((a,b),v) for (a,b),v in t.kernTable.iteritems()
1089 if a in glyphs and b in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001090 self.kernTables = [t for t in self.kernTables if t.kernTable]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001091 return bool(self.kernTables)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001092
Behdad Esfahbode7a0d562013-08-16 10:56:30 -04001093@_add_method(fontTools.ttLib.getTableClass('vmtx'),
1094 fontTools.ttLib.getTableClass('hmtx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001095def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001096 self.metrics = dict((g,v) for g,v in self.metrics.iteritems() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001097 return bool(self.metrics)
Behdad Esfahbodc7160442013-07-22 14:29:08 -04001098
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001099@_add_method(fontTools.ttLib.getTableClass('hdmx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001100def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001101 self.hdmx = dict((sz,_dict((g,v) for g,v in l.iteritems() if g in s.glyphs))
1102 for sz,l in self.hdmx.iteritems())
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001103 return bool(self.hdmx)
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -04001104
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001105@_add_method(fontTools.ttLib.getTableClass('VORG'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001106def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001107 self.VOriginRecords = dict((g,v) for g,v in self.VOriginRecords.iteritems()
1108 if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001109 self.numVertOriginYMetrics = len(self.VOriginRecords)
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001110 return True # Never drop; has default metrics
Behdad Esfahbode45d6af2013-07-22 15:29:17 -04001111
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001112@_add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001113def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001114 if not options.glyph_names:
1115 self.formatType = 3.0
1116 return True
Behdad Esfahbod42648242013-07-23 12:56:06 -04001117
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001118@_add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001119def subset_glyphs(self, s):
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001120 self.extraNames = [] # This seems to do it
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001121 return True
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001122
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001123@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001124def getComponentNamesFast(self, glyfTable):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001125 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001126 return [] # Not composite
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001127 data = self.data
1128 i = 10
1129 components = []
1130 more = 1
1131 while more:
1132 flags, glyphID = struct.unpack(">HH", data[i:i+4])
1133 i += 4
1134 flags = int(flags)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001135 components.append(glyfTable.getGlyphName(int(glyphID)))
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001136
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001137 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001138 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001139 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1140 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1141 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1142 more = flags & 0x0020 # MORE_COMPONENTS
1143
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001144 return components
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001145
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001146@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001147def remapComponentsFast(self, indices):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001148 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001149 return # Not composite
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001150 data = array.array("B", self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001151 i = 10
1152 more = 1
1153 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001154 flags =(data[i] << 8) | data[i+1]
1155 glyphID =(data[i+2] << 8) | data[i+3]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001156 # Remap
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001157 glyphID = indices.index(glyphID)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001158 data[i+2] = glyphID >> 8
1159 data[i+3] = glyphID & 0xFF
1160 i += 4
1161 flags = int(flags)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001162
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001163 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001164 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001165 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1166 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1167 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1168 more = flags & 0x0020 # MORE_COMPONENTS
1169
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001170 self.data = data.tostring()
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001171
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001172@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001173def dropInstructionsFast(self):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001174 if not self.data:
1175 return
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001176 numContours = struct.unpack(">h", self.data[:2])[0]
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001177 data = array.array("B", self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001178 i = 10
1179 if numContours >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001180 i += 2 * numContours # endPtsOfContours
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001181 instructionLen =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001182 # Zero it
1183 data[i] = data [i+1] = 0
1184 i += 2
1185 if instructionLen:
1186 # Splice it out
1187 data = data[:i] + data[i+instructionLen:]
1188 else:
1189 more = 1
1190 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001191 flags =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001192 # Turn instruction flag off
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001193 flags &= ~0x0100 # WE_HAVE_INSTRUCTIONS
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001194 data[i+0] = flags >> 8
1195 data[i+1] = flags & 0xFF
1196 i += 4
1197 flags = int(flags)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001198
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001199 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001200 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001201 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1202 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1203 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1204 more = flags & 0x0020 # MORE_COMPONENTS
1205
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001206 # Cut off
1207 data = data[:i]
1208 if len(data) % 4:
1209 # add pad bytes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001210 nPadBytes = 4 -(len(data) % 4)
1211 for i in range(nPadBytes):
1212 data.append(0)
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001213 self.data = data.tostring()
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001214
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001215@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001216def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001217 decompose = s.glyphs
1218 # I don't know if component glyphs can be composite themselves.
1219 # We handle them anyway.
1220 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001221 components = set()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001222 for g in decompose:
1223 if g not in self.glyphs:
1224 continue
1225 gl = self.glyphs[g]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001226 if hasattr(gl, "data"):
1227 for c in gl.getComponentNamesFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001228 if c not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001229 components.add(c)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001230 else:
1231 # TTX seems to expand gid0..3 always
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001232 if gl.isComposite():
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001233 for c in gl.components:
1234 if c.glyphName not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001235 components.add(c.glyphName)
1236 components = set(c for c in components if c not in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001237 if not components:
1238 break
1239 decompose = components
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001240 s.glyphs.update(components)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001241
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001242@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001243def prune_pre_subset(self, options):
1244 if options.notdef_glyph and not options.notdef_outline:
1245 g = self[self.glyphOrder[0]]
1246 # Yay, easy!
1247 g.__dict__.clear()
1248 g.data = ""
1249 return True
1250
1251@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001252def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001253 self.glyphs = dict((g,v) for g,v in self.glyphs.iteritems() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001254 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001255 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001256 if hasattr(v, "data"):
1257 v.remapComponentsFast(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001258 else:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001259 pass # No need
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001260 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbodb69b6712013-08-29 18:17:31 -04001261 # Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset.
1262 return True
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001263
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001264@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001265def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001266 if not options.hinting:
1267 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001268 if hasattr(v, "data"):
1269 v.dropInstructionsFast()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001270 else:
1271 v.program = fontTools.ttLib.tables.ttProgram.Program()
1272 v.program.fromBytecode([])
1273 return True
Behdad Esfahboded98c612013-07-23 12:37:41 -04001274
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001275@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001276def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001277 cff = self.cff
Behdad Esfahbode0622072013-09-10 14:33:19 -04001278 # CFF table must have one font only
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001279 cff.fontNames = cff.fontNames[:1]
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001280
1281 if options.notdef_glyph and not options.notdef_outline:
1282 for fontname in cff.keys():
1283 font = cff[fontname]
1284 c,_ = font.CharStrings.getItemAndSelector('.notdef')
Behdad Esfahbod21582e92013-09-12 16:47:52 -04001285 # XXX we should preserve the glyph width
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001286 c.bytecode = '\x0e' # endchar
1287 c.program = None
1288
Behdad Esfahbod50f83ef2013-08-29 18:18:17 -04001289 return True # bool(cff.fontNames)
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001290
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001291@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001292def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001293 cff = self.cff
1294 for fontname in cff.keys():
1295 font = cff[fontname]
1296 cs = font.CharStrings
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001297
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001298 # Load all glyphs
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001299 for g in font.charset:
1300 if g not in s.glyphs: continue
1301 c,sel = cs.getItemAndSelector(g)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001302
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001303 if cs.charStringsAreIndexed:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001304 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001305 csi = cs.charStringsIndex
1306 csi.items = [csi.items[i] for i in indices]
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001307 csi.count = len(csi.items)
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001308 del csi.file, csi.offsets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001309 if hasattr(font, "FDSelect"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001310 sel = font.FDSelect
1311 sel.format = None
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001312 sel.gidArray = [sel.gidArray[i] for i in indices]
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001313 cs.charStrings = dict((g,indices.index(v))
1314 for g,v in cs.charStrings.iteritems()
1315 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001316 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001317 cs.charStrings = dict((g,v)
1318 for g,v in cs.charStrings.iteritems()
1319 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001320 font.charset = [g for g in font.charset if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001321 font.numGlyphs = len(font.charset)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001322
Behdad Esfahbod50f83ef2013-08-29 18:18:17 -04001323 return True # any(cff[fontname].numGlyphs for fontname in cff.keys())
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001324
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001325@_add_method(fontTools.misc.psCharStrings.T2CharString)
1326def subset_subroutines(self, subrs, gsubrs):
1327 p = self.program
Behdad Esfahbode0622072013-09-10 14:33:19 -04001328 assert len(p)
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001329 for i in xrange(1, len(p)):
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001330 if p[i] == 'callsubr':
1331 assert type(p[i-1]) is int
1332 p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias
1333 elif p[i] == 'callgsubr':
1334 assert type(p[i-1]) is int
1335 p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias
1336
Behdad Esfahbode0622072013-09-10 14:33:19 -04001337@_add_method(fontTools.misc.psCharStrings.T2CharString)
1338def drop_hints(self):
1339 hints = self._hints
1340
1341 if hints.has_hint:
1342 self.program = self.program[hints.last_hint:]
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001343 if hasattr(self, 'width'):
1344 # Insert width back if needed
1345 if self.width != self.private.defaultWidthX:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001346 self.program.insert(0, self.width - self.private.nominalWidthX)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001347
1348 if hints.has_hintmask:
1349 i = 0
1350 p = self.program
1351 while i < len(p):
1352 if p[i] in ['hintmask', 'cntrmask']:
1353 assert i + 1 <= len(p)
1354 del p[i:i+2]
1355 continue
1356 i += 1
1357
1358 assert len(self.program)
1359
1360 del self._hints
1361
1362class _MarkingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1363
1364 def __init__(self, localSubrs, globalSubrs):
1365 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1366 localSubrs,
1367 globalSubrs)
1368 for subrs in [localSubrs, globalSubrs]:
1369 if subrs and not hasattr(subrs, "_used"):
1370 subrs._used = set()
1371
1372 def op_callsubr(self, index):
1373 self.localSubrs._used.add(self.operandStack[-1]+self.localBias)
1374 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
1375
1376 def op_callgsubr(self, index):
1377 self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias)
1378 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
1379
1380class _DehintingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1381
1382 class Hints:
1383 def __init__(self):
1384 # Whether calling this charstring produces any hint stems
1385 self.has_hint = False
1386 # Index to start at to drop all hints
1387 self.last_hint = 0
1388 # Index up to which we know more hints are possible. Only
1389 # relevant if status is 0 or 1.
1390 self.last_checked = 0
1391 # The status means:
1392 # 0: after dropping hints, this charstring is empty
1393 # 1: after dropping hints, there may be more hints continuing after this
1394 # 2: no more hints possible after this charstring
1395 self.status = 0
1396 # Has hintmask instructions; not recursive
1397 self.has_hintmask = False
1398 pass
1399
1400 def __init__(self, css, localSubrs, globalSubrs):
1401 self._css = css
1402 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1403 localSubrs,
1404 globalSubrs)
1405
1406 def execute(self, charString):
1407 old_hints = charString._hints if hasattr(charString, '_hints') else None
1408 charString._hints = self.Hints()
1409
1410 fontTools.misc.psCharStrings.SimpleT2Decompiler.execute(self, charString)
1411
1412 hints = charString._hints
1413
1414 if hints.has_hint or hints.has_hintmask:
1415 self._css.add(charString)
1416
1417 if hints.status != 2:
1418 # Check from last_check, make sure we didn't have any operators.
1419 for i in xrange(hints.last_checked, len(charString.program) - 1):
1420 if type(charString.program[i]) == str:
1421 hints.status = 2
1422 break;
1423 else:
1424 hints.status = 1 # There's *something* here
1425 hints.last_checked = len(charString.program)
1426
1427 if old_hints:
1428 assert hints.__dict__ == old_hints.__dict__
1429
1430 def op_callsubr(self, index):
1431 subr = self.localSubrs[self.operandStack[-1]+self.localBias]
1432 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
1433 self.processSubr(index, subr)
1434
1435 def op_callgsubr(self, index):
1436 subr = self.globalSubrs[self.operandStack[-1]+self.globalBias]
1437 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
1438 self.processSubr(index, subr)
1439
1440 def op_hstem(self, index):
1441 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_hstem(self, index)
1442 self.processHint(index)
1443 def op_vstem(self, index):
1444 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_vstem(self, index)
1445 self.processHint(index)
1446 def op_hstemhm(self, index):
1447 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_hstemhm(self, index)
1448 self.processHint(index)
1449 def op_vstemhm(self, index):
1450 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_vstemhm(self, index)
1451 self.processHint(index)
1452 def op_hintmask(self, index):
1453 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
1454 self.processHintmask(index)
1455 def op_cntrmask(self, index):
1456 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_cntrmask(self, index)
1457 self.processHintmask(index)
1458
1459 def processHintmask(self, index):
1460 cs = self.callingStack[-1]
1461 hints = cs._hints
1462 hints.has_hintmask = True
1463 if hints.status != 2 and hints.has_hint:
1464 # Check from last_check, see if we may be an implicit vstem
Behdad Esfahbod84763142013-09-10 19:00:48 -04001465 for i in xrange(hints.last_checked, index - 1):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001466 if type(cs.program[i]) == str:
Behdad Esfahbod84763142013-09-10 19:00:48 -04001467 hints.status = 2
Behdad Esfahbode0622072013-09-10 14:33:19 -04001468 break;
Behdad Esfahbod84763142013-09-10 19:00:48 -04001469 if hints.status != 2:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001470 # We are an implicit vstem
1471 hints.last_hint = index + 1
Behdad Esfahbod84763142013-09-10 19:00:48 -04001472 hints.status = 0
1473 hints.last_checked = index + 1
Behdad Esfahbode0622072013-09-10 14:33:19 -04001474
1475 def processHint(self, index):
1476 cs = self.callingStack[-1]
1477 hints = cs._hints
1478 hints.has_hint = True
1479 hints.last_hint = index
1480 hints.last_checked = index
1481
1482 def processSubr(self, index, subr):
1483 cs = self.callingStack[-1]
1484 hints = cs._hints
1485 subr_hints = subr._hints
1486
1487 if subr_hints.has_hint:
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001488 if hints.status != 2:
1489 hints.has_hint = True
Behdad Esfahbod99536852013-09-12 00:23:11 -04001490 hints.last_checked = index
1491 hints.status = subr_hints.status
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001492 # Decide where to chop off from
1493 if subr_hints.status == 0:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001494 hints.last_hint = index
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001495 else:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001496 hints.last_hint = index - 2 # Leave the subr call in
Behdad Esfahbode0622072013-09-10 14:33:19 -04001497 else:
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001498 # In my understanding, this is a font bug. Ie. it has hint stems
1499 # *after* path construction. I've seen this in widespread fonts.
1500 # Best to ignore the hints I suppose...
1501 pass
1502 #assert 0
Behdad Esfahbode0622072013-09-10 14:33:19 -04001503 else:
1504 hints.status = max(hints.status, subr_hints.status)
1505 if hints.status != 2:
1506 # Check from last_check, make sure we didn't have
1507 # any operators.
1508 for i in xrange(hints.last_checked, index - 1):
1509 if type(cs.program[i]) == str:
1510 hints.status = 2
1511 break;
1512 hints.last_checked = index
1513
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001514@_add_method(fontTools.ttLib.getTableClass('CFF '))
1515def prune_post_subset(self, options):
1516 cff = self.cff
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001517 for fontname in cff.keys():
1518 font = cff[fontname]
1519 cs = font.CharStrings
1520
Behdad Esfahbode0622072013-09-10 14:33:19 -04001521
1522 #
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001523 # Drop unused FontDictionaries
Behdad Esfahbode0622072013-09-10 14:33:19 -04001524 #
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001525 if hasattr(font, "FDSelect"):
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001526 sel = font.FDSelect
1527 indices = _uniq_sort(sel.gidArray)
1528 sel.gidArray = [indices.index (ss) for ss in sel.gidArray]
1529 arr = font.FDArray
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001530 arr.items = [arr[i] for i in indices]
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001531 arr.count = len(arr.items)
1532 del arr.file, arr.offsets
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001533
Behdad Esfahbode0622072013-09-10 14:33:19 -04001534
1535 #
1536 # Drop hints if not needed
1537 #
1538 if not options.hinting:
1539
1540 #
1541 # This can be tricky, but doesn't have to. What we do is:
1542 #
1543 # - Run all used glyph charstrings and recurse into subroutines,
1544 # - For each charstring (including subroutines), if it has any
1545 # of the hint stem operators, we mark it as such. Upon returning,
1546 # for each charstring we note all the subroutine calls it makes
1547 # that (recursively) contain a stem,
1548 # - Dropping hinting then consists of the following two ops:
1549 # * Drop the piece of the program in each charstring before the
1550 # last call to a stem op or a stem-calling subroutine,
1551 # * Drop all hintmask operations.
1552 # - It's trickier... A hintmask right after hints and a few numbers
1553 # will act as an implicit vstemhm. As such, we track whether
1554 # we have seen any non-hint operators so far and do the right
1555 # thing, recursively... Good luck understanding that :(
1556 #
1557 css = set()
1558 for g in font.charset:
1559 c,sel = cs.getItemAndSelector(g)
1560 # Make sure it's decompiled. We want our "decompiler" to walk
1561 # the program, not the bytecode.
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001562 c.draw(fontTools.pens.basePen.NullPen())
Behdad Esfahbode0622072013-09-10 14:33:19 -04001563 subrs = getattr(c.private, "Subrs", [])
1564 decompiler = _DehintingT2Decompiler(css, subrs, c.globalSubrs)
1565 decompiler.execute(c)
1566 for charstring in css:
1567 charstring.drop_hints()
1568
1569
1570 #
1571 # Renumber subroutines to remove unused ones
1572 #
1573
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001574 # Mark all used subroutines
1575 for g in font.charset:
1576 c,sel = cs.getItemAndSelector(g)
1577 subrs = getattr(c.private, "Subrs", [])
1578 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs)
1579 decompiler.execute(c)
1580
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001581 all_subrs = [font.GlobalSubrs]
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001582 if hasattr(font, 'FDSelect'):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001583 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs') and fd.Private.Subrs)
1584 elif hasattr(font.Private, 'Subrs') and font.Private.Subrs:
Behdad Esfahbodcbcaccf2013-08-30 16:21:38 -04001585 all_subrs.append(font.Private.Subrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001586
1587 subrs = set(subrs) # Remove duplicates
1588
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001589 # Prepare
1590 for subrs in all_subrs:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001591 if not hasattr(subrs, '_used'):
1592 subrs._used = set()
1593 subrs._used = _uniq_sort(subrs._used)
1594 subrs._old_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs)
1595 subrs._new_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs._used)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001596
Behdad Esfahboded107712013-08-14 19:54:13 -04001597 # Renumber glyph charstrings
1598 for g in font.charset:
1599 c,sel = cs.getItemAndSelector(g)
1600 subrs = getattr(c.private, "Subrs", [])
1601 c.subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001602
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001603 # Renumber subroutines themselves
1604 for subrs in all_subrs:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001605
1606 if subrs == font.GlobalSubrs:
1607 if not hasattr(font, 'FDSelect') and hasattr(font.Private, 'Subrs'):
1608 local_subrs = font.Private.Subrs
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001609 else:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001610 local_subrs = []
1611 else:
1612 local_subrs = subrs
1613
1614 subrs.items = [subrs.items[i] for i in subrs._used]
1615 subrs.count = len(subrs.items)
1616 del subrs.file
1617 if hasattr(subrs, 'offsets'):
1618 del subrs.offsets
1619
1620 for i in xrange (subrs.count):
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001621 subrs[i].subset_subroutines (local_subrs, font.GlobalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001622
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001623 # Cleanup
1624 for subrs in all_subrs:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001625 del subrs._used, subrs._old_bias, subrs._new_bias
1626
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001627 return True
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001628
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001629@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001630def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001631 tables = [t for t in self.tables
1632 if t.platformID == 3 and t.platEncID in [1, 10]]
1633 for u in s.unicodes_requested:
1634 found = False
1635 for table in tables:
1636 if u in table.cmap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001637 s.glyphs.add(table.cmap[u])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001638 found = True
1639 break
1640 if not found:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001641 s.log("No glyph for Unicode value %s; skipping." % u)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001642
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001643@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001644def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001645 if not options.legacy_cmap:
1646 # Drop non-Unicode / non-Symbol cmaps
1647 self.tables = [t for t in self.tables
1648 if t.platformID == 3 and t.platEncID in [0, 1, 10]]
1649 if not options.symbol_cmap:
1650 self.tables = [t for t in self.tables
1651 if t.platformID == 3 and t.platEncID in [1, 10]]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001652 # TODO(behdad) Only keep one subtable?
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001653 # For now, drop format=0 which can't be subset_glyphs easily?
1654 self.tables = [t for t in self.tables if t.format != 0]
Behdad Esfahbodfd92d4c2013-09-19 19:43:09 -04001655 self.numSubTables = len(self.tables)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001656 return bool(self.tables)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001657
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001658@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001659def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001660 s.glyphs = s.glyphs_cmaped
1661 for t in self.tables:
1662 # For reasons I don't understand I need this here
1663 # to force decompilation of the cmap format 14.
1664 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001665 getattr(t, "asdf")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001666 except AttributeError:
1667 pass
1668 if t.format == 14:
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001669 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None).
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001670 t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs])
1671 for v,l in t.uvsDict.iteritems())
1672 t.uvsDict = dict((v,l) for v,l in t.uvsDict.iteritems() if l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001673 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001674 t.cmap = dict((u,g) for u,g in t.cmap.iteritems()
1675 if g in s.glyphs_requested or u in s.unicodes_requested)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001676 self.tables = [t for t in self.tables
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001677 if (t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbodfd92d4c2013-09-19 19:43:09 -04001678 self.numSubTables = len(self.tables)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001679 # TODO(behdad) Convert formats when needed.
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001680 # In particular, if we have a format=12 without non-BMP
1681 # characters, either drop format=12 one or convert it
1682 # to format=4 if there's not one.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001683 return bool(self.tables)
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001684
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001685@_add_method(fontTools.ttLib.getTableClass('name'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001686def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001687 if '*' not in options.name_IDs:
1688 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1689 if not options.name_legacy:
1690 self.names = [n for n in self.names
1691 if n.platformID == 3 and n.platEncID == 1]
1692 if '*' not in options.name_languages:
1693 self.names = [n for n in self.names if n.langID in options.name_languages]
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001694 return True # Retain even if empty
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001695
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001696
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001697# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange?
1698# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
Behdad Esfahbod852e8a52013-08-29 18:19:22 -04001699# TODO(behdad) Drop empty GSUB/GPOS, and GDEF if no GSUB/GPOS left
1700# TODO(behdad) Drop GDEF subitems if unused by lookups
Behdad Esfahbod10195332013-08-14 19:55:24 -04001701# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001702# TODO(behdad) Text direction considerations.
1703# TODO(behdad) Text script / language considerations.
Behdad Esfahbod21582e92013-09-12 16:47:52 -04001704# TODO(behdad) Option to drop hmtx for CFF?
Behdad Esfahbod56ebd042013-07-22 13:02:24 -04001705
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001706
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001707class Options(object):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001708
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001709 class UnknownOptionError(Exception):
1710 pass
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001711
Behdad Esfahboda17743f2013-08-28 17:14:53 -04001712 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ',
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001713 'PCLT', 'LTSH']
1714 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
1715 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
1716 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1717 'loca', 'name', 'cvt ', 'fpgm', 'prep']
1718 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001719
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001720 # Based on HarfBuzz shapers
1721 _layout_features_groups = {
1722 # Default shaper
1723 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1724 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
1725 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
1726 'ltr': ['ltra', 'ltrm'],
1727 'rtl': ['rtla', 'rtlm'],
1728 # Complex shapers
1729 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1730 'cswh', 'mset'],
1731 'hangul': ['ljmo', 'vjmo', 'tjmo'],
1732 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'],
1733 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1734 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1735 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
1736 }
1737 _layout_features_default = _uniq_sort(sum(
1738 _layout_features_groups.itervalues(), []))
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001739
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001740 drop_tables = _drop_tables_default
1741 no_subset_tables = _no_subset_tables_default
1742 hinting_tables = _hinting_tables_default
1743 layout_features = _layout_features_default
1744 hinting = False
1745 glyph_names = False
1746 legacy_cmap = False
1747 symbol_cmap = False
1748 name_IDs = [1, 2] # Family and Style
1749 name_legacy = False
1750 name_languages = [0x0409] # English
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001751 notdef_glyph = True # gid0 for TrueType / .notdef for CFF
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001752 notdef_outline = False # No need for notdef to have an outline really
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001753 recommended_glyphs = False # gid1, gid2, gid3 for TrueType
Behdad Esfahbode911de12013-08-16 12:42:34 -04001754 recalc_bounds = False # Recalculate font bounding boxes
Behdad Esfahbod03d78da2013-08-29 16:42:00 -04001755 canonical_order = False # Order tables as recommended
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001756 flavor = None # May be 'woff'
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001757
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001758 def __init__(self, **kwargs):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001759
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001760 self.set(**kwargs)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001761
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001762 def set(self, **kwargs):
1763 for k,v in kwargs.iteritems():
1764 if not hasattr(self, k):
Behdad Esfahbodac10d812013-09-03 18:29:58 -04001765 raise self.UnknownOptionError("Unknown option '%s'" % k)
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001766 setattr(self, k, v)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001767
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001768 def parse_opts(self, argv, ignore_unknown=False):
1769 ret = []
1770 opts = {}
1771 for a in argv:
1772 orig_a = a
1773 if not a.startswith('--'):
1774 ret.append(a)
1775 continue
1776 a = a[2:]
1777 i = a.find('=')
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001778 op = '='
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001779 if i == -1:
1780 if a.startswith("no-"):
1781 k = a[3:]
1782 v = False
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001783 else:
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001784 k = a
1785 v = True
1786 else:
1787 k = a[:i]
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001788 if k[-1] in "-+":
1789 op = k[-1]+'=' # Ops is '-=' or '+=' now.
1790 k = k[:-1]
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001791 v = a[i+1:]
1792 k = k.replace('-', '_')
1793 if not hasattr(self, k):
1794 if ignore_unknown == True or k in ignore_unknown:
1795 ret.append(orig_a)
1796 continue
1797 else:
1798 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001799
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001800 ov = getattr(self, k)
1801 if isinstance(ov, bool):
1802 v = bool(v)
1803 elif isinstance(ov, int):
1804 v = int(v)
1805 elif isinstance(ov, list):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001806 vv = v.split(',')
1807 if vv == ['']:
1808 vv = []
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001809 vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001810 if op == '=':
1811 v = vv
1812 elif op == '+=':
1813 v = ov
1814 v.extend(vv)
1815 elif op == '-=':
1816 v = ov
1817 for x in vv:
1818 if x in v:
1819 v.remove(x)
1820 else:
1821 assert 0
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001822
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001823 opts[k] = v
1824 self.set(**opts)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001825
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001826 return ret
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001827
1828
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001829class Subsetter(object):
1830
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001831 def __init__(self, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001832
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001833 if not log:
1834 log = Logger()
1835 if not options:
1836 options = Options()
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001837
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001838 self.options = options
1839 self.log = log
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001840 self.unicodes_requested = set()
1841 self.glyphs_requested = set()
1842 self.glyphs = set()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001843
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001844 def populate(self, glyphs=[], unicodes=[], text=""):
1845 self.unicodes_requested.update(unicodes)
1846 if isinstance(text, str):
1847 text = text.decode("utf8")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001848 for u in text:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001849 self.unicodes_requested.add(ord(u))
1850 self.glyphs_requested.update(glyphs)
1851 self.glyphs.update(glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001852
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001853 def _prune_pre_subset(self, font):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001854
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001855 for tag in font.keys():
1856 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001857
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001858 if(tag in self.options.drop_tables or
1859 (tag in self.options.hinting_tables and not self.options.hinting)):
1860 self.log(tag, "dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001861 del font[tag]
1862 continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001863
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001864 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001865
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001866 if hasattr(clazz, 'prune_pre_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001867 table = font[tag]
Behdad Esfahbod010c5f92013-09-10 20:54:46 -04001868 self.log.lapse("load '%s'" % tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001869 retain = table.prune_pre_subset(self.options)
1870 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001871 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001872 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001873 del font[tag]
1874 continue
1875 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001876 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001877
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001878 def _closure_glyphs(self, font):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001879
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001880 self.glyphs = self.glyphs_requested.copy()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001881
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001882 if 'cmap' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001883 font['cmap'].closure_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001884 self.glyphs_cmaped = self.glyphs
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001885
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001886 if self.options.notdef_glyph:
1887 if 'glyf' in font:
1888 self.glyphs.add(font.getGlyphName(0))
1889 self.log("Added gid0 to subset")
1890 else:
1891 self.glyphs.add('.notdef')
1892 self.log("Added .notdef to subset")
1893 if self.options.recommended_glyphs:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001894 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001895 for i in range(4):
1896 self.glyphs.add(font.getGlyphName(i))
1897 self.log("Added first four glyphs to subset")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001898
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001899 if 'GSUB' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001900 self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1901 len(self.glyphs))
1902 self.log.glyphs(self.glyphs, font=font)
1903 font['GSUB'].closure_glyphs(self)
1904 self.log("Closed glyph list over 'GSUB': %d glyphs after" %
1905 len(self.glyphs))
1906 self.log.glyphs(self.glyphs, font=font)
1907 self.log.lapse("close glyph list over 'GSUB'")
1908 self.glyphs_gsubed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001909
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001910 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001911 self.log("Closing glyph list over 'glyf': %d glyphs before" %
1912 len(self.glyphs))
1913 self.log.glyphs(self.glyphs, font=font)
1914 font['glyf'].closure_glyphs(self)
1915 self.log("Closed glyph list over 'glyf': %d glyphs after" %
1916 len(self.glyphs))
1917 self.log.glyphs(self.glyphs, font=font)
1918 self.log.lapse("close glyph list over 'glyf'")
1919 self.glyphs_glyfed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001920
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001921 self.glyphs_all = self.glyphs.copy()
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001922
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001923 self.log("Retaining %d glyphs: " % len(self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001924
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001925 def _subset_glyphs(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001926 for tag in font.keys():
1927 if tag == 'GlyphOrder': continue
1928 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001929
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001930 if tag in self.options.no_subset_tables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001931 self.log(tag, "subsetting not needed")
1932 elif hasattr(clazz, 'subset_glyphs'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001933 table = font[tag]
1934 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001935 retain = table.subset_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001936 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001937 self.log.lapse("subset '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001938 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001939 self.log(tag, "subsetted to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001940 del font[tag]
1941 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001942 self.log(tag, "subsetted")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001943 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001944 self.log(tag, "NOT subset; don't know how to subset; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001945 del font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001946
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001947 glyphOrder = font.getGlyphOrder()
1948 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001949 font.setGlyphOrder(glyphOrder)
1950 font._buildReverseGlyphOrderDict()
1951 self.log.lapse("subset GlyphOrder")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001952
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001953 def _prune_post_subset(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001954 for tag in font.keys():
1955 if tag == 'GlyphOrder': continue
1956 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001957 if hasattr(clazz, 'prune_post_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001958 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001959 retain = table.prune_post_subset(self.options)
1960 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001961 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001962 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001963 del font[tag]
1964 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001965 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001966
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001967 def subset(self, font):
Behdad Esfahbod756af492013-08-01 12:05:26 -04001968
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001969 self._prune_pre_subset(font)
1970 self._closure_glyphs(font)
1971 self._subset_glyphs(font)
1972 self._prune_post_subset(font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001973
Behdad Esfahbod756af492013-08-01 12:05:26 -04001974
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04001975class Logger(object):
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001976
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001977 def __init__(self, verbose=False, xml=False, timing=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001978 self.verbose = verbose
1979 self.xml = xml
1980 self.timing = timing
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001981 self.last_time = self.start_time = time.time()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001982
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001983 def parse_opts(self, argv):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001984 argv = argv[:]
1985 for v in ['verbose', 'xml', 'timing']:
1986 if "--"+v in argv:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001987 setattr(self, v, True)
1988 argv.remove("--"+v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001989 return argv
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001990
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001991 def __call__(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001992 if not self.verbose:
1993 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001994 print ' '.join(str(x) for x in things)
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001995
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001996 def lapse(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001997 if not self.timing:
1998 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001999 new_time = time.time()
2000 print "Took %0.3fs to %s" %(new_time - self.last_time,
2001 ' '.join(str(x) for x in things))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002002 self.last_time = new_time
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002003
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04002004 def glyphs(self, glyphs, font=None):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002005 self("Names: ", sorted(glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002006 if font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002007 reverseGlyphMap = font.getReverseGlyphMap()
2008 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04002009
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002010 def font(self, font, file=sys.stdout):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002011 if not self.xml:
2012 return
Behdad Esfahbod28fc4982013-09-18 19:01:16 -04002013 from fontTools.misc import xmlWriter
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002014 writer = xmlWriter.XMLWriter(file)
Behdad Esfahbod45a84602013-08-19 14:44:49 -04002015 font.disassembleInstructions = False # Work around ttLib bug
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002016 for tag in font.keys():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002017 writer.begintag(tag)
2018 writer.newline()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002019 font[tag].toXML(writer, font)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002020 writer.endtag(tag)
2021 writer.newline()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002022
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002023
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002024def load_font(fontFile,
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04002025 options,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002026 checkChecksums=False,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002027 dontLoadGlyphNames=False):
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002028
Behdad Esfahbod45a84602013-08-19 14:44:49 -04002029 font = fontTools.ttLib.TTFont(fontFile,
2030 checkChecksums=checkChecksums,
2031 recalcBBoxes=options.recalc_bounds)
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002032
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002033 # Hack:
2034 #
2035 # If we don't need glyph names, change 'post' class to not try to
2036 # load them. It avoid lots of headache with broken fonts as well
2037 # as loading time.
2038 #
2039 # Ideally ttLib should provide a way to ask it to skip loading
2040 # glyph names. But it currently doesn't provide such a thing.
2041 #
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002042 if dontLoadGlyphNames:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002043 post = fontTools.ttLib.getTableClass('post')
2044 saved = post.decode_format_2_0
2045 post.decode_format_2_0 = post.decode_format_3_0
2046 f = font['post']
2047 if f.formatType == 2.0:
2048 f.formatType = 3.0
2049 post.decode_format_2_0 = saved
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002050
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002051 return font
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002052
Behdad Esfahbode911de12013-08-16 12:42:34 -04002053def save_font(font, outfile, options):
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04002054 if options.flavor and not hasattr(font, 'flavor'):
2055 raise Exception("fonttools version does not support flavors.")
2056 font.flavor = options.flavor
Behdad Esfahbode911de12013-08-16 12:42:34 -04002057 font.save(outfile, reorderTables=options.canonical_order)
Behdad Esfahbod41de4cc2013-08-15 12:09:55 -04002058
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002059def main(args):
Behdad Esfahbod610b0552013-07-23 14:52:18 -04002060
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002061 log = Logger()
2062 args = log.parse_opts(args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04002063
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04002064 options = Options()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002065 args = options.parse_opts(args, ignore_unknown=['text'])
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04002066
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002067 if len(args) < 2:
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002068 print >>sys.stderr, "usage: pyftsubset font-file glyph... [--text=ABC]... [--option=value]..."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002069 sys.exit(1)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002070
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002071 fontfile = args[0]
2072 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002073
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002074 dontLoadGlyphNames =(not options.glyph_names and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002075 all(any(g.startswith(p)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002076 for p in ['gid', 'glyph', 'uni', 'U+'])
2077 for g in args))
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002078
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04002079 font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002080 subsetter = Subsetter(options=options, log=log)
2081 log.lapse("load font")
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002082
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002083 names = font.getGlyphNames()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002084 log.lapse("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04002085
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002086 glyphs = []
2087 unicodes = []
2088 text = ""
2089 for g in args:
Behdad Esfahbod2be33d92013-09-10 19:28:59 -04002090 if g == '*':
2091 glyphs.extend(font.getGlyphOrder())
2092 continue
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002093 if g in names:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002094 glyphs.append(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002095 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002096 if g.startswith('--text='):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002097 text += g[7:]
2098 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002099 if g.startswith('uni') or g.startswith('U+'):
2100 if g.startswith('uni') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002101 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002102 elif g.startswith('U+') and len(g) > 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002103 g = g[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002104 u = int(g, 16)
2105 unicodes.append(u)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002106 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002107 if g.startswith('gid') or g.startswith('glyph'):
2108 if g.startswith('gid') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002109 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002110 elif g.startswith('glyph') and len(g) > 5:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002111 g = g[5:]
2112 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002113 glyphs.append(font.getGlyphName(int(g), requireReal=1))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002114 except ValueError:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002115 raise Exception("Invalid glyph identifier: %s" % g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002116 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002117 raise Exception("Invalid glyph identifier: %s" % g)
2118 log.lapse("compile glyph list")
2119 log("Unicodes:", unicodes)
2120 log("Glyphs:", glyphs)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04002121
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002122 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
2123 subsetter.subset(font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04002124
Behdad Esfahbod34426c12013-08-14 18:30:09 -04002125 outfile = fontfile + '.subset'
2126
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04002127 save_font (font, outfile, options)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002128 log.lapse("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04002129
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002130 log.last_time = log.start_time
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002131 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04002132
Behdad Esfahbod34426c12013-08-14 18:30:09 -04002133 if log.verbose:
2134 import os
2135 log("Input font: %d bytes" % os.path.getsize(fontfile))
2136 log("Subset font: %d bytes" % os.path.getsize(outfile))
2137
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002138 log.font(font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04002139
Behdad Esfahbodc56bf482013-08-13 20:13:33 -04002140 font.close()
2141
Behdad Esfahbod39a39ac2013-08-22 18:10:17 -04002142
2143__all__ = [
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002144 'Options',
2145 'Subsetter',
2146 'Logger',
2147 'load_font',
2148 'save_font',
2149 'main'
Behdad Esfahbod39a39ac2013-08-22 18:10:17 -04002150]
2151
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04002152if __name__ == '__main__':
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002153 main(sys.argv[1:])