blob: e1732b3cc3b05c4e5f0e0e29942a657952138378 [file] [log] [blame]
Behdad Esfahbod54660612013-07-21 18:16:55 -04001#!/usr/bin/python
Behdad Esfahbod616d36e2013-08-13 20:02:59 -04002
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04003# Copyright 2013 Google, Inc. All Rights Reserved.
4#
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04005# Licensed under the Apache License, Version 2.0(the "License");
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04006# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17# Google Author(s): Behdad Esfahbod
Behdad Esfahbod616d36e2013-08-13 20:02:59 -040018
19"""Python OpenType Layout Subsetter.
20
21Later grown into full OpenType subsetter, supporting all standard tables.
22"""
23
Behdad Esfahbod54660612013-07-21 18:16:55 -040024
Behdad Esfahbodfa3bc5e2013-07-24 14:37:58 -040025# Try running on PyPy
26try:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040027 import numpypy
Behdad Esfahbodfa3bc5e2013-07-24 14:37:58 -040028except ImportError:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040029 pass
Behdad Esfahbodfa3bc5e2013-07-24 14:37:58 -040030
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040031import sys
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -040032import struct
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040033import time
34
35import fontTools.ttx
Behdad Esfahbod54660612013-07-21 18:16:55 -040036
Behdad Esfahbod54660612013-07-21 18:16:55 -040037
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040038def _add_method(*clazzes):
Behdad Esfahbod616d36e2013-08-13 20:02:59 -040039 """Returns a decorator function that adds a new method to one or
40 more classes."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040041 def wrapper(method):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040042 for clazz in clazzes:
43 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040044 setattr(clazz, method.func_name, method)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040045 return None
46 return wrapper
Behdad Esfahbod54660612013-07-21 18:16:55 -040047
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040048def _uniq_sort(l):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040049 return sorted(set(l))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040050
51
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040052@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040053def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040054 "Returns ascending list of matching coverage values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040055 return [i for(i,g) in enumerate(self.glyphs) if g in glyphs]
Behdad Esfahbod610b0552013-07-23 14:52:18 -040056
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040057@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040058def intersect_glyphs(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040059 "Returns set of intersecting glyphs."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040060 return set(g for g in self.glyphs if g in glyphs)
Behdad Esfahbod849d25c2013-08-12 19:24:24 -040061
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040062@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040063def subset(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040064 "Returns ascending list of remaining coverage values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040065 indices = self.intersect(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040066 self.glyphs = [g for g in self.glyphs if g in glyphs]
67 return indices
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040068
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040069@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040070def remap(self, coverage_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040071 "Remaps coverage."
72 self.glyphs = [self.glyphs[i] for i in coverage_map]
Behdad Esfahbod14374262013-08-08 22:26:49 -040073
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040074@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040075def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040076 "Returns ascending list of matching class values."
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040077 return _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040078 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040079 [v for g,v in self.classDefs.iteritems() if g in glyphs])
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040080
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040081@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040082def intersect_class(self, glyphs, klass):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040083 "Returns set of glyphs matching class."
84 if klass == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040085 return set(g for g in glyphs if g not in self.classDefs)
86 return set(g for g,v in self.classDefs.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040087 if v == klass and g in glyphs)
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040088
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040089@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040090def subset(self, glyphs, remap=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040091 "Returns ascending list of remaining classes."
92 self.classDefs = {g:v for g,v in self.classDefs.iteritems() if g in glyphs}
93 # Note: while class 0 has the special meaning of "not matched",
94 # if no glyph will ever /not match/, we can optimize class 0 out too.
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040095 indices = _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040096 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040097 self.classDefs.itervalues())
98 if remap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040099 self.remap(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400100 return indices
Behdad Esfahbod4aa6ce32013-07-22 12:15:36 -0400101
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400102@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400103def remap(self, class_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400104 "Remaps classes."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400105 self.classDefs = {g:class_map.index(v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400106 for g,v in self.classDefs.iteritems()}
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400107
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400108@_add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400109def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400110 if cur_glyphs == None: cur_glyphs = s.glyphs
111 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400112 s.glyphs.update(v for g,v in self.mapping.iteritems() if g in cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400113 else:
114 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400115
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400116@_add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400117def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400118 if self.Format in [1, 2]:
119 self.mapping = {g:v for g,v in self.mapping.iteritems()
120 if g in s.glyphs and v in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400121 return bool(self.mapping)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400122 else:
123 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400124
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400125@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400126def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400127 if cur_glyphs == None: cur_glyphs = s.glyphs
128 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400129 indices = self.Coverage.intersect(cur_glyphs)
130 s.glyphs.update(*(self.Sequence[i].Substitute for i in indices))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400131 else:
132 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400133
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400134@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400135def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400136 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400137 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400138 self.Sequence = [self.Sequence[i] for i in indices]
139 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400140 indices = [i for i,seq in enumerate(self.Sequence)
141 if all(sub in s.glyphs for sub in seq.Substitute)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400142 self.Sequence = [self.Sequence[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400143 self.Coverage.remap(indices)
144 self.SequenceCount = len(self.Sequence)
145 return bool(self.SequenceCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400146 else:
147 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400148
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400149@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400150def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400151 if cur_glyphs == None: cur_glyphs = s.glyphs
152 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400153 s.glyphs.update(*(vlist for g,vlist in self.alternates.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400154 if g in cur_glyphs))
155 else:
156 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400157
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400158@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400159def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400160 if self.Format == 1:
161 self.alternates = {g:vlist for g,vlist in self.alternates.iteritems()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400162 if g in s.glyphs and all(v in s.glyphs for v in vlist)}
163 return bool(self.alternates)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400164 else:
165 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400166
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400167@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400168def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400169 if cur_glyphs == None: cur_glyphs = s.glyphs
170 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400171 s.glyphs.update(*([seq.LigGlyph for seq in seqs
172 if all(c in s.glyphs for c in seq.Component)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400173 for g,seqs in self.ligatures.iteritems()
174 if g in cur_glyphs))
175 else:
176 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400177
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400178@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400179def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400180 if self.Format == 1:
181 self.ligatures = {g:v for g,v in self.ligatures.iteritems()
182 if g in s.glyphs}
183 self.ligatures = {g:[seq for seq in seqs
184 if seq.LigGlyph in s.glyphs and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400185 all(c in s.glyphs for c in seq.Component)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400186 for g,seqs in self.ligatures.iteritems()}
187 self.ligatures = {g:v for g,v in self.ligatures.iteritems() if v}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400188 return bool(self.ligatures)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400189 else:
190 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400191
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400192@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400193def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400194 if cur_glyphs == None: cur_glyphs = s.glyphs
195 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400196 indices = self.Coverage.intersect(cur_glyphs)
197 if(not indices or
198 not all(c.intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400199 for c in self.LookAheadCoverage + self.BacktrackCoverage)):
200 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400201 s.glyphs.update(self.Substitute[i] for i in indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400202 else:
203 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400204
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400205@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400206def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400207 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400208 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400209 self.Substitute = [self.Substitute[i] for i in indices]
210 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400211 indices = [i for i,sub in enumerate(self.Substitute)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400212 if sub in s.glyphs]
213 self.Substitute = [self.Substitute[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400214 self.Coverage.remap(indices)
215 self.GlyphCount = len(self.Substitute)
216 return bool(self.GlyphCount and
217 all(c.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400218 for c in self.LookAheadCoverage+self.BacktrackCoverage))
219 else:
220 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400221
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400222@_add_method(fontTools.ttLib.tables.otTables.SinglePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400223def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400224 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400225 return len(self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400226 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400227 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400228 self.Value = [self.Value[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400229 self.ValueCount = len(self.Value)
230 return bool(self.ValueCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400231 else:
232 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400233
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400234@_add_method(fontTools.ttLib.tables.otTables.PairPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400235def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400236 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400237 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400238 self.PairSet = [self.PairSet[i] for i in indices]
239 for p in self.PairSet:
240 p.PairValueRecord = [r for r in p.PairValueRecord
241 if r.SecondGlyph in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400242 p.PairValueCount = len(p.PairValueRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400243 self.PairSet = [p for p in self.PairSet if p.PairValueCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400244 self.PairSetCount = len(self.PairSet)
245 return bool(self.PairSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400246 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400247 class1_map = self.ClassDef1.subset(s.glyphs, remap=True)
248 class2_map = self.ClassDef2.subset(s.glyphs, remap=True)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400249 self.Class1Record = [self.Class1Record[i] for i in class1_map]
250 for c in self.Class1Record:
251 c.Class2Record = [c.Class2Record[i] for i in class2_map]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400252 self.Class1Count = len(class1_map)
253 self.Class2Count = len(class2_map)
254 return bool(self.Class1Count and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400255 self.Class2Count and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400256 self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400257 else:
258 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400259
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400260@_add_method(fontTools.ttLib.tables.otTables.CursivePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400261def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400262 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400263 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400264 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400265 self.EntryExitCount = len(self.EntryExitRecord)
266 return bool(self.EntryExitCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400267 else:
268 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400269
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400270@_add_method(fontTools.ttLib.tables.otTables.MarkBasePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400271def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400272 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400273 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400274 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
275 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400276 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
277 base_indices = self.BaseCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400278 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i]
279 for i in base_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400280 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400281 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400282 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400283 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400284 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400285 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400286 for b in self.BaseArray.BaseRecord:
287 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400288 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400289 self.MarkArray.MarkCount and
290 self.BaseArray.BaseCount)
291 else:
292 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400293
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400294@_add_method(fontTools.ttLib.tables.otTables.MarkLigPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400295def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400296 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400297 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400298 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
299 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400300 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
301 ligature_indices = self.LigatureCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400302 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i]
303 for i in ligature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400304 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400305 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400306 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400307 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400308 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400309 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400310 for l in self.LigatureArray.LigatureAttach:
311 for c in l.ComponentRecord:
312 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400313 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400314 self.MarkArray.MarkCount and
315 self.LigatureArray.LigatureCount)
316 else:
317 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400318
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400319@_add_method(fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400320def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400321 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400322 mark1_indices = self.Mark1Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400323 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i]
324 for i in mark1_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400325 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
326 mark2_indices = self.Mark2Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400327 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i]
328 for i in mark2_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400329 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400330 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400331 class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400332 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400333 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400334 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400335 for b in self.Mark2Array.Mark2Record:
336 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400337 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400338 self.Mark1Array.MarkCount and
339 self.Mark2Array.MarkCount)
340 else:
341 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400342
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400343@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
344 fontTools.ttLib.tables.otTables.MultipleSubst,
345 fontTools.ttLib.tables.otTables.AlternateSubst,
346 fontTools.ttLib.tables.otTables.LigatureSubst,
347 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
348 fontTools.ttLib.tables.otTables.SinglePos,
349 fontTools.ttLib.tables.otTables.PairPos,
350 fontTools.ttLib.tables.otTables.CursivePos,
351 fontTools.ttLib.tables.otTables.MarkBasePos,
352 fontTools.ttLib.tables.otTables.MarkLigPos,
353 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400354def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400355 pass
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400356
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400357@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
358 fontTools.ttLib.tables.otTables.MultipleSubst,
359 fontTools.ttLib.tables.otTables.AlternateSubst,
360 fontTools.ttLib.tables.otTables.LigatureSubst,
361 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
362 fontTools.ttLib.tables.otTables.SinglePos,
363 fontTools.ttLib.tables.otTables.PairPos,
364 fontTools.ttLib.tables.otTables.CursivePos,
365 fontTools.ttLib.tables.otTables.MarkBasePos,
366 fontTools.ttLib.tables.otTables.MarkLigPos,
367 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400368def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400369 return []
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400370
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400371@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
372 fontTools.ttLib.tables.otTables.AlternateSubst,
373 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400374def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400375 return False
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400376
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400377@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst,
378 fontTools.ttLib.tables.otTables.LigatureSubst,
379 fontTools.ttLib.tables.otTables.ContextSubst,
380 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400381def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400382 return True
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400383
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400384@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
385 fontTools.ttLib.tables.otTables.ChainContextSubst,
386 fontTools.ttLib.tables.otTables.ContextPos,
387 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400388def __classify_context(self):
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400389
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -0400390 class ContextHelper(object):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400391 def __init__(self, klass, Format):
392 if klass.__name__.endswith('Subst'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400393 Typ = 'Sub'
394 Type = 'Subst'
395 else:
396 Typ = 'Pos'
397 Type = 'Pos'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400398 if klass.__name__.startswith('Chain'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400399 Chain = 'Chain'
400 else:
401 Chain = ''
402 ChainTyp = Chain+Typ
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400403
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400404 self.Typ = Typ
405 self.Type = Type
406 self.Chain = Chain
407 self.ChainTyp = ChainTyp
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400408
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400409 self.LookupRecord = Type+'LookupRecord'
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400410
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400411 if Format == 1:
412 Coverage = lambda r: r.Coverage
413 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400414 ContextData = lambda r:(None,)
415 ChainContextData = lambda r:(None, None, None)
416 RuleData = lambda r:(r.Input,)
417 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400418 SetRuleData = None
419 ChainSetRuleData = None
420 elif Format == 2:
421 Coverage = lambda r: r.Coverage
422 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400423 ContextData = lambda r:(r.ClassDef,)
424 ChainContextData = lambda r:(r.LookAheadClassDef,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400425 r.InputClassDef,
426 r.BacktrackClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400427 RuleData = lambda r:(r.Class,)
428 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack)
429 def SetRuleData(r, d):(r.Class,) = d
430 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400431 elif Format == 3:
432 Coverage = lambda r: r.Coverage[0]
433 ChainCoverage = lambda r: r.InputCoverage[0]
434 ContextData = None
435 ChainContextData = None
436 RuleData = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400437 ChainRuleData = lambda r:(r.LookAheadCoverage +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400438 r.InputCoverage +
439 r.BacktrackCoverage)
440 SetRuleData = None
441 ChainSetRuleData = None
442 else:
443 assert 0, "unknown format: %s" % Format
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400444
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400445 if Chain:
446 self.Coverage = ChainCoverage
447 self.ContextData = ChainContextData
448 self.RuleData = ChainRuleData
449 self.SetRuleData = ChainSetRuleData
450 else:
451 self.Coverage = Coverage
452 self.ContextData = ContextData
453 self.RuleData = RuleData
454 self.SetRuleData = SetRuleData
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400455
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400456 if Format == 1:
457 self.Rule = ChainTyp+'Rule'
458 self.RuleCount = ChainTyp+'RuleCount'
459 self.RuleSet = ChainTyp+'RuleSet'
460 self.RuleSetCount = ChainTyp+'RuleSetCount'
461 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
462 elif Format == 2:
463 self.Rule = ChainTyp+'ClassRule'
464 self.RuleCount = ChainTyp+'ClassRuleCount'
465 self.RuleSet = ChainTyp+'ClassSet'
466 self.RuleSetCount = ChainTyp+'ClassSetCount'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400467 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r)
Behdad Esfahbod89987002013-07-23 23:07:42 -0400468
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400469 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
Behdad Esfahbod27108392013-07-23 16:40:47 -0400470
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400471 if self.Format not in [1, 2, 3]:
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400472 return None # Don't shoot the messenger; let it go
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400473 if not hasattr(self.__class__, "__ContextHelpers"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400474 self.__class__.__ContextHelpers = {}
475 if self.Format not in self.__class__.__ContextHelpers:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400476 helper = ContextHelper(self.__class__, self.Format)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400477 self.__class__.__ContextHelpers[self.Format] = helper
478 return self.__class__.__ContextHelpers[self.Format]
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400479
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400480@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
481 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400482def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400483 if cur_glyphs == None: cur_glyphs = s.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400484 c = self.__classify_context()
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400485
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400486 indices = c.Coverage(self).intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400487 if not indices:
488 return []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400489 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs);
Behdad Esfahbod1d4fa132013-08-08 22:59:32 -0400490
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400491 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400492 ContextData = c.ContextData(self)
493 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400494 for i in indices:
495 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400496 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400497 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400498 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
499 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400500 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400501 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400502 if not ll: continue
503 seqi = ll.SequenceIndex
504 if seqi == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400505 pos_glyphs = set(c.Coverage(self).glyphs[i])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400506 else:
507 if chaos:
508 pos_glyphs = s.glyphs
509 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400510 pos_glyphs = set(r.Input[seqi - 1])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400511 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400512 chaos = chaos or lookup.may_have_non_1to1()
513 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400514 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400515 ClassDef = getattr(self, c.ClassDef)
516 indices = ClassDef.intersect(cur_glyphs)
517 ContextData = c.ContextData(self)
518 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400519 for i in indices:
520 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400521 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400522 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400523 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
524 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400525 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400526 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400527 if not ll: continue
528 seqi = ll.SequenceIndex
529 if seqi == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400530 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400531 else:
532 if chaos:
533 pos_glyphs = s.glyphs
534 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400535 pos_glyphs = ClassDef.intersect_class(s.glyphs,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400536 r.Input[seqi - 1])
537 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400538 chaos = chaos or lookup.may_have_non_1to1()
539 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400540 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400541 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400542 return []
543 r = self
544 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400545 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400546 if not ll: continue
547 seqi = ll.SequenceIndex
548 if seqi == 0:
549 pos_glyphs = cur_glyphs
550 else:
551 if chaos:
552 pos_glyphs = s.glyphs
553 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400554 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400555 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400556 chaos = chaos or lookup.may_have_non_1to1()
557 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400558 else:
559 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod00776972013-07-23 15:33:00 -0400560
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400561@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
562 fontTools.ttLib.tables.otTables.ContextPos,
563 fontTools.ttLib.tables.otTables.ChainContextSubst,
564 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400565def subset_glyphs(self, s):
566 c = self.__classify_context()
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400567
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400568 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400569 indices = self.Coverage.subset(s.glyphs)
570 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400571 rss = [rss[i] for i in indices]
572 for rs in rss:
573 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400574 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400575 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400576 if r and all(all(g in s.glyphs for g in glist)
577 for glist in c.RuleData(r))]
578 setattr(rs, c.Rule, ss)
579 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400580 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400581 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
582 setattr(self, c.RuleSet, rss)
583 setattr(self, c.RuleSetCount, len(rss))
584 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400585 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400586 if not self.Coverage.subset(s.glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400587 return False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400588 indices = getattr(self, c.ClassDef).subset(self.Coverage.glyphs,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400589 remap=False)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400590 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400591 rss = [rss[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400592 ContextData = c.ContextData(self)
593 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400594 for rs in rss:
595 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400596 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400597 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400598 if r and all(all(k in klass_map for k in klist)
599 for klass_map,klist in zip(klass_maps, c.RuleData(r)))]
600 setattr(rs, c.Rule, ss)
601 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400602
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400603 # Remap rule classes
604 for r in ss:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400605 c.SetRuleData(r, [[klass_map.index(k) for k in klist]
606 for klass_map,klist in zip(klass_maps, c.RuleData(r))])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400607 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400608 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
609 setattr(self, c.RuleSet, rss)
610 setattr(self, c.RuleSetCount, len(rss))
611 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400612 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400613 return all(x.subset(s.glyphs) for x in c.RuleData(self))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400614 else:
615 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400616
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400617@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
618 fontTools.ttLib.tables.otTables.ChainContextSubst,
619 fontTools.ttLib.tables.otTables.ContextPos,
620 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400621def subset_lookups(self, lookup_indices):
622 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400623
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400624 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400625 for rs in getattr(self, c.RuleSet):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400626 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400627 for r in getattr(rs, c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400628 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400629 setattr(r, c.LookupRecord,
630 [ll for ll in getattr(r, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400631 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400632 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400633 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400634 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400635 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400636 setattr(self, c.LookupRecord,
637 [ll for ll in getattr(self, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400638 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400639 for ll in getattr(self, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400640 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400641 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400642 else:
643 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400644
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400645@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
646 fontTools.ttLib.tables.otTables.ChainContextSubst,
647 fontTools.ttLib.tables.otTables.ContextPos,
648 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400649def collect_lookups(self):
650 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400651
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400652 if self.Format in [1, 2]:
653 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400654 for rs in getattr(self, c.RuleSet) if rs
655 for r in getattr(rs, c.Rule) if r
656 for ll in getattr(r, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400657 elif self.Format == 3:
658 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400659 for ll in getattr(self, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400660 else:
661 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400662
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400663@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400664def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400665 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400666 self.ExtSubTable.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400667 else:
668 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400669
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400670@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400671def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400672 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400673 return self.ExtSubTable.may_have_non_1to1()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400674 else:
675 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400676
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400677@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
678 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400679def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400680 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400681 return self.ExtSubTable.subset_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400682 else:
683 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400684
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400685@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
686 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400687def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400688 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400689 return self.ExtSubTable.subset_lookups(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400690 else:
691 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400692
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400693@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
694 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400695def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400696 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400697 return self.ExtSubTable.collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400698 else:
699 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400700
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400701@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400702def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400703 for st in self.SubTable:
704 if not st: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400705 st.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400706
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400707@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400708def subset_glyphs(self, s):
709 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)]
710 self.SubTableCount = len(self.SubTable)
711 return bool(self.SubTableCount)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400712
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400713@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400714def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400715 for s in self.SubTable:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400716 s.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400717
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400718@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400719def collect_lookups(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400720 return _uniq_sort(sum((st.collect_lookups() for st in self.SubTable
721 if st), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400722
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400723@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400724def may_have_non_1to1(self):
725 return any(st.may_have_non_1to1() for st in self.SubTable if st)
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400726
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400727@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400728def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400729 "Returns the indices of nonempty lookups."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400730 return [i for(i,l) in enumerate(self.Lookup) if l and l.subset_glyphs(s)]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400731
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400732@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400733def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400734 self.Lookup = [self.Lookup[i] for i in lookup_indices
735 if i < self.LookupCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400736 self.LookupCount = len(self.Lookup)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400737 for l in self.Lookup:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400738 l.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400739
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400740@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400741def closure_lookups(self, lookup_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400742 lookup_indices = _uniq_sort(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400743 recurse = lookup_indices
744 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400745 recurse_lookups = sum((self.Lookup[i].collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400746 for i in recurse if i < self.LookupCount), [])
747 recurse_lookups = [l for l in recurse_lookups
748 if l not in lookup_indices and l < self.LookupCount]
749 if not recurse_lookups:
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400750 return _uniq_sort(lookup_indices)
751 recurse_lookups = _uniq_sort(recurse_lookups)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400752 lookup_indices.extend(recurse_lookups)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400753 recurse = recurse_lookups
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400754
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400755@_add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400756def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400757 self.LookupListIndex = [l for l in self.LookupListIndex
758 if l in lookup_indices]
759 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400760 self.LookupListIndex = [lookup_indices.index(l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400761 for l in self.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400762 self.LookupCount = len(self.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400763 return self.LookupCount
Behdad Esfahbod54660612013-07-21 18:16:55 -0400764
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400765@_add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400766def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400767 return self.LookupListIndex[:]
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400768
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400769@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400770def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400771 "Returns the indices of nonempty features."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400772 feature_indices = [i for(i,f) in enumerate(self.FeatureRecord)
773 if f.Feature.subset_lookups(lookup_indices)]
774 self.subset_features(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400775 return feature_indices
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400776
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400777@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400778def collect_lookups(self, feature_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400779 return _uniq_sort(sum((self.FeatureRecord[i].Feature.collect_lookups()
780 for i in feature_indices
Behdad Esfahbod1ee298d2013-08-13 20:07:09 -0400781 if i < self.FeatureCount), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400782
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400783@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400784def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400785 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400786 self.FeatureCount = len(self.FeatureRecord)
787 return bool(self.FeatureCount)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400788
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400789@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
790 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400791def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400792 if self.ReqFeatureIndex in feature_indices:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400793 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400794 else:
795 self.ReqFeatureIndex = 65535
796 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
797 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400798 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400799 if f in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400800 self.FeatureCount = len(self.FeatureIndex)
801 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400802
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400803@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
804 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400805def collect_features(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400806 feature_indices = self.FeatureIndex[:]
807 if self.ReqFeatureIndex != 65535:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400808 feature_indices.append(self.ReqFeatureIndex)
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400809 return _uniq_sort(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400810
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400811@_add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400812def subset_features(self, feature_indices):
813 if(self.DefaultLangSys and
814 not self.DefaultLangSys.subset_features(feature_indices)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400815 self.DefaultLangSys = None
816 self.LangSysRecord = [l for l in self.LangSysRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400817 if l.LangSys.subset_features(feature_indices)]
818 self.LangSysCount = len(self.LangSysRecord)
819 return bool(self.LangSysCount or self.DefaultLangSys)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400820
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400821@_add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400822def collect_features(self):
823 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400824 if self.DefaultLangSys:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400825 feature_indices.append(self.DefaultLangSys.collect_features())
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400826 return _uniq_sort(sum(feature_indices, []))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400827
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400828@_add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400829def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400830 self.ScriptRecord = [s for s in self.ScriptRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400831 if s.Script.subset_features(feature_indices)]
832 self.ScriptCount = len(self.ScriptRecord)
833 return bool(self.ScriptCount)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400834
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400835@_add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400836def collect_features(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400837 return _uniq_sort(sum((s.Script.collect_features()
838 for s in self.ScriptRecord), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400839
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400840@_add_method(fontTools.ttLib.getTableClass('GSUB'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400841def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400842 s.table = self.table
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400843 feature_indices = self.table.ScriptList.collect_features()
844 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400845 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400846 orig_glyphs = s.glyphs.copy()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400847 for i in lookup_indices:
848 if i >= self.table.LookupList.LookupCount: continue
849 if not self.table.LookupList.Lookup[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400850 self.table.LookupList.Lookup[i].closure_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400851 if orig_glyphs == s.glyphs:
852 break
853 del s.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400854
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400855@_add_method(fontTools.ttLib.getTableClass('GSUB'),
856 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400857def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400858 s.glyphs = s.glyphs_gsubed
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400859 lookup_indices = self.table.LookupList.subset_glyphs(s)
860 self.subset_lookups(lookup_indices)
861 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400862 return True
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400863
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400864@_add_method(fontTools.ttLib.getTableClass('GSUB'),
865 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400866def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400867 """Retrains specified lookups, then removes empty features, language
868 systems, and scripts."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400869 self.table.LookupList.subset_lookups(lookup_indices)
870 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices)
871 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400872
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400873@_add_method(fontTools.ttLib.getTableClass('GSUB'),
874 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400875def prune_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400876 "Remove unreferenced lookups"
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400877 feature_indices = self.table.ScriptList.collect_features()
878 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
879 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices)
880 self.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400881
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400882@_add_method(fontTools.ttLib.getTableClass('GSUB'),
883 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400884def subset_feature_tags(self, feature_tags):
885 feature_indices = [i for(i,f) in
886 enumerate(self.table.FeatureList.FeatureRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400887 if f.FeatureTag in feature_tags]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400888 self.table.FeatureList.subset_features(feature_indices)
889 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400890
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400891@_add_method(fontTools.ttLib.getTableClass('GSUB'),
892 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400893def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400894 if options.layout_features and '*' not in options.layout_features:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400895 self.subset_feature_tags(options.layout_features)
896 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400897 return True
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400898
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400899@_add_method(fontTools.ttLib.getTableClass('GDEF'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400900def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400901 glyphs = s.glyphs_gsubed
902 table = self.table
903 if table.LigCaretList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400904 indices = table.LigCaretList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400905 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i]
906 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400907 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400908 if not table.LigCaretList.LigGlyphCount:
909 table.LigCaretList = None
910 if table.MarkAttachClassDef:
911 table.MarkAttachClassDef.classDefs = {g:v for g,v in
912 table.MarkAttachClassDef.classDefs.iteritems()
913 if g in glyphs}
914 if not table.MarkAttachClassDef.classDefs:
915 table.MarkAttachClassDef = None
916 if table.GlyphClassDef:
917 table.GlyphClassDef.classDefs = {g:v for g,v in
918 table.GlyphClassDef.classDefs.iteritems()
919 if g in glyphs}
920 if not table.GlyphClassDef.classDefs:
921 table.GlyphClassDef = None
922 if table.AttachList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400923 indices = table.AttachList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400924 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i]
925 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400926 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400927 if not table.AttachList.GlyphCount:
928 table.AttachList = None
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400929 return bool(table.LigCaretList or
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400930 table.MarkAttachClassDef or
931 table.GlyphClassDef or
932 table.AttachList)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -0400933
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400934@_add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400935def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400936 # Prune unknown kern table types
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400937 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')]
938 return bool(self.kernTables)
Behdad Esfahbodd4e33a72013-07-24 18:51:05 -0400939
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400940@_add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400941def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400942 glyphs = s.glyphs_gsubed
943 for t in self.kernTables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400944 t.kernTable = {(a,b):v for((a,b),v) in t.kernTable.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400945 if a in glyphs and b in glyphs}
946 self.kernTables = [t for t in self.kernTables if t.kernTable]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400947 return bool(self.kernTables)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -0400948
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400949@_add_method(fontTools.ttLib.getTableClass('hmtx'),
950 fontTools.ttLib.getTableClass('vmtx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400951def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400952 self.metrics = {g:v for g,v in self.metrics.iteritems() if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400953 return bool(self.metrics)
Behdad Esfahbodc7160442013-07-22 14:29:08 -0400954
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400955@_add_method(fontTools.ttLib.getTableClass('hdmx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400956def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400957 self.hdmx = {sz:{g:v for g,v in l.iteritems() if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400958 for(sz,l) in self.hdmx.iteritems()}
959 return bool(self.hdmx)
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -0400960
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400961@_add_method(fontTools.ttLib.getTableClass('VORG'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400962def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400963 self.VOriginRecords = {g:v for g,v in self.VOriginRecords.iteritems()
964 if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400965 self.numVertOriginYMetrics = len(self.VOriginRecords)
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400966 return True # Never drop; has default metrics
Behdad Esfahbode45d6af2013-07-22 15:29:17 -0400967
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400968@_add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400969def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400970 if not options.glyph_names:
971 self.formatType = 3.0
972 return True
Behdad Esfahbod42648242013-07-23 12:56:06 -0400973
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400974@_add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400975def subset_glyphs(self, s):
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400976 self.extraNames = [] # This seems to do it
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400977 return True
Behdad Esfahbod653e9742013-07-22 15:17:12 -0400978
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400979# Copied from _g_l_y_f.py
980ARG_1_AND_2_ARE_WORDS = 0x0001 # if set args are words otherwise they are bytes
981ARGS_ARE_XY_VALUES = 0x0002 # if set args are xy values, otherwise they are points
982ROUND_XY_TO_GRID = 0x0004 # for the xy values if above is true
983WE_HAVE_A_SCALE = 0x0008 # Sx = Sy, otherwise scale == 1.0
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400984NON_OVERLAPPING = 0x0010 # set to same value for all components(obsolete!)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400985MORE_COMPONENTS = 0x0020 # indicates at least one more glyph after this one
986WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Sx, Sy
987WE_HAVE_A_TWO_BY_TWO = 0x0080 # t00, t01, t10, t11
988WE_HAVE_INSTRUCTIONS = 0x0100 # instructions follow
989USE_MY_METRICS = 0x0200 # apply these metrics to parent glyph
990OVERLAP_COMPOUND = 0x0400 # used by Apple in GX fonts
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400991SCALED_COMPONENT_OFFSET = 0x0800 # composite designed to have the component offset scaled(designed for Apple)
992UNSCALED_COMPONENT_OFFSET = 0x1000 # composite designed not to have the component offset scaled(designed for MS)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400993
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400994@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400995def getComponentNamesFast(self, glyfTable):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400996 if struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400997 return [] # Not composite
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400998 data = self.data
999 i = 10
1000 components = []
1001 more = 1
1002 while more:
1003 flags, glyphID = struct.unpack(">HH", data[i:i+4])
1004 i += 4
1005 flags = int(flags)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001006 components.append(glyfTable.getGlyphName(int(glyphID)))
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001007
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001008 if flags & ARG_1_AND_2_ARE_WORDS: i += 4
1009 else: i += 2
1010 if flags & WE_HAVE_A_SCALE: i += 2
1011 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
1012 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
1013 more = flags & MORE_COMPONENTS
1014 return components
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001015
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001016@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001017def remapComponentsFast(self, indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001018 if struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001019 return # Not composite
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001020 data = bytearray(self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001021 i = 10
1022 more = 1
1023 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001024 flags =(data[i] << 8) | data[i+1]
1025 glyphID =(data[i+2] << 8) | data[i+3]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001026 # Remap
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001027 glyphID = indices.index(glyphID)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001028 data[i+2] = glyphID >> 8
1029 data[i+3] = glyphID & 0xFF
1030 i += 4
1031 flags = int(flags)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001032
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001033 if flags & ARG_1_AND_2_ARE_WORDS: i += 4
1034 else: i += 2
1035 if flags & WE_HAVE_A_SCALE: i += 2
1036 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
1037 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
1038 more = flags & MORE_COMPONENTS
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001039 self.data = str(data)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001040
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001041@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001042def dropInstructionsFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001043 numContours = struct.unpack(">h", self.data[:2])[0]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001044 data = bytearray(self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001045 i = 10
1046 if numContours >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001047 i += 2 * numContours # endPtsOfContours
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001048 instructionLen =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001049 # Zero it
1050 data[i] = data [i+1] = 0
1051 i += 2
1052 if instructionLen:
1053 # Splice it out
1054 data = data[:i] + data[i+instructionLen:]
1055 else:
1056 more = 1
1057 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001058 flags =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001059 # Turn instruction flag off
1060 flags &= ~WE_HAVE_INSTRUCTIONS
1061 data[i+0] = flags >> 8
1062 data[i+1] = flags & 0xFF
1063 i += 4
1064 flags = int(flags)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001065
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001066 if flags & ARG_1_AND_2_ARE_WORDS: i += 4
1067 else: i += 2
1068 if flags & WE_HAVE_A_SCALE: i += 2
1069 elif flags & WE_HAVE_AN_X_AND_Y_SCALE: i += 4
1070 elif flags & WE_HAVE_A_TWO_BY_TWO: i += 8
1071 more = flags & MORE_COMPONENTS
1072 # Cut off
1073 data = data[:i]
1074 if len(data) % 4:
1075 # add pad bytes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001076 nPadBytes = 4 -(len(data) % 4)
1077 for i in range(nPadBytes):
1078 data.append(0)
1079 self.data = str(data)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001080
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001081@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001082def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001083 decompose = s.glyphs
1084 # I don't know if component glyphs can be composite themselves.
1085 # We handle them anyway.
1086 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001087 components = set()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001088 for g in decompose:
1089 if g not in self.glyphs:
1090 continue
1091 gl = self.glyphs[g]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001092 if hasattr(gl, "data"):
1093 for c in gl.getComponentNamesFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001094 if c not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001095 components.add(c)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001096 else:
1097 # TTX seems to expand gid0..3 always
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001098 if gl.isComposite():
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001099 for c in gl.components:
1100 if c.glyphName not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001101 components.add(c.glyphName)
1102 components = set(c for c in components if c not in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001103 if not components:
1104 break
1105 decompose = components
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001106 s.glyphs.update(components)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001107
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001108@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001109def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001110 self.glyphs = {g:v for g,v in self.glyphs.iteritems() if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001111 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001112 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001113 if hasattr(v, "data"):
1114 v.remapComponentsFast(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001115 else:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001116 pass # No need
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001117 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001118 return bool(self.glyphs)
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001119
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001120@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001121def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001122 if not options.hinting:
1123 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001124 if hasattr(v, "data"):
1125 v.dropInstructionsFast()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001126 else:
1127 v.program = fontTools.ttLib.tables.ttProgram.Program()
1128 v.program.fromBytecode([])
1129 return True
Behdad Esfahboded98c612013-07-23 12:37:41 -04001130
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001131@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001132def prune_pre_subset(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001133 cff = self.cff
1134 # CFF table should have one font only
1135 cff.fontNames = cff.fontNames[:1]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001136 return bool(cff.fontNames)
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001137
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001138@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001139def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001140 cff = self.cff
1141 for fontname in cff.keys():
1142 font = cff[fontname]
1143 cs = font.CharStrings
1144 if cs.charStringsAreIndexed:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001145 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001146 # Load all glyphs
1147 for g in font.charset:
1148 if g not in s.glyphs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001149 cs.getItemAndSelector(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001150 csi = cs.charStringsIndex
1151 csi.items = [csi.items[i] for i in indices]
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001152 csi.offsets = [] # Don't need it; loaded all glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001153 if hasattr(font, "FDSelect"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001154 sel = font.FDSelect
1155 sel.format = None
1156 sel.gidArray = [font.FDSelect.gidArray[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001157 cs.charStrings = {g:indices.index(v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001158 for g,v in cs.charStrings.iteritems()
1159 if g in s.glyphs}
1160 else:
1161 cs.charStrings = {g:v
1162 for g,v in cs.charStrings.iteritems()
1163 if g in s.glyphs}
1164 font.charset = [g for g in font.charset if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001165 font.numGlyphs = len(font.charset)
1166 return any(cff[fontname].numGlyphs for fontname in cff.keys())
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001167
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001168@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001169def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001170 if not options.hinting:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001171 pass # Drop hints
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001172 return True
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001173
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001174@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001175def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001176 tables = [t for t in self.tables
1177 if t.platformID == 3 and t.platEncID in [1, 10]]
1178 for u in s.unicodes_requested:
1179 found = False
1180 for table in tables:
1181 if u in table.cmap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001182 s.glyphs.add(table.cmap[u])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001183 found = True
1184 break
1185 if not found:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001186 s.log("No glyph for Unicode value %s; skipping." % u)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001187
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001188@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001189def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001190 if not options.legacy_cmap:
1191 # Drop non-Unicode / non-Symbol cmaps
1192 self.tables = [t for t in self.tables
1193 if t.platformID == 3 and t.platEncID in [0, 1, 10]]
1194 if not options.symbol_cmap:
1195 self.tables = [t for t in self.tables
1196 if t.platformID == 3 and t.platEncID in [1, 10]]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001197 # TODO(behdad) Only keep one subtable?
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001198 # For now, drop format=0 which can't be subset_glyphs easily?
1199 self.tables = [t for t in self.tables if t.format != 0]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001200 return bool(self.tables)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001201
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001202@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001203def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001204 s.glyphs = s.glyphs_cmaped
1205 for t in self.tables:
1206 # For reasons I don't understand I need this here
1207 # to force decompilation of the cmap format 14.
1208 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001209 getattr(t, "asdf")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001210 except AttributeError:
1211 pass
1212 if t.format == 14:
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001213 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None).
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001214 t.uvsDict = {v:[(u,g) for(u,g) in l if g in s.glyphs]
1215 for(v,l) in t.uvsDict.iteritems()}
1216 t.uvsDict = {v:l for(v,l) in t.uvsDict.iteritems() if l}
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001217 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001218 t.cmap = {u:g for(u,g) in t.cmap.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001219 if g in s.glyphs_requested or u in s.unicodes_requested}
1220 self.tables = [t for t in self.tables
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001221 if(t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001222 # TODO(behdad) Convert formats when needed.
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001223 # In particular, if we have a format=12 without non-BMP
1224 # characters, either drop format=12 one or convert it
1225 # to format=4 if there's not one.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001226 return bool(self.tables)
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001227
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001228@_add_method(fontTools.ttLib.getTableClass('name'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001229def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001230 if '*' not in options.name_IDs:
1231 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1232 if not options.name_legacy:
1233 self.names = [n for n in self.names
1234 if n.platformID == 3 and n.platEncID == 1]
1235 if '*' not in options.name_languages:
1236 self.names = [n for n in self.names if n.langID in options.name_languages]
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001237 return True # Retain even if empty
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001238
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001239
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001240# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange?
1241# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
1242# TODO(behdad) Avoid recursing too much.
1243# TODO(behdad) Text direction considerations.
1244# TODO(behdad) Text script / language considerations.
1245# TODO(behdad) Drop unknown tables? Using DefaultTable.prune?
1246# TODO(behdad) Drop GPOS Device records if not hinting?
1247# TODO(behdad) Move font name loading hack to Subsetter?
Behdad Esfahbod56ebd042013-07-22 13:02:24 -04001248
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001249
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04001250class Subsetter(object):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001251
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04001252 class Options(object):
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001253
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001254 class UnknownOptionError(Exception):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001255 pass
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001256
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001257 drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC',
1258 'PCLT', 'LTSH']
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001259 drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
1260 drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001261 no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1262 'loca', 'name', 'cvt ', 'fpgm', 'prep']
1263 hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001264
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001265 # Based on HarfBuzz shapers
1266 layout_features_groups = {
1267 # Default shaper
Behdad Esfahbode9f0b152013-08-13 19:54:25 -04001268 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1269 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001270 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
Behdad Esfahbode9f0b152013-08-13 19:54:25 -04001271 'ltr': ['ltra', 'ltrm'],
1272 'rtl': ['rtla', 'rtlm'],
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001273 # Complex shapers
Behdad Esfahbode9f0b152013-08-13 19:54:25 -04001274 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1275 'cswh', 'mset'],
1276 'hangul': ['ljmo', 'vjmo', 'tjmo'],
1277 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'],
1278 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1279 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1280 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001281 }
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001282 layout_features_default = _uniq_sort(sum(
1283 layout_features_groups.itervalues(), []))
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001284
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001285 drop_tables = drop_tables_default
1286 no_subset_tables = no_subset_tables_default
1287 hinting_tables = hinting_tables_default
1288 layout_features = layout_features_default
1289 hinting = False
1290 glyph_names = False
1291 legacy_cmap = False
1292 symbol_cmap = False
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001293 name_IDs = [1, 2] # Family and Style
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001294 name_legacy = False
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001295 name_languages = [0x0409] # English
1296 mandatory_glyphs = True # First four for TrueType, .notdef for CFF
1297 recalc_bboxes = False # Slows us down
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001298
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001299 def __init__(self, **kwargs):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001300
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001301 self.set(**kwargs)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001302
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001303 def set(self, **kwargs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001304 for k,v in kwargs.iteritems():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001305 if not hasattr(self, k):
1306 raise self.UnknownOptionError("Unknown option '%s'" % k)
1307 setattr(self, k, v)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001308
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001309 def parse_opts(self, argv, ignore_unknown=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001310 ret = []
1311 opts = {}
1312 for a in argv:
1313 orig_a = a
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001314 if not a.startswith('--'):
1315 ret.append(a)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001316 continue
1317 a = a[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001318 i = a.find('=')
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001319 if i == -1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001320 if a.startswith("no-"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001321 k = a[3:]
1322 v = False
1323 else:
1324 k = a
1325 v = True
1326 else:
1327 k = a[:i]
1328 v = a[i+1:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001329 k = k.replace('-', '_')
1330 if not hasattr(self, k):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001331 if ignore_unknown == True or k in ignore_unknown:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001332 ret.append(orig_a)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001333 continue
1334 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001335 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001336
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001337 ov = getattr(self, k)
1338 if isinstance(ov, bool):
1339 v = bool(v)
1340 elif isinstance(ov, int):
1341 v = int(v)
1342 elif isinstance(ov, list):
1343 v = v.split(',')
1344 v = [int(x, 0) if x[0] in range(10) else x for x in v]
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001345
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001346 opts[k] = v
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001347 self.set(**opts)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001348
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001349 return ret
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001350
1351
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001352 def __init__(self, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001353
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001354 if not log:
1355 log = Logger()
1356 if not options:
1357 options = Options()
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001358
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001359 self.options = options
1360 self.log = log
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001361 self.unicodes_requested = set()
1362 self.glyphs_requested = set()
1363 self.glyphs = set()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001364
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001365 def populate(self, glyphs=[], unicodes=[], text=""):
1366 self.unicodes_requested.update(unicodes)
1367 if isinstance(text, str):
1368 text = text.decode("utf8")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001369 for u in text:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001370 self.unicodes_requested.add(ord(u))
1371 self.glyphs_requested.update(glyphs)
1372 self.glyphs.update(glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001373
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001374 def _prune_pre_subset(self, font):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001375
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001376 for tag in font.keys():
1377 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001378
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001379 if(tag in self.options.drop_tables or
1380 (tag in self.options.hinting_tables and not self.options.hinting)):
1381 self.log(tag, "dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001382 del font[tag]
1383 continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001384
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001385 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001386
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001387 if hasattr(clazz, 'prune_pre_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001388 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001389 retain = table.prune_pre_subset(self.options)
1390 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001391 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001392 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001393 del font[tag]
1394 continue
1395 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001396 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001397
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001398 def _closure_glyphs(self, font):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001399
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001400 self.glyphs = self.glyphs_requested.copy()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001401
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001402 if 'cmap' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001403 font['cmap'].closure_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001404 self.glyphs_cmaped = self.glyphs
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001405
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001406 if self.options.mandatory_glyphs:
1407 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001408 for i in range(4):
1409 self.glyphs.add(font.getGlyphName(i))
1410 self.log("Added first four glyphs to subset")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001411 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001412 self.glyphs.add('.notdef')
1413 self.log("Added .notdef glyph to subset")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001414
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001415 if 'GSUB' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001416 self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1417 len(self.glyphs))
1418 self.log.glyphs(self.glyphs, font=font)
1419 font['GSUB'].closure_glyphs(self)
1420 self.log("Closed glyph list over 'GSUB': %d glyphs after" %
1421 len(self.glyphs))
1422 self.log.glyphs(self.glyphs, font=font)
1423 self.log.lapse("close glyph list over 'GSUB'")
1424 self.glyphs_gsubed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001425
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001426 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001427 self.log("Closing glyph list over 'glyf': %d glyphs before" %
1428 len(self.glyphs))
1429 self.log.glyphs(self.glyphs, font=font)
1430 font['glyf'].closure_glyphs(self)
1431 self.log("Closed glyph list over 'glyf': %d glyphs after" %
1432 len(self.glyphs))
1433 self.log.glyphs(self.glyphs, font=font)
1434 self.log.lapse("close glyph list over 'glyf'")
1435 self.glyphs_glyfed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001436
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001437 self.glyphs_all = self.glyphs.copy()
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001438
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001439 self.log("Retaining %d glyphs: " % len(self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001440
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001441 def _subset_glyphs(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001442 for tag in font.keys():
1443 if tag == 'GlyphOrder': continue
1444 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001445
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001446 if tag in self.options.no_subset_tables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001447 self.log(tag, "subsetting not needed")
1448 elif hasattr(clazz, 'subset_glyphs'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001449 table = font[tag]
1450 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001451 retain = table.subset_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001452 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001453 self.log.lapse("subset '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001454 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001455 self.log(tag, "subsetted to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001456 del font[tag]
1457 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001458 self.log(tag, "subsetted")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001459 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001460 self.log(tag, "NOT subset; don't know how to subset; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001461 del font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001462
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001463 glyphOrder = font.getGlyphOrder()
1464 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001465 font.setGlyphOrder(glyphOrder)
1466 font._buildReverseGlyphOrderDict()
1467 self.log.lapse("subset GlyphOrder")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001468
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001469 def _prune_post_subset(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001470 for tag in font.keys():
1471 if tag == 'GlyphOrder': continue
1472 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001473 if hasattr(clazz, 'prune_post_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001474 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001475 retain = table.prune_post_subset(self.options)
1476 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001477 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001478 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001479 del font[tag]
1480 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001481 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001482
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001483 def subset(self, font):
Behdad Esfahbod756af492013-08-01 12:05:26 -04001484
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001485 font.recalcBBoxes = self.options.recalc_bboxes
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001486
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001487 self._prune_pre_subset(font)
1488 self._closure_glyphs(font)
1489 self._subset_glyphs(font)
1490 self._prune_post_subset(font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001491
Behdad Esfahbod756af492013-08-01 12:05:26 -04001492
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04001493class Logger(object):
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001494
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001495 def __init__(self, verbose=False, xml=False, timing=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001496 self.verbose = verbose
1497 self.xml = xml
1498 self.timing = timing
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001499 self.last_time = self.start_time = time.time()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001500
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001501 def parse_opts(self, argv):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001502 argv = argv[:]
1503 for v in ['verbose', 'xml', 'timing']:
1504 if "--"+v in argv:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001505 setattr(self, v, True)
1506 argv.remove("--"+v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001507 return argv
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001508
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001509 def __call__(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001510 if not self.verbose:
1511 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001512 print ' '.join(str(x) for x in things)
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001513
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001514 def lapse(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001515 if not self.timing:
1516 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001517 new_time = time.time()
1518 print "Took %0.3fs to %s" %(new_time - self.last_time,
1519 ' '.join(str(x) for x in things))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001520 self.last_time = new_time
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001521
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001522 def glyphs(self, glyphs, glyph_names=True, font=None):
1523 self("Names: ", sorted(glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001524 if font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001525 reverseGlyphMap = font.getReverseGlyphMap()
1526 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001527
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001528 def font(self, font, file=sys.stdout):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001529 if not self.xml:
1530 return
1531 import xmlWriter
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001532 writer = xmlWriter.XMLWriter(file)
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001533 font.disassembleInstructions = False # Work around ttx bug
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001534 for tag in font.keys():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001535 writer.begintag(tag)
1536 writer.newline()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001537 font[tag].toXML(writer, font)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001538 writer.endtag(tag)
1539 writer.newline()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001540
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001541
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001542def load_font(fontfile, dont_load_glyph_names=False):
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001543
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001544 # TODO(behdad) Option for ignoreDecompileErrors?
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001545
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001546 font = fontTools.ttx.TTFont(fontfile)
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001547
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001548 # Hack:
1549 #
1550 # If we don't need glyph names, change 'post' class to not try to
1551 # load them. It avoid lots of headache with broken fonts as well
1552 # as loading time.
1553 #
1554 # Ideally ttLib should provide a way to ask it to skip loading
1555 # glyph names. But it currently doesn't provide such a thing.
1556 #
1557 if dont_load_glyph_names:
1558 post = fontTools.ttLib.getTableClass('post')
1559 saved = post.decode_format_2_0
1560 post.decode_format_2_0 = post.decode_format_3_0
1561 f = font['post']
1562 if f.formatType == 2.0:
1563 f.formatType = 3.0
1564 post.decode_format_2_0 = saved
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001565
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001566 return font
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001567
1568
Behdad Esfahbod58280972013-08-13 20:46:52 -04001569# Cleanup module space
1570l = locals()
1571for k,v in l.items():
1572 if v == None:
1573 del l[k]
1574del l
1575
1576
Behdad Esfahbod23b2ee92013-08-13 20:29:10 -04001577def main():
1578
1579 args = sys.argv[1:]
Behdad Esfahbod610b0552013-07-23 14:52:18 -04001580
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001581 log = Logger()
1582 args = log.parse_opts(args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04001583
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001584 options = Subsetter.Options()
1585 args = options.parse_opts(args, ignore_unknown=['text'])
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001586
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001587 if len(args) < 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001588 print >>sys.stderr, "usage: pyotlss.py font-file glyph..."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001589 sys.exit(1)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001590
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001591 fontfile = args[0]
1592 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001593
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001594 dont_load_glyph_names =(not options.glyph_names and
1595 all(any(g.startswith(p)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001596 for p in ['gid', 'glyph', 'uni', 'U+'])
1597 for g in args))
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001598
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001599 font = load_font(fontfile, dont_load_glyph_names=dont_load_glyph_names)
1600 subsetter = Subsetter(options=options, log=log)
1601 log.lapse("load font")
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001602
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001603 names = font.getGlyphNames()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001604 log.lapse("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001605
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001606 glyphs = []
1607 unicodes = []
1608 text = ""
1609 for g in args:
1610 if g in names:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001611 glyphs.append(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001612 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001613 if g.startswith('--text='):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001614 text += g[7:]
1615 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001616 if g.startswith('uni') or g.startswith('U+'):
1617 if g.startswith('uni') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001618 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001619 elif g.startswith('U+') and len(g) > 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001620 g = g[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001621 u = int(g, 16)
1622 unicodes.append(u)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001623 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001624 if g.startswith('gid') or g.startswith('glyph'):
1625 if g.startswith('gid') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001626 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001627 elif g.startswith('glyph') and len(g) > 5:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001628 g = g[5:]
1629 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001630 glyphs.append(font.getGlyphName(int(g), requireReal=1))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001631 except ValueError:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001632 raise Exception("Invalid glyph identifier: %s" % g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001633 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001634 raise Exception("Invalid glyph identifier: %s" % g)
1635 log.lapse("compile glyph list")
1636 log("Unicodes:", unicodes)
1637 log("Glyphs:", glyphs)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001638
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001639 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
1640 subsetter.subset(font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04001641
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001642 font.save(fontfile + '.subset')
1643 log.lapse("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001644
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001645 log.last_time = log.start_time
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001646 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001647
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001648 log.font(font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001649
Behdad Esfahbodc56bf482013-08-13 20:13:33 -04001650 font.close()
1651
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001652if __name__ == '__main__':
Behdad Esfahbod23b2ee92013-08-13 20:29:10 -04001653 main()