blob: 62283de0b7954b62cc0d4e1c7e1d9c994cf3cc0b [file] [log] [blame]
Behdad Esfahbod54660612013-07-21 18:16:55 -04001#!/usr/bin/python
Behdad Esfahboddb6d2e92013-08-13 12:42:12 -04002#
Behdad Esfahbod54660612013-07-21 18:16:55 -04003# Python OpenType Layout Subsetter
Behdad Esfahboddb6d2e92013-08-13 12:42:12 -04004# Later grown into a full OpenType subsetter...
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04005#
6# Copyright 2013 Google, Inc. All Rights Reserved.
7#
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04008# Licensed under the Apache License, Version 2.0(the "License");
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04009# you may not use this file except in compliance with the License.
10# You may obtain a copy of the License at
11#
12# http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS,
16# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17# See the License for the specific language governing permissions and
18# limitations under the License.
19#
20# Google Author(s): Behdad Esfahbod
21#
Behdad Esfahbod54660612013-07-21 18:16:55 -040022
Behdad Esfahbodfa3bc5e2013-07-24 14:37:58 -040023# Try running on PyPy
24try:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040025 import numpypy
Behdad Esfahbodfa3bc5e2013-07-24 14:37:58 -040026except ImportError:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040027 pass
Behdad Esfahbodfa3bc5e2013-07-24 14:37:58 -040028
Behdad Esfahbod54660612013-07-21 18:16:55 -040029import fontTools.ttx
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -040030import struct
Behdad Esfahbod54660612013-07-21 18:16:55 -040031
Behdad Esfahbod54660612013-07-21 18:16:55 -040032
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040033def add_method(*clazzes):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040034 """A decorator-returning function to add a new method to one or
35 more classes."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040036 def wrapper(method):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040037 for clazz in clazzes:
38 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040039 setattr(clazz, method.func_name, method)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040040 return None
41 return wrapper
Behdad Esfahbod54660612013-07-21 18:16:55 -040042
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040043def unique_sorted(l):
44 return sorted(set(l))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040045
46
Behdad Esfahbod54660612013-07-21 18:16:55 -040047@add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040048def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040049 "Returns ascending list of matching coverage values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040050 return [i for(i,g) in enumerate(self.glyphs) if g in glyphs]
Behdad Esfahbod610b0552013-07-23 14:52:18 -040051
52@add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040053def intersect_glyphs(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040054 "Returns set of intersecting glyphs."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040055 return set(g for g in self.glyphs if g in glyphs)
Behdad Esfahbod849d25c2013-08-12 19:24:24 -040056
57@add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040058def subset(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040059 "Returns ascending list of remaining coverage values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040060 indices = self.intersect(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040061 self.glyphs = [g for g in self.glyphs if g in glyphs]
62 return indices
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040063
Behdad Esfahbod14374262013-08-08 22:26:49 -040064@add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040065def remap(self, coverage_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040066 "Remaps coverage."
67 self.glyphs = [self.glyphs[i] for i in coverage_map]
Behdad Esfahbod14374262013-08-08 22:26:49 -040068
Behdad Esfahbod54660612013-07-21 18:16:55 -040069@add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040070def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040071 "Returns ascending list of matching class values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040072 return unique_sorted(
73 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040074 [v for g,v in self.classDefs.iteritems() if g in glyphs])
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040075
76@add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040077def intersect_class(self, glyphs, klass):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040078 "Returns set of glyphs matching class."
79 if klass == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040080 return set(g for g in glyphs if g not in self.classDefs)
81 return set(g for g,v in self.classDefs.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040082 if v == klass and g in glyphs)
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040083
84@add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040085def subset(self, glyphs, remap=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040086 "Returns ascending list of remaining classes."
87 self.classDefs = {g:v for g,v in self.classDefs.iteritems() if g in glyphs}
88 # Note: while class 0 has the special meaning of "not matched",
89 # if no glyph will ever /not match/, we can optimize class 0 out too.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040090 indices = unique_sorted(
91 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040092 self.classDefs.itervalues())
93 if remap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040094 self.remap(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040095 return indices
Behdad Esfahbod4aa6ce32013-07-22 12:15:36 -040096
97@add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040098def remap(self, class_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040099 "Remaps classes."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400100 self.classDefs = {g:class_map.index(v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400101 for g,v in self.classDefs.iteritems()}
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400102
Behdad Esfahbod54660612013-07-21 18:16:55 -0400103@add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400104def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400105 if cur_glyphs == None: cur_glyphs = s.glyphs
106 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400107 s.glyphs.update(v for g,v in self.mapping.iteritems() if g in cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400108 else:
109 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400110
111@add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400112def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400113 if self.Format in [1, 2]:
114 self.mapping = {g:v for g,v in self.mapping.iteritems()
115 if g in s.glyphs and v in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400116 return bool(self.mapping)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400117 else:
118 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400119
120@add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400121def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400122 if cur_glyphs == None: cur_glyphs = s.glyphs
123 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400124 indices = self.Coverage.intersect(cur_glyphs)
125 s.glyphs.update(*(self.Sequence[i].Substitute for i in indices))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400126 else:
127 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400128
129@add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400130def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400131 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400132 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400133 self.Sequence = [self.Sequence[i] for i in indices]
134 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400135 indices = [i for i,seq in enumerate(self.Sequence)
136 if all(sub in s.glyphs for sub in seq.Substitute)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400137 self.Sequence = [self.Sequence[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400138 self.Coverage.remap(indices)
139 self.SequenceCount = len(self.Sequence)
140 return bool(self.SequenceCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400141 else:
142 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400143
144@add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400145def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400146 if cur_glyphs == None: cur_glyphs = s.glyphs
147 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400148 s.glyphs.update(*(vlist for g,vlist in self.alternates.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400149 if g in cur_glyphs))
150 else:
151 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400152
153@add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400154def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400155 if self.Format == 1:
156 self.alternates = {g:vlist for g,vlist in self.alternates.iteritems()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400157 if g in s.glyphs and all(v in s.glyphs for v in vlist)}
158 return bool(self.alternates)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400159 else:
160 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400161
162@add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400163def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400164 if cur_glyphs == None: cur_glyphs = s.glyphs
165 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400166 s.glyphs.update(*([seq.LigGlyph for seq in seqs
167 if all(c in s.glyphs for c in seq.Component)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400168 for g,seqs in self.ligatures.iteritems()
169 if g in cur_glyphs))
170 else:
171 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400172
173@add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400174def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400175 if self.Format == 1:
176 self.ligatures = {g:v for g,v in self.ligatures.iteritems()
177 if g in s.glyphs}
178 self.ligatures = {g:[seq for seq in seqs
179 if seq.LigGlyph in s.glyphs and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400180 all(c in s.glyphs for c in seq.Component)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400181 for g,seqs in self.ligatures.iteritems()}
182 self.ligatures = {g:v for g,v in self.ligatures.iteritems() if v}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400183 return bool(self.ligatures)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400184 else:
185 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400186
Behdad Esfahbod54660612013-07-21 18:16:55 -0400187@add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400188def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400189 if cur_glyphs == None: cur_glyphs = s.glyphs
190 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400191 indices = self.Coverage.intersect(cur_glyphs)
192 if(not indices or
193 not all(c.intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400194 for c in self.LookAheadCoverage + self.BacktrackCoverage)):
195 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400196 s.glyphs.update(self.Substitute[i] for i in indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400197 else:
198 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400199
200@add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400201def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400202 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400203 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400204 self.Substitute = [self.Substitute[i] for i in indices]
205 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400206 indices = [i for i,sub in enumerate(self.Substitute)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400207 if sub in s.glyphs]
208 self.Substitute = [self.Substitute[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400209 self.Coverage.remap(indices)
210 self.GlyphCount = len(self.Substitute)
211 return bool(self.GlyphCount and
212 all(c.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400213 for c in self.LookAheadCoverage+self.BacktrackCoverage))
214 else:
215 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400216
217@add_method(fontTools.ttLib.tables.otTables.SinglePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400218def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400219 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400220 return len(self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400221 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400222 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400223 self.Value = [self.Value[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400224 self.ValueCount = len(self.Value)
225 return bool(self.ValueCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400226 else:
227 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400228
229@add_method(fontTools.ttLib.tables.otTables.PairPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400230def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400231 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400232 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400233 self.PairSet = [self.PairSet[i] for i in indices]
234 for p in self.PairSet:
235 p.PairValueRecord = [r for r in p.PairValueRecord
236 if r.SecondGlyph in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400237 p.PairValueCount = len(p.PairValueRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400238 self.PairSet = [p for p in self.PairSet if p.PairValueCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400239 self.PairSetCount = len(self.PairSet)
240 return bool(self.PairSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400241 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400242 class1_map = self.ClassDef1.subset(s.glyphs, remap=True)
243 class2_map = self.ClassDef2.subset(s.glyphs, remap=True)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400244 self.Class1Record = [self.Class1Record[i] for i in class1_map]
245 for c in self.Class1Record:
246 c.Class2Record = [c.Class2Record[i] for i in class2_map]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400247 self.Class1Count = len(class1_map)
248 self.Class2Count = len(class2_map)
249 return bool(self.Class1Count and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400250 self.Class2Count and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400251 self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400252 else:
253 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400254
255@add_method(fontTools.ttLib.tables.otTables.CursivePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400256def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400257 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400258 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400259 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400260 self.EntryExitCount = len(self.EntryExitRecord)
261 return bool(self.EntryExitCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400262 else:
263 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400264
265@add_method(fontTools.ttLib.tables.otTables.MarkBasePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400266def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400267 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400268 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400269 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
270 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400271 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
272 base_indices = self.BaseCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400273 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i]
274 for i in base_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400275 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400276 # Prune empty classes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400277 class_indices = unique_sorted(v.Class for v in self.MarkArray.MarkRecord)
278 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400279 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400280 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400281 for b in self.BaseArray.BaseRecord:
282 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400283 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400284 self.MarkArray.MarkCount and
285 self.BaseArray.BaseCount)
286 else:
287 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400288
289@add_method(fontTools.ttLib.tables.otTables.MarkLigPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400290def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400291 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400292 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400293 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
294 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400295 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
296 ligature_indices = self.LigatureCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400297 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i]
298 for i in ligature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400299 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400300 # Prune empty classes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400301 class_indices = unique_sorted(v.Class for v in self.MarkArray.MarkRecord)
302 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400303 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400304 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400305 for l in self.LigatureArray.LigatureAttach:
306 for c in l.ComponentRecord:
307 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400308 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400309 self.MarkArray.MarkCount and
310 self.LigatureArray.LigatureCount)
311 else:
312 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400313
314@add_method(fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400315def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400316 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400317 mark1_indices = self.Mark1Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400318 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i]
319 for i in mark1_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400320 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
321 mark2_indices = self.Mark2Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400322 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i]
323 for i in mark2_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400324 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400325 # Prune empty classes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400326 class_indices = unique_sorted(v.Class for v in self.Mark1Array.MarkRecord)
327 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400328 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400329 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400330 for b in self.Mark2Array.Mark2Record:
331 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400332 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400333 self.Mark1Array.MarkCount and
334 self.Mark2Array.MarkCount)
335 else:
336 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400337
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400338@add_method(fontTools.ttLib.tables.otTables.SingleSubst,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400339 fontTools.ttLib.tables.otTables.MultipleSubst,
340 fontTools.ttLib.tables.otTables.AlternateSubst,
341 fontTools.ttLib.tables.otTables.LigatureSubst,
342 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
343 fontTools.ttLib.tables.otTables.SinglePos,
344 fontTools.ttLib.tables.otTables.PairPos,
345 fontTools.ttLib.tables.otTables.CursivePos,
346 fontTools.ttLib.tables.otTables.MarkBasePos,
347 fontTools.ttLib.tables.otTables.MarkLigPos,
348 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400349def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400350 pass
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400351
352@add_method(fontTools.ttLib.tables.otTables.SingleSubst,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400353 fontTools.ttLib.tables.otTables.MultipleSubst,
354 fontTools.ttLib.tables.otTables.AlternateSubst,
355 fontTools.ttLib.tables.otTables.LigatureSubst,
356 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
357 fontTools.ttLib.tables.otTables.SinglePos,
358 fontTools.ttLib.tables.otTables.PairPos,
359 fontTools.ttLib.tables.otTables.CursivePos,
360 fontTools.ttLib.tables.otTables.MarkBasePos,
361 fontTools.ttLib.tables.otTables.MarkLigPos,
362 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400363def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400364 return []
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400365
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400366@add_method(fontTools.ttLib.tables.otTables.SingleSubst,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400367 fontTools.ttLib.tables.otTables.AlternateSubst,
368 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400369def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400370 return False
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400371
372@add_method(fontTools.ttLib.tables.otTables.MultipleSubst,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400373 fontTools.ttLib.tables.otTables.LigatureSubst,
374 fontTools.ttLib.tables.otTables.ContextSubst,
375 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400376def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400377 return True
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400378
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400379@add_method(fontTools.ttLib.tables.otTables.ContextSubst,
380 fontTools.ttLib.tables.otTables.ChainContextSubst,
381 fontTools.ttLib.tables.otTables.ContextPos,
382 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400383def __classify_context(self):
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400384
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400385 class ContextHelper:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400386 def __init__(self, klass, Format):
387 if klass.__name__.endswith('Subst'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400388 Typ = 'Sub'
389 Type = 'Subst'
390 else:
391 Typ = 'Pos'
392 Type = 'Pos'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400393 if klass.__name__.startswith('Chain'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400394 Chain = 'Chain'
395 else:
396 Chain = ''
397 ChainTyp = Chain+Typ
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400398
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400399 self.Typ = Typ
400 self.Type = Type
401 self.Chain = Chain
402 self.ChainTyp = ChainTyp
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400403
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400404 self.LookupRecord = Type+'LookupRecord'
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400405
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400406 if Format == 1:
407 Coverage = lambda r: r.Coverage
408 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400409 ContextData = lambda r:(None,)
410 ChainContextData = lambda r:(None, None, None)
411 RuleData = lambda r:(r.Input,)
412 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400413 SetRuleData = None
414 ChainSetRuleData = None
415 elif Format == 2:
416 Coverage = lambda r: r.Coverage
417 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400418 ContextData = lambda r:(r.ClassDef,)
419 ChainContextData = lambda r:(r.LookAheadClassDef,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400420 r.InputClassDef,
421 r.BacktrackClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400422 RuleData = lambda r:(r.Class,)
423 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack)
424 def SetRuleData(r, d):(r.Class,) = d
425 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400426 elif Format == 3:
427 Coverage = lambda r: r.Coverage[0]
428 ChainCoverage = lambda r: r.InputCoverage[0]
429 ContextData = None
430 ChainContextData = None
431 RuleData = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400432 ChainRuleData = lambda r:(r.LookAheadCoverage +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400433 r.InputCoverage +
434 r.BacktrackCoverage)
435 SetRuleData = None
436 ChainSetRuleData = None
437 else:
438 assert 0, "unknown format: %s" % Format
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400439
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400440 if Chain:
441 self.Coverage = ChainCoverage
442 self.ContextData = ChainContextData
443 self.RuleData = ChainRuleData
444 self.SetRuleData = ChainSetRuleData
445 else:
446 self.Coverage = Coverage
447 self.ContextData = ContextData
448 self.RuleData = RuleData
449 self.SetRuleData = SetRuleData
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400450
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400451 if Format == 1:
452 self.Rule = ChainTyp+'Rule'
453 self.RuleCount = ChainTyp+'RuleCount'
454 self.RuleSet = ChainTyp+'RuleSet'
455 self.RuleSetCount = ChainTyp+'RuleSetCount'
456 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
457 elif Format == 2:
458 self.Rule = ChainTyp+'ClassRule'
459 self.RuleCount = ChainTyp+'ClassRuleCount'
460 self.RuleSet = ChainTyp+'ClassSet'
461 self.RuleSetCount = ChainTyp+'ClassSetCount'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400462 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r)
Behdad Esfahbod89987002013-07-23 23:07:42 -0400463
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400464 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
Behdad Esfahbod27108392013-07-23 16:40:47 -0400465
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400466 if self.Format not in [1, 2, 3]:
467 return None # Don't shoot the messenger; let it go
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400468 if not hasattr(self.__class__, "__ContextHelpers"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400469 self.__class__.__ContextHelpers = {}
470 if self.Format not in self.__class__.__ContextHelpers:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400471 helper = ContextHelper(self.__class__, self.Format)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400472 self.__class__.__ContextHelpers[self.Format] = helper
473 return self.__class__.__ContextHelpers[self.Format]
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400474
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400475@add_method(fontTools.ttLib.tables.otTables.ContextSubst,
476 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400477def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400478 if cur_glyphs == None: cur_glyphs = s.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400479 c = self.__classify_context()
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400480
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400481 indices = c.Coverage(self).intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400482 if not indices:
483 return []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400484 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs);
Behdad Esfahbod1d4fa132013-08-08 22:59:32 -0400485
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400486 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400487 ContextData = c.ContextData(self)
488 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400489 for i in indices:
490 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400491 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400492 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400493 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
494 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400495 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400496 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400497 if not ll: continue
498 seqi = ll.SequenceIndex
499 if seqi == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400500 pos_glyphs = set(c.Coverage(self).glyphs[i])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400501 else:
502 if chaos:
503 pos_glyphs = s.glyphs
504 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400505 pos_glyphs = set(r.Input[seqi - 1])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400506 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400507 chaos = chaos or lookup.may_have_non_1to1()
508 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400509 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400510 ClassDef = getattr(self, c.ClassDef)
511 indices = ClassDef.intersect(cur_glyphs)
512 ContextData = c.ContextData(self)
513 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400514 for i in indices:
515 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400516 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400517 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400518 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
519 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400520 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400521 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400522 if not ll: continue
523 seqi = ll.SequenceIndex
524 if seqi == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400525 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400526 else:
527 if chaos:
528 pos_glyphs = s.glyphs
529 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400530 pos_glyphs = ClassDef.intersect_class(s.glyphs,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400531 r.Input[seqi - 1])
532 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400533 chaos = chaos or lookup.may_have_non_1to1()
534 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400535 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400536 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400537 return []
538 r = self
539 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400540 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400541 if not ll: continue
542 seqi = ll.SequenceIndex
543 if seqi == 0:
544 pos_glyphs = cur_glyphs
545 else:
546 if chaos:
547 pos_glyphs = s.glyphs
548 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400549 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400550 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400551 chaos = chaos or lookup.may_have_non_1to1()
552 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400553 else:
554 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod00776972013-07-23 15:33:00 -0400555
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400556@add_method(fontTools.ttLib.tables.otTables.ContextSubst,
557 fontTools.ttLib.tables.otTables.ContextPos,
558 fontTools.ttLib.tables.otTables.ChainContextSubst,
559 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400560def subset_glyphs(self, s):
561 c = self.__classify_context()
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400562
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400563 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400564 indices = self.Coverage.subset(s.glyphs)
565 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400566 rss = [rss[i] for i in indices]
567 for rs in rss:
568 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400569 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400570 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400571 if r and all(all(g in s.glyphs for g in glist)
572 for glist in c.RuleData(r))]
573 setattr(rs, c.Rule, ss)
574 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400575 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400576 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
577 setattr(self, c.RuleSet, rss)
578 setattr(self, c.RuleSetCount, len(rss))
579 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400580 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400581 if not self.Coverage.subset(s.glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400582 return False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400583 indices = getattr(self, c.ClassDef).subset(self.Coverage.glyphs,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400584 remap=False)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400585 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400586 rss = [rss[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400587 ContextData = c.ContextData(self)
588 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400589 for rs in rss:
590 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400591 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400592 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400593 if r and all(all(k in klass_map for k in klist)
594 for klass_map,klist in zip(klass_maps, c.RuleData(r)))]
595 setattr(rs, c.Rule, ss)
596 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400597
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400598 # Remap rule classes
599 for r in ss:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400600 c.SetRuleData(r, [[klass_map.index(k) for k in klist]
601 for klass_map,klist in zip(klass_maps, c.RuleData(r))])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400602 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400603 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
604 setattr(self, c.RuleSet, rss)
605 setattr(self, c.RuleSetCount, len(rss))
606 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400607 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400608 return all(x.subset(s.glyphs) for x in c.RuleData(self))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400609 else:
610 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400611
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400612@add_method(fontTools.ttLib.tables.otTables.ContextSubst,
613 fontTools.ttLib.tables.otTables.ChainContextSubst,
614 fontTools.ttLib.tables.otTables.ContextPos,
615 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400616def subset_lookups(self, lookup_indices):
617 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400618
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400619 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400620 for rs in getattr(self, c.RuleSet):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400621 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400622 for r in getattr(rs, c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400623 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400624 setattr(r, c.LookupRecord,
625 [ll for ll in getattr(r, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400626 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400627 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400628 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400629 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400630 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400631 setattr(self, c.LookupRecord,
632 [ll for ll in getattr(self, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400633 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400634 for ll in getattr(self, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400635 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400636 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400637 else:
638 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400639
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400640@add_method(fontTools.ttLib.tables.otTables.ContextSubst,
641 fontTools.ttLib.tables.otTables.ChainContextSubst,
642 fontTools.ttLib.tables.otTables.ContextPos,
643 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400644def collect_lookups(self):
645 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400646
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400647 if self.Format in [1, 2]:
648 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400649 for rs in getattr(self, c.RuleSet) if rs
650 for r in getattr(rs, c.Rule) if r
651 for ll in getattr(r, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400652 elif self.Format == 3:
653 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400654 for ll in getattr(self, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400655 else:
656 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400657
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400658@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400659def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400660 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400661 self.ExtSubTable.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400662 else:
663 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400664
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400665@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400666def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400667 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400668 return self.ExtSubTable.may_have_non_1to1()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400669 else:
670 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400671
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400672@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
673 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400674def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400675 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400676 return self.ExtSubTable.subset_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400677 else:
678 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400679
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400680@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
681 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400682def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400683 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400684 return self.ExtSubTable.subset_lookups(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400685 else:
686 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400687
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400688@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
689 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400690def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400691 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400692 return self.ExtSubTable.collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400693 else:
694 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400695
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400696@add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400697def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400698 for st in self.SubTable:
699 if not st: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400700 st.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400701
702@add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400703def subset_glyphs(self, s):
704 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)]
705 self.SubTableCount = len(self.SubTable)
706 return bool(self.SubTableCount)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400707
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400708@add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400709def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400710 for s in self.SubTable:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400711 s.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400712
713@add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400714def collect_lookups(self):
715 return unique_sorted(sum((st.collect_lookups() for st in self.SubTable
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400716 if st), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400717
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400718@add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400719def may_have_non_1to1(self):
720 return any(st.may_have_non_1to1() for st in self.SubTable if st)
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400721
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400722@add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400723def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400724 "Returns the indices of nonempty lookups."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400725 return [i for(i,l) in enumerate(self.Lookup) if l and l.subset_glyphs(s)]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400726
727@add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400728def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400729 self.Lookup = [self.Lookup[i] for i in lookup_indices
730 if i < self.LookupCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400731 self.LookupCount = len(self.Lookup)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400732 for l in self.Lookup:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400733 l.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400734
735@add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400736def closure_lookups(self, lookup_indices):
737 lookup_indices = unique_sorted(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400738 recurse = lookup_indices
739 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400740 recurse_lookups = sum((self.Lookup[i].collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400741 for i in recurse if i < self.LookupCount), [])
742 recurse_lookups = [l for l in recurse_lookups
743 if l not in lookup_indices and l < self.LookupCount]
744 if not recurse_lookups:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400745 return unique_sorted(lookup_indices)
746 recurse_lookups = unique_sorted(recurse_lookups)
747 lookup_indices.extend(recurse_lookups)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400748 recurse = recurse_lookups
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400749
750@add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400751def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400752 self.LookupListIndex = [l for l in self.LookupListIndex
753 if l in lookup_indices]
754 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400755 self.LookupListIndex = [lookup_indices.index(l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400756 for l in self.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400757 self.LookupCount = len(self.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400758 return self.LookupCount
Behdad Esfahbod54660612013-07-21 18:16:55 -0400759
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400760@add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400761def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400762 return self.LookupListIndex[:]
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400763
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400764@add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400765def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400766 "Returns the indices of nonempty features."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400767 feature_indices = [i for(i,f) in enumerate(self.FeatureRecord)
768 if f.Feature.subset_lookups(lookup_indices)]
769 self.subset_features(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400770 return feature_indices
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400771
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400772@add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400773def collect_lookups(self, feature_indices):
774 return unique_sorted(sum((self.FeatureRecord[i].Feature.collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400775 for i in feature_indices
776 if i < self.FeatureCount), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400777
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400778@add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400779def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400780 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400781 self.FeatureCount = len(self.FeatureRecord)
782 return bool(self.FeatureCount)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400783
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400784@add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
785 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400786def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400787 if self.ReqFeatureIndex in feature_indices:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400788 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400789 else:
790 self.ReqFeatureIndex = 65535
791 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
792 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400793 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400794 if f in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400795 self.FeatureCount = len(self.FeatureIndex)
796 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400797
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400798@add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
799 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400800def collect_features(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400801 feature_indices = self.FeatureIndex[:]
802 if self.ReqFeatureIndex != 65535:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400803 feature_indices.append(self.ReqFeatureIndex)
804 return unique_sorted(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400805
806@add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400807def subset_features(self, feature_indices):
808 if(self.DefaultLangSys and
809 not self.DefaultLangSys.subset_features(feature_indices)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400810 self.DefaultLangSys = None
811 self.LangSysRecord = [l for l in self.LangSysRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400812 if l.LangSys.subset_features(feature_indices)]
813 self.LangSysCount = len(self.LangSysRecord)
814 return bool(self.LangSysCount or self.DefaultLangSys)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400815
816@add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400817def collect_features(self):
818 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400819 if self.DefaultLangSys:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400820 feature_indices.append(self.DefaultLangSys.collect_features())
821 return unique_sorted(sum(feature_indices, []))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400822
823@add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400824def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400825 self.ScriptRecord = [s for s in self.ScriptRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400826 if s.Script.subset_features(feature_indices)]
827 self.ScriptCount = len(self.ScriptRecord)
828 return bool(self.ScriptCount)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400829
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400830@add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400831def collect_features(self):
832 return unique_sorted(sum((s.Script.collect_features()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400833 for s in self.ScriptRecord), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400834
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400835@add_method(fontTools.ttLib.getTableClass('GSUB'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400836def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400837 s.table = self.table
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400838 feature_indices = self.table.ScriptList.collect_features()
839 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400840 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400841 orig_glyphs = s.glyphs.copy()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400842 for i in lookup_indices:
843 if i >= self.table.LookupList.LookupCount: continue
844 if not self.table.LookupList.Lookup[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400845 self.table.LookupList.Lookup[i].closure_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400846 if orig_glyphs == s.glyphs:
847 break
848 del s.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400849
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400850@add_method(fontTools.ttLib.getTableClass('GSUB'),
851 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400852def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400853 s.glyphs = s.glyphs_gsubed
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400854 lookup_indices = self.table.LookupList.subset_glyphs(s)
855 self.subset_lookups(lookup_indices)
856 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400857 return True
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400858
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400859@add_method(fontTools.ttLib.getTableClass('GSUB'),
860 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400861def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400862 """Retrains specified lookups, then removes empty features, language
863 systems, and scripts."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400864 self.table.LookupList.subset_lookups(lookup_indices)
865 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices)
866 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400867
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400868@add_method(fontTools.ttLib.getTableClass('GSUB'),
869 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400870def prune_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400871 "Remove unreferenced lookups"
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400872 feature_indices = self.table.ScriptList.collect_features()
873 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
874 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices)
875 self.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400876
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400877@add_method(fontTools.ttLib.getTableClass('GSUB'),
878 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400879def subset_feature_tags(self, feature_tags):
880 feature_indices = [i for(i,f) in
881 enumerate(self.table.FeatureList.FeatureRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400882 if f.FeatureTag in feature_tags]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400883 self.table.FeatureList.subset_features(feature_indices)
884 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400885
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400886@add_method(fontTools.ttLib.getTableClass('GSUB'),
887 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400888def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400889 if options.layout_features and '*' not in options.layout_features:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400890 self.subset_feature_tags(options.layout_features)
891 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400892 return True
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400893
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400894@add_method(fontTools.ttLib.getTableClass('GDEF'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400895def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400896 glyphs = s.glyphs_gsubed
897 table = self.table
898 if table.LigCaretList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400899 indices = table.LigCaretList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400900 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i]
901 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400902 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400903 if not table.LigCaretList.LigGlyphCount:
904 table.LigCaretList = None
905 if table.MarkAttachClassDef:
906 table.MarkAttachClassDef.classDefs = {g:v for g,v in
907 table.MarkAttachClassDef.classDefs.iteritems()
908 if g in glyphs}
909 if not table.MarkAttachClassDef.classDefs:
910 table.MarkAttachClassDef = None
911 if table.GlyphClassDef:
912 table.GlyphClassDef.classDefs = {g:v for g,v in
913 table.GlyphClassDef.classDefs.iteritems()
914 if g in glyphs}
915 if not table.GlyphClassDef.classDefs:
916 table.GlyphClassDef = None
917 if table.AttachList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400918 indices = table.AttachList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400919 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i]
920 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400921 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400922 if not table.AttachList.GlyphCount:
923 table.AttachList = None
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400924 return bool(table.LigCaretList or
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400925 table.MarkAttachClassDef or
926 table.GlyphClassDef or
927 table.AttachList)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -0400928
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400929@add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400930def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400931 # Prune unknown kern table types
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400932 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')]
933 return bool(self.kernTables)
Behdad Esfahbodd4e33a72013-07-24 18:51:05 -0400934
935@add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400936def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400937 glyphs = s.glyphs_gsubed
938 for t in self.kernTables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400939 t.kernTable = {(a,b):v for((a,b),v) in t.kernTable.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400940 if a in glyphs and b in glyphs}
941 self.kernTables = [t for t in self.kernTables if t.kernTable]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400942 return bool(self.kernTables)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -0400943
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400944@add_method(fontTools.ttLib.getTableClass('hmtx'),
945 fontTools.ttLib.getTableClass('vmtx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400946def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400947 self.metrics = {g:v for g,v in self.metrics.iteritems() if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400948 return bool(self.metrics)
Behdad Esfahbodc7160442013-07-22 14:29:08 -0400949
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -0400950@add_method(fontTools.ttLib.getTableClass('hdmx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400951def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400952 self.hdmx = {sz:{g:v for g,v in l.iteritems() if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400953 for(sz,l) in self.hdmx.iteritems()}
954 return bool(self.hdmx)
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -0400955
Behdad Esfahbode45d6af2013-07-22 15:29:17 -0400956@add_method(fontTools.ttLib.getTableClass('VORG'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400957def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400958 self.VOriginRecords = {g:v for g,v in self.VOriginRecords.iteritems()
959 if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400960 self.numVertOriginYMetrics = len(self.VOriginRecords)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400961 return True # Never drop; has default metrics
Behdad Esfahbode45d6af2013-07-22 15:29:17 -0400962
Behdad Esfahbod8c646f62013-07-22 15:06:23 -0400963@add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400964def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400965 if not options.glyph_names:
966 self.formatType = 3.0
967 return True
Behdad Esfahbod42648242013-07-23 12:56:06 -0400968
969@add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400970def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400971 self.extraNames = [] # This seems to do it
972 return True
Behdad Esfahbod653e9742013-07-22 15:17:12 -0400973
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400974# Copied from _g_l_y_f.py
975ARG_1_AND_2_ARE_WORDS = 0x0001 # if set args are words otherwise they are bytes
976ARGS_ARE_XY_VALUES = 0x0002 # if set args are xy values, otherwise they are points
977ROUND_XY_TO_GRID = 0x0004 # for the xy values if above is true
978WE_HAVE_A_SCALE = 0x0008 # Sx = Sy, otherwise scale == 1.0
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400979NON_OVERLAPPING = 0x0010 # set to same value for all components(obsolete!)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400980MORE_COMPONENTS = 0x0020 # indicates at least one more glyph after this one
981WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Sx, Sy
982WE_HAVE_A_TWO_BY_TWO = 0x0080 # t00, t01, t10, t11
983WE_HAVE_INSTRUCTIONS = 0x0100 # instructions follow
984USE_MY_METRICS = 0x0200 # apply these metrics to parent glyph
985OVERLAP_COMPOUND = 0x0400 # used by Apple in GX fonts
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400986SCALED_COMPONENT_OFFSET = 0x0800 # composite designed to have the component offset scaled(designed for Apple)
987UNSCALED_COMPONENT_OFFSET = 0x1000 # composite designed not to have the component offset scaled(designed for MS)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400988
989@add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400990def getComponentNamesFast(self, glyfTable):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400991 if struct.unpack(">h", self.data[:2])[0] >= 0:
992 return [] # Not composite
993 data = self.data
994 i = 10
995 components = []
996 more = 1
997 while more:
998 flags, glyphID = struct.unpack(">HH", data[i:i+4])
999 i += 4
1000 flags = int(flags)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001001 components.append(glyfTable.getGlyphName(int(glyphID)))
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001002
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001003 if flags & ARG_1_AND_2_ARE_WORDS: i += 4
1004 else: i += 2
1005 if flags & WE_HAVE_A_SCALE: i += 2
1006 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
1007 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
1008 more = flags & MORE_COMPONENTS
1009 return components
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001010
1011@add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001012def remapComponentsFast(self, indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001013 if struct.unpack(">h", self.data[:2])[0] >= 0:
1014 return # Not composite
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001015 data = bytearray(self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001016 i = 10
1017 more = 1
1018 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001019 flags =(data[i] << 8) | data[i+1]
1020 glyphID =(data[i+2] << 8) | data[i+3]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001021 # Remap
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001022 glyphID = indices.index(glyphID)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001023 data[i+2] = glyphID >> 8
1024 data[i+3] = glyphID & 0xFF
1025 i += 4
1026 flags = int(flags)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001027
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001028 if flags & ARG_1_AND_2_ARE_WORDS: i += 4
1029 else: i += 2
1030 if flags & WE_HAVE_A_SCALE: i += 2
1031 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
1032 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
1033 more = flags & MORE_COMPONENTS
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001034 self.data = str(data)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001035
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001036@add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001037def dropInstructionsFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001038 numContours = struct.unpack(">h", self.data[:2])[0]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001039 data = bytearray(self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001040 i = 10
1041 if numContours >= 0:
1042 i += 2 * numContours # endPtsOfContours
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001043 instructionLen =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001044 # Zero it
1045 data[i] = data [i+1] = 0
1046 i += 2
1047 if instructionLen:
1048 # Splice it out
1049 data = data[:i] + data[i+instructionLen:]
1050 else:
1051 more = 1
1052 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001053 flags =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001054 # Turn instruction flag off
1055 flags &= ~WE_HAVE_INSTRUCTIONS
1056 data[i+0] = flags >> 8
1057 data[i+1] = flags & 0xFF
1058 i += 4
1059 flags = int(flags)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001060
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001061 if flags & ARG_1_AND_2_ARE_WORDS: i += 4
1062 else: i += 2
1063 if flags & WE_HAVE_A_SCALE: i += 2
1064 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
1065 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
1066 more = flags & MORE_COMPONENTS
1067 # Cut off
1068 data = data[:i]
1069 if len(data) % 4:
1070 # add pad bytes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001071 nPadBytes = 4 -(len(data) % 4)
1072 for i in range(nPadBytes):
1073 data.append(0)
1074 self.data = str(data)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001075
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001076@add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001077def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001078 decompose = s.glyphs
1079 # I don't know if component glyphs can be composite themselves.
1080 # We handle them anyway.
1081 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001082 components = set()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001083 for g in decompose:
1084 if g not in self.glyphs:
1085 continue
1086 gl = self.glyphs[g]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001087 if hasattr(gl, "data"):
1088 for c in gl.getComponentNamesFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001089 if c not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001090 components.add(c)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001091 else:
1092 # TTX seems to expand gid0..3 always
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001093 if gl.isComposite():
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001094 for c in gl.components:
1095 if c.glyphName not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001096 components.add(c.glyphName)
1097 components = set(c for c in components if c not in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001098 if not components:
1099 break
1100 decompose = components
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001101 s.glyphs.update(components)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001102
1103@add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001104def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001105 self.glyphs = {g:v for g,v in self.glyphs.iteritems() if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001106 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001107 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001108 if hasattr(v, "data"):
1109 v.remapComponentsFast(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001110 else:
1111 pass # No need
1112 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001113 return bool(self.glyphs)
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001114
Behdad Esfahboded98c612013-07-23 12:37:41 -04001115@add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001116def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001117 if not options.hinting:
1118 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001119 if hasattr(v, "data"):
1120 v.dropInstructionsFast()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001121 else:
1122 v.program = fontTools.ttLib.tables.ttProgram.Program()
1123 v.program.fromBytecode([])
1124 return True
Behdad Esfahboded98c612013-07-23 12:37:41 -04001125
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001126@add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001127def prune_pre_subset(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001128 cff = self.cff
1129 # CFF table should have one font only
1130 cff.fontNames = cff.fontNames[:1]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001131 return bool(cff.fontNames)
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001132
1133@add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001134def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001135 cff = self.cff
1136 for fontname in cff.keys():
1137 font = cff[fontname]
1138 cs = font.CharStrings
1139 if cs.charStringsAreIndexed:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001140 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001141 # Load all glyphs
1142 for g in font.charset:
1143 if g not in s.glyphs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001144 cs.getItemAndSelector(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001145 csi = cs.charStringsIndex
1146 csi.items = [csi.items[i] for i in indices]
1147 csi.offsets = [] # Don't need it; loaded all glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001148 if hasattr(font, "FDSelect"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001149 sel = font.FDSelect
1150 sel.format = None
1151 sel.gidArray = [font.FDSelect.gidArray[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001152 cs.charStrings = {g:indices.index(v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001153 for g,v in cs.charStrings.iteritems()
1154 if g in s.glyphs}
1155 else:
1156 cs.charStrings = {g:v
1157 for g,v in cs.charStrings.iteritems()
1158 if g in s.glyphs}
1159 font.charset = [g for g in font.charset if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001160 font.numGlyphs = len(font.charset)
1161 return any(cff[fontname].numGlyphs for fontname in cff.keys())
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001162
1163@add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001164def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001165 if not options.hinting:
1166 pass # Drop hints
1167 return True
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001168
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001169@add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001170def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001171 tables = [t for t in self.tables
1172 if t.platformID == 3 and t.platEncID in [1, 10]]
1173 for u in s.unicodes_requested:
1174 found = False
1175 for table in tables:
1176 if u in table.cmap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001177 s.glyphs.add(table.cmap[u])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001178 found = True
1179 break
1180 if not found:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001181 s.log("No glyph for Unicode value %s; skipping." % u)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001182
1183@add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001184def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001185 if not options.legacy_cmap:
1186 # Drop non-Unicode / non-Symbol cmaps
1187 self.tables = [t for t in self.tables
1188 if t.platformID == 3 and t.platEncID in [0, 1, 10]]
1189 if not options.symbol_cmap:
1190 self.tables = [t for t in self.tables
1191 if t.platformID == 3 and t.platEncID in [1, 10]]
1192 # TODO Only keep one subtable?
1193 # For now, drop format=0 which can't be subset_glyphs easily?
1194 self.tables = [t for t in self.tables if t.format != 0]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001195 return bool(self.tables)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001196
1197@add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001198def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001199 s.glyphs = s.glyphs_cmaped
1200 for t in self.tables:
1201 # For reasons I don't understand I need this here
1202 # to force decompilation of the cmap format 14.
1203 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001204 getattr(t, "asdf")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001205 except AttributeError:
1206 pass
1207 if t.format == 14:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001208 # XXX We drop all the default-UVS mappings(g==None)
1209 t.uvsDict = {v:[(u,g) for(u,g) in l if g in s.glyphs]
1210 for(v,l) in t.uvsDict.iteritems()}
1211 t.uvsDict = {v:l for(v,l) in t.uvsDict.iteritems() if l}
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001212 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001213 t.cmap = {u:g for(u,g) in t.cmap.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001214 if g in s.glyphs_requested or u in s.unicodes_requested}
1215 self.tables = [t for t in self.tables
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001216 if(t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001217 # XXX Convert formats when needed
1218 # In particular, if we have a format=12 without non-BMP
1219 # characters, either drop format=12 one or convert it
1220 # to format=4 if there's not one.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001221 return bool(self.tables)
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001222
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001223@add_method(fontTools.ttLib.getTableClass('name'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001224def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001225 if '*' not in options.name_IDs:
1226 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1227 if not options.name_legacy:
1228 self.names = [n for n in self.names
1229 if n.platformID == 3 and n.platEncID == 1]
1230 if '*' not in options.name_languages:
1231 self.names = [n for n in self.names if n.langID in options.name_languages]
1232 return True # Retain even if empty
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001233
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001234
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -04001235# TODO OS/2 ulUnicodeRange / ulCodePageRange?
Behdad Esfahbodf71267b2013-07-23 12:59:13 -04001236# TODO Drop unneeded GSUB/GPOS Script/LangSys entries
Behdad Esfahbod398d3892013-07-23 15:29:40 -04001237# TODO Avoid recursing too much
Behdad Esfahbode94aa0e2013-07-23 13:22:04 -04001238# TODO Text direction considerations
1239# TODO Text script / language considerations
Behdad Esfahbodb3ee60c2013-07-24 19:21:40 -04001240# TODO Drop unknown tables? Using DefaultTable.prune?
Behdad Esfahbod8c4f7cc2013-07-24 17:58:29 -04001241# TODO Drop GPOS Device records if not hinting?
Behdad Esfahbod93e26362013-08-09 14:22:48 -04001242# TODO Move font name loading hack to Subsetter?
Behdad Esfahbod56ebd042013-07-22 13:02:24 -04001243
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001244
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001245class Subsetter:
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001246
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001247 class Options:
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001248
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001249 class UnknownOptionError(Exception):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001250 pass
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001251
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001252 drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC',
1253 'PCLT', 'LTSH']
1254 drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
1255 drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
1256 no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1257 'loca', 'name', 'cvt ', 'fpgm', 'prep']
1258 hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001259
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001260 # Based on HarfBuzz shapers
1261 layout_features_groups = {
1262 # Default shaper
Behdad Esfahbode9f0b152013-08-13 19:54:25 -04001263 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1264 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001265 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
Behdad Esfahbode9f0b152013-08-13 19:54:25 -04001266 'ltr': ['ltra', 'ltrm'],
1267 'rtl': ['rtla', 'rtlm'],
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001268 # Complex shapers
Behdad Esfahbode9f0b152013-08-13 19:54:25 -04001269 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1270 'cswh', 'mset'],
1271 'hangul': ['ljmo', 'vjmo', 'tjmo'],
1272 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'],
1273 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1274 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1275 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001276 }
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001277 layout_features_default = unique_sorted(sum(
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001278 layout_features_groups.itervalues(), []))
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001279
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001280 drop_tables = drop_tables_default
1281 no_subset_tables = no_subset_tables_default
1282 hinting_tables = hinting_tables_default
1283 layout_features = layout_features_default
1284 hinting = False
1285 glyph_names = False
1286 legacy_cmap = False
1287 symbol_cmap = False
1288 name_IDs = [1, 2] # Family and Style
1289 name_legacy = False
1290 name_languages = [0x0409] # English
1291 mandatory_glyphs = True # First four for TrueType, .notdef for CFF
1292 recalc_bboxes = False # Slows us down
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001293
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001294 def __init__(self, **kwargs):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001295
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001296 self.set(**kwargs)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001297
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001298 def set(self, **kwargs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001299 for k,v in kwargs.iteritems():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001300 if not hasattr(self, k):
1301 raise self.UnknownOptionError("Unknown option '%s'" % k)
1302 setattr(self, k, v)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001303
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001304 def parse_opts(self, argv, ignore_unknown=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001305 ret = []
1306 opts = {}
1307 for a in argv:
1308 orig_a = a
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001309 if not a.startswith('--'):
1310 ret.append(a)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001311 continue
1312 a = a[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001313 i = a.find('=')
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001314 if i == -1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001315 if a.startswith("no-"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001316 k = a[3:]
1317 v = False
1318 else:
1319 k = a
1320 v = True
1321 else:
1322 k = a[:i]
1323 v = a[i+1:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001324 k = k.replace('-', '_')
1325 if not hasattr(self, k):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001326 if ignore_unknown == True or k in ignore_unknown:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001327 ret.append(orig_a)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001328 continue
1329 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001330 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001331
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001332 ov = getattr(self, k)
1333 if isinstance(ov, bool):
1334 v = bool(v)
1335 elif isinstance(ov, int):
1336 v = int(v)
1337 elif isinstance(ov, list):
1338 v = v.split(',')
1339 v = [int(x, 0) if x[0] in range(10) else x for x in v]
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001340
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001341 opts[k] = v
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001342 self.set(**opts)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001343
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001344 return ret
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001345
1346
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001347 def __init__(self, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001348
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001349 if not log:
1350 log = Logger()
1351 if not options:
1352 options = Options()
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001353
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001354 self.options = options
1355 self.log = log
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001356 self.unicodes_requested = set()
1357 self.glyphs_requested = set()
1358 self.glyphs = set()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001359
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001360 def populate(self, glyphs=[], unicodes=[], text=""):
1361 self.unicodes_requested.update(unicodes)
1362 if isinstance(text, str):
1363 text = text.decode("utf8")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001364 for u in text:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001365 self.unicodes_requested.add(ord(u))
1366 self.glyphs_requested.update(glyphs)
1367 self.glyphs.update(glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001368
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001369 def pre_prune(self, font):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001370
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001371 for tag in font.keys():
1372 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001373
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001374 if(tag in self.options.drop_tables or
1375 (tag in self.options.hinting_tables and not self.options.hinting)):
1376 self.log(tag, "dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001377 del font[tag]
1378 continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001379
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001380 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001381
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001382 if hasattr(clazz, 'prune_pre_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001383 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001384 retain = table.prune_pre_subset(self.options)
1385 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001386 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001387 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001388 del font[tag]
1389 continue
1390 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001391 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001392
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001393 def closure_glyphs(self, font):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001394
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001395 self.glyphs = self.glyphs_requested.copy()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001396
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001397 if 'cmap' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001398 font['cmap'].closure_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001399 self.glyphs_cmaped = self.glyphs
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001400
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001401 if self.options.mandatory_glyphs:
1402 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001403 for i in range(4):
1404 self.glyphs.add(font.getGlyphName(i))
1405 self.log("Added first four glyphs to subset")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001406 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001407 self.glyphs.add('.notdef')
1408 self.log("Added .notdef glyph to subset")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001409
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001410 if 'GSUB' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001411 self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1412 len(self.glyphs))
1413 self.log.glyphs(self.glyphs, font=font)
1414 font['GSUB'].closure_glyphs(self)
1415 self.log("Closed glyph list over 'GSUB': %d glyphs after" %
1416 len(self.glyphs))
1417 self.log.glyphs(self.glyphs, font=font)
1418 self.log.lapse("close glyph list over 'GSUB'")
1419 self.glyphs_gsubed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001420
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001421 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001422 self.log("Closing glyph list over 'glyf': %d glyphs before" %
1423 len(self.glyphs))
1424 self.log.glyphs(self.glyphs, font=font)
1425 font['glyf'].closure_glyphs(self)
1426 self.log("Closed glyph list over 'glyf': %d glyphs after" %
1427 len(self.glyphs))
1428 self.log.glyphs(self.glyphs, font=font)
1429 self.log.lapse("close glyph list over 'glyf'")
1430 self.glyphs_glyfed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001431
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001432 self.glyphs_all = self.glyphs.copy()
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001433
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001434 self.log("Retaining %d glyphs: " % len(self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001435
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001436 def subset_glyphs(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001437 for tag in font.keys():
1438 if tag == 'GlyphOrder': continue
1439 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001440
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001441 if tag in self.options.no_subset_tables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001442 self.log(tag, "subsetting not needed")
1443 elif hasattr(clazz, 'subset_glyphs'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001444 table = font[tag]
1445 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001446 retain = table.subset_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001447 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001448 self.log.lapse("subset '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001449 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001450 self.log(tag, "subsetted to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001451 del font[tag]
1452 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001453 self.log(tag, "subsetted")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001454 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001455 self.log(tag, "NOT subset; don't know how to subset; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001456 del font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001457
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001458 glyphOrder = font.getGlyphOrder()
1459 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001460 font.setGlyphOrder(glyphOrder)
1461 font._buildReverseGlyphOrderDict()
1462 self.log.lapse("subset GlyphOrder")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001463
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001464 def post_prune(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001465 for tag in font.keys():
1466 if tag == 'GlyphOrder': continue
1467 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001468 if hasattr(clazz, 'prune_post_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001469 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001470 retain = table.prune_post_subset(self.options)
1471 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001472 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001473 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001474 del font[tag]
1475 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001476 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001477
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001478 def subset(self, font):
Behdad Esfahbod756af492013-08-01 12:05:26 -04001479
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001480 font.recalcBBoxes = self.options.recalc_bboxes
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001481
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001482 self.pre_prune(font)
1483 self.closure_glyphs(font)
1484 self.subset_glyphs(font)
1485 self.post_prune(font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001486
Behdad Esfahbod756af492013-08-01 12:05:26 -04001487
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001488import sys, time
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001489
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001490class Logger:
1491
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001492 def __init__(self, verbose=False, xml=False, timing=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001493 self.verbose = verbose
1494 self.xml = xml
1495 self.timing = timing
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001496 self.last_time = self.start_time = time.time()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001497
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001498 def parse_opts(self, argv):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001499 argv = argv[:]
1500 for v in ['verbose', 'xml', 'timing']:
1501 if "--"+v in argv:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001502 setattr(self, v, True)
1503 argv.remove("--"+v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001504 return argv
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001505
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001506 def __call__(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001507 if not self.verbose:
1508 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001509 print ' '.join(str(x) for x in things)
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001510
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001511 def lapse(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001512 if not self.timing:
1513 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001514 new_time = time.time()
1515 print "Took %0.3fs to %s" %(new_time - self.last_time,
1516 ' '.join(str(x) for x in things))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001517 self.last_time = new_time
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001518
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001519 def glyphs(self, glyphs, glyph_names=True, font=None):
1520 self("Names: ", sorted(glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001521 if font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001522 reverseGlyphMap = font.getReverseGlyphMap()
1523 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001524
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001525 def font(self, font, file=sys.stdout):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001526 if not self.xml:
1527 return
1528 import xmlWriter
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001529 writer = xmlWriter.XMLWriter(file)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001530 font.disassembleInstructions = False # Work around ttx bug
1531 for tag in font.keys():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001532 writer.begintag(tag)
1533 writer.newline()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001534 font[tag].toXML(writer, font)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001535 writer.endtag(tag)
1536 writer.newline()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001537
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001538
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001539def load_font(fontfile, dont_load_glyph_names=False):
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001540
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001541 # TODO Option for ignoreDecompileErrors?
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001542
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001543 font = fontTools.ttx.TTFont(fontfile)
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001544
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001545 # Hack:
1546 #
1547 # If we don't need glyph names, change 'post' class to not try to
1548 # load them. It avoid lots of headache with broken fonts as well
1549 # as loading time.
1550 #
1551 # Ideally ttLib should provide a way to ask it to skip loading
1552 # glyph names. But it currently doesn't provide such a thing.
1553 #
1554 if dont_load_glyph_names:
1555 post = fontTools.ttLib.getTableClass('post')
1556 saved = post.decode_format_2_0
1557 post.decode_format_2_0 = post.decode_format_3_0
1558 f = font['post']
1559 if f.formatType == 2.0:
1560 f.formatType = 3.0
1561 post.decode_format_2_0 = saved
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001562
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001563 return font
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001564
1565
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001566def main(args):
Behdad Esfahbod610b0552013-07-23 14:52:18 -04001567
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001568 log = Logger()
1569 args = log.parse_opts(args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04001570
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001571 options = Subsetter.Options()
1572 args = options.parse_opts(args, ignore_unknown=['text'])
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001573
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001574 if len(args) < 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001575 print >>sys.stderr, "usage: pyotlss.py font-file glyph..."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001576 sys.exit(1)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001577
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001578 fontfile = args[0]
1579 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001580
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001581 dont_load_glyph_names =(not options.glyph_names and
1582 all(any(g.startswith(p)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001583 for p in ['gid', 'glyph', 'uni', 'U+'])
1584 for g in args))
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001585
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001586 font = load_font(fontfile, dont_load_glyph_names=dont_load_glyph_names)
1587 subsetter = Subsetter(options=options, log=log)
1588 log.lapse("load font")
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001589
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001590 names = font.getGlyphNames()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001591 log.lapse("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001592
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001593 glyphs = []
1594 unicodes = []
1595 text = ""
1596 for g in args:
1597 if g in names:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001598 glyphs.append(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001599 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001600 if g.startswith('--text='):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001601 text += g[7:]
1602 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001603 if g.startswith('uni') or g.startswith('U+'):
1604 if g.startswith('uni') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001605 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001606 elif g.startswith('U+') and len(g) > 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001607 g = g[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001608 u = int(g, 16)
1609 unicodes.append(u)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001610 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001611 if g.startswith('gid') or g.startswith('glyph'):
1612 if g.startswith('gid') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001613 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001614 elif g.startswith('glyph') and len(g) > 5:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001615 g = g[5:]
1616 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001617 glyphs.append(font.getGlyphName(int(g), requireReal=1))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001618 except ValueError:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001619 raise Exception("Invalid glyph identifier: %s" % g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001620 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001621 raise Exception("Invalid glyph identifier: %s" % g)
1622 log.lapse("compile glyph list")
1623 log("Unicodes:", unicodes)
1624 log("Glyphs:", glyphs)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001625
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001626 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
1627 subsetter.subset(font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04001628
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001629 font.save(fontfile + '.subset')
1630 log.lapse("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001631
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001632 log.last_time = log.start_time
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001633 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001634
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001635 log.font(font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001636
1637if __name__ == '__main__':
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001638 main(sys.argv[1:])