blob: afbae47212295c89fe851ea9884d008d1dc43fcb [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 Esfahbod46d260f2013-09-19 20:36:49 -040015from fontTools import ttLib
16from fontTools.ttLib.tables import otTables
17from fontTools.misc import psCharStrings
18from fontTools.pens import basePen
Behdad Esfahbod54660612013-07-21 18:16:55 -040019
Behdad Esfahbod54660612013-07-21 18:16:55 -040020
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040021def _add_method(*clazzes):
Behdad Esfahbod616d36e2013-08-13 20:02:59 -040022 """Returns a decorator function that adds a new method to one or
23 more classes."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040024 def wrapper(method):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040025 for clazz in clazzes:
26 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
Behdad Esfahbode7a0d562013-08-16 10:56:30 -040027 assert not hasattr(clazz, method.func_name), \
Behdad Esfahbodd77f1572013-08-15 19:24:36 -040028 "Oops, class '%s' has method '%s'." % (clazz.__name__,
29 method.func_name)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040030 setattr(clazz, method.func_name, method)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040031 return None
32 return wrapper
Behdad Esfahbod54660612013-07-21 18:16:55 -040033
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040034def _uniq_sort(l):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040035 return sorted(set(l))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040036
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -040037def _set_update(s, *others):
38 # Jython's set.update only takes one other argument.
39 # Emulate real set.update...
40 for other in others:
41 s.update(other)
42
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040043
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040044@_add_method(otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040045def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040046 "Returns ascending list of matching coverage values."
Behdad Esfahbod4734be52013-08-14 19:47:42 -040047 return [i for i,g in enumerate(self.glyphs) if g in glyphs]
Behdad Esfahbod610b0552013-07-23 14:52:18 -040048
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040049@_add_method(otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040050def intersect_glyphs(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040051 "Returns set of intersecting glyphs."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040052 return set(g for g in self.glyphs if g in glyphs)
Behdad Esfahbod849d25c2013-08-12 19:24:24 -040053
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040054@_add_method(otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040055def subset(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040056 "Returns ascending list of remaining coverage values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040057 indices = self.intersect(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040058 self.glyphs = [g for g in self.glyphs if g in glyphs]
59 return indices
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040060
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040061@_add_method(otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040062def remap(self, coverage_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040063 "Remaps coverage."
64 self.glyphs = [self.glyphs[i] for i in coverage_map]
Behdad Esfahbod14374262013-08-08 22:26:49 -040065
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040066@_add_method(otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040067def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040068 "Returns ascending list of matching class values."
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040069 return _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040070 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040071 [v for g,v in self.classDefs.iteritems() if g in glyphs])
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040072
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040073@_add_method(otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040074def intersect_class(self, glyphs, klass):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040075 "Returns set of glyphs matching class."
76 if klass == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040077 return set(g for g in glyphs if g not in self.classDefs)
78 return set(g for g,v in self.classDefs.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040079 if v == klass and g in glyphs)
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040080
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040081@_add_method(otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040082def subset(self, glyphs, remap=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040083 "Returns ascending list of remaining classes."
Behdad Esfahbodd73f2252013-08-16 10:58:25 -040084 self.classDefs = dict((g,v) for g,v in self.classDefs.iteritems() if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040085 # Note: while class 0 has the special meaning of "not matched",
86 # if no glyph will ever /not match/, we can optimize class 0 out too.
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040087 indices = _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040088 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod4e5d9672013-08-14 19:49:53 -040089 self.classDefs.values())
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040090 if remap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040091 self.remap(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040092 return indices
Behdad Esfahbod4aa6ce32013-07-22 12:15:36 -040093
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040094@_add_method(otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040095def remap(self, class_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040096 "Remaps classes."
Behdad Esfahbodd73f2252013-08-16 10:58:25 -040097 self.classDefs = dict((g,class_map.index(v))
98 for g,v in self.classDefs.iteritems())
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040099
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400100@_add_method(otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400101def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400102 if cur_glyphs == None: cur_glyphs = s.glyphs
103 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400104 s.glyphs.update(v for g,v in self.mapping.iteritems() if g in cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400105 else:
106 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400107
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400108@_add_method(otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400109def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400110 if self.Format in [1, 2]:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400111 self.mapping = dict((g,v) for g,v in self.mapping.iteritems()
112 if g in s.glyphs and v in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400113 return bool(self.mapping)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400114 else:
115 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400116
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400117@_add_method(otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400118def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400119 if cur_glyphs == None: cur_glyphs = s.glyphs
120 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400121 indices = self.Coverage.intersect(cur_glyphs)
Behdad Esfahboda9bfec12013-08-16 16:21:25 -0400122 _set_update(s.glyphs, *(self.Sequence[i].Substitute for i in indices))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400123 else:
124 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400125
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400126@_add_method(otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400127def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400128 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400129 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400130 self.Sequence = [self.Sequence[i] for i in indices]
131 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400132 indices = [i for i,seq in enumerate(self.Sequence)
133 if all(sub in s.glyphs for sub in seq.Substitute)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400134 self.Sequence = [self.Sequence[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400135 self.Coverage.remap(indices)
136 self.SequenceCount = len(self.Sequence)
137 return bool(self.SequenceCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400138 else:
139 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400140
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400141@_add_method(otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400142def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400143 if cur_glyphs == None: cur_glyphs = s.glyphs
144 if self.Format == 1:
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -0400145 _set_update(s.glyphs, *(vlist for g,vlist in self.alternates.iteritems()
146 if g in cur_glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400147 else:
148 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400149
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400150@_add_method(otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400151def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400152 if self.Format == 1:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400153 self.alternates = dict((g,vlist)
154 for g,vlist in self.alternates.iteritems()
155 if g in s.glyphs and
156 all(v in s.glyphs for v in vlist))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400157 return bool(self.alternates)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400158 else:
159 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400160
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400161@_add_method(otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400162def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400163 if cur_glyphs == None: cur_glyphs = s.glyphs
164 if self.Format == 1:
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -0400165 _set_update(s.glyphs, *([seq.LigGlyph for seq in seqs
166 if all(c in s.glyphs for c in seq.Component)]
167 for g,seqs in self.ligatures.iteritems()
168 if g in cur_glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400169 else:
170 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400171
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400172@_add_method(otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400173def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400174 if self.Format == 1:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400175 self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems()
176 if g in s.glyphs)
177 self.ligatures = dict((g,[seq for seq in seqs
178 if seq.LigGlyph in s.glyphs and
179 all(c in s.glyphs for c in seq.Component)])
180 for g,seqs in self.ligatures.iteritems())
181 self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems() if v)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400182 return bool(self.ligatures)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400183 else:
184 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400185
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400186@_add_method(otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400187def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400188 if cur_glyphs == None: cur_glyphs = s.glyphs
189 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400190 indices = self.Coverage.intersect(cur_glyphs)
191 if(not indices or
192 not all(c.intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400193 for c in self.LookAheadCoverage + self.BacktrackCoverage)):
194 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400195 s.glyphs.update(self.Substitute[i] for i in indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400196 else:
197 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400198
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400199@_add_method(otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400200def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400201 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400202 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400203 self.Substitute = [self.Substitute[i] for i in indices]
204 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400205 indices = [i for i,sub in enumerate(self.Substitute)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400206 if sub in s.glyphs]
207 self.Substitute = [self.Substitute[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400208 self.Coverage.remap(indices)
209 self.GlyphCount = len(self.Substitute)
210 return bool(self.GlyphCount and
211 all(c.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400212 for c in self.LookAheadCoverage+self.BacktrackCoverage))
213 else:
214 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400215
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400216@_add_method(otTables.SinglePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400217def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400218 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400219 return len(self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400220 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400221 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400222 self.Value = [self.Value[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400223 self.ValueCount = len(self.Value)
224 return bool(self.ValueCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400225 else:
226 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400227
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400228@_add_method(otTables.SinglePos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400229def prune_post_subset(self, options):
230 if not options.hinting:
231 # Drop device tables
232 self.ValueFormat &= ~0x00F0
233 return True
234
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400235@_add_method(otTables.PairPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400236def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400237 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400238 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400239 self.PairSet = [self.PairSet[i] for i in indices]
240 for p in self.PairSet:
241 p.PairValueRecord = [r for r in p.PairValueRecord
242 if r.SecondGlyph in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400243 p.PairValueCount = len(p.PairValueRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400244 self.PairSet = [p for p in self.PairSet if p.PairValueCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400245 self.PairSetCount = len(self.PairSet)
246 return bool(self.PairSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400247 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400248 class1_map = self.ClassDef1.subset(s.glyphs, remap=True)
249 class2_map = self.ClassDef2.subset(s.glyphs, remap=True)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400250 self.Class1Record = [self.Class1Record[i] for i in class1_map]
251 for c in self.Class1Record:
252 c.Class2Record = [c.Class2Record[i] for i in class2_map]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400253 self.Class1Count = len(class1_map)
254 self.Class2Count = len(class2_map)
255 return bool(self.Class1Count and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400256 self.Class2Count and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400257 self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400258 else:
259 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400260
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400261@_add_method(otTables.PairPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400262def prune_post_subset(self, options):
263 if not options.hinting:
264 # Drop device tables
265 self.ValueFormat1 &= ~0x00F0
266 self.ValueFormat2 &= ~0x00F0
267 return True
268
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400269@_add_method(otTables.CursivePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400270def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400271 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400272 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400273 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400274 self.EntryExitCount = len(self.EntryExitRecord)
275 return bool(self.EntryExitCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400276 else:
277 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400278
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400279@_add_method(otTables.Anchor)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400280def prune_hints(self):
281 # Drop device tables / contour anchor point
282 self.Format = 1
283
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400284@_add_method(otTables.CursivePos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400285def prune_post_subset(self, options):
286 if not options.hinting:
287 for rec in self.EntryExitRecord:
288 if rec.EntryAnchor: rec.EntryAnchor.prune_hints()
289 if rec.ExitAnchor: rec.ExitAnchor.prune_hints()
290 return True
291
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400292@_add_method(otTables.MarkBasePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400293def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400294 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400295 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400296 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
297 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400298 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
299 base_indices = self.BaseCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400300 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i]
301 for i in base_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400302 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400303 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400304 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400305 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400306 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400307 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400308 for b in self.BaseArray.BaseRecord:
309 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400310 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400311 self.MarkArray.MarkCount and
312 self.BaseArray.BaseCount)
313 else:
314 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400315
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400316@_add_method(otTables.MarkBasePos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400317def prune_post_subset(self, options):
318 if not options.hinting:
319 for m in self.MarkArray.MarkRecord:
320 m.MarkAnchor.prune_hints()
321 for b in self.BaseArray.BaseRecord:
322 for a in b.BaseAnchor:
323 a.prune_hints()
324 return True
325
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400326@_add_method(otTables.MarkLigPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400327def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400328 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400329 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400330 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
331 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400332 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
333 ligature_indices = self.LigatureCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400334 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i]
335 for i in ligature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400336 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400337 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400338 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400339 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400340 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400341 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400342 for l in self.LigatureArray.LigatureAttach:
343 for c in l.ComponentRecord:
344 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400345 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400346 self.MarkArray.MarkCount and
347 self.LigatureArray.LigatureCount)
348 else:
349 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400350
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400351@_add_method(otTables.MarkLigPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400352def prune_post_subset(self, options):
353 if not options.hinting:
354 for m in self.MarkArray.MarkRecord:
355 m.MarkAnchor.prune_hints()
356 for l in self.LigatureArray.LigatureAttach:
357 for c in l.ComponentRecord:
358 for a in c.LigatureAnchor:
359 a.prune_hints()
360 return True
361
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400362@_add_method(otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400363def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400364 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400365 mark1_indices = self.Mark1Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400366 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i]
367 for i in mark1_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400368 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
369 mark2_indices = self.Mark2Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400370 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i]
371 for i in mark2_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400372 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400373 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400374 class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400375 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400376 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400377 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400378 for b in self.Mark2Array.Mark2Record:
379 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400380 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400381 self.Mark1Array.MarkCount and
382 self.Mark2Array.MarkCount)
383 else:
384 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400385
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400386@_add_method(otTables.MarkMarkPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400387def prune_post_subset(self, options):
388 if not options.hinting:
389 # Drop device tables or contour anchor point
390 for m in self.Mark1Array.MarkRecord:
391 m.MarkAnchor.prune_hints()
392 for b in self.Mark2Array.Mark2Record:
Behdad Esfahbod0ec17d92013-09-15 18:30:41 -0400393 for m in b.Mark2Anchor:
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400394 m.prune_hints()
395 return True
396
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400397@_add_method(otTables.SingleSubst,
398 otTables.MultipleSubst,
399 otTables.AlternateSubst,
400 otTables.LigatureSubst,
401 otTables.ReverseChainSingleSubst,
402 otTables.SinglePos,
403 otTables.PairPos,
404 otTables.CursivePos,
405 otTables.MarkBasePos,
406 otTables.MarkLigPos,
407 otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400408def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400409 pass
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400410
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400411@_add_method(otTables.SingleSubst,
412 otTables.MultipleSubst,
413 otTables.AlternateSubst,
414 otTables.LigatureSubst,
415 otTables.ReverseChainSingleSubst,
416 otTables.SinglePos,
417 otTables.PairPos,
418 otTables.CursivePos,
419 otTables.MarkBasePos,
420 otTables.MarkLigPos,
421 otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400422def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400423 return []
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400424
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400425@_add_method(otTables.SingleSubst,
426 otTables.MultipleSubst,
427 otTables.AlternateSubst,
428 otTables.LigatureSubst,
429 otTables.ContextSubst,
430 otTables.ChainContextSubst,
431 otTables.ReverseChainSingleSubst,
432 otTables.SinglePos,
433 otTables.PairPos,
434 otTables.CursivePos,
435 otTables.MarkBasePos,
436 otTables.MarkLigPos,
437 otTables.MarkMarkPos,
438 otTables.ContextPos,
439 otTables.ChainContextPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400440def prune_pre_subset(self, options):
441 return True
442
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400443@_add_method(otTables.SingleSubst,
444 otTables.MultipleSubst,
445 otTables.AlternateSubst,
446 otTables.LigatureSubst,
447 otTables.ReverseChainSingleSubst,
448 otTables.ContextSubst,
449 otTables.ChainContextSubst,
450 otTables.ContextPos,
451 otTables.ChainContextPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400452def prune_post_subset(self, options):
453 return True
454
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400455@_add_method(otTables.SingleSubst,
456 otTables.AlternateSubst,
457 otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400458def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400459 return False
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400460
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400461@_add_method(otTables.MultipleSubst,
462 otTables.LigatureSubst,
463 otTables.ContextSubst,
464 otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400465def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400466 return True
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400467
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400468@_add_method(otTables.ContextSubst,
469 otTables.ChainContextSubst,
470 otTables.ContextPos,
471 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400472def __classify_context(self):
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400473
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -0400474 class ContextHelper(object):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400475 def __init__(self, klass, Format):
476 if klass.__name__.endswith('Subst'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400477 Typ = 'Sub'
478 Type = 'Subst'
479 else:
480 Typ = 'Pos'
481 Type = 'Pos'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400482 if klass.__name__.startswith('Chain'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400483 Chain = 'Chain'
484 else:
485 Chain = ''
486 ChainTyp = Chain+Typ
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400487
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400488 self.Typ = Typ
489 self.Type = Type
490 self.Chain = Chain
491 self.ChainTyp = ChainTyp
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400492
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400493 self.LookupRecord = Type+'LookupRecord'
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400494
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400495 if Format == 1:
496 Coverage = lambda r: r.Coverage
497 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400498 ContextData = lambda r:(None,)
499 ChainContextData = lambda r:(None, None, None)
500 RuleData = lambda r:(r.Input,)
501 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400502 SetRuleData = None
503 ChainSetRuleData = None
504 elif Format == 2:
505 Coverage = lambda r: r.Coverage
506 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400507 ContextData = lambda r:(r.ClassDef,)
508 ChainContextData = lambda r:(r.LookAheadClassDef,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400509 r.InputClassDef,
510 r.BacktrackClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400511 RuleData = lambda r:(r.Class,)
512 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack)
513 def SetRuleData(r, d):(r.Class,) = d
514 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400515 elif Format == 3:
516 Coverage = lambda r: r.Coverage[0]
517 ChainCoverage = lambda r: r.InputCoverage[0]
518 ContextData = None
519 ChainContextData = None
520 RuleData = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400521 ChainRuleData = lambda r:(r.LookAheadCoverage +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400522 r.InputCoverage +
523 r.BacktrackCoverage)
524 SetRuleData = None
525 ChainSetRuleData = None
526 else:
527 assert 0, "unknown format: %s" % Format
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400528
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400529 if Chain:
530 self.Coverage = ChainCoverage
531 self.ContextData = ChainContextData
532 self.RuleData = ChainRuleData
533 self.SetRuleData = ChainSetRuleData
534 else:
535 self.Coverage = Coverage
536 self.ContextData = ContextData
537 self.RuleData = RuleData
538 self.SetRuleData = SetRuleData
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400539
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400540 if Format == 1:
541 self.Rule = ChainTyp+'Rule'
542 self.RuleCount = ChainTyp+'RuleCount'
543 self.RuleSet = ChainTyp+'RuleSet'
544 self.RuleSetCount = ChainTyp+'RuleSetCount'
545 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
546 elif Format == 2:
547 self.Rule = ChainTyp+'ClassRule'
548 self.RuleCount = ChainTyp+'ClassRuleCount'
549 self.RuleSet = ChainTyp+'ClassSet'
550 self.RuleSetCount = ChainTyp+'ClassSetCount'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400551 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r)
Behdad Esfahbod89987002013-07-23 23:07:42 -0400552
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400553 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
Behdad Esfahbod11763302013-08-14 15:33:08 -0400554 self.Input = 'Input' if Chain else 'Class'
Behdad Esfahbod27108392013-07-23 16:40:47 -0400555
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400556 if self.Format not in [1, 2, 3]:
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400557 return None # Don't shoot the messenger; let it go
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400558 if not hasattr(self.__class__, "__ContextHelpers"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400559 self.__class__.__ContextHelpers = {}
560 if self.Format not in self.__class__.__ContextHelpers:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400561 helper = ContextHelper(self.__class__, self.Format)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400562 self.__class__.__ContextHelpers[self.Format] = helper
563 return self.__class__.__ContextHelpers[self.Format]
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400564
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400565@_add_method(otTables.ContextSubst,
566 otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400567def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400568 if cur_glyphs == None: cur_glyphs = s.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400569 c = self.__classify_context()
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400570
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400571 indices = c.Coverage(self).intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400572 if not indices:
573 return []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400574 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs);
Behdad Esfahbod1d4fa132013-08-08 22:59:32 -0400575
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400576 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400577 ContextData = c.ContextData(self)
578 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400579 for i in indices:
580 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400581 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400582 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400583 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
584 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400585 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400586 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400587 if not ll: continue
588 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400589 if chaos:
590 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400591 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400592 if seqi == 0:
593 pos_glyphs = set([c.Coverage(self).glyphs[i]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400594 else:
Behdad Esfahbodd3fdcc72013-08-14 17:59:31 -0400595 pos_glyphs = set([r.Input[seqi - 1]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400596 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400597 chaos = chaos or lookup.may_have_non_1to1()
598 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400599 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400600 ClassDef = getattr(self, c.ClassDef)
601 indices = ClassDef.intersect(cur_glyphs)
602 ContextData = c.ContextData(self)
603 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400604 for i in indices:
605 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400606 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400607 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400608 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
609 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400610 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400611 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400612 if not ll: continue
613 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400614 if chaos:
615 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400616 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400617 if seqi == 0:
618 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400619 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400620 pos_glyphs = ClassDef.intersect_class(s.glyphs,
Behdad Esfahbod11763302013-08-14 15:33:08 -0400621 getattr(r, c.Input)[seqi - 1])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400622 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400623 chaos = chaos or lookup.may_have_non_1to1()
624 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400625 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400626 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400627 return []
628 r = self
629 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400630 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400631 if not ll: continue
632 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400633 if chaos:
634 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400635 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400636 if seqi == 0:
637 pos_glyphs = cur_glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400638 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400639 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400640 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400641 chaos = chaos or lookup.may_have_non_1to1()
642 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400643 else:
644 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod00776972013-07-23 15:33:00 -0400645
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400646@_add_method(otTables.ContextSubst,
647 otTables.ContextPos,
648 otTables.ChainContextSubst,
649 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400650def subset_glyphs(self, s):
651 c = self.__classify_context()
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400652
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400653 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400654 indices = self.Coverage.subset(s.glyphs)
655 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400656 rss = [rss[i] for i in indices]
657 for rs in rss:
658 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400659 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400660 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400661 if r and all(all(g in s.glyphs for g in glist)
662 for glist in c.RuleData(r))]
663 setattr(rs, c.Rule, ss)
664 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400665 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400666 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
667 setattr(self, c.RuleSet, rss)
668 setattr(self, c.RuleSetCount, len(rss))
669 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400670 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400671 if not self.Coverage.subset(s.glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400672 return False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400673 indices = getattr(self, c.ClassDef).subset(self.Coverage.glyphs,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400674 remap=False)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400675 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400676 rss = [rss[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400677 ContextData = c.ContextData(self)
678 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400679 for rs in rss:
680 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400681 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400682 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400683 if r and all(all(k in klass_map for k in klist)
684 for klass_map,klist in zip(klass_maps, c.RuleData(r)))]
685 setattr(rs, c.Rule, ss)
686 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400687
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400688 # Remap rule classes
689 for r in ss:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400690 c.SetRuleData(r, [[klass_map.index(k) for k in klist]
691 for klass_map,klist in zip(klass_maps, c.RuleData(r))])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400692 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400693 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
694 setattr(self, c.RuleSet, rss)
695 setattr(self, c.RuleSetCount, len(rss))
696 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400697 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400698 return all(x.subset(s.glyphs) for x in c.RuleData(self))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400699 else:
700 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400701
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400702@_add_method(otTables.ContextSubst,
703 otTables.ChainContextSubst,
704 otTables.ContextPos,
705 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400706def subset_lookups(self, lookup_indices):
707 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400708
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400709 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400710 for rs in getattr(self, c.RuleSet):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400711 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400712 for r in getattr(rs, c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400713 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400714 setattr(r, c.LookupRecord,
715 [ll for ll in getattr(r, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400716 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400717 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400718 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400719 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400720 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400721 setattr(self, c.LookupRecord,
722 [ll for ll in getattr(self, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400723 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400724 for ll in getattr(self, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400725 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400726 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400727 else:
728 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400729
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400730@_add_method(otTables.ContextSubst,
731 otTables.ChainContextSubst,
732 otTables.ContextPos,
733 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400734def collect_lookups(self):
735 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400736
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400737 if self.Format in [1, 2]:
738 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400739 for rs in getattr(self, c.RuleSet) if rs
740 for r in getattr(rs, c.Rule) if r
741 for ll in getattr(r, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400742 elif self.Format == 3:
743 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400744 for ll in getattr(self, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400745 else:
746 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400747
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400748@_add_method(otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400749def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400750 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400751 self.ExtSubTable.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400752 else:
753 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400754
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400755@_add_method(otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400756def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400757 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400758 return self.ExtSubTable.may_have_non_1to1()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400759 else:
760 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400761
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400762@_add_method(otTables.ExtensionSubst,
763 otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400764def prune_pre_subset(self, options):
765 if self.Format == 1:
766 return self.ExtSubTable.prune_pre_subset(options)
767 else:
768 assert 0, "unknown format: %s" % self.Format
769
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400770@_add_method(otTables.ExtensionSubst,
771 otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400772def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400773 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400774 return self.ExtSubTable.subset_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400775 else:
776 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400777
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400778@_add_method(otTables.ExtensionSubst,
779 otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400780def prune_post_subset(self, options):
781 if self.Format == 1:
782 return self.ExtSubTable.prune_post_subset(options)
783 else:
784 assert 0, "unknown format: %s" % self.Format
785
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400786@_add_method(otTables.ExtensionSubst,
787 otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400788def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400789 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400790 return self.ExtSubTable.subset_lookups(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400791 else:
792 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400793
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400794@_add_method(otTables.ExtensionSubst,
795 otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400796def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400797 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400798 return self.ExtSubTable.collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400799 else:
800 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400801
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400802@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400803def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400804 for st in self.SubTable:
805 if not st: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400806 st.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400807
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400808@_add_method(otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400809def prune_pre_subset(self, options):
810 ret = False
811 for st in self.SubTable:
812 if not st: continue
813 if st.prune_pre_subset(options): ret = True
814 return ret
815
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400816@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400817def subset_glyphs(self, s):
818 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)]
819 self.SubTableCount = len(self.SubTable)
820 return bool(self.SubTableCount)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400821
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400822@_add_method(otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400823def prune_post_subset(self, options):
824 ret = False
825 for st in self.SubTable:
826 if not st: continue
827 if st.prune_post_subset(options): ret = True
828 return ret
829
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400830@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400831def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400832 for s in self.SubTable:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400833 s.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400834
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400835@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400836def collect_lookups(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400837 return _uniq_sort(sum((st.collect_lookups() for st in self.SubTable
838 if st), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400839
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400840@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400841def may_have_non_1to1(self):
842 return any(st.may_have_non_1to1() for st in self.SubTable if st)
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400843
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400844@_add_method(otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400845def prune_pre_subset(self, options):
846 ret = False
847 for l in self.Lookup:
848 if not l: continue
849 if l.prune_pre_subset(options): ret = True
850 return ret
851
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400852@_add_method(otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400853def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400854 "Returns the indices of nonempty lookups."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400855 return [i for i,l in enumerate(self.Lookup) if l and l.subset_glyphs(s)]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400856
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400857@_add_method(otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400858def prune_post_subset(self, options):
859 ret = False
860 for l in self.Lookup:
861 if not l: continue
862 if l.prune_post_subset(options): ret = True
863 return ret
864
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400865@_add_method(otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400866def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400867 self.Lookup = [self.Lookup[i] for i in lookup_indices
868 if i < self.LookupCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400869 self.LookupCount = len(self.Lookup)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400870 for l in self.Lookup:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400871 l.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400872
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400873@_add_method(otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400874def closure_lookups(self, lookup_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400875 lookup_indices = _uniq_sort(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400876 recurse = lookup_indices
877 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400878 recurse_lookups = sum((self.Lookup[i].collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400879 for i in recurse if i < self.LookupCount), [])
880 recurse_lookups = [l for l in recurse_lookups
881 if l not in lookup_indices and l < self.LookupCount]
882 if not recurse_lookups:
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400883 return _uniq_sort(lookup_indices)
884 recurse_lookups = _uniq_sort(recurse_lookups)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400885 lookup_indices.extend(recurse_lookups)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400886 recurse = recurse_lookups
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400887
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400888@_add_method(otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400889def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400890 self.LookupListIndex = [l for l in self.LookupListIndex
891 if l in lookup_indices]
892 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400893 self.LookupListIndex = [lookup_indices.index(l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400894 for l in self.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400895 self.LookupCount = len(self.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400896 return self.LookupCount
Behdad Esfahbod54660612013-07-21 18:16:55 -0400897
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400898@_add_method(otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400899def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400900 return self.LookupListIndex[:]
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400901
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400902@_add_method(otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400903def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400904 "Returns the indices of nonempty features."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400905 feature_indices = [i for i,f in enumerate(self.FeatureRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400906 if f.Feature.subset_lookups(lookup_indices)]
907 self.subset_features(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400908 return feature_indices
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400909
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400910@_add_method(otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400911def collect_lookups(self, feature_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400912 return _uniq_sort(sum((self.FeatureRecord[i].Feature.collect_lookups()
913 for i in feature_indices
Behdad Esfahbod1ee298d2013-08-13 20:07:09 -0400914 if i < self.FeatureCount), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400915
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400916@_add_method(otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400917def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400918 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400919 self.FeatureCount = len(self.FeatureRecord)
920 return bool(self.FeatureCount)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400921
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400922@_add_method(otTables.DefaultLangSys,
923 otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400924def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400925 if self.ReqFeatureIndex in feature_indices:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400926 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400927 else:
928 self.ReqFeatureIndex = 65535
929 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
930 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400931 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400932 if f in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400933 self.FeatureCount = len(self.FeatureIndex)
934 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400935
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400936@_add_method(otTables.DefaultLangSys,
937 otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400938def collect_features(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400939 feature_indices = self.FeatureIndex[:]
940 if self.ReqFeatureIndex != 65535:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400941 feature_indices.append(self.ReqFeatureIndex)
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400942 return _uniq_sort(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400943
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400944@_add_method(otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400945def subset_features(self, feature_indices):
946 if(self.DefaultLangSys and
947 not self.DefaultLangSys.subset_features(feature_indices)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400948 self.DefaultLangSys = None
949 self.LangSysRecord = [l for l in self.LangSysRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400950 if l.LangSys.subset_features(feature_indices)]
951 self.LangSysCount = len(self.LangSysRecord)
952 return bool(self.LangSysCount or self.DefaultLangSys)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400953
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400954@_add_method(otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400955def collect_features(self):
956 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400957 if self.DefaultLangSys:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400958 feature_indices.append(self.DefaultLangSys.collect_features())
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400959 return _uniq_sort(sum(feature_indices, []))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400960
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400961@_add_method(otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400962def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400963 self.ScriptRecord = [s for s in self.ScriptRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400964 if s.Script.subset_features(feature_indices)]
965 self.ScriptCount = len(self.ScriptRecord)
966 return bool(self.ScriptCount)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400967
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400968@_add_method(otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400969def collect_features(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400970 return _uniq_sort(sum((s.Script.collect_features()
971 for s in self.ScriptRecord), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400972
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400973@_add_method(ttLib.getTableClass('GSUB'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400974def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400975 s.table = self.table
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400976 feature_indices = self.table.ScriptList.collect_features()
977 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400978 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400979 orig_glyphs = s.glyphs.copy()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400980 for i in lookup_indices:
981 if i >= self.table.LookupList.LookupCount: continue
982 if not self.table.LookupList.Lookup[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400983 self.table.LookupList.Lookup[i].closure_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400984 if orig_glyphs == s.glyphs:
985 break
986 del s.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400987
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400988@_add_method(ttLib.getTableClass('GSUB'),
989 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400990def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400991 s.glyphs = s.glyphs_gsubed
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400992 lookup_indices = self.table.LookupList.subset_glyphs(s)
993 self.subset_lookups(lookup_indices)
994 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400995 return True
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400996
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400997@_add_method(ttLib.getTableClass('GSUB'),
998 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400999def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001000 """Retrains specified lookups, then removes empty features, language
1001 systems, and scripts."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001002 self.table.LookupList.subset_lookups(lookup_indices)
1003 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices)
1004 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -04001005
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001006@_add_method(ttLib.getTableClass('GSUB'),
1007 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001008def prune_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001009 "Remove unreferenced lookups"
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001010 feature_indices = self.table.ScriptList.collect_features()
1011 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
1012 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices)
1013 self.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -04001014
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001015@_add_method(ttLib.getTableClass('GSUB'),
1016 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001017def subset_feature_tags(self, feature_tags):
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001018 feature_indices = [i for i,f in
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001019 enumerate(self.table.FeatureList.FeatureRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001020 if f.FeatureTag in feature_tags]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001021 self.table.FeatureList.subset_features(feature_indices)
1022 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001023
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001024@_add_method(ttLib.getTableClass('GSUB'),
1025 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001026def prune_pre_subset(self, options):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001027 if '*' not in options.layout_features:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001028 self.subset_feature_tags(options.layout_features)
1029 self.prune_lookups()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -04001030 self.table.LookupList.prune_pre_subset(options);
1031 return True
1032
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001033@_add_method(ttLib.getTableClass('GSUB'),
1034 ttLib.getTableClass('GPOS'))
Behdad Esfahbodd77f1572013-08-15 19:24:36 -04001035def prune_post_subset(self, options):
1036 self.table.LookupList.prune_post_subset(options);
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001037 return True
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001038
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001039@_add_method(ttLib.getTableClass('GDEF'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001040def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001041 glyphs = s.glyphs_gsubed
1042 table = self.table
1043 if table.LigCaretList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001044 indices = table.LigCaretList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001045 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i]
1046 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001047 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001048 if not table.LigCaretList.LigGlyphCount:
1049 table.LigCaretList = None
1050 if table.MarkAttachClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001051 table.MarkAttachClassDef.classDefs = dict((g,v) for g,v in
1052 table.MarkAttachClassDef.
1053 classDefs.iteritems()
1054 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001055 if not table.MarkAttachClassDef.classDefs:
1056 table.MarkAttachClassDef = None
1057 if table.GlyphClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001058 table.GlyphClassDef.classDefs = dict((g,v) for g,v in
1059 table.GlyphClassDef.
1060 classDefs.iteritems()
1061 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001062 if not table.GlyphClassDef.classDefs:
1063 table.GlyphClassDef = None
1064 if table.AttachList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001065 indices = table.AttachList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001066 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i]
1067 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001068 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001069 if not table.AttachList.GlyphCount:
1070 table.AttachList = None
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001071 return bool(table.LigCaretList or
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001072 table.MarkAttachClassDef or
1073 table.GlyphClassDef or
1074 table.AttachList)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001075
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001076@_add_method(ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001077def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001078 # Prune unknown kern table types
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001079 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')]
1080 return bool(self.kernTables)
Behdad Esfahbodd4e33a72013-07-24 18:51:05 -04001081
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001082@_add_method(ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001083def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001084 glyphs = s.glyphs_gsubed
1085 for t in self.kernTables:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001086 t.kernTable = dict(((a,b),v) for (a,b),v in t.kernTable.iteritems()
1087 if a in glyphs and b in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001088 self.kernTables = [t for t in self.kernTables if t.kernTable]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001089 return bool(self.kernTables)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001090
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001091@_add_method(ttLib.getTableClass('vmtx'),
1092 ttLib.getTableClass('hmtx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001093def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001094 self.metrics = dict((g,v) for g,v in self.metrics.iteritems() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001095 return bool(self.metrics)
Behdad Esfahbodc7160442013-07-22 14:29:08 -04001096
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001097@_add_method(ttLib.getTableClass('hdmx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001098def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001099 self.hdmx = dict((sz,_dict((g,v) for g,v in l.iteritems() if g in s.glyphs))
1100 for sz,l in self.hdmx.iteritems())
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001101 return bool(self.hdmx)
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -04001102
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001103@_add_method(ttLib.getTableClass('VORG'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001104def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001105 self.VOriginRecords = dict((g,v) for g,v in self.VOriginRecords.iteritems()
1106 if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001107 self.numVertOriginYMetrics = len(self.VOriginRecords)
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001108 return True # Never drop; has default metrics
Behdad Esfahbode45d6af2013-07-22 15:29:17 -04001109
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001110@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001111def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001112 if not options.glyph_names:
1113 self.formatType = 3.0
1114 return True
Behdad Esfahbod42648242013-07-23 12:56:06 -04001115
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001116@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001117def subset_glyphs(self, s):
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001118 self.extraNames = [] # This seems to do it
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001119 return True
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001120
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001121@_add_method(ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001122def getComponentNamesFast(self, glyfTable):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001123 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001124 return [] # Not composite
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001125 data = self.data
1126 i = 10
1127 components = []
1128 more = 1
1129 while more:
1130 flags, glyphID = struct.unpack(">HH", data[i:i+4])
1131 i += 4
1132 flags = int(flags)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001133 components.append(glyfTable.getGlyphName(int(glyphID)))
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001134
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001135 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001136 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001137 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1138 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1139 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1140 more = flags & 0x0020 # MORE_COMPONENTS
1141
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001142 return components
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001143
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001144@_add_method(ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001145def remapComponentsFast(self, indices):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001146 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001147 return # Not composite
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001148 data = array.array("B", self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001149 i = 10
1150 more = 1
1151 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001152 flags =(data[i] << 8) | data[i+1]
1153 glyphID =(data[i+2] << 8) | data[i+3]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001154 # Remap
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001155 glyphID = indices.index(glyphID)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001156 data[i+2] = glyphID >> 8
1157 data[i+3] = glyphID & 0xFF
1158 i += 4
1159 flags = int(flags)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001160
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001161 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001162 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001163 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1164 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1165 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1166 more = flags & 0x0020 # MORE_COMPONENTS
1167
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001168 self.data = data.tostring()
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001169
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001170@_add_method(ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001171def dropInstructionsFast(self):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001172 if not self.data:
1173 return
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001174 numContours = struct.unpack(">h", self.data[:2])[0]
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001175 data = array.array("B", self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001176 i = 10
1177 if numContours >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001178 i += 2 * numContours # endPtsOfContours
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001179 instructionLen =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001180 # Zero it
1181 data[i] = data [i+1] = 0
1182 i += 2
1183 if instructionLen:
1184 # Splice it out
1185 data = data[:i] + data[i+instructionLen:]
1186 else:
1187 more = 1
1188 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001189 flags =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001190 # Turn instruction flag off
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001191 flags &= ~0x0100 # WE_HAVE_INSTRUCTIONS
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001192 data[i+0] = flags >> 8
1193 data[i+1] = flags & 0xFF
1194 i += 4
1195 flags = int(flags)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001196
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001197 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001198 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001199 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1200 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1201 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1202 more = flags & 0x0020 # MORE_COMPONENTS
1203
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001204 # Cut off
1205 data = data[:i]
1206 if len(data) % 4:
1207 # add pad bytes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001208 nPadBytes = 4 -(len(data) % 4)
1209 for i in range(nPadBytes):
1210 data.append(0)
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001211 self.data = data.tostring()
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001212
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001213@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001214def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001215 decompose = s.glyphs
1216 # I don't know if component glyphs can be composite themselves.
1217 # We handle them anyway.
1218 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001219 components = set()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001220 for g in decompose:
1221 if g not in self.glyphs:
1222 continue
1223 gl = self.glyphs[g]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001224 if hasattr(gl, "data"):
1225 for c in gl.getComponentNamesFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001226 if c not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001227 components.add(c)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001228 else:
1229 # TTX seems to expand gid0..3 always
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001230 if gl.isComposite():
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001231 for c in gl.components:
1232 if c.glyphName not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001233 components.add(c.glyphName)
1234 components = set(c for c in components if c not in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001235 if not components:
1236 break
1237 decompose = components
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001238 s.glyphs.update(components)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001239
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001240@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001241def prune_pre_subset(self, options):
1242 if options.notdef_glyph and not options.notdef_outline:
1243 g = self[self.glyphOrder[0]]
1244 # Yay, easy!
1245 g.__dict__.clear()
1246 g.data = ""
1247 return True
1248
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001249@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001250def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001251 self.glyphs = dict((g,v) for g,v in self.glyphs.iteritems() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001252 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001253 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001254 if hasattr(v, "data"):
1255 v.remapComponentsFast(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001256 else:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001257 pass # No need
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001258 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbodb69b6712013-08-29 18:17:31 -04001259 # Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset.
1260 return True
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001261
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001262@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001263def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001264 if not options.hinting:
1265 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001266 if hasattr(v, "data"):
1267 v.dropInstructionsFast()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001268 else:
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001269 v.program = ttLib.tables.ttProgram.Program()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001270 v.program.fromBytecode([])
1271 return True
Behdad Esfahboded98c612013-07-23 12:37:41 -04001272
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001273@_add_method(ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001274def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001275 cff = self.cff
Behdad Esfahbode0622072013-09-10 14:33:19 -04001276 # CFF table must have one font only
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001277 cff.fontNames = cff.fontNames[:1]
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001278
1279 if options.notdef_glyph and not options.notdef_outline:
1280 for fontname in cff.keys():
1281 font = cff[fontname]
1282 c,_ = font.CharStrings.getItemAndSelector('.notdef')
Behdad Esfahbod21582e92013-09-12 16:47:52 -04001283 # XXX we should preserve the glyph width
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001284 c.bytecode = '\x0e' # endchar
1285 c.program = None
1286
Behdad Esfahbod50f83ef2013-08-29 18:18:17 -04001287 return True # bool(cff.fontNames)
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001288
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001289@_add_method(ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001290def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001291 cff = self.cff
1292 for fontname in cff.keys():
1293 font = cff[fontname]
1294 cs = font.CharStrings
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001295
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001296 # Load all glyphs
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001297 for g in font.charset:
1298 if g not in s.glyphs: continue
1299 c,sel = cs.getItemAndSelector(g)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001300
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001301 if cs.charStringsAreIndexed:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001302 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001303 csi = cs.charStringsIndex
1304 csi.items = [csi.items[i] for i in indices]
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001305 csi.count = len(csi.items)
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001306 del csi.file, csi.offsets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001307 if hasattr(font, "FDSelect"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001308 sel = font.FDSelect
1309 sel.format = None
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001310 sel.gidArray = [sel.gidArray[i] for i in indices]
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001311 cs.charStrings = dict((g,indices.index(v))
1312 for g,v in cs.charStrings.iteritems()
1313 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001314 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001315 cs.charStrings = dict((g,v)
1316 for g,v in cs.charStrings.iteritems()
1317 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001318 font.charset = [g for g in font.charset if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001319 font.numGlyphs = len(font.charset)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001320
Behdad Esfahbod50f83ef2013-08-29 18:18:17 -04001321 return True # any(cff[fontname].numGlyphs for fontname in cff.keys())
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001322
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001323@_add_method(psCharStrings.T2CharString)
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001324def subset_subroutines(self, subrs, gsubrs):
1325 p = self.program
Behdad Esfahbode0622072013-09-10 14:33:19 -04001326 assert len(p)
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001327 for i in xrange(1, len(p)):
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001328 if p[i] == 'callsubr':
1329 assert type(p[i-1]) is int
1330 p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias
1331 elif p[i] == 'callgsubr':
1332 assert type(p[i-1]) is int
1333 p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias
1334
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001335@_add_method(psCharStrings.T2CharString)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001336def drop_hints(self):
1337 hints = self._hints
1338
1339 if hints.has_hint:
1340 self.program = self.program[hints.last_hint:]
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001341 if hasattr(self, 'width'):
1342 # Insert width back if needed
1343 if self.width != self.private.defaultWidthX:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001344 self.program.insert(0, self.width - self.private.nominalWidthX)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001345
1346 if hints.has_hintmask:
1347 i = 0
1348 p = self.program
1349 while i < len(p):
1350 if p[i] in ['hintmask', 'cntrmask']:
1351 assert i + 1 <= len(p)
1352 del p[i:i+2]
1353 continue
1354 i += 1
1355
1356 assert len(self.program)
1357
1358 del self._hints
1359
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001360class _MarkingT2Decompiler(psCharStrings.SimpleT2Decompiler):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001361
1362 def __init__(self, localSubrs, globalSubrs):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001363 psCharStrings.SimpleT2Decompiler.__init__(self,
1364 localSubrs,
1365 globalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001366 for subrs in [localSubrs, globalSubrs]:
1367 if subrs and not hasattr(subrs, "_used"):
1368 subrs._used = set()
1369
1370 def op_callsubr(self, index):
1371 self.localSubrs._used.add(self.operandStack[-1]+self.localBias)
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001372 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001373
1374 def op_callgsubr(self, index):
1375 self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias)
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001376 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001377
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001378class _DehintingT2Decompiler(psCharStrings.SimpleT2Decompiler):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001379
1380 class Hints:
1381 def __init__(self):
1382 # Whether calling this charstring produces any hint stems
1383 self.has_hint = False
1384 # Index to start at to drop all hints
1385 self.last_hint = 0
1386 # Index up to which we know more hints are possible. Only
1387 # relevant if status is 0 or 1.
1388 self.last_checked = 0
1389 # The status means:
1390 # 0: after dropping hints, this charstring is empty
1391 # 1: after dropping hints, there may be more hints continuing after this
1392 # 2: no more hints possible after this charstring
1393 self.status = 0
1394 # Has hintmask instructions; not recursive
1395 self.has_hintmask = False
1396 pass
1397
1398 def __init__(self, css, localSubrs, globalSubrs):
1399 self._css = css
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001400 psCharStrings.SimpleT2Decompiler.__init__(self,
1401 localSubrs,
1402 globalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001403
1404 def execute(self, charString):
1405 old_hints = charString._hints if hasattr(charString, '_hints') else None
1406 charString._hints = self.Hints()
1407
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001408 psCharStrings.SimpleT2Decompiler.execute(self, charString)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001409
1410 hints = charString._hints
1411
1412 if hints.has_hint or hints.has_hintmask:
1413 self._css.add(charString)
1414
1415 if hints.status != 2:
1416 # Check from last_check, make sure we didn't have any operators.
1417 for i in xrange(hints.last_checked, len(charString.program) - 1):
1418 if type(charString.program[i]) == str:
1419 hints.status = 2
1420 break;
1421 else:
1422 hints.status = 1 # There's *something* here
1423 hints.last_checked = len(charString.program)
1424
1425 if old_hints:
1426 assert hints.__dict__ == old_hints.__dict__
1427
1428 def op_callsubr(self, index):
1429 subr = self.localSubrs[self.operandStack[-1]+self.localBias]
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001430 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001431 self.processSubr(index, subr)
1432
1433 def op_callgsubr(self, index):
1434 subr = self.globalSubrs[self.operandStack[-1]+self.globalBias]
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001435 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001436 self.processSubr(index, subr)
1437
1438 def op_hstem(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001439 psCharStrings.SimpleT2Decompiler.op_hstem(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001440 self.processHint(index)
1441 def op_vstem(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001442 psCharStrings.SimpleT2Decompiler.op_vstem(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001443 self.processHint(index)
1444 def op_hstemhm(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001445 psCharStrings.SimpleT2Decompiler.op_hstemhm(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001446 self.processHint(index)
1447 def op_vstemhm(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001448 psCharStrings.SimpleT2Decompiler.op_vstemhm(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001449 self.processHint(index)
1450 def op_hintmask(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001451 psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001452 self.processHintmask(index)
1453 def op_cntrmask(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001454 psCharStrings.SimpleT2Decompiler.op_cntrmask(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001455 self.processHintmask(index)
1456
1457 def processHintmask(self, index):
1458 cs = self.callingStack[-1]
1459 hints = cs._hints
1460 hints.has_hintmask = True
1461 if hints.status != 2 and hints.has_hint:
1462 # Check from last_check, see if we may be an implicit vstem
Behdad Esfahbod84763142013-09-10 19:00:48 -04001463 for i in xrange(hints.last_checked, index - 1):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001464 if type(cs.program[i]) == str:
Behdad Esfahbod84763142013-09-10 19:00:48 -04001465 hints.status = 2
Behdad Esfahbode0622072013-09-10 14:33:19 -04001466 break;
Behdad Esfahbod84763142013-09-10 19:00:48 -04001467 if hints.status != 2:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001468 # We are an implicit vstem
1469 hints.last_hint = index + 1
Behdad Esfahbod84763142013-09-10 19:00:48 -04001470 hints.status = 0
1471 hints.last_checked = index + 1
Behdad Esfahbode0622072013-09-10 14:33:19 -04001472
1473 def processHint(self, index):
1474 cs = self.callingStack[-1]
1475 hints = cs._hints
1476 hints.has_hint = True
1477 hints.last_hint = index
1478 hints.last_checked = index
1479
1480 def processSubr(self, index, subr):
1481 cs = self.callingStack[-1]
1482 hints = cs._hints
1483 subr_hints = subr._hints
1484
1485 if subr_hints.has_hint:
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001486 if hints.status != 2:
1487 hints.has_hint = True
Behdad Esfahbod99536852013-09-12 00:23:11 -04001488 hints.last_checked = index
1489 hints.status = subr_hints.status
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001490 # Decide where to chop off from
1491 if subr_hints.status == 0:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001492 hints.last_hint = index
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001493 else:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001494 hints.last_hint = index - 2 # Leave the subr call in
Behdad Esfahbode0622072013-09-10 14:33:19 -04001495 else:
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001496 # In my understanding, this is a font bug. Ie. it has hint stems
1497 # *after* path construction. I've seen this in widespread fonts.
1498 # Best to ignore the hints I suppose...
1499 pass
1500 #assert 0
Behdad Esfahbode0622072013-09-10 14:33:19 -04001501 else:
1502 hints.status = max(hints.status, subr_hints.status)
1503 if hints.status != 2:
1504 # Check from last_check, make sure we didn't have
1505 # any operators.
1506 for i in xrange(hints.last_checked, index - 1):
1507 if type(cs.program[i]) == str:
1508 hints.status = 2
1509 break;
1510 hints.last_checked = index
1511
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001512@_add_method(ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001513def prune_post_subset(self, options):
1514 cff = self.cff
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001515 for fontname in cff.keys():
1516 font = cff[fontname]
1517 cs = font.CharStrings
1518
Behdad Esfahbode0622072013-09-10 14:33:19 -04001519
1520 #
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001521 # Drop unused FontDictionaries
Behdad Esfahbode0622072013-09-10 14:33:19 -04001522 #
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001523 if hasattr(font, "FDSelect"):
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001524 sel = font.FDSelect
1525 indices = _uniq_sort(sel.gidArray)
1526 sel.gidArray = [indices.index (ss) for ss in sel.gidArray]
1527 arr = font.FDArray
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001528 arr.items = [arr[i] for i in indices]
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001529 arr.count = len(arr.items)
1530 del arr.file, arr.offsets
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001531
Behdad Esfahbode0622072013-09-10 14:33:19 -04001532
1533 #
1534 # Drop hints if not needed
1535 #
1536 if not options.hinting:
1537
1538 #
1539 # This can be tricky, but doesn't have to. What we do is:
1540 #
1541 # - Run all used glyph charstrings and recurse into subroutines,
1542 # - For each charstring (including subroutines), if it has any
1543 # of the hint stem operators, we mark it as such. Upon returning,
1544 # for each charstring we note all the subroutine calls it makes
1545 # that (recursively) contain a stem,
1546 # - Dropping hinting then consists of the following two ops:
1547 # * Drop the piece of the program in each charstring before the
1548 # last call to a stem op or a stem-calling subroutine,
1549 # * Drop all hintmask operations.
1550 # - It's trickier... A hintmask right after hints and a few numbers
1551 # will act as an implicit vstemhm. As such, we track whether
1552 # we have seen any non-hint operators so far and do the right
1553 # thing, recursively... Good luck understanding that :(
1554 #
1555 css = set()
1556 for g in font.charset:
1557 c,sel = cs.getItemAndSelector(g)
1558 # Make sure it's decompiled. We want our "decompiler" to walk
1559 # the program, not the bytecode.
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001560 c.draw(basePen.NullPen())
Behdad Esfahbode0622072013-09-10 14:33:19 -04001561 subrs = getattr(c.private, "Subrs", [])
1562 decompiler = _DehintingT2Decompiler(css, subrs, c.globalSubrs)
1563 decompiler.execute(c)
1564 for charstring in css:
1565 charstring.drop_hints()
1566
1567
1568 #
1569 # Renumber subroutines to remove unused ones
1570 #
1571
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001572 # Mark all used subroutines
1573 for g in font.charset:
1574 c,sel = cs.getItemAndSelector(g)
1575 subrs = getattr(c.private, "Subrs", [])
1576 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs)
1577 decompiler.execute(c)
1578
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001579 all_subrs = [font.GlobalSubrs]
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001580 if hasattr(font, 'FDSelect'):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001581 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs') and fd.Private.Subrs)
1582 elif hasattr(font.Private, 'Subrs') and font.Private.Subrs:
Behdad Esfahbodcbcaccf2013-08-30 16:21:38 -04001583 all_subrs.append(font.Private.Subrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001584
1585 subrs = set(subrs) # Remove duplicates
1586
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001587 # Prepare
1588 for subrs in all_subrs:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001589 if not hasattr(subrs, '_used'):
1590 subrs._used = set()
1591 subrs._used = _uniq_sort(subrs._used)
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001592 subrs._old_bias = psCharStrings.calcSubrBias(subrs)
1593 subrs._new_bias = psCharStrings.calcSubrBias(subrs._used)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001594
Behdad Esfahboded107712013-08-14 19:54:13 -04001595 # Renumber glyph charstrings
1596 for g in font.charset:
1597 c,sel = cs.getItemAndSelector(g)
1598 subrs = getattr(c.private, "Subrs", [])
1599 c.subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001600
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001601 # Renumber subroutines themselves
1602 for subrs in all_subrs:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001603
1604 if subrs == font.GlobalSubrs:
1605 if not hasattr(font, 'FDSelect') and hasattr(font.Private, 'Subrs'):
1606 local_subrs = font.Private.Subrs
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001607 else:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001608 local_subrs = []
1609 else:
1610 local_subrs = subrs
1611
1612 subrs.items = [subrs.items[i] for i in subrs._used]
1613 subrs.count = len(subrs.items)
1614 del subrs.file
1615 if hasattr(subrs, 'offsets'):
1616 del subrs.offsets
1617
1618 for i in xrange (subrs.count):
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001619 subrs[i].subset_subroutines (local_subrs, font.GlobalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001620
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001621 # Cleanup
1622 for subrs in all_subrs:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001623 del subrs._used, subrs._old_bias, subrs._new_bias
1624
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001625 return True
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001626
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001627@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001628def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001629 tables = [t for t in self.tables
1630 if t.platformID == 3 and t.platEncID in [1, 10]]
1631 for u in s.unicodes_requested:
1632 found = False
1633 for table in tables:
1634 if u in table.cmap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001635 s.glyphs.add(table.cmap[u])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001636 found = True
1637 break
1638 if not found:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001639 s.log("No glyph for Unicode value %s; skipping." % u)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001640
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001641@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001642def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001643 if not options.legacy_cmap:
1644 # Drop non-Unicode / non-Symbol cmaps
1645 self.tables = [t for t in self.tables
1646 if t.platformID == 3 and t.platEncID in [0, 1, 10]]
1647 if not options.symbol_cmap:
1648 self.tables = [t for t in self.tables
1649 if t.platformID == 3 and t.platEncID in [1, 10]]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001650 # TODO(behdad) Only keep one subtable?
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001651 # For now, drop format=0 which can't be subset_glyphs easily?
1652 self.tables = [t for t in self.tables if t.format != 0]
Behdad Esfahbodfd92d4c2013-09-19 19:43:09 -04001653 self.numSubTables = len(self.tables)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001654 return bool(self.tables)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001655
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001656@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001657def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001658 s.glyphs = s.glyphs_cmaped
1659 for t in self.tables:
1660 # For reasons I don't understand I need this here
1661 # to force decompilation of the cmap format 14.
1662 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001663 getattr(t, "asdf")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001664 except AttributeError:
1665 pass
1666 if t.format == 14:
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001667 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None).
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001668 t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs])
1669 for v,l in t.uvsDict.iteritems())
1670 t.uvsDict = dict((v,l) for v,l in t.uvsDict.iteritems() if l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001671 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001672 t.cmap = dict((u,g) for u,g in t.cmap.iteritems()
1673 if g in s.glyphs_requested or u in s.unicodes_requested)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001674 self.tables = [t for t in self.tables
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001675 if (t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbodfd92d4c2013-09-19 19:43:09 -04001676 self.numSubTables = len(self.tables)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001677 # TODO(behdad) Convert formats when needed.
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001678 # In particular, if we have a format=12 without non-BMP
1679 # characters, either drop format=12 one or convert it
1680 # to format=4 if there's not one.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001681 return bool(self.tables)
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001682
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001683@_add_method(ttLib.getTableClass('name'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001684def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001685 if '*' not in options.name_IDs:
1686 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1687 if not options.name_legacy:
1688 self.names = [n for n in self.names
1689 if n.platformID == 3 and n.platEncID == 1]
1690 if '*' not in options.name_languages:
1691 self.names = [n for n in self.names if n.langID in options.name_languages]
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001692 return True # Retain even if empty
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001693
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001694
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001695# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange?
1696# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
Behdad Esfahbod852e8a52013-08-29 18:19:22 -04001697# TODO(behdad) Drop empty GSUB/GPOS, and GDEF if no GSUB/GPOS left
1698# TODO(behdad) Drop GDEF subitems if unused by lookups
Behdad Esfahbod10195332013-08-14 19:55:24 -04001699# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001700# TODO(behdad) Text direction considerations.
1701# TODO(behdad) Text script / language considerations.
Behdad Esfahbod21582e92013-09-12 16:47:52 -04001702# TODO(behdad) Option to drop hmtx for CFF?
Behdad Esfahbod56ebd042013-07-22 13:02:24 -04001703
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001704
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001705class Options(object):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001706
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001707 class UnknownOptionError(Exception):
1708 pass
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001709
Behdad Esfahboda17743f2013-08-28 17:14:53 -04001710 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ',
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001711 'PCLT', 'LTSH']
1712 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
1713 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
1714 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1715 'loca', 'name', 'cvt ', 'fpgm', 'prep']
1716 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001717
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001718 # Based on HarfBuzz shapers
1719 _layout_features_groups = {
1720 # Default shaper
1721 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1722 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
1723 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
1724 'ltr': ['ltra', 'ltrm'],
1725 'rtl': ['rtla', 'rtlm'],
1726 # Complex shapers
1727 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1728 'cswh', 'mset'],
1729 'hangul': ['ljmo', 'vjmo', 'tjmo'],
1730 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'],
1731 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1732 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1733 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
1734 }
1735 _layout_features_default = _uniq_sort(sum(
1736 _layout_features_groups.itervalues(), []))
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001737
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001738 drop_tables = _drop_tables_default
1739 no_subset_tables = _no_subset_tables_default
1740 hinting_tables = _hinting_tables_default
1741 layout_features = _layout_features_default
1742 hinting = False
1743 glyph_names = False
1744 legacy_cmap = False
1745 symbol_cmap = False
1746 name_IDs = [1, 2] # Family and Style
1747 name_legacy = False
1748 name_languages = [0x0409] # English
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001749 notdef_glyph = True # gid0 for TrueType / .notdef for CFF
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001750 notdef_outline = False # No need for notdef to have an outline really
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001751 recommended_glyphs = False # gid1, gid2, gid3 for TrueType
Behdad Esfahbode911de12013-08-16 12:42:34 -04001752 recalc_bounds = False # Recalculate font bounding boxes
Behdad Esfahbod03d78da2013-08-29 16:42:00 -04001753 canonical_order = False # Order tables as recommended
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001754 flavor = None # May be 'woff'
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001755
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001756 def __init__(self, **kwargs):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001757
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001758 self.set(**kwargs)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001759
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001760 def set(self, **kwargs):
1761 for k,v in kwargs.iteritems():
1762 if not hasattr(self, k):
Behdad Esfahbodac10d812013-09-03 18:29:58 -04001763 raise self.UnknownOptionError("Unknown option '%s'" % k)
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001764 setattr(self, k, v)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001765
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001766 def parse_opts(self, argv, ignore_unknown=False):
1767 ret = []
1768 opts = {}
1769 for a in argv:
1770 orig_a = a
1771 if not a.startswith('--'):
1772 ret.append(a)
1773 continue
1774 a = a[2:]
1775 i = a.find('=')
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001776 op = '='
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001777 if i == -1:
1778 if a.startswith("no-"):
1779 k = a[3:]
1780 v = False
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001781 else:
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001782 k = a
1783 v = True
1784 else:
1785 k = a[:i]
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001786 if k[-1] in "-+":
1787 op = k[-1]+'=' # Ops is '-=' or '+=' now.
1788 k = k[:-1]
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001789 v = a[i+1:]
1790 k = k.replace('-', '_')
1791 if not hasattr(self, k):
1792 if ignore_unknown == True or k in ignore_unknown:
1793 ret.append(orig_a)
1794 continue
1795 else:
1796 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001797
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001798 ov = getattr(self, k)
1799 if isinstance(ov, bool):
1800 v = bool(v)
1801 elif isinstance(ov, int):
1802 v = int(v)
1803 elif isinstance(ov, list):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001804 vv = v.split(',')
1805 if vv == ['']:
1806 vv = []
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001807 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 -04001808 if op == '=':
1809 v = vv
1810 elif op == '+=':
1811 v = ov
1812 v.extend(vv)
1813 elif op == '-=':
1814 v = ov
1815 for x in vv:
1816 if x in v:
1817 v.remove(x)
1818 else:
1819 assert 0
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001820
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001821 opts[k] = v
1822 self.set(**opts)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001823
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001824 return ret
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001825
1826
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001827class Subsetter(object):
1828
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001829 def __init__(self, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001830
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001831 if not log:
1832 log = Logger()
1833 if not options:
1834 options = Options()
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001835
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001836 self.options = options
1837 self.log = log
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001838 self.unicodes_requested = set()
1839 self.glyphs_requested = set()
1840 self.glyphs = set()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001841
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001842 def populate(self, glyphs=[], unicodes=[], text=""):
1843 self.unicodes_requested.update(unicodes)
1844 if isinstance(text, str):
1845 text = text.decode("utf8")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001846 for u in text:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001847 self.unicodes_requested.add(ord(u))
1848 self.glyphs_requested.update(glyphs)
1849 self.glyphs.update(glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001850
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001851 def _prune_pre_subset(self, font):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001852
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001853 for tag in font.keys():
1854 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001855
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001856 if(tag in self.options.drop_tables or
1857 (tag in self.options.hinting_tables and not self.options.hinting)):
1858 self.log(tag, "dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001859 del font[tag]
1860 continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001861
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001862 clazz = ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001863
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001864 if hasattr(clazz, 'prune_pre_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001865 table = font[tag]
Behdad Esfahbod010c5f92013-09-10 20:54:46 -04001866 self.log.lapse("load '%s'" % tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001867 retain = table.prune_pre_subset(self.options)
1868 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001869 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001870 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001871 del font[tag]
1872 continue
1873 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001874 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001875
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001876 def _closure_glyphs(self, font):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001877
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001878 self.glyphs = self.glyphs_requested.copy()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001879
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001880 if 'cmap' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001881 font['cmap'].closure_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001882 self.glyphs_cmaped = self.glyphs
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001883
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001884 if self.options.notdef_glyph:
1885 if 'glyf' in font:
1886 self.glyphs.add(font.getGlyphName(0))
1887 self.log("Added gid0 to subset")
1888 else:
1889 self.glyphs.add('.notdef')
1890 self.log("Added .notdef to subset")
1891 if self.options.recommended_glyphs:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001892 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001893 for i in range(4):
1894 self.glyphs.add(font.getGlyphName(i))
1895 self.log("Added first four glyphs to subset")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001896
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001897 if 'GSUB' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001898 self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1899 len(self.glyphs))
1900 self.log.glyphs(self.glyphs, font=font)
1901 font['GSUB'].closure_glyphs(self)
1902 self.log("Closed glyph list over 'GSUB': %d glyphs after" %
1903 len(self.glyphs))
1904 self.log.glyphs(self.glyphs, font=font)
1905 self.log.lapse("close glyph list over 'GSUB'")
1906 self.glyphs_gsubed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001907
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001908 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001909 self.log("Closing glyph list over 'glyf': %d glyphs before" %
1910 len(self.glyphs))
1911 self.log.glyphs(self.glyphs, font=font)
1912 font['glyf'].closure_glyphs(self)
1913 self.log("Closed glyph list over 'glyf': %d glyphs after" %
1914 len(self.glyphs))
1915 self.log.glyphs(self.glyphs, font=font)
1916 self.log.lapse("close glyph list over 'glyf'")
1917 self.glyphs_glyfed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001918
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001919 self.glyphs_all = self.glyphs.copy()
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001920
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001921 self.log("Retaining %d glyphs: " % len(self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001922
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001923 def _subset_glyphs(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001924 for tag in font.keys():
1925 if tag == 'GlyphOrder': continue
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001926 clazz = ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001927
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001928 if tag in self.options.no_subset_tables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001929 self.log(tag, "subsetting not needed")
1930 elif hasattr(clazz, 'subset_glyphs'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001931 table = font[tag]
1932 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001933 retain = table.subset_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001934 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001935 self.log.lapse("subset '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001936 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001937 self.log(tag, "subsetted to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001938 del font[tag]
1939 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001940 self.log(tag, "subsetted")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001941 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001942 self.log(tag, "NOT subset; don't know how to subset; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001943 del font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001944
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001945 glyphOrder = font.getGlyphOrder()
1946 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001947 font.setGlyphOrder(glyphOrder)
1948 font._buildReverseGlyphOrderDict()
1949 self.log.lapse("subset GlyphOrder")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001950
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001951 def _prune_post_subset(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001952 for tag in font.keys():
1953 if tag == 'GlyphOrder': continue
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001954 clazz = ttLib.getTableClass(tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001955 if hasattr(clazz, 'prune_post_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001956 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001957 retain = table.prune_post_subset(self.options)
1958 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001959 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001960 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001961 del font[tag]
1962 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001963 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001964
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001965 def subset(self, font):
Behdad Esfahbod756af492013-08-01 12:05:26 -04001966
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001967 self._prune_pre_subset(font)
1968 self._closure_glyphs(font)
1969 self._subset_glyphs(font)
1970 self._prune_post_subset(font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001971
Behdad Esfahbod756af492013-08-01 12:05:26 -04001972
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04001973class Logger(object):
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001974
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001975 def __init__(self, verbose=False, xml=False, timing=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001976 self.verbose = verbose
1977 self.xml = xml
1978 self.timing = timing
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001979 self.last_time = self.start_time = time.time()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001980
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001981 def parse_opts(self, argv):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001982 argv = argv[:]
1983 for v in ['verbose', 'xml', 'timing']:
1984 if "--"+v in argv:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001985 setattr(self, v, True)
1986 argv.remove("--"+v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001987 return argv
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001988
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001989 def __call__(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001990 if not self.verbose:
1991 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001992 print ' '.join(str(x) for x in things)
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001993
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001994 def lapse(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001995 if not self.timing:
1996 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001997 new_time = time.time()
1998 print "Took %0.3fs to %s" %(new_time - self.last_time,
1999 ' '.join(str(x) for x in things))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002000 self.last_time = new_time
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002001
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04002002 def glyphs(self, glyphs, font=None):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002003 self("Names: ", sorted(glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002004 if font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002005 reverseGlyphMap = font.getReverseGlyphMap()
2006 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04002007
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002008 def font(self, font, file=sys.stdout):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002009 if not self.xml:
2010 return
Behdad Esfahbod28fc4982013-09-18 19:01:16 -04002011 from fontTools.misc import xmlWriter
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002012 writer = xmlWriter.XMLWriter(file)
Behdad Esfahbod45a84602013-08-19 14:44:49 -04002013 font.disassembleInstructions = False # Work around ttLib bug
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002014 for tag in font.keys():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002015 writer.begintag(tag)
2016 writer.newline()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002017 font[tag].toXML(writer, font)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002018 writer.endtag(tag)
2019 writer.newline()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002020
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002021
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002022def load_font(fontFile,
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04002023 options,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002024 checkChecksums=False,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002025 dontLoadGlyphNames=False):
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002026
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04002027 font = ttLib.TTFont(fontFile,
Behdad Esfahbod45a84602013-08-19 14:44:49 -04002028 checkChecksums=checkChecksums,
2029 recalcBBoxes=options.recalc_bounds)
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002030
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002031 # Hack:
2032 #
2033 # If we don't need glyph names, change 'post' class to not try to
2034 # load them. It avoid lots of headache with broken fonts as well
2035 # as loading time.
2036 #
2037 # Ideally ttLib should provide a way to ask it to skip loading
2038 # glyph names. But it currently doesn't provide such a thing.
2039 #
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002040 if dontLoadGlyphNames:
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04002041 post = ttLib.getTableClass('post')
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002042 saved = post.decode_format_2_0
2043 post.decode_format_2_0 = post.decode_format_3_0
2044 f = font['post']
2045 if f.formatType == 2.0:
2046 f.formatType = 3.0
2047 post.decode_format_2_0 = saved
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002048
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002049 return font
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002050
Behdad Esfahbode911de12013-08-16 12:42:34 -04002051def save_font(font, outfile, options):
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04002052 if options.flavor and not hasattr(font, 'flavor'):
2053 raise Exception("fonttools version does not support flavors.")
2054 font.flavor = options.flavor
Behdad Esfahbode911de12013-08-16 12:42:34 -04002055 font.save(outfile, reorderTables=options.canonical_order)
Behdad Esfahbod41de4cc2013-08-15 12:09:55 -04002056
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002057def main(args):
Behdad Esfahbod610b0552013-07-23 14:52:18 -04002058
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002059 log = Logger()
2060 args = log.parse_opts(args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04002061
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04002062 options = Options()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002063 args = options.parse_opts(args, ignore_unknown=['text'])
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04002064
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002065 if len(args) < 2:
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002066 print >>sys.stderr, "usage: pyftsubset font-file glyph... [--text=ABC]... [--option=value]..."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002067 sys.exit(1)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002068
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002069 fontfile = args[0]
2070 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002071
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002072 dontLoadGlyphNames =(not options.glyph_names and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002073 all(any(g.startswith(p)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002074 for p in ['gid', 'glyph', 'uni', 'U+'])
2075 for g in args))
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002076
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04002077 font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002078 subsetter = Subsetter(options=options, log=log)
2079 log.lapse("load font")
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002080
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002081 names = font.getGlyphNames()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002082 log.lapse("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04002083
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002084 glyphs = []
2085 unicodes = []
2086 text = ""
2087 for g in args:
Behdad Esfahbod2be33d92013-09-10 19:28:59 -04002088 if g == '*':
2089 glyphs.extend(font.getGlyphOrder())
2090 continue
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002091 if g in names:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002092 glyphs.append(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002093 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002094 if g.startswith('--text='):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002095 text += g[7:]
2096 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002097 if g.startswith('uni') or g.startswith('U+'):
2098 if g.startswith('uni') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002099 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002100 elif g.startswith('U+') and len(g) > 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002101 g = g[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002102 u = int(g, 16)
2103 unicodes.append(u)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002104 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002105 if g.startswith('gid') or g.startswith('glyph'):
2106 if g.startswith('gid') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002107 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002108 elif g.startswith('glyph') and len(g) > 5:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002109 g = g[5:]
2110 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002111 glyphs.append(font.getGlyphName(int(g), requireReal=1))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002112 except ValueError:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002113 raise Exception("Invalid glyph identifier: %s" % g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002114 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002115 raise Exception("Invalid glyph identifier: %s" % g)
2116 log.lapse("compile glyph list")
2117 log("Unicodes:", unicodes)
2118 log("Glyphs:", glyphs)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04002119
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002120 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
2121 subsetter.subset(font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04002122
Behdad Esfahbod34426c12013-08-14 18:30:09 -04002123 outfile = fontfile + '.subset'
2124
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04002125 save_font (font, outfile, options)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002126 log.lapse("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04002127
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002128 log.last_time = log.start_time
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002129 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04002130
Behdad Esfahbod34426c12013-08-14 18:30:09 -04002131 if log.verbose:
2132 import os
2133 log("Input font: %d bytes" % os.path.getsize(fontfile))
2134 log("Subset font: %d bytes" % os.path.getsize(outfile))
2135
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002136 log.font(font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04002137
Behdad Esfahbodc56bf482013-08-13 20:13:33 -04002138 font.close()
2139
Behdad Esfahbod39a39ac2013-08-22 18:10:17 -04002140
2141__all__ = [
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002142 'Options',
2143 'Subsetter',
2144 'Logger',
2145 'load_font',
2146 'save_font',
2147 'main'
Behdad Esfahbod39a39ac2013-08-22 18:10:17 -04002148]
2149
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04002150if __name__ == '__main__':
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002151 main(sys.argv[1:])