blob: 97bc510bf6129b067aeb9c7cf38eeebda9bb29d3 [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 Esfahbode7a0d562013-08-16 10:56:30 -040044 assert not hasattr(clazz, method.func_name), \
Behdad Esfahbodd77f1572013-08-15 19:24:36 -040045 "Oops, class '%s' has method '%s'." % (clazz.__name__,
46 method.func_name)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040047 setattr(clazz, method.func_name, method)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040048 return None
49 return wrapper
Behdad Esfahbod54660612013-07-21 18:16:55 -040050
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040051def _uniq_sort(l):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040052 return sorted(set(l))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040053
54
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040055@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040056def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040057 "Returns ascending list of matching coverage values."
Behdad Esfahbod4734be52013-08-14 19:47:42 -040058 return [i for i,g in enumerate(self.glyphs) if g in glyphs]
Behdad Esfahbod610b0552013-07-23 14:52:18 -040059
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040060@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040061def intersect_glyphs(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040062 "Returns set of intersecting glyphs."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040063 return set(g for g in self.glyphs if g in glyphs)
Behdad Esfahbod849d25c2013-08-12 19:24:24 -040064
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040065@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040066def subset(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040067 "Returns ascending list of remaining coverage values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040068 indices = self.intersect(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040069 self.glyphs = [g for g in self.glyphs if g in glyphs]
70 return indices
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040071
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040072@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040073def remap(self, coverage_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040074 "Remaps coverage."
75 self.glyphs = [self.glyphs[i] for i in coverage_map]
Behdad Esfahbod14374262013-08-08 22:26:49 -040076
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040077@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040078def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040079 "Returns ascending list of matching class values."
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040080 return _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040081 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040082 [v for g,v in self.classDefs.iteritems() if g in glyphs])
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040083
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040084@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040085def intersect_class(self, glyphs, klass):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040086 "Returns set of glyphs matching class."
87 if klass == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040088 return set(g for g in glyphs if g not in self.classDefs)
89 return set(g for g,v in self.classDefs.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040090 if v == klass and g in glyphs)
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040091
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040092@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040093def subset(self, glyphs, remap=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040094 "Returns ascending list of remaining classes."
Behdad Esfahbodd73f2252013-08-16 10:58:25 -040095 self.classDefs = dict((g,v) for g,v in self.classDefs.iteritems() if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040096 # Note: while class 0 has the special meaning of "not matched",
97 # if no glyph will ever /not match/, we can optimize class 0 out too.
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040098 indices = _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040099 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod4e5d9672013-08-14 19:49:53 -0400100 self.classDefs.values())
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400101 if remap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400102 self.remap(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400103 return indices
Behdad Esfahbod4aa6ce32013-07-22 12:15:36 -0400104
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400105@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400106def remap(self, class_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400107 "Remaps classes."
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400108 self.classDefs = dict((g,class_map.index(v))
109 for g,v in self.classDefs.iteritems())
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400110
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400111@_add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400112def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400113 if cur_glyphs == None: cur_glyphs = s.glyphs
114 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400115 s.glyphs.update(v for g,v in self.mapping.iteritems() if g in cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400116 else:
117 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400118
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400119@_add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400120def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400121 if self.Format in [1, 2]:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400122 self.mapping = dict((g,v) for g,v in self.mapping.iteritems()
123 if g in s.glyphs and v in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400124 return bool(self.mapping)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400125 else:
126 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400127
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400128@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400129def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400130 if cur_glyphs == None: cur_glyphs = s.glyphs
131 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400132 indices = self.Coverage.intersect(cur_glyphs)
133 s.glyphs.update(*(self.Sequence[i].Substitute for i in indices))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400134 else:
135 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400136
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400137@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400138def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400139 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400140 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400141 self.Sequence = [self.Sequence[i] for i in indices]
142 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400143 indices = [i for i,seq in enumerate(self.Sequence)
144 if all(sub in s.glyphs for sub in seq.Substitute)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400145 self.Sequence = [self.Sequence[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400146 self.Coverage.remap(indices)
147 self.SequenceCount = len(self.Sequence)
148 return bool(self.SequenceCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400149 else:
150 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400151
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400152@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400153def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400154 if cur_glyphs == None: cur_glyphs = s.glyphs
155 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400156 s.glyphs.update(*(vlist for g,vlist in self.alternates.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400157 if g in cur_glyphs))
158 else:
159 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400160
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400161@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400162def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400163 if self.Format == 1:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400164 self.alternates = dict((g,vlist)
165 for g,vlist in self.alternates.iteritems()
166 if g in s.glyphs and
167 all(v in s.glyphs for v in vlist))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400168 return bool(self.alternates)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400169 else:
170 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400171
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400172@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400173def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400174 if cur_glyphs == None: cur_glyphs = s.glyphs
175 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400176 s.glyphs.update(*([seq.LigGlyph for seq in seqs
177 if all(c in s.glyphs for c in seq.Component)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400178 for g,seqs in self.ligatures.iteritems()
179 if g in cur_glyphs))
180 else:
181 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400182
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400183@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400184def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400185 if self.Format == 1:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400186 self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems()
187 if g in s.glyphs)
188 self.ligatures = dict((g,[seq for seq in seqs
189 if seq.LigGlyph in s.glyphs and
190 all(c in s.glyphs for c in seq.Component)])
191 for g,seqs in self.ligatures.iteritems())
192 self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems() if v)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400193 return bool(self.ligatures)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400194 else:
195 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400196
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400197@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400198def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400199 if cur_glyphs == None: cur_glyphs = s.glyphs
200 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400201 indices = self.Coverage.intersect(cur_glyphs)
202 if(not indices or
203 not all(c.intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400204 for c in self.LookAheadCoverage + self.BacktrackCoverage)):
205 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400206 s.glyphs.update(self.Substitute[i] for i in indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400207 else:
208 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400209
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400210@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400211def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400212 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400213 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400214 self.Substitute = [self.Substitute[i] for i in indices]
215 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400216 indices = [i for i,sub in enumerate(self.Substitute)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400217 if sub in s.glyphs]
218 self.Substitute = [self.Substitute[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400219 self.Coverage.remap(indices)
220 self.GlyphCount = len(self.Substitute)
221 return bool(self.GlyphCount and
222 all(c.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400223 for c in self.LookAheadCoverage+self.BacktrackCoverage))
224 else:
225 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400226
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400227@_add_method(fontTools.ttLib.tables.otTables.SinglePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400228def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400229 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400230 return len(self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400231 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400232 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400233 self.Value = [self.Value[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400234 self.ValueCount = len(self.Value)
235 return bool(self.ValueCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400236 else:
237 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400238
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400239@_add_method(fontTools.ttLib.tables.otTables.SinglePos)
240def prune_post_subset(self, options):
241 if not options.hinting:
242 # Drop device tables
243 self.ValueFormat &= ~0x00F0
244 return True
245
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400246@_add_method(fontTools.ttLib.tables.otTables.PairPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400247def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400248 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400249 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400250 self.PairSet = [self.PairSet[i] for i in indices]
251 for p in self.PairSet:
252 p.PairValueRecord = [r for r in p.PairValueRecord
253 if r.SecondGlyph in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400254 p.PairValueCount = len(p.PairValueRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400255 self.PairSet = [p for p in self.PairSet if p.PairValueCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400256 self.PairSetCount = len(self.PairSet)
257 return bool(self.PairSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400258 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400259 class1_map = self.ClassDef1.subset(s.glyphs, remap=True)
260 class2_map = self.ClassDef2.subset(s.glyphs, remap=True)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400261 self.Class1Record = [self.Class1Record[i] for i in class1_map]
262 for c in self.Class1Record:
263 c.Class2Record = [c.Class2Record[i] for i in class2_map]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400264 self.Class1Count = len(class1_map)
265 self.Class2Count = len(class2_map)
266 return bool(self.Class1Count and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400267 self.Class2Count and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400268 self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400269 else:
270 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400271
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400272@_add_method(fontTools.ttLib.tables.otTables.PairPos)
273def prune_post_subset(self, options):
274 if not options.hinting:
275 # Drop device tables
276 self.ValueFormat1 &= ~0x00F0
277 self.ValueFormat2 &= ~0x00F0
278 return True
279
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400280@_add_method(fontTools.ttLib.tables.otTables.CursivePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400281def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400282 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400283 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400284 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400285 self.EntryExitCount = len(self.EntryExitRecord)
286 return bool(self.EntryExitCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400287 else:
288 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400289
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400290@_add_method(fontTools.ttLib.tables.otTables.Anchor)
291def prune_hints(self):
292 # Drop device tables / contour anchor point
293 self.Format = 1
294
295@_add_method(fontTools.ttLib.tables.otTables.CursivePos)
296def prune_post_subset(self, options):
297 if not options.hinting:
298 for rec in self.EntryExitRecord:
299 if rec.EntryAnchor: rec.EntryAnchor.prune_hints()
300 if rec.ExitAnchor: rec.ExitAnchor.prune_hints()
301 return True
302
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400303@_add_method(fontTools.ttLib.tables.otTables.MarkBasePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400304def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400305 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400306 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400307 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
308 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400309 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
310 base_indices = self.BaseCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400311 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i]
312 for i in base_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400313 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400314 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400315 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400316 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400317 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400318 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400319 for b in self.BaseArray.BaseRecord:
320 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400321 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400322 self.MarkArray.MarkCount and
323 self.BaseArray.BaseCount)
324 else:
325 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400326
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400327@_add_method(fontTools.ttLib.tables.otTables.MarkBasePos)
328def prune_post_subset(self, options):
329 if not options.hinting:
330 for m in self.MarkArray.MarkRecord:
331 m.MarkAnchor.prune_hints()
332 for b in self.BaseArray.BaseRecord:
333 for a in b.BaseAnchor:
334 a.prune_hints()
335 return True
336
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400337@_add_method(fontTools.ttLib.tables.otTables.MarkLigPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400338def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400339 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400340 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400341 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
342 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400343 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
344 ligature_indices = self.LigatureCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400345 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i]
346 for i in ligature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400347 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400348 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400349 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400350 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400351 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400352 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400353 for l in self.LigatureArray.LigatureAttach:
354 for c in l.ComponentRecord:
355 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400356 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400357 self.MarkArray.MarkCount and
358 self.LigatureArray.LigatureCount)
359 else:
360 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400361
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400362@_add_method(fontTools.ttLib.tables.otTables.MarkLigPos)
363def prune_post_subset(self, options):
364 if not options.hinting:
365 for m in self.MarkArray.MarkRecord:
366 m.MarkAnchor.prune_hints()
367 for l in self.LigatureArray.LigatureAttach:
368 for c in l.ComponentRecord:
369 for a in c.LigatureAnchor:
370 a.prune_hints()
371 return True
372
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400373@_add_method(fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400374def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400375 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400376 mark1_indices = self.Mark1Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400377 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i]
378 for i in mark1_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400379 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
380 mark2_indices = self.Mark2Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400381 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i]
382 for i in mark2_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400383 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400384 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400385 class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400386 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400387 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400388 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400389 for b in self.Mark2Array.Mark2Record:
390 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400391 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400392 self.Mark1Array.MarkCount and
393 self.Mark2Array.MarkCount)
394 else:
395 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400396
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400397@_add_method(fontTools.ttLib.tables.otTables.MarkMarkPos)
398def prune_post_subset(self, options):
399 if not options.hinting:
400 # Drop device tables or contour anchor point
401 for m in self.Mark1Array.MarkRecord:
402 m.MarkAnchor.prune_hints()
403 for b in self.Mark2Array.Mark2Record:
404 for m in rec.Mark2Anchor:
405 m.prune_hints()
406 return True
407
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400408@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
409 fontTools.ttLib.tables.otTables.MultipleSubst,
410 fontTools.ttLib.tables.otTables.AlternateSubst,
411 fontTools.ttLib.tables.otTables.LigatureSubst,
412 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
413 fontTools.ttLib.tables.otTables.SinglePos,
414 fontTools.ttLib.tables.otTables.PairPos,
415 fontTools.ttLib.tables.otTables.CursivePos,
416 fontTools.ttLib.tables.otTables.MarkBasePos,
417 fontTools.ttLib.tables.otTables.MarkLigPos,
418 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400419def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400420 pass
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400421
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400422@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
423 fontTools.ttLib.tables.otTables.MultipleSubst,
424 fontTools.ttLib.tables.otTables.AlternateSubst,
425 fontTools.ttLib.tables.otTables.LigatureSubst,
426 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
427 fontTools.ttLib.tables.otTables.SinglePos,
428 fontTools.ttLib.tables.otTables.PairPos,
429 fontTools.ttLib.tables.otTables.CursivePos,
430 fontTools.ttLib.tables.otTables.MarkBasePos,
431 fontTools.ttLib.tables.otTables.MarkLigPos,
432 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400433def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400434 return []
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400435
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400436@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400437 fontTools.ttLib.tables.otTables.MultipleSubst,
438 fontTools.ttLib.tables.otTables.AlternateSubst,
439 fontTools.ttLib.tables.otTables.LigatureSubst,
440 fontTools.ttLib.tables.otTables.ContextSubst,
441 fontTools.ttLib.tables.otTables.ChainContextSubst,
442 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
443 fontTools.ttLib.tables.otTables.SinglePos,
444 fontTools.ttLib.tables.otTables.PairPos,
445 fontTools.ttLib.tables.otTables.CursivePos,
446 fontTools.ttLib.tables.otTables.MarkBasePos,
447 fontTools.ttLib.tables.otTables.MarkLigPos,
448 fontTools.ttLib.tables.otTables.MarkMarkPos,
449 fontTools.ttLib.tables.otTables.ContextPos,
450 fontTools.ttLib.tables.otTables.ChainContextPos)
451def prune_pre_subset(self, options):
452 return True
453
454@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
455 fontTools.ttLib.tables.otTables.MultipleSubst,
456 fontTools.ttLib.tables.otTables.AlternateSubst,
457 fontTools.ttLib.tables.otTables.LigatureSubst,
458 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
459 fontTools.ttLib.tables.otTables.ContextSubst,
460 fontTools.ttLib.tables.otTables.ChainContextSubst,
461 fontTools.ttLib.tables.otTables.ContextPos,
462 fontTools.ttLib.tables.otTables.ChainContextPos)
463def prune_post_subset(self, options):
464 return True
465
466@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400467 fontTools.ttLib.tables.otTables.AlternateSubst,
468 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400469def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400470 return False
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400471
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400472@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst,
473 fontTools.ttLib.tables.otTables.LigatureSubst,
474 fontTools.ttLib.tables.otTables.ContextSubst,
475 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400476def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400477 return True
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400478
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400479@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
480 fontTools.ttLib.tables.otTables.ChainContextSubst,
481 fontTools.ttLib.tables.otTables.ContextPos,
482 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400483def __classify_context(self):
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400484
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -0400485 class ContextHelper(object):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400486 def __init__(self, klass, Format):
487 if klass.__name__.endswith('Subst'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400488 Typ = 'Sub'
489 Type = 'Subst'
490 else:
491 Typ = 'Pos'
492 Type = 'Pos'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400493 if klass.__name__.startswith('Chain'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400494 Chain = 'Chain'
495 else:
496 Chain = ''
497 ChainTyp = Chain+Typ
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400498
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400499 self.Typ = Typ
500 self.Type = Type
501 self.Chain = Chain
502 self.ChainTyp = ChainTyp
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400503
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400504 self.LookupRecord = Type+'LookupRecord'
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400505
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400506 if Format == 1:
507 Coverage = lambda r: r.Coverage
508 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400509 ContextData = lambda r:(None,)
510 ChainContextData = lambda r:(None, None, None)
511 RuleData = lambda r:(r.Input,)
512 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400513 SetRuleData = None
514 ChainSetRuleData = None
515 elif Format == 2:
516 Coverage = lambda r: r.Coverage
517 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400518 ContextData = lambda r:(r.ClassDef,)
519 ChainContextData = lambda r:(r.LookAheadClassDef,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400520 r.InputClassDef,
521 r.BacktrackClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400522 RuleData = lambda r:(r.Class,)
523 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack)
524 def SetRuleData(r, d):(r.Class,) = d
525 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400526 elif Format == 3:
527 Coverage = lambda r: r.Coverage[0]
528 ChainCoverage = lambda r: r.InputCoverage[0]
529 ContextData = None
530 ChainContextData = None
531 RuleData = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400532 ChainRuleData = lambda r:(r.LookAheadCoverage +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400533 r.InputCoverage +
534 r.BacktrackCoverage)
535 SetRuleData = None
536 ChainSetRuleData = None
537 else:
538 assert 0, "unknown format: %s" % Format
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400539
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400540 if Chain:
541 self.Coverage = ChainCoverage
542 self.ContextData = ChainContextData
543 self.RuleData = ChainRuleData
544 self.SetRuleData = ChainSetRuleData
545 else:
546 self.Coverage = Coverage
547 self.ContextData = ContextData
548 self.RuleData = RuleData
549 self.SetRuleData = SetRuleData
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400550
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400551 if Format == 1:
552 self.Rule = ChainTyp+'Rule'
553 self.RuleCount = ChainTyp+'RuleCount'
554 self.RuleSet = ChainTyp+'RuleSet'
555 self.RuleSetCount = ChainTyp+'RuleSetCount'
556 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
557 elif Format == 2:
558 self.Rule = ChainTyp+'ClassRule'
559 self.RuleCount = ChainTyp+'ClassRuleCount'
560 self.RuleSet = ChainTyp+'ClassSet'
561 self.RuleSetCount = ChainTyp+'ClassSetCount'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400562 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r)
Behdad Esfahbod89987002013-07-23 23:07:42 -0400563
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400564 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
Behdad Esfahbod11763302013-08-14 15:33:08 -0400565 self.Input = 'Input' if Chain else 'Class'
Behdad Esfahbod27108392013-07-23 16:40:47 -0400566
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400567 if self.Format not in [1, 2, 3]:
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400568 return None # Don't shoot the messenger; let it go
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400569 if not hasattr(self.__class__, "__ContextHelpers"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400570 self.__class__.__ContextHelpers = {}
571 if self.Format not in self.__class__.__ContextHelpers:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400572 helper = ContextHelper(self.__class__, self.Format)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400573 self.__class__.__ContextHelpers[self.Format] = helper
574 return self.__class__.__ContextHelpers[self.Format]
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400575
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400576@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
577 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400578def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400579 if cur_glyphs == None: cur_glyphs = s.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400580 c = self.__classify_context()
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400581
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400582 indices = c.Coverage(self).intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400583 if not indices:
584 return []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400585 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs);
Behdad Esfahbod1d4fa132013-08-08 22:59:32 -0400586
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400587 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400588 ContextData = c.ContextData(self)
589 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400590 for i in indices:
591 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400592 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400593 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400594 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
595 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400596 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400597 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400598 if not ll: continue
599 seqi = ll.SequenceIndex
600 if seqi == 0:
Behdad Esfahbodd3fdcc72013-08-14 17:59:31 -0400601 pos_glyphs = set([c.Coverage(self).glyphs[i]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400602 else:
603 if chaos:
604 pos_glyphs = s.glyphs
605 else:
Behdad Esfahbodd3fdcc72013-08-14 17:59:31 -0400606 pos_glyphs = set([r.Input[seqi - 1]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400607 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400608 chaos = chaos or lookup.may_have_non_1to1()
609 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400610 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400611 ClassDef = getattr(self, c.ClassDef)
612 indices = ClassDef.intersect(cur_glyphs)
613 ContextData = c.ContextData(self)
614 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400615 for i in indices:
616 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400617 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400618 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400619 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
620 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400621 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400622 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400623 if not ll: continue
624 seqi = ll.SequenceIndex
625 if seqi == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400626 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400627 else:
628 if chaos:
629 pos_glyphs = s.glyphs
630 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400631 pos_glyphs = ClassDef.intersect_class(s.glyphs,
Behdad Esfahbod11763302013-08-14 15:33:08 -0400632 getattr(r, c.Input)[seqi - 1])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400633 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400634 chaos = chaos or lookup.may_have_non_1to1()
635 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400636 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400637 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400638 return []
639 r = self
640 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400641 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400642 if not ll: continue
643 seqi = ll.SequenceIndex
644 if seqi == 0:
645 pos_glyphs = cur_glyphs
646 else:
647 if chaos:
648 pos_glyphs = s.glyphs
649 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400650 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400651 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400652 chaos = chaos or lookup.may_have_non_1to1()
653 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400654 else:
655 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod00776972013-07-23 15:33:00 -0400656
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400657@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
658 fontTools.ttLib.tables.otTables.ContextPos,
659 fontTools.ttLib.tables.otTables.ChainContextSubst,
660 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400661def subset_glyphs(self, s):
662 c = self.__classify_context()
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400663
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400664 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400665 indices = self.Coverage.subset(s.glyphs)
666 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400667 rss = [rss[i] for i in indices]
668 for rs in rss:
669 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400670 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400671 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400672 if r and all(all(g in s.glyphs for g in glist)
673 for glist in c.RuleData(r))]
674 setattr(rs, c.Rule, ss)
675 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400676 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400677 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
678 setattr(self, c.RuleSet, rss)
679 setattr(self, c.RuleSetCount, len(rss))
680 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400681 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400682 if not self.Coverage.subset(s.glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400683 return False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400684 indices = getattr(self, c.ClassDef).subset(self.Coverage.glyphs,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400685 remap=False)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400686 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400687 rss = [rss[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400688 ContextData = c.ContextData(self)
689 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400690 for rs in rss:
691 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400692 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400693 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400694 if r and all(all(k in klass_map for k in klist)
695 for klass_map,klist in zip(klass_maps, c.RuleData(r)))]
696 setattr(rs, c.Rule, ss)
697 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400698
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400699 # Remap rule classes
700 for r in ss:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400701 c.SetRuleData(r, [[klass_map.index(k) for k in klist]
702 for klass_map,klist in zip(klass_maps, c.RuleData(r))])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400703 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400704 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
705 setattr(self, c.RuleSet, rss)
706 setattr(self, c.RuleSetCount, len(rss))
707 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400708 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400709 return all(x.subset(s.glyphs) for x in c.RuleData(self))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400710 else:
711 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400712
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400713@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
714 fontTools.ttLib.tables.otTables.ChainContextSubst,
715 fontTools.ttLib.tables.otTables.ContextPos,
716 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400717def subset_lookups(self, lookup_indices):
718 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400719
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400720 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400721 for rs in getattr(self, c.RuleSet):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400722 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400723 for r in getattr(rs, c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400724 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400725 setattr(r, c.LookupRecord,
726 [ll for ll in getattr(r, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400727 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400728 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400729 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400730 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400731 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400732 setattr(self, c.LookupRecord,
733 [ll for ll in getattr(self, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400734 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400735 for ll in getattr(self, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400736 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400737 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400738 else:
739 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400740
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400741@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
742 fontTools.ttLib.tables.otTables.ChainContextSubst,
743 fontTools.ttLib.tables.otTables.ContextPos,
744 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400745def collect_lookups(self):
746 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400747
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400748 if self.Format in [1, 2]:
749 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400750 for rs in getattr(self, c.RuleSet) if rs
751 for r in getattr(rs, c.Rule) if r
752 for ll in getattr(r, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400753 elif self.Format == 3:
754 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400755 for ll in getattr(self, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400756 else:
757 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400758
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400759@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400760def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400761 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400762 self.ExtSubTable.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400763 else:
764 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400765
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400766@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400767def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400768 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400769 return self.ExtSubTable.may_have_non_1to1()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400770 else:
771 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400772
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400773@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
774 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400775def prune_pre_subset(self, options):
776 if self.Format == 1:
777 return self.ExtSubTable.prune_pre_subset(options)
778 else:
779 assert 0, "unknown format: %s" % self.Format
780
781@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
782 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400783def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400784 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400785 return self.ExtSubTable.subset_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400786 else:
787 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400788
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400789@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
790 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400791def prune_post_subset(self, options):
792 if self.Format == 1:
793 return self.ExtSubTable.prune_post_subset(options)
794 else:
795 assert 0, "unknown format: %s" % self.Format
796
797@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
798 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400799def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400800 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400801 return self.ExtSubTable.subset_lookups(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400802 else:
803 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400804
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400805@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
806 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400807def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400808 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400809 return self.ExtSubTable.collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400810 else:
811 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400812
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400813@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400814def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400815 for st in self.SubTable:
816 if not st: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400817 st.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400818
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400819@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400820def prune_pre_subset(self, options):
821 ret = False
822 for st in self.SubTable:
823 if not st: continue
824 if st.prune_pre_subset(options): ret = True
825 return ret
826
827@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400828def subset_glyphs(self, s):
829 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)]
830 self.SubTableCount = len(self.SubTable)
831 return bool(self.SubTableCount)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400832
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400833@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400834def prune_post_subset(self, options):
835 ret = False
836 for st in self.SubTable:
837 if not st: continue
838 if st.prune_post_subset(options): ret = True
839 return ret
840
841@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400842def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400843 for s in self.SubTable:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400844 s.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400845
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400846@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400847def collect_lookups(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400848 return _uniq_sort(sum((st.collect_lookups() for st in self.SubTable
849 if st), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400850
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400851@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400852def may_have_non_1to1(self):
853 return any(st.may_have_non_1to1() for st in self.SubTable if st)
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400854
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400855@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400856def prune_pre_subset(self, options):
857 ret = False
858 for l in self.Lookup:
859 if not l: continue
860 if l.prune_pre_subset(options): ret = True
861 return ret
862
863@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400864def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400865 "Returns the indices of nonempty lookups."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400866 return [i for i,l in enumerate(self.Lookup) if l and l.subset_glyphs(s)]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400867
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400868@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400869def prune_post_subset(self, options):
870 ret = False
871 for l in self.Lookup:
872 if not l: continue
873 if l.prune_post_subset(options): ret = True
874 return ret
875
876@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400877def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400878 self.Lookup = [self.Lookup[i] for i in lookup_indices
879 if i < self.LookupCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400880 self.LookupCount = len(self.Lookup)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400881 for l in self.Lookup:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400882 l.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400883
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400884@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400885def closure_lookups(self, lookup_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400886 lookup_indices = _uniq_sort(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400887 recurse = lookup_indices
888 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400889 recurse_lookups = sum((self.Lookup[i].collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400890 for i in recurse if i < self.LookupCount), [])
891 recurse_lookups = [l for l in recurse_lookups
892 if l not in lookup_indices and l < self.LookupCount]
893 if not recurse_lookups:
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400894 return _uniq_sort(lookup_indices)
895 recurse_lookups = _uniq_sort(recurse_lookups)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400896 lookup_indices.extend(recurse_lookups)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400897 recurse = recurse_lookups
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400898
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400899@_add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400900def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400901 self.LookupListIndex = [l for l in self.LookupListIndex
902 if l in lookup_indices]
903 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400904 self.LookupListIndex = [lookup_indices.index(l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400905 for l in self.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400906 self.LookupCount = len(self.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400907 return self.LookupCount
Behdad Esfahbod54660612013-07-21 18:16:55 -0400908
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400909@_add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400910def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400911 return self.LookupListIndex[:]
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400912
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400913@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400914def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400915 "Returns the indices of nonempty features."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400916 feature_indices = [i for i,f in enumerate(self.FeatureRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400917 if f.Feature.subset_lookups(lookup_indices)]
918 self.subset_features(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400919 return feature_indices
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400920
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400921@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400922def collect_lookups(self, feature_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400923 return _uniq_sort(sum((self.FeatureRecord[i].Feature.collect_lookups()
924 for i in feature_indices
Behdad Esfahbod1ee298d2013-08-13 20:07:09 -0400925 if i < self.FeatureCount), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400926
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400927@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400928def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400929 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400930 self.FeatureCount = len(self.FeatureRecord)
931 return bool(self.FeatureCount)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400932
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400933@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
934 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400935def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400936 if self.ReqFeatureIndex in feature_indices:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400937 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400938 else:
939 self.ReqFeatureIndex = 65535
940 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
941 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400942 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400943 if f in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400944 self.FeatureCount = len(self.FeatureIndex)
945 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400946
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400947@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
948 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400949def collect_features(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400950 feature_indices = self.FeatureIndex[:]
951 if self.ReqFeatureIndex != 65535:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400952 feature_indices.append(self.ReqFeatureIndex)
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400953 return _uniq_sort(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400954
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400955@_add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400956def subset_features(self, feature_indices):
957 if(self.DefaultLangSys and
958 not self.DefaultLangSys.subset_features(feature_indices)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400959 self.DefaultLangSys = None
960 self.LangSysRecord = [l for l in self.LangSysRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400961 if l.LangSys.subset_features(feature_indices)]
962 self.LangSysCount = len(self.LangSysRecord)
963 return bool(self.LangSysCount or self.DefaultLangSys)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400964
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400965@_add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400966def collect_features(self):
967 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400968 if self.DefaultLangSys:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400969 feature_indices.append(self.DefaultLangSys.collect_features())
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400970 return _uniq_sort(sum(feature_indices, []))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400971
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400972@_add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400973def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400974 self.ScriptRecord = [s for s in self.ScriptRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400975 if s.Script.subset_features(feature_indices)]
976 self.ScriptCount = len(self.ScriptRecord)
977 return bool(self.ScriptCount)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400978
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400979@_add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400980def collect_features(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400981 return _uniq_sort(sum((s.Script.collect_features()
982 for s in self.ScriptRecord), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400983
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400984@_add_method(fontTools.ttLib.getTableClass('GSUB'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400985def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400986 s.table = self.table
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400987 feature_indices = self.table.ScriptList.collect_features()
988 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400989 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400990 orig_glyphs = s.glyphs.copy()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400991 for i in lookup_indices:
992 if i >= self.table.LookupList.LookupCount: continue
993 if not self.table.LookupList.Lookup[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400994 self.table.LookupList.Lookup[i].closure_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400995 if orig_glyphs == s.glyphs:
996 break
997 del s.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400998
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400999@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1000 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001001def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001002 s.glyphs = s.glyphs_gsubed
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001003 lookup_indices = self.table.LookupList.subset_glyphs(s)
1004 self.subset_lookups(lookup_indices)
1005 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001006 return True
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001007
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001008@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1009 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001010def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001011 """Retrains specified lookups, then removes empty features, language
1012 systems, and scripts."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001013 self.table.LookupList.subset_lookups(lookup_indices)
1014 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices)
1015 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -04001016
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001017@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1018 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001019def prune_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001020 "Remove unreferenced lookups"
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001021 feature_indices = self.table.ScriptList.collect_features()
1022 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
1023 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices)
1024 self.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -04001025
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001026@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1027 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001028def subset_feature_tags(self, feature_tags):
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001029 feature_indices = [i for i,f in
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001030 enumerate(self.table.FeatureList.FeatureRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001031 if f.FeatureTag in feature_tags]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001032 self.table.FeatureList.subset_features(feature_indices)
1033 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001034
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001035@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1036 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001037def prune_pre_subset(self, options):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001038 if '*' not in options.layout_features:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001039 self.subset_feature_tags(options.layout_features)
1040 self.prune_lookups()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -04001041 self.table.LookupList.prune_pre_subset(options);
1042 return True
1043
1044@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1045 fontTools.ttLib.getTableClass('GPOS'))
1046def prune_post_subset(self, options):
1047 self.table.LookupList.prune_post_subset(options);
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001048 return True
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001049
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001050@_add_method(fontTools.ttLib.getTableClass('GDEF'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001051def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001052 glyphs = s.glyphs_gsubed
1053 table = self.table
1054 if table.LigCaretList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001055 indices = table.LigCaretList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001056 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i]
1057 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001058 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001059 if not table.LigCaretList.LigGlyphCount:
1060 table.LigCaretList = None
1061 if table.MarkAttachClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001062 table.MarkAttachClassDef.classDefs = dict((g,v) for g,v in
1063 table.MarkAttachClassDef.
1064 classDefs.iteritems()
1065 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001066 if not table.MarkAttachClassDef.classDefs:
1067 table.MarkAttachClassDef = None
1068 if table.GlyphClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001069 table.GlyphClassDef.classDefs = dict((g,v) for g,v in
1070 table.GlyphClassDef.
1071 classDefs.iteritems()
1072 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001073 if not table.GlyphClassDef.classDefs:
1074 table.GlyphClassDef = None
1075 if table.AttachList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001076 indices = table.AttachList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001077 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i]
1078 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001079 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001080 if not table.AttachList.GlyphCount:
1081 table.AttachList = None
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001082 return bool(table.LigCaretList or
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001083 table.MarkAttachClassDef or
1084 table.GlyphClassDef or
1085 table.AttachList)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001086
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001087@_add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001088def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001089 # Prune unknown kern table types
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001090 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')]
1091 return bool(self.kernTables)
Behdad Esfahbodd4e33a72013-07-24 18:51:05 -04001092
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001093@_add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001094def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001095 glyphs = s.glyphs_gsubed
1096 for t in self.kernTables:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001097 t.kernTable = dict(((a,b),v) for (a,b),v in t.kernTable.iteritems()
1098 if a in glyphs and b in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001099 self.kernTables = [t for t in self.kernTables if t.kernTable]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001100 return bool(self.kernTables)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001101
Behdad Esfahbode7a0d562013-08-16 10:56:30 -04001102@_add_method(fontTools.ttLib.getTableClass('vmtx'),
1103 fontTools.ttLib.getTableClass('hmtx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001104def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001105 self.metrics = dict((g,v) for g,v in self.metrics.iteritems() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001106 return bool(self.metrics)
Behdad Esfahbodc7160442013-07-22 14:29:08 -04001107
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001108@_add_method(fontTools.ttLib.getTableClass('hdmx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001109def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001110 self.hdmx = dict((sz,_dict((g,v) for g,v in l.iteritems() if g in s.glyphs))
1111 for sz,l in self.hdmx.iteritems())
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001112 return bool(self.hdmx)
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -04001113
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001114@_add_method(fontTools.ttLib.getTableClass('VORG'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001115def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001116 self.VOriginRecords = dict((g,v) for g,v in self.VOriginRecords.iteritems()
1117 if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001118 self.numVertOriginYMetrics = len(self.VOriginRecords)
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001119 return True # Never drop; has default metrics
Behdad Esfahbode45d6af2013-07-22 15:29:17 -04001120
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001121@_add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001122def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001123 if not options.glyph_names:
1124 self.formatType = 3.0
1125 return True
Behdad Esfahbod42648242013-07-23 12:56:06 -04001126
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001127@_add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001128def subset_glyphs(self, s):
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001129 self.extraNames = [] # This seems to do it
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001130 return True
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001131
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001132@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001133def getComponentNamesFast(self, glyfTable):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001134 if struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001135 return [] # Not composite
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001136 data = self.data
1137 i = 10
1138 components = []
1139 more = 1
1140 while more:
1141 flags, glyphID = struct.unpack(">HH", data[i:i+4])
1142 i += 4
1143 flags = int(flags)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001144 components.append(glyfTable.getGlyphName(int(glyphID)))
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001145
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001146 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001147 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001148 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1149 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1150 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1151 more = flags & 0x0020 # MORE_COMPONENTS
1152
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001153 return components
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001154
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001155@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001156def remapComponentsFast(self, indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001157 if struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001158 return # Not composite
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001159 data = bytearray(self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001160 i = 10
1161 more = 1
1162 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001163 flags =(data[i] << 8) | data[i+1]
1164 glyphID =(data[i+2] << 8) | data[i+3]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001165 # Remap
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001166 glyphID = indices.index(glyphID)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001167 data[i+2] = glyphID >> 8
1168 data[i+3] = glyphID & 0xFF
1169 i += 4
1170 flags = int(flags)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001171
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001172 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001173 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001174 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1175 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1176 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1177 more = flags & 0x0020 # MORE_COMPONENTS
1178
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001179 self.data = str(data)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001180
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001181@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001182def dropInstructionsFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001183 numContours = struct.unpack(">h", self.data[:2])[0]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001184 data = bytearray(self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001185 i = 10
1186 if numContours >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001187 i += 2 * numContours # endPtsOfContours
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001188 instructionLen =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001189 # Zero it
1190 data[i] = data [i+1] = 0
1191 i += 2
1192 if instructionLen:
1193 # Splice it out
1194 data = data[:i] + data[i+instructionLen:]
1195 else:
1196 more = 1
1197 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001198 flags =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001199 # Turn instruction flag off
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001200 flags &= ~0x0100 # WE_HAVE_INSTRUCTIONS
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001201 data[i+0] = flags >> 8
1202 data[i+1] = flags & 0xFF
1203 i += 4
1204 flags = int(flags)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001205
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001206 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001207 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001208 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1209 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1210 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1211 more = flags & 0x0020 # MORE_COMPONENTS
1212
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001213 # Cut off
1214 data = data[:i]
1215 if len(data) % 4:
1216 # add pad bytes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001217 nPadBytes = 4 -(len(data) % 4)
1218 for i in range(nPadBytes):
1219 data.append(0)
1220 self.data = str(data)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001221
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001222@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001223def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001224 decompose = s.glyphs
1225 # I don't know if component glyphs can be composite themselves.
1226 # We handle them anyway.
1227 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001228 components = set()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001229 for g in decompose:
1230 if g not in self.glyphs:
1231 continue
1232 gl = self.glyphs[g]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001233 if hasattr(gl, "data"):
1234 for c in gl.getComponentNamesFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001235 if c not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001236 components.add(c)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001237 else:
1238 # TTX seems to expand gid0..3 always
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001239 if gl.isComposite():
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001240 for c in gl.components:
1241 if c.glyphName not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001242 components.add(c.glyphName)
1243 components = set(c for c in components if c not in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001244 if not components:
1245 break
1246 decompose = components
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001247 s.glyphs.update(components)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001248
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001249@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001250def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001251 self.glyphs = dict((g,v) for g,v in self.glyphs.iteritems() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001252 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001253 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001254 if hasattr(v, "data"):
1255 v.remapComponentsFast(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001256 else:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001257 pass # No need
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001258 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001259 return bool(self.glyphs)
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001260
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001261@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001262def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001263 if not options.hinting:
1264 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001265 if hasattr(v, "data"):
1266 v.dropInstructionsFast()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001267 else:
1268 v.program = fontTools.ttLib.tables.ttProgram.Program()
1269 v.program.fromBytecode([])
1270 return True
Behdad Esfahboded98c612013-07-23 12:37:41 -04001271
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001272@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001273def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001274 cff = self.cff
1275 # CFF table should have one font only
1276 cff.fontNames = cff.fontNames[:1]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001277 return bool(cff.fontNames)
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001278
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001279@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001280def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001281 cff = self.cff
1282 for fontname in cff.keys():
1283 font = cff[fontname]
1284 cs = font.CharStrings
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001285
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001286 # Load all glyphs
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001287 for g in font.charset:
1288 if g not in s.glyphs: continue
1289 c,sel = cs.getItemAndSelector(g)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001290
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001291 if cs.charStringsAreIndexed:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001292 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001293 csi = cs.charStringsIndex
1294 csi.items = [csi.items[i] for i in indices]
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001295 csi.count = len(csi.items)
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001296 del csi.file, csi.offsets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001297 if hasattr(font, "FDSelect"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001298 sel = font.FDSelect
1299 sel.format = None
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001300 sel.gidArray = [sel.gidArray[i] for i in indices]
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001301 cs.charStrings = dict((g,indices.index(v))
1302 for g,v in cs.charStrings.iteritems()
1303 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001304 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001305 cs.charStrings = dict((g,v)
1306 for g,v in cs.charStrings.iteritems()
1307 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001308 font.charset = [g for g in font.charset if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001309 font.numGlyphs = len(font.charset)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001310
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001311 return any(cff[fontname].numGlyphs for fontname in cff.keys())
1312
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001313@_add_method(fontTools.misc.psCharStrings.T2CharString)
1314def subset_subroutines(self, subrs, gsubrs):
1315 p = self.program
1316 for i in range(1, len(p)):
1317 if p[i] == 'callsubr':
1318 assert type(p[i-1]) is int
1319 p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias
1320 elif p[i] == 'callgsubr':
1321 assert type(p[i-1]) is int
1322 p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias
1323
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001324@_add_method(fontTools.ttLib.getTableClass('CFF '))
1325def prune_post_subset(self, options):
1326 cff = self.cff
1327
1328 class _MarkingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1329
1330 def __init__(self, localSubrs, globalSubrs):
1331 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1332 localSubrs,
1333 globalSubrs)
1334 for subrs in [localSubrs, globalSubrs]:
1335 if subrs and not hasattr(subrs, "_used"):
1336 subrs._used = set()
1337
1338 def op_callsubr(self, index):
1339 self.localSubrs._used.add(self.operandStack[-1]+self.localBias)
1340 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
1341
1342 def op_callgsubr(self, index):
1343 self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias)
1344 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
1345
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001346 class _NonrecursingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1347
1348 def __init__(self, localSubrs, globalSubrs):
1349 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1350 localSubrs,
1351 globalSubrs)
1352
1353 def op_callsubr(self, index):
1354 self.pop()
1355
1356 def op_callgsubr(self, index):
1357 self.pop()
1358
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001359 for fontname in cff.keys():
1360 font = cff[fontname]
1361 cs = font.CharStrings
1362
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001363 # Drop unused FontDictionaries
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001364 if hasattr(font, "FDSelect"):
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001365 sel = font.FDSelect
1366 indices = _uniq_sort(sel.gidArray)
1367 sel.gidArray = [indices.index (ss) for ss in sel.gidArray]
1368 arr = font.FDArray
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001369 arr.items = [arr[i] for i in indices]
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001370 arr.count = len(arr.items)
1371 del arr.file, arr.offsets
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001372
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001373 # Mark all used subroutines
1374 for g in font.charset:
1375 c,sel = cs.getItemAndSelector(g)
1376 subrs = getattr(c.private, "Subrs", [])
1377 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs)
1378 decompiler.execute(c)
1379
1380 # Renumber subroutines to remove unused ones
1381 all_subrs = [font.GlobalSubrs]
1382 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs'))
1383 # Prepare
1384 for subrs in all_subrs:
1385 if not subrs: continue
1386 if not hasattr(subrs, '_used'):
1387 subrs._used = set()
1388 subrs._used = _uniq_sort(subrs._used)
1389 subrs._old_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs)
1390 subrs._new_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs._used)
Behdad Esfahboded107712013-08-14 19:54:13 -04001391 # Renumber glyph charstrings
1392 for g in font.charset:
1393 c,sel = cs.getItemAndSelector(g)
1394 subrs = getattr(c.private, "Subrs", [])
1395 c.subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001396 # Renumber subroutines themselves
1397 for subrs in all_subrs:
1398 if not subrs: continue
1399 decompiler = _NonrecursingT2Decompiler(subrs, font.GlobalSubrs)
1400 for i in range (subrs.count):
1401 if i not in subrs._used: continue
1402 decompiler.reset()
1403 decompiler.execute(subrs[i])
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001404 subrs[i].subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001405 # Cleanup
1406 for subrs in all_subrs:
1407 if not subrs: continue
1408 subrs.items = [subrs.items[i] for i in subrs._used]
1409 del subrs.file, subrs.offsets
1410 del subrs._used, subrs._old_bias, subrs._new_bias
1411
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001412 if not options.hinting:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001413 pass # TODO(behdad) Drop hints
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001414
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001415 return True
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001416
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001417@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001418def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001419 tables = [t for t in self.tables
1420 if t.platformID == 3 and t.platEncID in [1, 10]]
1421 for u in s.unicodes_requested:
1422 found = False
1423 for table in tables:
1424 if u in table.cmap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001425 s.glyphs.add(table.cmap[u])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001426 found = True
1427 break
1428 if not found:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001429 s.log("No glyph for Unicode value %s; skipping." % u)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001430
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001431@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001432def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001433 if not options.legacy_cmap:
1434 # Drop non-Unicode / non-Symbol cmaps
1435 self.tables = [t for t in self.tables
1436 if t.platformID == 3 and t.platEncID in [0, 1, 10]]
1437 if not options.symbol_cmap:
1438 self.tables = [t for t in self.tables
1439 if t.platformID == 3 and t.platEncID in [1, 10]]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001440 # TODO(behdad) Only keep one subtable?
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001441 # For now, drop format=0 which can't be subset_glyphs easily?
1442 self.tables = [t for t in self.tables if t.format != 0]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001443 return bool(self.tables)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001444
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001445@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001446def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001447 s.glyphs = s.glyphs_cmaped
1448 for t in self.tables:
1449 # For reasons I don't understand I need this here
1450 # to force decompilation of the cmap format 14.
1451 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001452 getattr(t, "asdf")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001453 except AttributeError:
1454 pass
1455 if t.format == 14:
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001456 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None).
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001457 t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs])
1458 for v,l in t.uvsDict.iteritems())
1459 t.uvsDict = dict((v,l) for v,l in t.uvsDict.iteritems() if l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001460 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001461 t.cmap = dict((u,g) for u,g in t.cmap.iteritems()
1462 if g in s.glyphs_requested or u in s.unicodes_requested)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001463 self.tables = [t for t in self.tables
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001464 if (t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001465 # TODO(behdad) Convert formats when needed.
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001466 # In particular, if we have a format=12 without non-BMP
1467 # characters, either drop format=12 one or convert it
1468 # to format=4 if there's not one.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001469 return bool(self.tables)
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001470
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001471@_add_method(fontTools.ttLib.getTableClass('name'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001472def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001473 if '*' not in options.name_IDs:
1474 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1475 if not options.name_legacy:
1476 self.names = [n for n in self.names
1477 if n.platformID == 3 and n.platEncID == 1]
1478 if '*' not in options.name_languages:
1479 self.names = [n for n in self.names if n.langID in options.name_languages]
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001480 return True # Retain even if empty
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001481
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001482
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001483# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange?
1484# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
Behdad Esfahbod10195332013-08-14 19:55:24 -04001485# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001486# TODO(behdad) Text direction considerations.
1487# TODO(behdad) Text script / language considerations.
Behdad Esfahbod56ebd042013-07-22 13:02:24 -04001488
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001489
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001490class Options(object):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001491
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001492 class UnknownOptionError(Exception):
1493 pass
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001494
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001495 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC',
1496 'PCLT', 'LTSH']
1497 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
1498 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
1499 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1500 'loca', 'name', 'cvt ', 'fpgm', 'prep']
1501 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001502
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001503 # Based on HarfBuzz shapers
1504 _layout_features_groups = {
1505 # Default shaper
1506 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1507 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
1508 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
1509 'ltr': ['ltra', 'ltrm'],
1510 'rtl': ['rtla', 'rtlm'],
1511 # Complex shapers
1512 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1513 'cswh', 'mset'],
1514 'hangul': ['ljmo', 'vjmo', 'tjmo'],
1515 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'],
1516 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1517 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1518 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
1519 }
1520 _layout_features_default = _uniq_sort(sum(
1521 _layout_features_groups.itervalues(), []))
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001522
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001523 drop_tables = _drop_tables_default
1524 no_subset_tables = _no_subset_tables_default
1525 hinting_tables = _hinting_tables_default
1526 layout_features = _layout_features_default
1527 hinting = False
1528 glyph_names = False
1529 legacy_cmap = False
1530 symbol_cmap = False
1531 name_IDs = [1, 2] # Family and Style
1532 name_legacy = False
1533 name_languages = [0x0409] # English
1534 mandatory_glyphs = True # First four for TrueType, .notdef for CFF
Behdad Esfahbode911de12013-08-16 12:42:34 -04001535 recalc_bounds = False # Recalculate font bounding boxes
1536 canonical_order = True # Order tables as recommended
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001537 flavor = None # May be 'woff'
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001538
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001539 def __init__(self, **kwargs):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001540
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001541 self.set(**kwargs)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001542
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001543 def set(self, **kwargs):
1544 for k,v in kwargs.iteritems():
1545 if not hasattr(self, k):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001546 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001547 setattr(self, k, v)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001548
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001549 def parse_opts(self, argv, ignore_unknown=False):
1550 ret = []
1551 opts = {}
1552 for a in argv:
1553 orig_a = a
1554 if not a.startswith('--'):
1555 ret.append(a)
1556 continue
1557 a = a[2:]
1558 i = a.find('=')
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001559 op = '='
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001560 if i == -1:
1561 if a.startswith("no-"):
1562 k = a[3:]
1563 v = False
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001564 else:
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001565 k = a
1566 v = True
1567 else:
1568 k = a[:i]
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001569 if k[-1] in "-+":
1570 op = k[-1]+'=' # Ops is '-=' or '+=' now.
1571 k = k[:-1]
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001572 v = a[i+1:]
1573 k = k.replace('-', '_')
1574 if not hasattr(self, k):
1575 if ignore_unknown == True or k in ignore_unknown:
1576 ret.append(orig_a)
1577 continue
1578 else:
1579 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001580
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001581 ov = getattr(self, k)
1582 if isinstance(ov, bool):
1583 v = bool(v)
1584 elif isinstance(ov, int):
1585 v = int(v)
1586 elif isinstance(ov, list):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001587 vv = v.split(',')
1588 if vv == ['']:
1589 vv = []
1590 vv = [int(x, 0) if len(x) and x[0] in range(10) else x for x in vv]
1591 if op == '=':
1592 v = vv
1593 elif op == '+=':
1594 v = ov
1595 v.extend(vv)
1596 elif op == '-=':
1597 v = ov
1598 for x in vv:
1599 if x in v:
1600 v.remove(x)
1601 else:
1602 assert 0
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001603
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001604 opts[k] = v
1605 self.set(**opts)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001606
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001607 return ret
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001608
1609
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001610class Subsetter(object):
1611
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001612 def __init__(self, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001613
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001614 if not log:
1615 log = Logger()
1616 if not options:
1617 options = Options()
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001618
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001619 self.options = options
1620 self.log = log
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001621 self.unicodes_requested = set()
1622 self.glyphs_requested = set()
1623 self.glyphs = set()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001624
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001625 def populate(self, glyphs=[], unicodes=[], text=""):
1626 self.unicodes_requested.update(unicodes)
1627 if isinstance(text, str):
1628 text = text.decode("utf8")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001629 for u in text:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001630 self.unicodes_requested.add(ord(u))
1631 self.glyphs_requested.update(glyphs)
1632 self.glyphs.update(glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001633
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001634 def _prune_pre_subset(self, font):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001635
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001636 for tag in font.keys():
1637 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001638
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001639 if(tag in self.options.drop_tables or
1640 (tag in self.options.hinting_tables and not self.options.hinting)):
1641 self.log(tag, "dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001642 del font[tag]
1643 continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001644
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001645 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001646
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001647 if hasattr(clazz, 'prune_pre_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001648 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001649 retain = table.prune_pre_subset(self.options)
1650 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001651 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001652 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001653 del font[tag]
1654 continue
1655 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001656 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001657
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001658 def _closure_glyphs(self, font):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001659
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001660 self.glyphs = self.glyphs_requested.copy()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001661
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001662 if 'cmap' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001663 font['cmap'].closure_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001664 self.glyphs_cmaped = self.glyphs
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001665
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001666 if self.options.mandatory_glyphs:
1667 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001668 for i in range(4):
1669 self.glyphs.add(font.getGlyphName(i))
1670 self.log("Added first four glyphs to subset")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001671 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001672 self.glyphs.add('.notdef')
1673 self.log("Added .notdef glyph to subset")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001674
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001675 if 'GSUB' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001676 self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1677 len(self.glyphs))
1678 self.log.glyphs(self.glyphs, font=font)
1679 font['GSUB'].closure_glyphs(self)
1680 self.log("Closed glyph list over 'GSUB': %d glyphs after" %
1681 len(self.glyphs))
1682 self.log.glyphs(self.glyphs, font=font)
1683 self.log.lapse("close glyph list over 'GSUB'")
1684 self.glyphs_gsubed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001685
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001686 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001687 self.log("Closing glyph list over 'glyf': %d glyphs before" %
1688 len(self.glyphs))
1689 self.log.glyphs(self.glyphs, font=font)
1690 font['glyf'].closure_glyphs(self)
1691 self.log("Closed glyph list over 'glyf': %d glyphs after" %
1692 len(self.glyphs))
1693 self.log.glyphs(self.glyphs, font=font)
1694 self.log.lapse("close glyph list over 'glyf'")
1695 self.glyphs_glyfed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001696
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001697 self.glyphs_all = self.glyphs.copy()
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001698
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001699 self.log("Retaining %d glyphs: " % len(self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001700
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001701 def _subset_glyphs(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001702 for tag in font.keys():
1703 if tag == 'GlyphOrder': continue
1704 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001705
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001706 if tag in self.options.no_subset_tables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001707 self.log(tag, "subsetting not needed")
1708 elif hasattr(clazz, 'subset_glyphs'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001709 table = font[tag]
1710 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001711 retain = table.subset_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001712 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001713 self.log.lapse("subset '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001714 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001715 self.log(tag, "subsetted to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001716 del font[tag]
1717 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001718 self.log(tag, "subsetted")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001719 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001720 self.log(tag, "NOT subset; don't know how to subset; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001721 del font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001722
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001723 glyphOrder = font.getGlyphOrder()
1724 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001725 font.setGlyphOrder(glyphOrder)
1726 font._buildReverseGlyphOrderDict()
1727 self.log.lapse("subset GlyphOrder")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001728
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001729 def _prune_post_subset(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001730 for tag in font.keys():
1731 if tag == 'GlyphOrder': continue
1732 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001733 if hasattr(clazz, 'prune_post_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001734 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001735 retain = table.prune_post_subset(self.options)
1736 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001737 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001738 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001739 del font[tag]
1740 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001741 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001742
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001743 def subset(self, font):
Behdad Esfahbod756af492013-08-01 12:05:26 -04001744
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001745 self._prune_pre_subset(font)
1746 self._closure_glyphs(font)
1747 self._subset_glyphs(font)
1748 self._prune_post_subset(font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001749
Behdad Esfahbod756af492013-08-01 12:05:26 -04001750
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04001751class Logger(object):
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001752
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001753 def __init__(self, verbose=False, xml=False, timing=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001754 self.verbose = verbose
1755 self.xml = xml
1756 self.timing = timing
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001757 self.last_time = self.start_time = time.time()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001758
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001759 def parse_opts(self, argv):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001760 argv = argv[:]
1761 for v in ['verbose', 'xml', 'timing']:
1762 if "--"+v in argv:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001763 setattr(self, v, True)
1764 argv.remove("--"+v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001765 return argv
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001766
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001767 def __call__(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001768 if not self.verbose:
1769 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001770 print ' '.join(str(x) for x in things)
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001771
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001772 def lapse(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001773 if not self.timing:
1774 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001775 new_time = time.time()
1776 print "Took %0.3fs to %s" %(new_time - self.last_time,
1777 ' '.join(str(x) for x in things))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001778 self.last_time = new_time
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001779
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001780 def glyphs(self, glyphs, font=None):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001781 self("Names: ", sorted(glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001782 if font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001783 reverseGlyphMap = font.getReverseGlyphMap()
1784 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001785
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001786 def font(self, font, file=sys.stdout):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001787 if not self.xml:
1788 return
1789 import xmlWriter
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001790 writer = xmlWriter.XMLWriter(file)
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001791 font.disassembleInstructions = False # Work around ttx bug
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001792 for tag in font.keys():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001793 writer.begintag(tag)
1794 writer.newline()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001795 font[tag].toXML(writer, font)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001796 writer.endtag(tag)
1797 writer.newline()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001798
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001799
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001800def load_font(fontFile,
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04001801 options,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001802 checkChecksums=False,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001803 dontLoadGlyphNames=False):
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001804
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001805 font = fontTools.ttx.TTFont(fontFile,
1806 checkChecksums=checkChecksums,
Behdad Esfahbode911de12013-08-16 12:42:34 -04001807 recalcBBoxes=options.recalc_bounds)
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001808
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001809 # Hack:
1810 #
1811 # If we don't need glyph names, change 'post' class to not try to
1812 # load them. It avoid lots of headache with broken fonts as well
1813 # as loading time.
1814 #
1815 # Ideally ttLib should provide a way to ask it to skip loading
1816 # glyph names. But it currently doesn't provide such a thing.
1817 #
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001818 if dontLoadGlyphNames:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001819 post = fontTools.ttLib.getTableClass('post')
1820 saved = post.decode_format_2_0
1821 post.decode_format_2_0 = post.decode_format_3_0
1822 f = font['post']
1823 if f.formatType == 2.0:
1824 f.formatType = 3.0
1825 post.decode_format_2_0 = saved
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001826
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001827 return font
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001828
Behdad Esfahbode911de12013-08-16 12:42:34 -04001829def save_font(font, outfile, options):
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001830 if options.flavor and not hasattr(font, 'flavor'):
1831 raise Exception("fonttools version does not support flavors.")
1832 font.flavor = options.flavor
Behdad Esfahbode911de12013-08-16 12:42:34 -04001833 font.save(outfile, reorderTables=options.canonical_order)
Behdad Esfahbod41de4cc2013-08-15 12:09:55 -04001834
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001835
Behdad Esfahbod58280972013-08-13 20:46:52 -04001836# Cleanup module space
1837l = locals()
1838for k,v in l.items():
Behdad Esfahbod11763302013-08-14 15:33:08 -04001839 if v == None:
1840 del l[k]
Behdad Esfahbodcc4fc4a2013-08-13 20:53:25 -04001841del k, v, l
Behdad Esfahbod58280972013-08-13 20:46:52 -04001842
1843
Behdad Esfahbodc0ded942013-08-14 13:53:33 -04001844def main(args=None):
Behdad Esfahbod23b2ee92013-08-13 20:29:10 -04001845
Behdad Esfahbodc0ded942013-08-14 13:53:33 -04001846 if args == None: args = sys.argv
Behdad Esfahbod3c472002013-08-14 13:52:27 -04001847 arg0, args = args[0], args[1:]
Behdad Esfahbod610b0552013-07-23 14:52:18 -04001848
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001849 log = Logger()
1850 args = log.parse_opts(args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04001851
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001852 options = Options()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001853 args = options.parse_opts(args, ignore_unknown=['text'])
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001854
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001855 if len(args) < 2:
Behdad Esfahbodf55013e2013-08-15 18:39:55 -04001856 print >>sys.stderr, "usage: %s font-file glyph... [--text=ABC]... [--option=value]..." % arg0
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001857 sys.exit(1)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001858
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001859 fontfile = args[0]
1860 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001861
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001862 dontLoadGlyphNames =(not options.glyph_names and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001863 all(any(g.startswith(p)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001864 for p in ['gid', 'glyph', 'uni', 'U+'])
1865 for g in args))
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001866
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04001867 font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001868 subsetter = Subsetter(options=options, log=log)
1869 log.lapse("load font")
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001870
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001871 names = font.getGlyphNames()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001872 log.lapse("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001873
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001874 glyphs = []
1875 unicodes = []
1876 text = ""
1877 for g in args:
1878 if g in names:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001879 glyphs.append(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001880 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001881 if g.startswith('--text='):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001882 text += g[7:]
1883 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001884 if g.startswith('uni') or g.startswith('U+'):
1885 if g.startswith('uni') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001886 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001887 elif g.startswith('U+') and len(g) > 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001888 g = g[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001889 u = int(g, 16)
1890 unicodes.append(u)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001891 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001892 if g.startswith('gid') or g.startswith('glyph'):
1893 if g.startswith('gid') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001894 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001895 elif g.startswith('glyph') and len(g) > 5:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001896 g = g[5:]
1897 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001898 glyphs.append(font.getGlyphName(int(g), requireReal=1))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001899 except ValueError:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001900 raise Exception("Invalid glyph identifier: %s" % g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001901 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001902 raise Exception("Invalid glyph identifier: %s" % g)
1903 log.lapse("compile glyph list")
1904 log("Unicodes:", unicodes)
1905 log("Glyphs:", glyphs)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001906
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001907 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
1908 subsetter.subset(font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04001909
Behdad Esfahbod34426c12013-08-14 18:30:09 -04001910 outfile = fontfile + '.subset'
1911
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001912 save_font (font, outfile, options)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001913 log.lapse("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001914
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001915 log.last_time = log.start_time
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001916 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001917
Behdad Esfahbod34426c12013-08-14 18:30:09 -04001918 if log.verbose:
1919 import os
1920 log("Input font: %d bytes" % os.path.getsize(fontfile))
1921 log("Subset font: %d bytes" % os.path.getsize(outfile))
1922
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001923 log.font(font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001924
Behdad Esfahbodc56bf482013-08-13 20:13:33 -04001925 font.close()
1926
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001927if __name__ == '__main__':
Behdad Esfahbod23b2ee92013-08-13 20:29:10 -04001928 main()