blob: da079925723bdaed8a4e66f235f34f9cb2e3cf28 [file] [log] [blame]
Behdad Esfahbod54660612013-07-21 18:16:55 -04001#!/usr/bin/python
2
3# Python OpenType Layout Subsetter
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04004#
5# Copyright 2013 Google, Inc. All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# Google Author(s): Behdad Esfahbod
20#
Behdad Esfahbod54660612013-07-21 18:16:55 -040021
Behdad Esfahbodfa3bc5e2013-07-24 14:37:58 -040022# Try running on PyPy
23try:
24 import numpypy
25except ImportError:
26 pass
27
Behdad Esfahbod54660612013-07-21 18:16:55 -040028import fontTools.ttx
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -040029import struct
Behdad Esfahbod54660612013-07-21 18:16:55 -040030
Behdad Esfahbod54660612013-07-21 18:16:55 -040031
Behdad Esfahbod02b92062013-07-21 18:40:59 -040032def add_method (*clazzes):
Behdad Esfahbod54660612013-07-21 18:16:55 -040033 def wrapper(method):
Behdad Esfahbod02b92062013-07-21 18:40:59 -040034 for clazz in clazzes:
Behdad Esfahbodc0d59592013-07-24 14:41:47 -040035 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
Behdad Esfahbod02b92062013-07-21 18:40:59 -040036 setattr (clazz, method.func_name, method)
Behdad Esfahbod54660612013-07-21 18:16:55 -040037 return wrapper
38
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040039def unique_sorted (l):
Behdad Esfahbod2d9a0962013-07-31 13:33:31 -040040 return sorted (set (l))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040041
Behdad Esfahbod97e17b82013-07-31 15:59:21 -040042def safeEval(data, eval=eval):
43 """A (kindof) safe replacement for eval."""
44 return eval(data, {"__builtins__":{}}, {})
45
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040046
Behdad Esfahbod54660612013-07-21 18:16:55 -040047@add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod327dcc32013-07-31 13:50:51 -040048def intersect (self, glyphs):
Behdad Esfahbod610b0552013-07-23 14:52:18 -040049 "Returns ascending list of matching coverage values."
50 return [i for (i,g) in enumerate (self.glyphs) if g in glyphs]
51
52@add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod327dcc32013-07-31 13:50:51 -040053def subset (self, glyphs):
Behdad Esfahbodd821ea02013-07-23 10:50:43 -040054 "Returns ascending list of remaining coverage values."
Behdad Esfahbod327dcc32013-07-31 13:50:51 -040055 indices = self.intersect (glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -040056 self.glyphs = [g for g in self.glyphs if g in glyphs]
57 return indices
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040058
Behdad Esfahbod14374262013-08-08 22:26:49 -040059@add_method(fontTools.ttLib.tables.otTables.Coverage)
60def remap (self, coverage_map):
61 "Remaps coverage."
62 self.glyphs = [self.glyphs[i] for i in coverage_map]
63
Behdad Esfahbod54660612013-07-21 18:16:55 -040064@add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod327dcc32013-07-31 13:50:51 -040065def intersect (self, glyphs):
Behdad Esfahbode10803e2013-08-08 21:09:27 -040066 "Returns ascending list of matching class values."
Behdad Esfahboda1e0f132013-08-08 21:12:45 -040067 return unique_sorted (([0] if any (g not in self.classDefs for g in glyphs) else []) + \
Behdad Esfahbode10803e2013-08-08 21:09:27 -040068 [v for g,v in self.classDefs.items() if g in glyphs])
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040069
70@add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod327dcc32013-07-31 13:50:51 -040071def intersects_class (self, glyphs, klass):
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040072 "Returns true if any of glyphs has requested class."
Behdad Esfahbode10803e2013-08-08 21:09:27 -040073 assert isinstance (klass, int)
Behdad Esfahbod0befd6b2013-08-05 22:47:14 -040074 if klass == 0:
Behdad Esfahboda1e0f132013-08-08 21:12:45 -040075 if any (g not in self.classDefs for g in glyphs):
Behdad Esfahbod0befd6b2013-08-05 22:47:14 -040076 return True
77 # Fall through
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040078 return any (g in glyphs for g,v in self.classDefs.items() if v == klass)
79
80@add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod327dcc32013-07-31 13:50:51 -040081def subset (self, glyphs, remap=False):
Behdad Esfahboda1e0f132013-08-08 21:12:45 -040082 "Returns ascending list of remaining classes."
Behdad Esfahbod54660612013-07-21 18:16:55 -040083 self.classDefs = {g:v for g,v in self.classDefs.items() if g in glyphs}
Behdad Esfahboda1e0f132013-08-08 21:12:45 -040084 # Note: while class 0 has the special meaning of "not matched", if no glyph will
85 # ever /not match/, we can optimize class 0 out too.
86 indices = unique_sorted (([0] if any (g not in self.classDefs for g in glyphs) else []) + \
87 self.classDefs.values ())
Behdad Esfahbodde71dca2013-07-24 12:40:54 -040088 if remap:
89 self.remap (indices)
90 return indices
Behdad Esfahbod4aa6ce32013-07-22 12:15:36 -040091
92@add_method(fontTools.ttLib.tables.otTables.ClassDef)
93def remap (self, class_map):
94 "Remaps classes."
95 self.classDefs = {g:class_map.index (v) for g,v in self.classDefs.items()}
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040096
Behdad Esfahbod54660612013-07-21 18:16:55 -040097@add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod254442b2013-07-31 14:20:13 -040098def closure_glyphs (self, s):
Behdad Esfahbod610b0552013-07-23 14:52:18 -040099 if self.Format in [1, 2]:
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400100 return [v for g,v in self.mapping.items() if g in s.glyphs]
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400101 else:
102 assert 0, "unknown format: %s" % self.Format
103
104@add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400105def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400106 if self.Format in [1, 2]:
Behdad Esfahbod14374262013-08-08 22:26:49 -0400107 self.mapping = {g:v for g,v in self.mapping.items() if g in s.glyphs and v in s.glyphs}
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400108 return bool (self.mapping)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400109 else:
110 assert 0, "unknown format: %s" % self.Format
111
112@add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400113def closure_glyphs (self, s):
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400114 if self.Format == 1:
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400115 indices = self.Coverage.intersect (s.glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400116 return sum ((self.Sequence[i].Substitute for i in indices), [])
117 else:
118 assert 0, "unknown format: %s" % self.Format
119
120@add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400121def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400122 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400123 indices = self.Coverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400124 self.Sequence = [self.Sequence[i] for i in indices]
Behdad Esfahbod14374262013-08-08 22:26:49 -0400125 # Now drop rules generating glyphs we don't want
126 indices = [i for i,seq in enumerate (self.Sequence) \
127 if all (sub in s.glyphs for sub in seq.Substitute)]
128 self.Sequence = [self.Sequence[i] for i in indices]
129 self.Coverage.remap (indices)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400130 self.SequenceCount = len (self.Sequence)
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400131 return bool (self.SequenceCount)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400132 else:
133 assert 0, "unknown format: %s" % self.Format
134
135@add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400136def closure_glyphs (self, s):
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400137 if self.Format == 1:
Behdad Esfahbod14374262013-08-08 22:26:49 -0400138 return sum ((vlist for g,vlist in self.alternates.items() if g in s.glyphs), [])
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400139 else:
140 assert 0, "unknown format: %s" % self.Format
141
142@add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400143def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400144 if self.Format == 1:
Behdad Esfahbod14374262013-08-08 22:26:49 -0400145 self.alternates = {g:vlist for g,vlist in self.alternates.items() \
146 if g in s.glyphs and all (v in s.glyphs for v in vlist)}
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400147 return bool (self.alternates)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400148 else:
149 assert 0, "unknown format: %s" % self.Format
150
151@add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400152def closure_glyphs (self, s):
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400153 if self.Format == 1:
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400154 return sum (([seq.LigGlyph for seq in seqs if all(c in s.glyphs for c in seq.Component)]
155 for g,seqs in self.ligatures.items() if g in s.glyphs), [])
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400156 else:
157 assert 0, "unknown format: %s" % self.Format
158
159@add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400160def subset_glyphs (self, s):
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400161 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400162 self.ligatures = {g:v for g,v in self.ligatures.items() if g in s.glyphs}
Behdad Esfahbod14374262013-08-08 22:26:49 -0400163 self.ligatures = {g:[seq for seq in seqs \
164 if seq.LigGlyph in s.glyphs and \
165 all(c in s.glyphs for c in seq.Component)]
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400166 for g,seqs in self.ligatures.items()}
167 self.ligatures = {g:v for g,v in self.ligatures.items() if v}
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400168 return bool (self.ligatures)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400169 else:
170 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400171
Behdad Esfahbod54660612013-07-21 18:16:55 -0400172@add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400173def closure_glyphs (self, s):
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400174 if self.Format == 1:
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400175 indices = self.Coverage.intersect (s.glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400176 if not indices or \
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400177 not all (c.intersect (s.glyphs) for c in self.LookAheadCoverage + self.BacktrackCoverage):
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400178 return []
179 return [self.Substitute[i] for i in indices]
180 else:
181 assert 0, "unknown format: %s" % self.Format
182
183@add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400184def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400185 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400186 indices = self.Coverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400187 self.Substitute = [self.Substitute[i] for i in indices]
Behdad Esfahbod14374262013-08-08 22:26:49 -0400188 # Now drop rules generating glyphs we don't want
189 indices = [i for i,sub in enumerate (self.Substitute) \
190 if sub in s.glyphs]
191 self.Substitute = [self.Substitute[i] for i in indices]
192 self.Coverage.remap (indices)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400193 self.GlyphCount = len (self.Substitute)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400194 return bool (self.GlyphCount and all (c.subset (s.glyphs) for c in self.LookAheadCoverage + self.BacktrackCoverage))
Behdad Esfahbod54660612013-07-21 18:16:55 -0400195 else:
196 assert 0, "unknown format: %s" % self.Format
197
198@add_method(fontTools.ttLib.tables.otTables.SinglePos)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400199def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400200 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400201 return len (self.Coverage.subset (s.glyphs))
Behdad Esfahbod54660612013-07-21 18:16:55 -0400202 elif self.Format == 2:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400203 indices = self.Coverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400204 self.Value = [self.Value[i] for i in indices]
205 self.ValueCount = len (self.Value)
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400206 return bool (self.ValueCount)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400207 else:
208 assert 0, "unknown format: %s" % self.Format
209
210@add_method(fontTools.ttLib.tables.otTables.PairPos)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400211def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400212 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400213 indices = self.Coverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400214 self.PairSet = [self.PairSet[i] for i in indices]
215 for p in self.PairSet:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400216 p.PairValueRecord = [r for r in p.PairValueRecord if r.SecondGlyph in s.glyphs]
Behdad Esfahbod54660612013-07-21 18:16:55 -0400217 p.PairValueCount = len (p.PairValueRecord)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400218 self.PairSet = [p for p in self.PairSet if p.PairValueCount]
Behdad Esfahbod54660612013-07-21 18:16:55 -0400219 self.PairSetCount = len (self.PairSet)
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400220 return bool (self.PairSetCount)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400221 elif self.Format == 2:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400222 class1_map = self.ClassDef1.subset (s.glyphs, remap=True)
223 class2_map = self.ClassDef2.subset (s.glyphs, remap=True)
Behdad Esfahbod4aa6ce32013-07-22 12:15:36 -0400224 self.Class1Record = [self.Class1Record[i] for i in class1_map]
225 for c in self.Class1Record:
226 c.Class2Record = [c.Class2Record[i] for i in class2_map]
227 self.Class1Count = len (class1_map)
228 self.Class2Count = len (class2_map)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400229 return bool (self.Class1Count and self.Class2Count and self.Coverage.subset (s.glyphs))
Behdad Esfahbod54660612013-07-21 18:16:55 -0400230 else:
231 assert 0, "unknown format: %s" % self.Format
232
233@add_method(fontTools.ttLib.tables.otTables.CursivePos)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400234def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400235 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400236 indices = self.Coverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400237 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices]
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400238 self.EntryExitCount = len (self.EntryExitRecord)
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400239 return bool (self.EntryExitCount)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400240 else:
241 assert 0, "unknown format: %s" % self.Format
242
243@add_method(fontTools.ttLib.tables.otTables.MarkBasePos)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400244def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400245 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400246 mark_indices = self.MarkCoverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400247 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i] for i in mark_indices]
248 self.MarkArray.MarkCount = len (self.MarkArray.MarkRecord)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400249 base_indices = self.BaseCoverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400250 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i] for i in base_indices]
251 self.BaseArray.BaseCount = len (self.BaseArray.BaseRecord)
Behdad Esfahbodc6396b72013-07-22 12:31:33 -0400252 # Prune empty classes
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400253 class_indices = unique_sorted (v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbodc6396b72013-07-22 12:31:33 -0400254 self.ClassCount = len (class_indices)
255 for m in self.MarkArray.MarkRecord:
256 m.Class = class_indices.index (m.Class)
257 for b in self.BaseArray.BaseRecord:
258 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400259 return bool (self.ClassCount and self.MarkArray.MarkCount and self.BaseArray.BaseCount)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400260 else:
261 assert 0, "unknown format: %s" % self.Format
262
263@add_method(fontTools.ttLib.tables.otTables.MarkLigPos)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400264def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400265 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400266 mark_indices = self.MarkCoverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400267 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i] for i in mark_indices]
268 self.MarkArray.MarkCount = len (self.MarkArray.MarkRecord)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400269 ligature_indices = self.LigatureCoverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400270 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i] for i in ligature_indices]
271 self.LigatureArray.LigatureCount = len (self.LigatureArray.LigatureAttach)
Behdad Esfahbodc6396b72013-07-22 12:31:33 -0400272 # Prune empty classes
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400273 class_indices = unique_sorted (v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbodc6396b72013-07-22 12:31:33 -0400274 self.ClassCount = len (class_indices)
275 for m in self.MarkArray.MarkRecord:
276 m.Class = class_indices.index (m.Class)
277 for l in self.LigatureArray.LigatureAttach:
278 for c in l.ComponentRecord:
279 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400280 return bool (self.ClassCount and self.MarkArray.MarkCount and self.LigatureArray.LigatureCount)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400281 else:
282 assert 0, "unknown format: %s" % self.Format
283
284@add_method(fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400285def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400286 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400287 mark1_indices = self.Mark1Coverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400288 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i] for i in mark1_indices]
289 self.Mark1Array.MarkCount = len (self.Mark1Array.MarkRecord)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400290 mark2_indices = self.Mark2Coverage.subset (s.glyphs)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400291 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i] for i in mark2_indices]
292 self.Mark2Array.MarkCount = len (self.Mark2Array.Mark2Record)
Behdad Esfahbodc6396b72013-07-22 12:31:33 -0400293 # Prune empty classes
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400294 class_indices = unique_sorted (v.Class for v in self.Mark1Array.MarkRecord)
Behdad Esfahbodc6396b72013-07-22 12:31:33 -0400295 self.ClassCount = len (class_indices)
296 for m in self.Mark1Array.MarkRecord:
297 m.Class = class_indices.index (m.Class)
298 for b in self.Mark2Array.Mark2Record:
299 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400300 return bool (self.ClassCount and self.Mark1Array.MarkCount and self.Mark2Array.MarkCount)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400301 else:
302 assert 0, "unknown format: %s" % self.Format
303
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400304@add_method(fontTools.ttLib.tables.otTables.SingleSubst,
305 fontTools.ttLib.tables.otTables.MultipleSubst,
306 fontTools.ttLib.tables.otTables.AlternateSubst,
307 fontTools.ttLib.tables.otTables.LigatureSubst,
308 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
309 fontTools.ttLib.tables.otTables.SinglePos,
310 fontTools.ttLib.tables.otTables.PairPos,
311 fontTools.ttLib.tables.otTables.CursivePos,
312 fontTools.ttLib.tables.otTables.MarkBasePos,
313 fontTools.ttLib.tables.otTables.MarkLigPos,
314 fontTools.ttLib.tables.otTables.MarkMarkPos)
315def subset_lookups (self, lookup_indices):
316 pass
317
318@add_method(fontTools.ttLib.tables.otTables.SingleSubst,
319 fontTools.ttLib.tables.otTables.MultipleSubst,
320 fontTools.ttLib.tables.otTables.AlternateSubst,
321 fontTools.ttLib.tables.otTables.LigatureSubst,
322 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
323 fontTools.ttLib.tables.otTables.SinglePos,
324 fontTools.ttLib.tables.otTables.PairPos,
325 fontTools.ttLib.tables.otTables.CursivePos,
326 fontTools.ttLib.tables.otTables.MarkBasePos,
327 fontTools.ttLib.tables.otTables.MarkLigPos,
328 fontTools.ttLib.tables.otTables.MarkMarkPos)
329def collect_lookups (self):
330 return []
331
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400332@add_method(fontTools.ttLib.tables.otTables.ContextSubst, fontTools.ttLib.tables.otTables.ChainContextSubst,
333 fontTools.ttLib.tables.otTables.ContextPos, fontTools.ttLib.tables.otTables.ChainContextPos)
334def __classify_context (self):
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400335
336 class ContextHelper:
337 def __init__ (self, klass, Format):
338 if klass.__name__.endswith ('Subst'):
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400339 Typ = 'Sub'
340 Type = 'Subst'
Behdad Esfahbod6870f8a2013-07-23 16:18:30 -0400341 else:
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400342 Typ = 'Pos'
343 Type = 'Pos'
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400344 if klass.__name__.startswith ('Chain'):
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400345 Chain = 'Chain'
346 else:
347 Chain = ''
348 ChainTyp = Chain+Typ
349
350 self.Typ = Typ
351 self.Type = Type
352 self.Chain = Chain
353 self.ChainTyp = ChainTyp
354
355 self.LookupRecord = Type+'LookupRecord'
356
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400357 if Format == 1:
358 ContextData = None
359 ChainContextData = None
360 RuleData = lambda r: r.Input
361 ChainRuleData = lambda r: r.Backtrack + r.Input + r.LookAhead
Behdad Esfahbod44fc6f62013-07-24 11:24:39 -0400362 SetRuleData = None
363 ChainSetRuleData = None
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400364 elif Format == 2:
Behdad Esfahbode3f20732013-07-24 11:26:43 -0400365 ContextData = lambda r: (r.ClassDef,)
366 ChainContextData = lambda r: (r.LookAheadClassDef, r.InputClassDef, r.BacktrackClassDef)
367 RuleData = lambda r: (r.Class,)
368 ChainRuleData = lambda r: (r.LookAhead, r.Input, r.Backtrack)
369 def SetRuleData (r, d): (r.Class,) = d
370 def ChainSetRuleData (r, d): (r.LookAhead, r.Input, r.Backtrack) = d
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400371 elif Format == 3:
372 ContextData = None
373 ChainContextData = None
374 RuleData = lambda r: r.Coverage
375 ChainRuleData = lambda r: r.LookAheadCoverage + r.InputCoverage + r.BacktrackCoverage
Behdad Esfahbod44fc6f62013-07-24 11:24:39 -0400376 SetRuleData = None
377 ChainSetRuleData = None
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400378 else:
379 assert 0, "unknown format: %s" % Format
380
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400381 if Chain:
Behdad Esfahbod707a37a2013-07-23 21:08:26 -0400382 self.ContextData = ChainContextData
Behdad Esfahbodb8d55882013-07-23 22:17:39 -0400383 self.RuleData = ChainRuleData
Behdad Esfahbod44fc6f62013-07-24 11:24:39 -0400384 self.SetRuleData = ChainSetRuleData
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400385 else:
Behdad Esfahbod707a37a2013-07-23 21:08:26 -0400386 self.ContextData = ContextData
Behdad Esfahbodb8d55882013-07-23 22:17:39 -0400387 self.RuleData = RuleData
Behdad Esfahbod44fc6f62013-07-24 11:24:39 -0400388 self.SetRuleData = SetRuleData
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400389
Behdad Esfahbodbac31f52013-07-23 23:00:39 -0400390 if Format == 1:
391 self.Rule = ChainTyp+'Rule'
392 self.RuleCount = ChainTyp+'RuleCount'
393 self.RuleSet = ChainTyp+'RuleSet'
394 self.RuleSetCount = ChainTyp+'RuleSetCount'
395 elif Format == 2:
396 self.Rule = ChainTyp+'ClassRule'
397 self.RuleCount = ChainTyp+'ClassRuleCount'
398 self.RuleSet = ChainTyp+'ClassSet'
399 self.RuleSetCount = ChainTyp+'ClassSetCount'
Behdad Esfahbod89987002013-07-23 23:07:42 -0400400
Behdad Esfahbodbac31f52013-07-23 23:00:39 -0400401 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
Behdad Esfahbod27108392013-07-23 16:40:47 -0400402
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400403 if self.Format not in [1, 2, 3]:
404 return None # Don't shoot the messenger; let it go
405 if not hasattr (self.__class__, "__ContextHelpers"):
406 self.__class__.__ContextHelpers = {}
407 if self.Format not in self.__class__.__ContextHelpers:
408 self.__class__.__ContextHelpers[self.Format] = ContextHelper (self.__class__, self.Format)
409 return self.__class__.__ContextHelpers[self.Format]
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400410
Behdad Esfahbodf2b6d9c2013-07-23 17:31:54 -0400411@add_method(fontTools.ttLib.tables.otTables.ContextSubst, fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400412def closure_glyphs (self, s):
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400413 c = self.__classify_context ()
414
Behdad Esfahbod00776972013-07-23 15:33:00 -0400415 if self.Format == 1:
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400416 indices = self.Coverage.intersect (s.glyphs)
Behdad Esfahbodeeca9822013-07-23 17:42:17 -0400417 rss = getattr (self, c.RuleSet)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400418 return sum ((s.table.LookupList.Lookup[ll.LookupListIndex].closure_glyphs (s) \
Behdad Esfahboddd6fc842013-08-06 11:12:49 -0400419 for i in indices if rss[i] \
Behdad Esfahbodeeca9822013-07-23 17:42:17 -0400420 for r in getattr (rss[i], c.Rule) \
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400421 if r and all (g in s.glyphs for g in c.RuleData (r)) \
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400422 for ll in getattr (r, c.LookupRecord) if ll \
Behdad Esfahbodeeca9822013-07-23 17:42:17 -0400423 ), [])
Behdad Esfahbod00776972013-07-23 15:33:00 -0400424 elif self.Format == 2:
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400425 if not self.Coverage.intersect (s.glyphs):
Behdad Esfahbod31084302013-07-23 22:22:38 -0400426 return []
Behdad Esfahbode10803e2013-08-08 21:09:27 -0400427 # XXX Intersect glyphs with coverage before going further
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400428 indices = getattr (self, c.ClassDef).intersect (s.glyphs)
Behdad Esfahbodbac31f52013-07-23 23:00:39 -0400429 rss = getattr (self, c.RuleSet)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400430 return sum ((s.table.LookupList.Lookup[ll.LookupListIndex].closure_glyphs (s) \
Behdad Esfahboddd6fc842013-08-06 11:12:49 -0400431 for i in indices if rss[i] \
Behdad Esfahbodbac31f52013-07-23 23:00:39 -0400432 for r in getattr (rss[i], c.Rule) \
Behdad Esfahbode10803e2013-08-08 21:09:27 -0400433 if r and all (all (cd.intersects_class (s.glyphs, k) for k in klist) \
434 for cd,klist in zip (c.ContextData (self), c.RuleData (r))) \
Behdad Esfahbodb8d55882013-07-23 22:17:39 -0400435 for ll in getattr (r, c.LookupRecord) if ll \
436 ), [])
Behdad Esfahbod00776972013-07-23 15:33:00 -0400437 elif self.Format == 3:
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400438 if not all (x.intersect (s.glyphs) for x in c.RuleData (self)):
Behdad Esfahbod00776972013-07-23 15:33:00 -0400439 return []
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400440 return sum ((s.table.LookupList.Lookup[ll.LookupListIndex].closure_glyphs (s) \
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400441 for ll in getattr (self, c.LookupRecord) if ll), [])
Behdad Esfahbod00776972013-07-23 15:33:00 -0400442 else:
443 assert 0, "unknown format: %s" % self.Format
444
Behdad Esfahbodcbba4a62013-07-23 17:27:18 -0400445@add_method(fontTools.ttLib.tables.otTables.ContextSubst, fontTools.ttLib.tables.otTables.ContextPos,
446 fontTools.ttLib.tables.otTables.ChainContextSubst, fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400447def subset_glyphs (self, s):
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400448 c = self.__classify_context ()
449
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400450 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400451 indices = self.Coverage.subset (s.glyphs)
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400452 rss = getattr (self, c.RuleSet)
453 rss = [rss[i] for i in indices]
454 for rs in rss:
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400455 if rs:
456 ss = getattr (rs, c.Rule)
457 ss = [r for r in ss \
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400458 if r and all (g in s.glyphs for g in c.RuleData (r))]
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400459 setattr (rs, c.Rule, ss)
460 setattr (rs, c.RuleCount, len (ss))
Behdad Esfahbodcbba4a62013-07-23 17:27:18 -0400461 # Prune empty subrulesets
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400462 rss = [rs for rs in rss if rs and getattr (rs, c.Rule)]
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400463 setattr (self, c.RuleSet, rss)
464 setattr (self, c.RuleSetCount, len (rss))
465 return bool (rss)
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400466 elif self.Format == 2:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400467 if not self.Coverage.subset (s.glyphs):
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400468 return False
Behdad Esfahbode10803e2013-08-08 21:09:27 -0400469 # XXX Intersect glyphs with coverage before going further
470 indices = getattr (self, c.ClassDef).subset (s.glyphs, remap=False)
Behdad Esfahbodbac31f52013-07-23 23:00:39 -0400471 rss = getattr (self, c.RuleSet)
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400472 rss = [rss[i] for i in indices]
473 ContextData = c.ContextData (self)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400474 klass_maps = [x.subset (s.glyphs, remap=True) for x in ContextData]
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400475 for rs in rss:
476 if rs:
Behdad Esfahbodbac31f52013-07-23 23:00:39 -0400477 ss = getattr (rs, c.Rule)
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400478 ss = [r for r in ss \
Behdad Esfahbode10803e2013-08-08 21:09:27 -0400479 if r and all (all (k in klass_map for k in klist) \
480 for klass_map,klist in zip (klass_maps, c.RuleData (r)))]
Behdad Esfahbodbac31f52013-07-23 23:00:39 -0400481 setattr (rs, c.Rule, ss)
482 setattr (rs, c.RuleCount, len (ss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400483
484 # Remap rule classes
485 for r in ss:
Behdad Esfahbode10803e2013-08-08 21:09:27 -0400486 c.SetRuleData (r, [[klass_map.index (k) for k in klist] \
487 for klass_map,klist in zip (klass_maps, c.RuleData (r))])
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400488 # Prune empty subrulesets
Behdad Esfahbodbac31f52013-07-23 23:00:39 -0400489 rss = [rs for rs in rss if rs and getattr (rs, c.Rule)]
490 setattr (self, c.RuleSet, rss)
491 setattr (self, c.RuleSetCount, len (rss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400492 return bool (rss)
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400493 elif self.Format == 3:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400494 return all (x.subset (s.glyphs) for x in c.RuleData (self))
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400495 else:
496 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400497
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400498@add_method(fontTools.ttLib.tables.otTables.ContextSubst, fontTools.ttLib.tables.otTables.ChainContextSubst,
499 fontTools.ttLib.tables.otTables.ContextPos, fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400500def subset_lookups (self, lookup_indices):
Behdad Esfahbod6870f8a2013-07-23 16:18:30 -0400501 c = self.__classify_context ()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400502
Behdad Esfahbod1f573632013-07-23 23:04:43 -0400503 if self.Format in [1, 2]:
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400504 for rs in getattr (self, c.RuleSet):
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400505 if rs:
506 for r in getattr (rs, c.Rule):
507 if r:
Behdad Esfahbod1f573632013-07-23 23:04:43 -0400508 setattr (r, c.LookupRecord, [ll for ll in getattr (r, c.LookupRecord) if ll \
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400509 if ll.LookupListIndex in lookup_indices])
510 for ll in getattr (r, c.LookupRecord):
511 if ll:
512 ll.LookupListIndex = lookup_indices.index (ll.LookupListIndex)
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400513 elif self.Format == 3:
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400514 setattr (self, c.LookupRecord, [ll for ll in getattr (self, c.LookupRecord) if ll \
Behdad Esfahbod6870f8a2013-07-23 16:18:30 -0400515 if ll.LookupListIndex in lookup_indices])
516 for ll in getattr (self, c.LookupRecord):
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400517 if ll:
518 ll.LookupListIndex = lookup_indices.index (ll.LookupListIndex)
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400519 else:
520 assert 0, "unknown format: %s" % self.Format
521
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400522@add_method(fontTools.ttLib.tables.otTables.ContextSubst, fontTools.ttLib.tables.otTables.ChainContextSubst,
523 fontTools.ttLib.tables.otTables.ContextPos, fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400524def collect_lookups (self):
Behdad Esfahbod6870f8a2013-07-23 16:18:30 -0400525 c = self.__classify_context ()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400526
Behdad Esfahbod1f573632013-07-23 23:04:43 -0400527 if self.Format in [1, 2]:
Behdad Esfahbod27108392013-07-23 16:40:47 -0400528 return [ll.LookupListIndex \
Behdad Esfahbodbac31f52013-07-23 23:00:39 -0400529 for rs in getattr (self, c.RuleSet) if rs \
530 for r in getattr (rs, c.Rule) if r \
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400531 for ll in getattr (r, c.LookupRecord) if ll]
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400532 elif self.Format == 3:
Behdad Esfahbod6870f8a2013-07-23 16:18:30 -0400533 return [ll.LookupListIndex \
Behdad Esfahbod7c225a62013-07-23 21:33:13 -0400534 for ll in getattr (self, c.LookupRecord) if ll]
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400535 else:
536 assert 0, "unknown format: %s" % self.Format
537
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400538@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400539def closure_glyphs (self, s):
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400540 if self.Format == 1:
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400541 return self.ExtSubTable.closure_glyphs (s)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400542 else:
543 assert 0, "unknown format: %s" % self.Format
544
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400545@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400546def subset_glyphs (self, s):
Behdad Esfahbod54660612013-07-21 18:16:55 -0400547 if self.Format == 1:
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400548 return self.ExtSubTable.subset_glyphs (s)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400549 else:
550 assert 0, "unknown format: %s" % self.Format
551
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400552@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, fontTools.ttLib.tables.otTables.ExtensionPos)
553def subset_lookups (self, lookup_indices):
554 if self.Format == 1:
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400555 return self.ExtSubTable.subset_lookups (lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400556 else:
557 assert 0, "unknown format: %s" % self.Format
558
559@add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, fontTools.ttLib.tables.otTables.ExtensionPos)
560def collect_lookups (self):
561 if self.Format == 1:
562 return self.ExtSubTable.collect_lookups ()
563 else:
564 assert 0, "unknown format: %s" % self.Format
565
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400566@add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400567def closure_glyphs (self, s):
568 return sum ((st.closure_glyphs (s) for st in self.SubTable if st), [])
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400569
570@add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400571def subset_glyphs (self, s):
572 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs (s)]
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400573 self.SubTableCount = len (self.SubTable)
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400574 return bool (self.SubTableCount)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400575
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400576@add_method(fontTools.ttLib.tables.otTables.Lookup)
577def subset_lookups (self, lookup_indices):
578 for s in self.SubTable:
579 s.subset_lookups (lookup_indices)
580
581@add_method(fontTools.ttLib.tables.otTables.Lookup)
582def collect_lookups (self):
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400583 return unique_sorted (sum ((st.collect_lookups () for st in self.SubTable if st), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400584
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400585@add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400586def subset_glyphs (self, s):
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400587 "Returns the indices of nonempty lookups."
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400588 return [i for (i,l) in enumerate (self.Lookup) if l and l.subset_glyphs (s)]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400589
590@add_method(fontTools.ttLib.tables.otTables.LookupList)
591def subset_lookups (self, lookup_indices):
Behdad Esfahbodafae8322013-07-24 18:57:06 -0400592 self.Lookup = [self.Lookup[i] for i in lookup_indices if i < self.LookupCount]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400593 self.LookupCount = len (self.Lookup)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400594 for l in self.Lookup:
595 l.subset_lookups (lookup_indices)
596
597@add_method(fontTools.ttLib.tables.otTables.LookupList)
598def closure_lookups (self, lookup_indices):
Behdad Esfahbodbb7e2132013-07-23 13:48:35 -0400599 lookup_indices = unique_sorted (lookup_indices)
600 recurse = lookup_indices
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400601 while True:
Behdad Esfahbodafae8322013-07-24 18:57:06 -0400602 recurse_lookups = sum ((self.Lookup[i].collect_lookups () for i in recurse if i < self.LookupCount), [])
603 recurse_lookups = [l for l in recurse_lookups if l not in lookup_indices and l < self.LookupCount]
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400604 if not recurse_lookups:
Behdad Esfahbodbb7e2132013-07-23 13:48:35 -0400605 return unique_sorted (lookup_indices)
606 recurse_lookups = unique_sorted (recurse_lookups)
607 lookup_indices.extend (recurse_lookups)
608 recurse = recurse_lookups
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400609
610@add_method(fontTools.ttLib.tables.otTables.Feature)
611def subset_lookups (self, lookup_indices):
612 self.LookupListIndex = [l for l in self.LookupListIndex if l in lookup_indices]
613 # Now map them.
614 self.LookupListIndex = [lookup_indices.index (l) for l in self.LookupListIndex]
615 self.LookupCount = len (self.LookupListIndex)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400616 return self.LookupCount
Behdad Esfahbod54660612013-07-21 18:16:55 -0400617
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400618@add_method(fontTools.ttLib.tables.otTables.Feature)
619def collect_lookups (self):
620 return self.LookupListIndex[:]
621
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400622@add_method(fontTools.ttLib.tables.otTables.FeatureList)
623def subset_lookups (self, lookup_indices):
624 "Returns the indices of nonempty features."
625 feature_indices = [i for (i,f) in enumerate (self.FeatureRecord) if f.Feature.subset_lookups (lookup_indices)]
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400626 self.subset_features (feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400627 return feature_indices
628
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400629@add_method(fontTools.ttLib.tables.otTables.FeatureList)
630def collect_lookups (self, feature_indices):
631 return unique_sorted (sum ((self.FeatureRecord[i].Feature.collect_lookups () for i in feature_indices
632 if i < self.FeatureCount), []))
633
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400634@add_method(fontTools.ttLib.tables.otTables.FeatureList)
635def subset_features (self, feature_indices):
636 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
637 self.FeatureCount = len (self.FeatureRecord)
638 return bool (self.FeatureCount)
639
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400640@add_method(fontTools.ttLib.tables.otTables.DefaultLangSys, fontTools.ttLib.tables.otTables.LangSys)
641def subset_features (self, feature_indices):
Behdad Esfahbod69ce1502013-07-22 18:00:31 -0400642 if self.ReqFeatureIndex in feature_indices:
643 self.ReqFeatureIndex = feature_indices.index (self.ReqFeatureIndex)
644 else:
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400645 self.ReqFeatureIndex = 65535
646 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
Behdad Esfahbod69ce1502013-07-22 18:00:31 -0400647 # Now map them.
648 self.FeatureIndex = [feature_indices.index (f) for f in self.FeatureIndex if f in feature_indices]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400649 self.FeatureCount = len (self.FeatureIndex)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400650 return bool (self.FeatureCount or self.ReqFeatureIndex != 65535)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400651
652@add_method(fontTools.ttLib.tables.otTables.DefaultLangSys, fontTools.ttLib.tables.otTables.LangSys)
653def collect_features (self):
654 feature_indices = self.FeatureIndex[:]
655 if self.ReqFeatureIndex != 65535:
656 feature_indices.append (self.ReqFeatureIndex)
657 return unique_sorted (feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400658
659@add_method(fontTools.ttLib.tables.otTables.Script)
660def subset_features (self, feature_indices):
661 if self.DefaultLangSys and not self.DefaultLangSys.subset_features (feature_indices):
662 self.DefaultLangSys = None
663 self.LangSysRecord = [l for l in self.LangSysRecord if l.LangSys.subset_features (feature_indices)]
664 self.LangSysCount = len (self.LangSysRecord)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400665 return bool (self.LangSysCount or self.DefaultLangSys)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400666
667@add_method(fontTools.ttLib.tables.otTables.Script)
668def collect_features (self):
Behdad Esfahbod2307c8b2013-07-23 11:18:13 -0400669 feature_indices = [l.LangSys.collect_features () for l in self.LangSysRecord]
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400670 if self.DefaultLangSys:
671 feature_indices.append (self.DefaultLangSys.collect_features ())
672 return unique_sorted (sum (feature_indices, []))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400673
674@add_method(fontTools.ttLib.tables.otTables.ScriptList)
675def subset_features (self, feature_indices):
676 self.ScriptRecord = [s for s in self.ScriptRecord if s.Script.subset_features (feature_indices)]
677 self.ScriptCount = len (self.ScriptRecord)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400678 return bool (self.ScriptCount)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400679
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400680@add_method(fontTools.ttLib.tables.otTables.ScriptList)
681def collect_features (self):
682 return unique_sorted (sum ((s.Script.collect_features () for s in self.ScriptRecord), []))
683
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400684@add_method(fontTools.ttLib.getTableClass('GSUB'))
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400685def closure_glyphs (self, s):
686 s.table = self.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400687 feature_indices = self.table.ScriptList.collect_features ()
688 lookup_indices = self.table.FeatureList.collect_lookups (feature_indices)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400689 orig_glyphs = s.glyphs
690 glyphs = unique_sorted (s.glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400691 while True:
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400692 s.glyphs = glyphs
693 additions = (sum ((self.table.LookupList.Lookup[i].closure_glyphs (s) \
Behdad Esfahbodafae8322013-07-24 18:57:06 -0400694 for i in lookup_indices if i < self.table.LookupList.LookupCount), []))
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400695 additions = unique_sorted (g for g in additions if g not in glyphs)
696 if not additions:
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400697 s.glyphs = orig_glyphs
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400698 return glyphs
699 glyphs.extend (additions)
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400700 del s.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400701
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400702@add_method(fontTools.ttLib.getTableClass('GSUB'), fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400703def subset_glyphs (self, s):
Behdad Esfahboda6dbb7a2013-07-31 19:53:57 -0400704 s.glyphs = s.glyphs_gsubed
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400705 lookup_indices = self.table.LookupList.subset_glyphs (s)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400706 self.subset_lookups (lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400707 self.prune_lookups ()
708 return True
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400709
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400710@add_method(fontTools.ttLib.getTableClass('GSUB'), fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400711def subset_lookups (self, lookup_indices):
712 "Retrains specified lookups, then removes empty features, language systems, and scripts."
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400713 self.table.LookupList.subset_lookups (lookup_indices)
714 feature_indices = self.table.FeatureList.subset_lookups (lookup_indices)
715 self.table.ScriptList.subset_features (feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400716
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400717@add_method(fontTools.ttLib.getTableClass('GSUB'), fontTools.ttLib.getTableClass('GPOS'))
718def prune_lookups (self):
719 "Remove unreferenced lookups"
720 feature_indices = self.table.ScriptList.collect_features ()
721 lookup_indices = self.table.FeatureList.collect_lookups (feature_indices)
722 lookup_indices = self.table.LookupList.closure_lookups (lookup_indices)
723 self.subset_lookups (lookup_indices)
724
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400725@add_method(fontTools.ttLib.getTableClass('GSUB'), fontTools.ttLib.getTableClass('GPOS'))
726def subset_feature_tags (self, feature_tags):
727 feature_indices = [i for (i,f) in enumerate (self.table.FeatureList.FeatureRecord) if f.FeatureTag in feature_tags]
728 self.table.FeatureList.subset_features (feature_indices)
729 self.table.ScriptList.subset_features (feature_indices)
730
731@add_method(fontTools.ttLib.getTableClass('GSUB'), fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbodd7b6f8f2013-07-23 12:46:52 -0400732def prune_pre_subset (self, options):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -0400733 if options.layout_features and '*' not in options.layout_features:
734 self.subset_feature_tags (options.layout_features)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400735 self.prune_lookups ()
736 return True
737
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400738@add_method(fontTools.ttLib.getTableClass('GDEF'))
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400739def subset_glyphs (self, s):
Behdad Esfahboda6dbb7a2013-07-31 19:53:57 -0400740 glyphs = s.glyphs_gsubed
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400741 table = self.table
742 if table.LigCaretList:
Behdad Esfahboda6dbb7a2013-07-31 19:53:57 -0400743 indices = table.LigCaretList.Coverage.subset (glyphs)
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400744 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i] for i in indices]
745 table.LigCaretList.LigGlyphCount = len (table.LigCaretList.LigGlyph)
746 if not table.LigCaretList.LigGlyphCount:
747 table.LigCaretList = None
748 if table.MarkAttachClassDef:
Behdad Esfahboda6dbb7a2013-07-31 19:53:57 -0400749 table.MarkAttachClassDef.classDefs = {g:v for g,v in table.MarkAttachClassDef.classDefs.items() if g in glyphs}
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400750 if not table.MarkAttachClassDef.classDefs:
751 table.MarkAttachClassDef = None
752 if table.GlyphClassDef:
Behdad Esfahboda6dbb7a2013-07-31 19:53:57 -0400753 table.GlyphClassDef.classDefs = {g:v for g,v in table.GlyphClassDef.classDefs.items() if g in glyphs}
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400754 if not table.GlyphClassDef.classDefs:
755 table.GlyphClassDef = None
756 if table.AttachList:
Behdad Esfahboda6dbb7a2013-07-31 19:53:57 -0400757 indices = table.AttachList.Coverage.subset (glyphs)
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400758 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i] for i in indices]
759 table.AttachList.GlyphCount = len (table.AttachList.AttachPoint)
760 if not table.AttachList.GlyphCount:
761 table.AttachList = None
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400762 return bool (table.LigCaretList or table.MarkAttachClassDef or table.GlyphClassDef or table.AttachList)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -0400763
Behdad Esfahbodfd3923e2013-07-22 12:48:17 -0400764@add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbodd4e33a72013-07-24 18:51:05 -0400765def prune_pre_subset (self, options):
766 # Prune unknown kern table types
767 self.kernTables = [t for t in self.kernTables if hasattr (t, 'kernTable')]
768 return bool (self.kernTables)
769
770@add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400771def subset_glyphs (self, s):
Behdad Esfahboda6dbb7a2013-07-31 19:53:57 -0400772 glyphs = s.glyphs_gsubed
Behdad Esfahbod5270ec42013-07-22 12:57:02 -0400773 for t in self.kernTables:
Behdad Esfahboda6dbb7a2013-07-31 19:53:57 -0400774 t.kernTable = {(a,b):v for ((a,b),v) in t.kernTable.items() if a in glyphs and b in glyphs}
Behdad Esfahbod5270ec42013-07-22 12:57:02 -0400775 self.kernTables = [t for t in self.kernTables if t.kernTable]
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400776 return bool (self.kernTables)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -0400777
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -0400778@add_method(fontTools.ttLib.getTableClass('hmtx'), fontTools.ttLib.getTableClass('vmtx'))
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400779def subset_glyphs (self, s):
780 self.metrics = {g:v for g,v in self.metrics.items() if g in s.glyphs}
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400781 return bool (self.metrics)
Behdad Esfahbodc7160442013-07-22 14:29:08 -0400782
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -0400783@add_method(fontTools.ttLib.getTableClass('hdmx'))
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400784def subset_glyphs (self, s):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -0400785 self.hdmx = {sz:{g:v for g,v in l.items() if g in s.glyphs} for (sz,l) in self.hdmx.items()}
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400786 return bool (self.hdmx)
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -0400787
Behdad Esfahbode45d6af2013-07-22 15:29:17 -0400788@add_method(fontTools.ttLib.getTableClass('VORG'))
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400789def subset_glyphs (self, s):
790 self.VOriginRecords = {g:v for g,v in self.VOriginRecords.items() if g in s.glyphs}
Behdad Esfahbode45d6af2013-07-22 15:29:17 -0400791 self.numVertOriginYMetrics = len (self.VOriginRecords)
792 return True # Never drop; has default metrics
793
Behdad Esfahbod8c646f62013-07-22 15:06:23 -0400794@add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod8c486d82013-07-24 13:34:47 -0400795def prune_pre_subset (self, options):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -0400796 if not options.glyph_names:
Behdad Esfahbod42648242013-07-23 12:56:06 -0400797 self.formatType = 3.0
798 return True
799
800@add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400801def subset_glyphs (self, s):
Behdad Esfahbod42648242013-07-23 12:56:06 -0400802 self.extraNames = [] # This seems to do it
Behdad Esfahbodc9dec9d2013-07-23 10:28:47 -0400803 return True
Behdad Esfahbod653e9742013-07-22 15:17:12 -0400804
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400805# Copied from _g_l_y_f.py
806ARG_1_AND_2_ARE_WORDS = 0x0001 # if set args are words otherwise they are bytes
807ARGS_ARE_XY_VALUES = 0x0002 # if set args are xy values, otherwise they are points
808ROUND_XY_TO_GRID = 0x0004 # for the xy values if above is true
809WE_HAVE_A_SCALE = 0x0008 # Sx = Sy, otherwise scale == 1.0
810NON_OVERLAPPING = 0x0010 # set to same value for all components (obsolete!)
811MORE_COMPONENTS = 0x0020 # indicates at least one more glyph after this one
812WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Sx, Sy
813WE_HAVE_A_TWO_BY_TWO = 0x0080 # t00, t01, t10, t11
814WE_HAVE_INSTRUCTIONS = 0x0100 # instructions follow
815USE_MY_METRICS = 0x0200 # apply these metrics to parent glyph
816OVERLAP_COMPOUND = 0x0400 # used by Apple in GX fonts
817SCALED_COMPONENT_OFFSET = 0x0800 # composite designed to have the component offset scaled (designed for Apple)
818UNSCALED_COMPONENT_OFFSET = 0x1000 # composite designed not to have the component offset scaled (designed for MS)
819
820@add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
821def getComponentNamesFast (self, glyfTable):
822 if struct.unpack(">h", self.data[:2])[0] >= 0:
823 return [] # Not composite
824 data = self.data
825 i = 10
826 components = []
827 more = 1
828 while more:
829 flags, glyphID = struct.unpack(">HH", data[i:i+4])
830 i += 4
831 flags = int(flags)
832 components.append (glyfTable.getGlyphName (int (glyphID)))
833
834 if flags & ARG_1_AND_2_ARE_WORDS: i += 4
835 else: i += 2
836 if flags & WE_HAVE_A_SCALE: i += 2
837 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
838 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
839 more = flags & MORE_COMPONENTS
840 return components
841
842@add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
843def remapComponentsFast (self, indices):
844 if struct.unpack(">h", self.data[:2])[0] >= 0:
845 return # Not composite
846 data = bytearray (self.data)
847 i = 10
848 more = 1
849 while more:
850 flags = (data[i] << 8) | data[i+1]
851 glyphID = (data[i+2] << 8) | data[i+3]
852 # Remap
853 glyphID = indices.index (glyphID)
854 data[i+2] = glyphID >> 8
855 data[i+3] = glyphID & 0xFF
856 i += 4
857 flags = int(flags)
858
859 if flags & ARG_1_AND_2_ARE_WORDS: i += 4
860 else: i += 2
861 if flags & WE_HAVE_A_SCALE: i += 2
862 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
863 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
864 more = flags & MORE_COMPONENTS
865 self.data = str (data)
866
Behdad Esfahbod6ec88542013-07-24 16:52:47 -0400867@add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
868def dropInstructionsFast (self):
Behdad Esfahbod6ec88542013-07-24 16:52:47 -0400869 numContours = struct.unpack(">h", self.data[:2])[0]
870 data = bytearray (self.data)
871 i = 10
872 if numContours >= 0:
873 i += 2 * numContours # endPtsOfContours
874 instructionLen = (data[i] << 8) | data[i+1]
875 # Zero it
876 data[i] = data [i+1] = 0
877 i += 2
Behdad Esfahbod0fb69882013-07-24 17:25:35 -0400878 if instructionLen:
879 # Splice it out
880 data = data[:i] + data[i+instructionLen:]
Behdad Esfahbod6ec88542013-07-24 16:52:47 -0400881 else:
882 more = 1
883 while more:
884 flags = (data[i] << 8) | data[i+1]
885 # Turn instruction flag off
886 flags &= ~WE_HAVE_INSTRUCTIONS
887 data[i+0] = flags >> 8
888 data[i+1] = flags & 0xFF
889 i += 4
890 flags = int(flags)
891
892 if flags & ARG_1_AND_2_ARE_WORDS: i += 4
893 else: i += 2
894 if flags & WE_HAVE_A_SCALE: i += 2
895 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
896 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
897 more = flags & MORE_COMPONENTS
898 # Cut off
899 data = data[:i]
900 if len(data) % 4:
901 # add pad bytes
902 nPadBytes = 4 - (len(data) % 4)
903 for i in range (nPadBytes):
904 data.append (0)
905 self.data = str (data)
906
Behdad Esfahbod861d9152013-07-22 16:47:24 -0400907@add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod254442b2013-07-31 14:20:13 -0400908def closure_glyphs (self, s):
909 glyphs = unique_sorted (s.glyphs)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -0400910 decompose = glyphs
911 # I don't know if component glyphs can be composite themselves.
912 # We handle them anyway.
913 while True:
914 components = []
915 for g in decompose:
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400916 if g not in self.glyphs:
Behdad Esfahbodf8c20e42013-07-23 23:13:23 -0400917 continue
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400918 gl = self.glyphs[g]
919 if hasattr (gl, "data"):
920 for c in gl.getComponentNamesFast (self):
921 if c not in glyphs:
922 components.append (c)
923 else:
924 # TTX seems to expand gid0..3 always
925 if gl.isComposite ():
926 for c in gl.components:
927 if c.glyphName not in glyphs:
928 components.append (c.glyphName)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -0400929 components = [c for c in components if c not in glyphs]
930 if not components:
931 return glyphs
932 decompose = unique_sorted (components)
933 glyphs.extend (components)
934
935@add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400936def subset_glyphs (self, s):
937 self.glyphs = {g:v for g,v in self.glyphs.items() if g in s.glyphs}
938 indices = [i for i,g in enumerate (self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400939 for v in self.glyphs.values ():
940 if hasattr (v, "data"):
941 v.remapComponentsFast (indices)
942 else:
943 pass # No need
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400944 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbod4027dd82013-07-23 10:56:04 -0400945 return bool (self.glyphs)
Behdad Esfahbod861d9152013-07-22 16:47:24 -0400946
Behdad Esfahboded98c612013-07-23 12:37:41 -0400947@add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbodd7b6f8f2013-07-23 12:46:52 -0400948def prune_post_subset (self, options):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -0400949 if not options.hinting:
Behdad Esfahbod6ec88542013-07-24 16:52:47 -0400950 for v in self.glyphs.values ():
951 if hasattr (v, "data"):
952 v.dropInstructionsFast ()
953 else:
954 v.program = fontTools.ttLib.tables.ttProgram.Program()
955 v.program.fromBytecode([])
Behdad Esfahboded98c612013-07-23 12:37:41 -0400956 return True
957
Behdad Esfahbod2b677c82013-07-23 13:37:13 -0400958@add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400959def subset_glyphs (self, s):
Behdad Esfahbod2b677c82013-07-23 13:37:13 -0400960 assert 0, "unimplemented"
961
Behdad Esfahbod653e9742013-07-22 15:17:12 -0400962@add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -0400963def closure_glyphs (self, s):
964 tables = [t for t in self.tables if t.platformID == 3 and t.platEncID in [1, 10]]
965 extra = []
Behdad Esfahbod98259f22013-07-31 20:16:24 -0400966 for u in s.unicodes_requested:
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -0400967 found = False
968 for table in tables:
969 if u in table.cmap:
970 extra.append (table.cmap[u])
971 found = True
972 break
973 if not found:
974 s.log ("No glyph for Unicode value %s; skipping." % u)
975 return extra
976
977@add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbodabb50a12013-07-23 12:58:37 -0400978def prune_pre_subset (self, options):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -0400979 if not options.legacy_cmap:
Behdad Esfahbodde4a15b2013-07-23 13:05:42 -0400980 # Drop non-Unicode / non-Symbol cmaps
981 self.tables = [t for t in self.tables if t.platformID == 3 and t.platEncID in [0, 1, 10]]
Behdad Esfahbod97e17b82013-07-31 15:59:21 -0400982 if not options.symbol_cmap:
Behdad Esfahbodde4a15b2013-07-23 13:05:42 -0400983 self.tables = [t for t in self.tables if t.platformID == 3 and t.platEncID in [1, 10]]
Behdad Esfahbodabb50a12013-07-23 12:58:37 -0400984 # TODO Only keep one subtable?
Behdad Esfahbodabb50a12013-07-23 12:58:37 -0400985 # For now, drop format=0 which can't be subset_glyphs easily?
986 self.tables = [t for t in self.tables if t.format != 0]
987 return bool (self.tables)
988
989@add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod3d513b72013-07-31 14:11:40 -0400990def subset_glyphs (self, s):
Behdad Esfahboda6dbb7a2013-07-31 19:53:57 -0400991 s.glyphs = s.glyphs_cmaped
Behdad Esfahbod653e9742013-07-22 15:17:12 -0400992 for t in self.tables:
Behdad Esfahbod9453a362013-07-22 16:21:24 -0400993 # For reasons I don't understand I need this here
994 # to force decompilation of the cmap format 14.
995 try:
996 getattr (t, "asdf")
997 except AttributeError:
998 pass
Behdad Esfahbodb13d7902013-07-22 16:01:15 -0400999 if t.format == 14:
Behdad Esfahbod9453a362013-07-22 16:21:24 -04001000 # XXX We drop all the default-UVS mappings (g==None)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001001 t.uvsDict = {v:[(u,g) for (u,g) in l if g in s.glyphs] for (v,l) in t.uvsDict.items()}
Behdad Esfahbodb13d7902013-07-22 16:01:15 -04001002 t.uvsDict = {v:l for (v,l) in t.uvsDict.items() if l}
1003 else:
Behdad Esfahbodc4eb3db2013-07-31 19:56:19 -04001004 t.cmap = {u:g for (u,g) in t.cmap.items() if g in s.glyphs_requested or u in s.unicodes_requested}
Behdad Esfahbodb13d7902013-07-22 16:01:15 -04001005 self.tables = [t for t in self.tables if (t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbod7e4bfc32013-07-22 18:47:32 -04001006 # XXX Convert formats when needed
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001007 return bool (self.tables)
1008
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001009@add_method(fontTools.ttLib.getTableClass('name'))
Behdad Esfahbodd7b6f8f2013-07-23 12:46:52 -04001010def prune_pre_subset (self, options):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001011 if '*' not in options.name_IDs:
1012 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1013 if not options.name_legacy:
Behdad Esfahbod20faeb02013-07-23 13:19:03 -04001014 self.names = [n for n in self.names if n.platformID == 3 and n.platEncID == 1]
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001015 if '*' not in options.name_languages:
1016 self.names = [n for n in self.names if n.langID in options.name_languages]
Behdad Esfahbod20faeb02013-07-23 13:19:03 -04001017 return True # Retain even if empty
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001018
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001019
Behdad Esfahbodbc25f162013-07-23 12:56:54 -04001020drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'PCLT', 'LTSH']
Behdad Esfahbod103a12f2013-07-23 15:08:39 -04001021drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
Behdad Esfahbod38e852c2013-07-23 16:56:50 -04001022drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
Behdad Esfahboded98c612013-07-23 12:37:41 -04001023no_subset_tables = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2', 'loca', 'name', 'cvt ', 'fpgm', 'prep']
Behdad Esfahbodbc25f162013-07-23 12:56:54 -04001024hinting_tables = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod29df0462013-07-23 11:05:25 -04001025
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001026# Based on HarfBuzz shapers
1027layout_features_dict = {
1028 # Default shaper
1029 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1030 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
1031 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
1032 'ltr': ['ltra', 'ltrm'],
1033 'rtl': ['rtla', 'rtlm'],
1034 # Complex shapers
Behdad Esfahbodf36b5a92013-08-04 16:54:46 -04001035 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3', 'cswh', 'mset'],
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001036 'hangul': ['ljmo', 'vjmo', 'tjmo'],
1037 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'],
1038 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half', 'abvf', 'pstf', 'cfar', 'vatu', 'cjct',
1039 'init', 'pres', 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
1040}
1041layout_features_all = unique_sorted (sum (layout_features_dict.values (), []))
1042
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -04001043# TODO OS/2 ulUnicodeRange / ulCodePageRange?
Behdad Esfahbodf71267b2013-07-23 12:59:13 -04001044# TODO Drop unneeded GSUB/GPOS Script/LangSys entries
Behdad Esfahbod398d3892013-07-23 15:29:40 -04001045# TODO Avoid recursing too much
Behdad Esfahbode94aa0e2013-07-23 13:22:04 -04001046# TODO Text direction considerations
1047# TODO Text script / language considerations
Behdad Esfahbodb3ee60c2013-07-24 19:21:40 -04001048# TODO Drop unknown tables? Using DefaultTable.prune?
Behdad Esfahbod8c4f7cc2013-07-24 17:58:29 -04001049# TODO Drop GPOS Device records if not hinting?
Behdad Esfahbod56ebd042013-07-22 13:02:24 -04001050
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001051
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001052class Subsetter:
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001053
1054 class Options:
1055 drop_tables = drop_tables_default
1056 layout_features = layout_features_all
1057 hinting = False
1058 glyph_names = False
1059 legacy_cmap = False
1060 symbol_cmap = False
1061 name_IDs = [1, 2] # Family and Style
1062 name_legacy = False
1063 name_languages = [0x0409] # English
1064 mandatory_glyphs = True # First four for TrueType, .notdef for CFF
1065 recalc_bboxes = False # Slows us down
1066
1067 def __init__ (self, **kwargs):
1068
1069 self.set (**kwargs)
1070
1071 def set (self, **kwargs):
1072 for k,v in kwargs.items ():
1073 if not hasattr (self, k):
1074 raise Exception ("Unknown option '%s'" % k)
1075 setattr (self, k, v)
1076
1077 def parse_opts (self, argv, ignore_unknown=False):
1078 ret = []
1079 opts = {}
1080 for a in argv:
1081 if not a.startswith ('--'):
1082 ret.append (a)
1083 continue
1084 a = a[2:]
1085 i = a.find ('=')
1086 if i == -1:
1087 if a.startswith ("no-"):
1088 k = a[3:]
1089 v = False
1090 else:
1091 k = a
1092 v = True
1093 else:
1094 k = a[:i]
1095 v = a[i+1:]
1096 k = k.replace ('-', '_')
1097 if not hasattr (self, k):
1098 if ignore_unknown:
1099 ret.append (a)
1100 continue
1101 else:
1102 raise Exception ("Unknown option '%s'" % a)
1103
1104 ov = getattr (self, k)
1105 if isinstance (ov, bool):
1106 v = bool (v)
1107 elif isinstance (ov, int):
1108 v = int (v)
1109 elif isinstance (ov, list):
1110 v = v.split (',')
1111 v = [int (x, 0) if x[0] in range (10) else x for x in v]
1112
1113 opts[k] = v
1114 self.set (**opts)
1115
1116 return ret
1117
1118
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001119 def __init__ (self, font=None, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001120
1121 if isinstance (font, basestring):
1122 font = fontTools.ttx.TTFont (font)
1123 if not log:
1124 log = Logger()
1125 if not options:
1126 options = Options()
1127
Behdad Esfahbod88264a62013-07-31 14:45:13 -04001128 self.font = font
1129 self.options = options
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001130 self.log = log
Behdad Esfahbodc4eb3db2013-07-31 19:56:19 -04001131 self.unicodes_requested = set ()
1132 self.glyphs_requested = set ()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001133
Behdad Esfahbod618c0862013-07-31 20:11:17 -04001134 def populate (self, glyphs=[], unicodes=[], text=""):
Behdad Esfahbodc4eb3db2013-07-31 19:56:19 -04001135 self.unicodes_requested.update (unicodes)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001136 if isinstance (text, str):
Behdad Esfahbod618c0862013-07-31 20:11:17 -04001137 text = text.decode ("utf8")
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001138 for u in text:
Behdad Esfahbod618c0862013-07-31 20:11:17 -04001139 self.unicodes_requested.add (ord (u))
Behdad Esfahbodc4eb3db2013-07-31 19:56:19 -04001140 self.glyphs_requested.update (glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001141
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001142 def pre_prune (self):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001143
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001144 for tag in self.font.keys():
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001145 if tag == 'GlyphOrder': continue
1146
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001147 if tag in self.options.drop_tables or \
1148 (tag in hinting_tables and not self.options.hinting):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001149 self.log (tag, "dropped")
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001150 del self.font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001151 continue
1152
1153 clazz = fontTools.ttLib.getTableClass(tag)
1154
1155 if hasattr (clazz, 'prune_pre_subset'):
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001156 table = self.font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001157 retain = table.prune_pre_subset (self.options)
1158 self.log.lapse ("prune '%s'" % tag)
1159 if not retain:
1160 self.log (tag, "pruned to empty; dropped")
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001161 del self.font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001162 continue
1163 else:
1164 self.log (tag, "pruned")
1165
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001166 def closure_glyphs (self):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001167
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001168 self.glyphs = self.glyphs_requested
1169
1170 if 'cmap' in self.font:
1171 extra_glyphs = self.font['cmap'].closure_glyphs (self)
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001172 self.glyph = self.glyphs.copy ()
1173 self.glyphs.update (extra_glyphs)
1174 self.glyphs_cmaped = self.glyphs
1175
1176 if self.options.mandatory_glyphs:
1177 self.glyphs = self.glyphs.copy ()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001178 if 'glyf' in self.font:
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001179 for i in range (4):
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001180 self.glyphs.add (self.font.getGlyphName (i))
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001181 self.log ("Added first four glyphs to subset")
1182 else:
1183 self.glyphs.add ('.notdef')
1184 self.log ("Added .notdef glyph to subset")
1185
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001186 if 'GSUB' in self.font:
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001187 self.log ("Closing glyph list over 'GSUB': %d glyphs before" % len (self.glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001188 self.log.glyphs (self.glyphs, font=self.font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001189 self.glyphs = set (self.font['GSUB'].closure_glyphs (self))
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001190 self.log ("Closed glyph list over 'GSUB': %d glyphs after" % len (self.glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001191 self.log.glyphs (self.glyphs, font=self.font)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001192 self.log.lapse ("close glyph list over 'GSUB'")
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001193 self.glyphs_gsubed = self.glyphs
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001194
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001195 if 'glyf' in self.font:
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001196 self.log ("Closing glyph list over 'glyf': %d glyphs before" % len (self.glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001197 self.log.glyphs (self.glyphs, font=self.font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001198 self.glyphs = set (self.font['glyf'].closure_glyphs (self))
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001199 self.log ("Closed glyph list over 'glyf': %d glyphs after" % len (self.glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001200 self.log.glyphs (self.glyphs, font=self.font)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001201 self.log.lapse ("close glyph list over 'glyf'")
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001202 self.glyphs_glyfed = self.glyphs
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001203
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001204 self.glyphs_all = self.glyphs
1205
1206 self.log ("Retaining %d glyphs: " % len (self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001207
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001208 def subset_glyphs (self):
1209 for tag in self.font.keys():
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001210 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001211 clazz = fontTools.ttLib.getTableClass(tag)
1212
1213 if tag in no_subset_tables:
1214 self.log (tag, "subsetting not needed")
1215 elif hasattr (clazz, 'subset_glyphs'):
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001216 table = self.font[tag]
Behdad Esfahboda6dbb7a2013-07-31 19:53:57 -04001217 self.glyphs = self.glyphs_all
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001218 retain = table.subset_glyphs (self)
1219 self.log.lapse ("subset '%s'" % tag)
1220 if not retain:
1221 self.log (tag, "subsetted to empty; dropped")
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001222 del self.font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001223 else:
1224 self.log (tag, "subsetted")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001225 else:
1226 self.log (tag, "NOT subset; don't know how to subset")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001227
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001228 glyphOrder = self.font.getGlyphOrder()
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001229 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001230 self.font.setGlyphOrder (glyphOrder)
1231 self.font._buildReverseGlyphOrderDict ()
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001232 self.log.lapse ("subset GlyphOrder")
1233
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001234 def post_prune (self):
1235 for tag in self.font.keys():
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001236 if tag == 'GlyphOrder': continue
1237 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001238 if hasattr (clazz, 'prune_post_subset'):
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001239 table = self.font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001240 retain = table.prune_post_subset (self.options)
1241 self.log.lapse ("prune '%s'" % tag)
1242 if not retain:
1243 self.log (tag, "pruned to empty; dropped")
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001244 del self.font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001245 else:
1246 self.log (tag, "pruned")
1247
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001248 def subset (self, font):
1249
1250 self.font = font
Behdad Esfahbod756af492013-08-01 12:05:26 -04001251
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001252 self.font.recalcBBoxes = self.options.recalc_bboxes
1253
1254 self.pre_prune ()
1255 self.closure_glyphs ()
1256 self.subset_glyphs ()
1257 self.post_prune ()
1258
Behdad Esfahbod756af492013-08-01 12:05:26 -04001259 del self.font
1260
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001261import sys, time
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001262
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001263class Logger:
1264
1265 def __init__ (self, verbose=False, xml=False, timing=False):
1266 self.verbose = verbose
1267 self.xml = xml
1268 self.timing = timing
1269 self.last_time = self.start_time = time.time ()
1270
1271 def parse_opts (self, argv):
1272 argv = argv[:]
1273 for v in ['verbose', 'xml', 'timing']:
1274 if "--"+v in argv:
1275 setattr (self, v, True)
1276 argv.remove ("--"+v)
1277 return argv
1278
1279 def __call__ (self, *things):
1280 if not self.verbose:
1281 return
1282 print ' '.join (str (x) for x in things)
1283
1284 def lapse (self, *things):
1285 if not self.timing:
1286 return
1287 new_time = time.time ()
1288 print "Took %0.3fs to %s" % (new_time - self.last_time, ' '.join (str (x) for x in things))
1289 self.last_time = new_time
1290
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001291 def glyphs (self, glyphs, glyph_names=True, font=None):
1292 self ("Names: ", sorted (glyphs))
1293 if font:
1294 glyphOrder = font.getGlyphOrder()
1295 self ("Gids : ", sorted (glyphOrder.index (g) for g in glyphs))
1296
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001297 def font (self, font, file=sys.stdout):
1298 if not self.xml:
1299 return
1300 import xmlWriter, sys
1301 writer = xmlWriter.XMLWriter (file)
1302 font.disassembleInstructions = False # Work around ttx bug
1303 for tag in font.keys():
1304 writer.begintag (tag)
1305 writer.newline ()
1306 font[tag].toXML(writer, font)
1307 writer.endtag (tag)
1308 writer.newline ()
1309
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001310def main (args):
Behdad Esfahbod610b0552013-07-23 14:52:18 -04001311
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001312 log = Logger ()
1313 args = log.parse_opts (args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04001314
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001315 options = Subsetter.Options ()
1316 args = options.parse_opts (args)
1317
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001318 if len (args) < 2:
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001319 import sys
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001320 print >>sys.stderr, "usage: pyotlss.py font-file glyph..."
1321 sys.exit (1)
1322
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001323 fontfile = args[0]
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001324 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001325
Behdad Esfahbodb3ee60c2013-07-24 19:21:40 -04001326 # TODO Option for ignoreDecompileErrors?
Behdad Esfahbod88264a62013-07-31 14:45:13 -04001327 font = fontTools.ttx.TTFont (fontfile)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001328 s = Subsetter (font=font, options=options, log=log)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001329 log.lapse ("load font")
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001330
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001331 # Hack:
1332 #
1333 # If we don't need glyph names, change 'post' class to not try to
1334 # load them. It avoid lots of headache with broken fonts as well
1335 # as loading time.
1336 #
1337 # Ideally ttLib should provide a way to ask it to skip loading
1338 # glyph names. But it currently doesn't provide such a thing.
1339 #
1340 if not options.glyph_names \
Behdad Esfahbod9ae5d282013-08-08 21:18:17 -04001341 and all (any (g.startswith (p) for p in ['gid', 'glyph', 'uni', 'U+']) \
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001342 for g in args):
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001343 post = fontTools.ttLib.getTableClass('post')
1344 saved = post.decode_format_2_0
1345 post.decode_format_2_0 = post.decode_format_3_0
1346 f = font['post']
1347 if f.formatType == 2.0:
1348 f.formatType = 3.0
1349 post.decode_format_2_0 = saved
1350 del post, saved, f
1351
1352 names = font.getGlyphNames()
1353 log.lapse ("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001354
1355 glyphs = []
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001356 unicodes = []
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001357 for g in args:
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001358 if g in names:
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001359 glyphs.append (g)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001360 continue
Behdad Esfahbod9ae5d282013-08-08 21:18:17 -04001361 if g.startswith ('uni') or g.startswith ('U+'):
1362 if g.startswith ('uni') and len (g) > 3:
1363 g = g[3:]
1364 elif g.startswith ('U+') and len (g) > 2:
1365 g = g[2:]
1366 u = int (g, 16)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001367 unicodes.append (u)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001368 continue
1369 if g.startswith ('gid') or g.startswith ('glyph'):
1370 if g.startswith ('gid') and len (g) > 3:
1371 g = g[3:]
1372 elif g.startswith ('glyph') and len (g) > 5:
1373 g = g[5:]
1374 try:
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001375 glyphs.append (font.getGlyphName (int (g), requireReal=1))
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001376 except ValueError:
1377 raise Exception ("Invalid glyph identifier %s" % g)
1378 continue
1379 raise Exception ("Invalid glyph identifier %s" % g)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001380 log.lapse ("compile glyph list")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001381 log ("Unicodes:", unicodes)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001382 log ("Glyphs:", glyphs)
1383
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001384 s.populate (glyphs=glyphs, unicodes=unicodes)
1385 s.subset (font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04001386
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001387 font.save (fontfile + '.subset')
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001388 log.lapse ("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001389
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001390 log.last_time = s.log.start_time
1391 log.lapse ("make one with everything (TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001392
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001393 log.font (font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001394
1395if __name__ == '__main__':
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001396 import sys
1397 main (sys.argv[1:])