blob: f1a4ddef8b1c3c74f83805c89030fafbf20cf9e4 [file] [log] [blame]
Behdad Esfahbod54660612013-07-21 18:16:55 -04001#!/usr/bin/python
Behdad Esfahbod616d36e2013-08-13 20:02:59 -04002
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04003# Copyright 2013 Google, Inc. All Rights Reserved.
4#
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04005# Licensed under the Apache License, Version 2.0(the "License");
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04006# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17# Google Author(s): Behdad Esfahbod
Behdad Esfahbod616d36e2013-08-13 20:02:59 -040018
19"""Python OpenType Layout Subsetter.
20
21Later grown into full OpenType subsetter, supporting all standard tables.
22"""
23
Behdad Esfahbod54660612013-07-21 18:16:55 -040024
Behdad Esfahbodfa3bc5e2013-07-24 14:37:58 -040025# Try running on PyPy
26try:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040027 import numpypy
Behdad Esfahbodfa3bc5e2013-07-24 14:37:58 -040028except ImportError:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040029 pass
Behdad Esfahbodfa3bc5e2013-07-24 14:37:58 -040030
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040031import sys
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -040032import struct
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040033import time
34
35import fontTools.ttx
Behdad Esfahbod54660612013-07-21 18:16:55 -040036
Behdad Esfahbod54660612013-07-21 18:16:55 -040037
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040038def _add_method(*clazzes):
Behdad Esfahbod616d36e2013-08-13 20:02:59 -040039 """Returns a decorator function that adds a new method to one or
40 more classes."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040041 def wrapper(method):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040042 for clazz in clazzes:
43 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040044 setattr(clazz, method.func_name, method)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040045 return None
46 return wrapper
Behdad Esfahbod54660612013-07-21 18:16:55 -040047
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040048def _uniq_sort(l):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040049 return sorted(set(l))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040050
51
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040052@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040053def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040054 "Returns ascending list of matching coverage values."
Behdad Esfahbod4734be52013-08-14 19:47:42 -040055 return [i for i,g in enumerate(self.glyphs) if g in glyphs]
Behdad Esfahbod610b0552013-07-23 14:52:18 -040056
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040057@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040058def intersect_glyphs(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040059 "Returns set of intersecting glyphs."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040060 return set(g for g in self.glyphs if g in glyphs)
Behdad Esfahbod849d25c2013-08-12 19:24:24 -040061
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040062@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040063def subset(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040064 "Returns ascending list of remaining coverage values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040065 indices = self.intersect(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040066 self.glyphs = [g for g in self.glyphs if g in glyphs]
67 return indices
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040068
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040069@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040070def remap(self, coverage_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040071 "Remaps coverage."
72 self.glyphs = [self.glyphs[i] for i in coverage_map]
Behdad Esfahbod14374262013-08-08 22:26:49 -040073
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040074@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040075def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040076 "Returns ascending list of matching class values."
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040077 return _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040078 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040079 [v for g,v in self.classDefs.iteritems() if g in glyphs])
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040080
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040081@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040082def intersect_class(self, glyphs, klass):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040083 "Returns set of glyphs matching class."
84 if klass == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040085 return set(g for g in glyphs if g not in self.classDefs)
86 return set(g for g,v in self.classDefs.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040087 if v == klass and g in glyphs)
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040088
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040089@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040090def subset(self, glyphs, remap=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040091 "Returns ascending list of remaining classes."
92 self.classDefs = {g:v for g,v in self.classDefs.iteritems() if g in glyphs}
93 # Note: while class 0 has the special meaning of "not matched",
94 # if no glyph will ever /not match/, we can optimize class 0 out too.
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040095 indices = _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040096 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod4e5d9672013-08-14 19:49:53 -040097 self.classDefs.values())
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040098 if remap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040099 self.remap(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400100 return indices
Behdad Esfahbod4aa6ce32013-07-22 12:15:36 -0400101
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400102@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400103def remap(self, class_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400104 "Remaps classes."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400105 self.classDefs = {g:class_map.index(v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400106 for g,v in self.classDefs.iteritems()}
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400107
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400108@_add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400109def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400110 if cur_glyphs == None: cur_glyphs = s.glyphs
111 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400112 s.glyphs.update(v for g,v in self.mapping.iteritems() if g in cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400113 else:
114 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400115
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400116@_add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400117def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400118 if self.Format in [1, 2]:
119 self.mapping = {g:v for g,v in self.mapping.iteritems()
120 if g in s.glyphs and v in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400121 return bool(self.mapping)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400122 else:
123 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400124
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400125@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400126def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400127 if cur_glyphs == None: cur_glyphs = s.glyphs
128 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400129 indices = self.Coverage.intersect(cur_glyphs)
130 s.glyphs.update(*(self.Sequence[i].Substitute for i in indices))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400131 else:
132 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400133
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400134@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400135def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400136 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400137 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400138 self.Sequence = [self.Sequence[i] for i in indices]
139 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400140 indices = [i for i,seq in enumerate(self.Sequence)
141 if all(sub in s.glyphs for sub in seq.Substitute)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400142 self.Sequence = [self.Sequence[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400143 self.Coverage.remap(indices)
144 self.SequenceCount = len(self.Sequence)
145 return bool(self.SequenceCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400146 else:
147 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400148
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400149@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400150def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400151 if cur_glyphs == None: cur_glyphs = s.glyphs
152 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400153 s.glyphs.update(*(vlist for g,vlist in self.alternates.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400154 if g in cur_glyphs))
155 else:
156 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400157
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400158@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400159def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400160 if self.Format == 1:
161 self.alternates = {g:vlist for g,vlist in self.alternates.iteritems()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400162 if g in s.glyphs and all(v in s.glyphs for v in vlist)}
163 return bool(self.alternates)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400164 else:
165 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400166
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400167@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400168def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400169 if cur_glyphs == None: cur_glyphs = s.glyphs
170 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400171 s.glyphs.update(*([seq.LigGlyph for seq in seqs
172 if all(c in s.glyphs for c in seq.Component)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400173 for g,seqs in self.ligatures.iteritems()
174 if g in cur_glyphs))
175 else:
176 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400177
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400178@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400179def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400180 if self.Format == 1:
181 self.ligatures = {g:v for g,v in self.ligatures.iteritems()
182 if g in s.glyphs}
183 self.ligatures = {g:[seq for seq in seqs
184 if seq.LigGlyph in s.glyphs and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400185 all(c in s.glyphs for c in seq.Component)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400186 for g,seqs in self.ligatures.iteritems()}
187 self.ligatures = {g:v for g,v in self.ligatures.iteritems() if v}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400188 return bool(self.ligatures)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400189 else:
190 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400191
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400192@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400193def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400194 if cur_glyphs == None: cur_glyphs = s.glyphs
195 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400196 indices = self.Coverage.intersect(cur_glyphs)
197 if(not indices or
198 not all(c.intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400199 for c in self.LookAheadCoverage + self.BacktrackCoverage)):
200 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400201 s.glyphs.update(self.Substitute[i] for i in indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400202 else:
203 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400204
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400205@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400206def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400207 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400208 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400209 self.Substitute = [self.Substitute[i] for i in indices]
210 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400211 indices = [i for i,sub in enumerate(self.Substitute)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400212 if sub in s.glyphs]
213 self.Substitute = [self.Substitute[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400214 self.Coverage.remap(indices)
215 self.GlyphCount = len(self.Substitute)
216 return bool(self.GlyphCount and
217 all(c.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400218 for c in self.LookAheadCoverage+self.BacktrackCoverage))
219 else:
220 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400221
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400222@_add_method(fontTools.ttLib.tables.otTables.SinglePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400223def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400224 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400225 return len(self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400226 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400227 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400228 self.Value = [self.Value[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400229 self.ValueCount = len(self.Value)
230 return bool(self.ValueCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400231 else:
232 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400233
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400234@_add_method(fontTools.ttLib.tables.otTables.PairPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400235def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400236 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400237 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400238 self.PairSet = [self.PairSet[i] for i in indices]
239 for p in self.PairSet:
240 p.PairValueRecord = [r for r in p.PairValueRecord
241 if r.SecondGlyph in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400242 p.PairValueCount = len(p.PairValueRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400243 self.PairSet = [p for p in self.PairSet if p.PairValueCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400244 self.PairSetCount = len(self.PairSet)
245 return bool(self.PairSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400246 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400247 class1_map = self.ClassDef1.subset(s.glyphs, remap=True)
248 class2_map = self.ClassDef2.subset(s.glyphs, remap=True)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400249 self.Class1Record = [self.Class1Record[i] for i in class1_map]
250 for c in self.Class1Record:
251 c.Class2Record = [c.Class2Record[i] for i in class2_map]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400252 self.Class1Count = len(class1_map)
253 self.Class2Count = len(class2_map)
254 return bool(self.Class1Count and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400255 self.Class2Count and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400256 self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400257 else:
258 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400259
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400260@_add_method(fontTools.ttLib.tables.otTables.CursivePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400261def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400262 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400263 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400264 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400265 self.EntryExitCount = len(self.EntryExitRecord)
266 return bool(self.EntryExitCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400267 else:
268 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400269
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400270@_add_method(fontTools.ttLib.tables.otTables.MarkBasePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400271def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400272 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400273 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400274 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
275 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400276 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
277 base_indices = self.BaseCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400278 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i]
279 for i in base_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400280 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400281 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400282 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400283 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400284 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400285 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400286 for b in self.BaseArray.BaseRecord:
287 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400288 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400289 self.MarkArray.MarkCount and
290 self.BaseArray.BaseCount)
291 else:
292 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400293
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400294@_add_method(fontTools.ttLib.tables.otTables.MarkLigPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400295def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400296 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400297 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400298 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
299 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400300 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
301 ligature_indices = self.LigatureCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400302 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i]
303 for i in ligature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400304 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400305 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400306 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400307 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400308 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400309 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400310 for l in self.LigatureArray.LigatureAttach:
311 for c in l.ComponentRecord:
312 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400313 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400314 self.MarkArray.MarkCount and
315 self.LigatureArray.LigatureCount)
316 else:
317 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400318
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400319@_add_method(fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400320def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400321 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400322 mark1_indices = self.Mark1Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400323 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i]
324 for i in mark1_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400325 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
326 mark2_indices = self.Mark2Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400327 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i]
328 for i in mark2_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400329 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400330 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400331 class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400332 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400333 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400334 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400335 for b in self.Mark2Array.Mark2Record:
336 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400337 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400338 self.Mark1Array.MarkCount and
339 self.Mark2Array.MarkCount)
340 else:
341 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400342
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400343@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
344 fontTools.ttLib.tables.otTables.MultipleSubst,
345 fontTools.ttLib.tables.otTables.AlternateSubst,
346 fontTools.ttLib.tables.otTables.LigatureSubst,
347 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
348 fontTools.ttLib.tables.otTables.SinglePos,
349 fontTools.ttLib.tables.otTables.PairPos,
350 fontTools.ttLib.tables.otTables.CursivePos,
351 fontTools.ttLib.tables.otTables.MarkBasePos,
352 fontTools.ttLib.tables.otTables.MarkLigPos,
353 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400354def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400355 pass
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400356
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400357@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
358 fontTools.ttLib.tables.otTables.MultipleSubst,
359 fontTools.ttLib.tables.otTables.AlternateSubst,
360 fontTools.ttLib.tables.otTables.LigatureSubst,
361 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
362 fontTools.ttLib.tables.otTables.SinglePos,
363 fontTools.ttLib.tables.otTables.PairPos,
364 fontTools.ttLib.tables.otTables.CursivePos,
365 fontTools.ttLib.tables.otTables.MarkBasePos,
366 fontTools.ttLib.tables.otTables.MarkLigPos,
367 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400368def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400369 return []
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400370
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400371@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
372 fontTools.ttLib.tables.otTables.AlternateSubst,
373 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400374def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400375 return False
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400376
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400377@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst,
378 fontTools.ttLib.tables.otTables.LigatureSubst,
379 fontTools.ttLib.tables.otTables.ContextSubst,
380 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400381def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400382 return True
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400383
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400384@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
385 fontTools.ttLib.tables.otTables.ChainContextSubst,
386 fontTools.ttLib.tables.otTables.ContextPos,
387 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400388def __classify_context(self):
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400389
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -0400390 class ContextHelper(object):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400391 def __init__(self, klass, Format):
392 if klass.__name__.endswith('Subst'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400393 Typ = 'Sub'
394 Type = 'Subst'
395 else:
396 Typ = 'Pos'
397 Type = 'Pos'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400398 if klass.__name__.startswith('Chain'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400399 Chain = 'Chain'
400 else:
401 Chain = ''
402 ChainTyp = Chain+Typ
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400403
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400404 self.Typ = Typ
405 self.Type = Type
406 self.Chain = Chain
407 self.ChainTyp = ChainTyp
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400408
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400409 self.LookupRecord = Type+'LookupRecord'
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400410
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400411 if Format == 1:
412 Coverage = lambda r: r.Coverage
413 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400414 ContextData = lambda r:(None,)
415 ChainContextData = lambda r:(None, None, None)
416 RuleData = lambda r:(r.Input,)
417 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400418 SetRuleData = None
419 ChainSetRuleData = None
420 elif Format == 2:
421 Coverage = lambda r: r.Coverage
422 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400423 ContextData = lambda r:(r.ClassDef,)
424 ChainContextData = lambda r:(r.LookAheadClassDef,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400425 r.InputClassDef,
426 r.BacktrackClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400427 RuleData = lambda r:(r.Class,)
428 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack)
429 def SetRuleData(r, d):(r.Class,) = d
430 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400431 elif Format == 3:
432 Coverage = lambda r: r.Coverage[0]
433 ChainCoverage = lambda r: r.InputCoverage[0]
434 ContextData = None
435 ChainContextData = None
436 RuleData = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400437 ChainRuleData = lambda r:(r.LookAheadCoverage +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400438 r.InputCoverage +
439 r.BacktrackCoverage)
440 SetRuleData = None
441 ChainSetRuleData = None
442 else:
443 assert 0, "unknown format: %s" % Format
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400444
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400445 if Chain:
446 self.Coverage = ChainCoverage
447 self.ContextData = ChainContextData
448 self.RuleData = ChainRuleData
449 self.SetRuleData = ChainSetRuleData
450 else:
451 self.Coverage = Coverage
452 self.ContextData = ContextData
453 self.RuleData = RuleData
454 self.SetRuleData = SetRuleData
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400455
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400456 if Format == 1:
457 self.Rule = ChainTyp+'Rule'
458 self.RuleCount = ChainTyp+'RuleCount'
459 self.RuleSet = ChainTyp+'RuleSet'
460 self.RuleSetCount = ChainTyp+'RuleSetCount'
461 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
462 elif Format == 2:
463 self.Rule = ChainTyp+'ClassRule'
464 self.RuleCount = ChainTyp+'ClassRuleCount'
465 self.RuleSet = ChainTyp+'ClassSet'
466 self.RuleSetCount = ChainTyp+'ClassSetCount'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400467 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r)
Behdad Esfahbod89987002013-07-23 23:07:42 -0400468
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400469 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
Behdad Esfahbod11763302013-08-14 15:33:08 -0400470 self.Input = 'Input' if Chain else 'Class'
Behdad Esfahbod27108392013-07-23 16:40:47 -0400471
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400472 if self.Format not in [1, 2, 3]:
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400473 return None # Don't shoot the messenger; let it go
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400474 if not hasattr(self.__class__, "__ContextHelpers"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400475 self.__class__.__ContextHelpers = {}
476 if self.Format not in self.__class__.__ContextHelpers:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400477 helper = ContextHelper(self.__class__, self.Format)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400478 self.__class__.__ContextHelpers[self.Format] = helper
479 return self.__class__.__ContextHelpers[self.Format]
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400480
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400481@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
482 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400483def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400484 if cur_glyphs == None: cur_glyphs = s.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400485 c = self.__classify_context()
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400486
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400487 indices = c.Coverage(self).intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400488 if not indices:
489 return []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400490 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs);
Behdad Esfahbod1d4fa132013-08-08 22:59:32 -0400491
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400492 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400493 ContextData = c.ContextData(self)
494 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400495 for i in indices:
496 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400497 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400498 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400499 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
500 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400501 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400502 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400503 if not ll: continue
504 seqi = ll.SequenceIndex
505 if seqi == 0:
Behdad Esfahbodd3fdcc72013-08-14 17:59:31 -0400506 pos_glyphs = set([c.Coverage(self).glyphs[i]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400507 else:
508 if chaos:
509 pos_glyphs = s.glyphs
510 else:
Behdad Esfahbodd3fdcc72013-08-14 17:59:31 -0400511 pos_glyphs = set([r.Input[seqi - 1]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400512 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400513 chaos = chaos or lookup.may_have_non_1to1()
514 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400515 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400516 ClassDef = getattr(self, c.ClassDef)
517 indices = ClassDef.intersect(cur_glyphs)
518 ContextData = c.ContextData(self)
519 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400520 for i in indices:
521 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400522 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400523 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400524 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
525 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400526 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400527 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400528 if not ll: continue
529 seqi = ll.SequenceIndex
530 if seqi == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400531 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400532 else:
533 if chaos:
534 pos_glyphs = s.glyphs
535 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400536 pos_glyphs = ClassDef.intersect_class(s.glyphs,
Behdad Esfahbod11763302013-08-14 15:33:08 -0400537 getattr(r, c.Input)[seqi - 1])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400538 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400539 chaos = chaos or lookup.may_have_non_1to1()
540 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400541 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400542 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400543 return []
544 r = self
545 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400546 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400547 if not ll: continue
548 seqi = ll.SequenceIndex
549 if seqi == 0:
550 pos_glyphs = cur_glyphs
551 else:
552 if chaos:
553 pos_glyphs = s.glyphs
554 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400555 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400556 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400557 chaos = chaos or lookup.may_have_non_1to1()
558 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400559 else:
560 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod00776972013-07-23 15:33:00 -0400561
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400562@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
563 fontTools.ttLib.tables.otTables.ContextPos,
564 fontTools.ttLib.tables.otTables.ChainContextSubst,
565 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400566def subset_glyphs(self, s):
567 c = self.__classify_context()
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400568
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400569 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400570 indices = self.Coverage.subset(s.glyphs)
571 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400572 rss = [rss[i] for i in indices]
573 for rs in rss:
574 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400575 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400576 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400577 if r and all(all(g in s.glyphs for g in glist)
578 for glist in c.RuleData(r))]
579 setattr(rs, c.Rule, ss)
580 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400581 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400582 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
583 setattr(self, c.RuleSet, rss)
584 setattr(self, c.RuleSetCount, len(rss))
585 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400586 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400587 if not self.Coverage.subset(s.glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400588 return False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400589 indices = getattr(self, c.ClassDef).subset(self.Coverage.glyphs,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400590 remap=False)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400591 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400592 rss = [rss[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400593 ContextData = c.ContextData(self)
594 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400595 for rs in rss:
596 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400597 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400598 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400599 if r and all(all(k in klass_map for k in klist)
600 for klass_map,klist in zip(klass_maps, c.RuleData(r)))]
601 setattr(rs, c.Rule, ss)
602 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400603
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400604 # Remap rule classes
605 for r in ss:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400606 c.SetRuleData(r, [[klass_map.index(k) for k in klist]
607 for klass_map,klist in zip(klass_maps, c.RuleData(r))])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400608 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400609 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
610 setattr(self, c.RuleSet, rss)
611 setattr(self, c.RuleSetCount, len(rss))
612 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400613 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400614 return all(x.subset(s.glyphs) for x in c.RuleData(self))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400615 else:
616 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400617
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400618@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
619 fontTools.ttLib.tables.otTables.ChainContextSubst,
620 fontTools.ttLib.tables.otTables.ContextPos,
621 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400622def subset_lookups(self, lookup_indices):
623 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400624
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400625 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400626 for rs in getattr(self, c.RuleSet):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400627 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400628 for r in getattr(rs, c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400629 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400630 setattr(r, c.LookupRecord,
631 [ll for ll in getattr(r, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400632 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400633 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400634 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400635 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400636 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400637 setattr(self, c.LookupRecord,
638 [ll for ll in getattr(self, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400639 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400640 for ll in getattr(self, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400641 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400642 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400643 else:
644 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400645
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400646@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
647 fontTools.ttLib.tables.otTables.ChainContextSubst,
648 fontTools.ttLib.tables.otTables.ContextPos,
649 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400650def collect_lookups(self):
651 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400652
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400653 if self.Format in [1, 2]:
654 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400655 for rs in getattr(self, c.RuleSet) if rs
656 for r in getattr(rs, c.Rule) if r
657 for ll in getattr(r, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400658 elif self.Format == 3:
659 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400660 for ll in getattr(self, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400661 else:
662 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400663
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400664@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400665def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400666 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400667 self.ExtSubTable.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400668 else:
669 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400670
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400671@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400672def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400673 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400674 return self.ExtSubTable.may_have_non_1to1()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400675 else:
676 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400677
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400678@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
679 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400680def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400681 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400682 return self.ExtSubTable.subset_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400683 else:
684 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400685
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400686@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
687 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400688def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400689 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400690 return self.ExtSubTable.subset_lookups(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400691 else:
692 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400693
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400694@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
695 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400696def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400697 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400698 return self.ExtSubTable.collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400699 else:
700 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400701
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400702@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400703def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400704 for st in self.SubTable:
705 if not st: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400706 st.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400707
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400708@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400709def subset_glyphs(self, s):
710 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)]
711 self.SubTableCount = len(self.SubTable)
712 return bool(self.SubTableCount)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400713
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400714@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400715def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400716 for s in self.SubTable:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400717 s.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400718
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400719@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400720def collect_lookups(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400721 return _uniq_sort(sum((st.collect_lookups() for st in self.SubTable
722 if st), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400723
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400724@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400725def may_have_non_1to1(self):
726 return any(st.may_have_non_1to1() for st in self.SubTable if st)
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400727
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400728@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400729def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400730 "Returns the indices of nonempty lookups."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400731 return [i for i,l in enumerate(self.Lookup) if l and l.subset_glyphs(s)]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400732
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400733@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400734def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400735 self.Lookup = [self.Lookup[i] for i in lookup_indices
736 if i < self.LookupCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400737 self.LookupCount = len(self.Lookup)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400738 for l in self.Lookup:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400739 l.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400740
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400741@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400742def closure_lookups(self, lookup_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400743 lookup_indices = _uniq_sort(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400744 recurse = lookup_indices
745 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400746 recurse_lookups = sum((self.Lookup[i].collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400747 for i in recurse if i < self.LookupCount), [])
748 recurse_lookups = [l for l in recurse_lookups
749 if l not in lookup_indices and l < self.LookupCount]
750 if not recurse_lookups:
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400751 return _uniq_sort(lookup_indices)
752 recurse_lookups = _uniq_sort(recurse_lookups)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400753 lookup_indices.extend(recurse_lookups)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400754 recurse = recurse_lookups
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400755
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400756@_add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400757def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400758 self.LookupListIndex = [l for l in self.LookupListIndex
759 if l in lookup_indices]
760 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400761 self.LookupListIndex = [lookup_indices.index(l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400762 for l in self.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400763 self.LookupCount = len(self.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400764 return self.LookupCount
Behdad Esfahbod54660612013-07-21 18:16:55 -0400765
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400766@_add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400767def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400768 return self.LookupListIndex[:]
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400769
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400770@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400771def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400772 "Returns the indices of nonempty features."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400773 feature_indices = [i for i,f in enumerate(self.FeatureRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400774 if f.Feature.subset_lookups(lookup_indices)]
775 self.subset_features(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400776 return feature_indices
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400777
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400778@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400779def collect_lookups(self, feature_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400780 return _uniq_sort(sum((self.FeatureRecord[i].Feature.collect_lookups()
781 for i in feature_indices
Behdad Esfahbod1ee298d2013-08-13 20:07:09 -0400782 if i < self.FeatureCount), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400783
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400784@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400785def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400786 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400787 self.FeatureCount = len(self.FeatureRecord)
788 return bool(self.FeatureCount)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400789
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400790@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
791 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400792def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400793 if self.ReqFeatureIndex in feature_indices:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400794 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400795 else:
796 self.ReqFeatureIndex = 65535
797 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
798 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400799 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400800 if f in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400801 self.FeatureCount = len(self.FeatureIndex)
802 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400803
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400804@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
805 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400806def collect_features(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400807 feature_indices = self.FeatureIndex[:]
808 if self.ReqFeatureIndex != 65535:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400809 feature_indices.append(self.ReqFeatureIndex)
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400810 return _uniq_sort(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400811
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400812@_add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400813def subset_features(self, feature_indices):
814 if(self.DefaultLangSys and
815 not self.DefaultLangSys.subset_features(feature_indices)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400816 self.DefaultLangSys = None
817 self.LangSysRecord = [l for l in self.LangSysRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400818 if l.LangSys.subset_features(feature_indices)]
819 self.LangSysCount = len(self.LangSysRecord)
820 return bool(self.LangSysCount or self.DefaultLangSys)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400821
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400822@_add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400823def collect_features(self):
824 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400825 if self.DefaultLangSys:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400826 feature_indices.append(self.DefaultLangSys.collect_features())
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400827 return _uniq_sort(sum(feature_indices, []))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400828
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400829@_add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400830def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400831 self.ScriptRecord = [s for s in self.ScriptRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400832 if s.Script.subset_features(feature_indices)]
833 self.ScriptCount = len(self.ScriptRecord)
834 return bool(self.ScriptCount)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400835
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400836@_add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400837def collect_features(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400838 return _uniq_sort(sum((s.Script.collect_features()
839 for s in self.ScriptRecord), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400840
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400841@_add_method(fontTools.ttLib.getTableClass('GSUB'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400842def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400843 s.table = self.table
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400844 feature_indices = self.table.ScriptList.collect_features()
845 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400846 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400847 orig_glyphs = s.glyphs.copy()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400848 for i in lookup_indices:
849 if i >= self.table.LookupList.LookupCount: continue
850 if not self.table.LookupList.Lookup[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400851 self.table.LookupList.Lookup[i].closure_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400852 if orig_glyphs == s.glyphs:
853 break
854 del s.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400855
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400856@_add_method(fontTools.ttLib.getTableClass('GSUB'),
857 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400858def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400859 s.glyphs = s.glyphs_gsubed
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400860 lookup_indices = self.table.LookupList.subset_glyphs(s)
861 self.subset_lookups(lookup_indices)
862 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400863 return True
Behdad Esfahbod02b92062013-07-21 18:40:59 -0400864
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400865@_add_method(fontTools.ttLib.getTableClass('GSUB'),
866 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400867def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400868 """Retrains specified lookups, then removes empty features, language
869 systems, and scripts."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400870 self.table.LookupList.subset_lookups(lookup_indices)
871 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices)
872 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400873
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400874@_add_method(fontTools.ttLib.getTableClass('GSUB'),
875 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400876def prune_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400877 "Remove unreferenced lookups"
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400878 feature_indices = self.table.ScriptList.collect_features()
879 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
880 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices)
881 self.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400882
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400883@_add_method(fontTools.ttLib.getTableClass('GSUB'),
884 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400885def subset_feature_tags(self, feature_tags):
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400886 feature_indices = [i for i,f in
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400887 enumerate(self.table.FeatureList.FeatureRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400888 if f.FeatureTag in feature_tags]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400889 self.table.FeatureList.subset_features(feature_indices)
890 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400891
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400892@_add_method(fontTools.ttLib.getTableClass('GSUB'),
893 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400894def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400895 if options.layout_features and '*' not in options.layout_features:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400896 self.subset_feature_tags(options.layout_features)
897 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400898 return True
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400899
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400900@_add_method(fontTools.ttLib.getTableClass('GDEF'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400901def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400902 glyphs = s.glyphs_gsubed
903 table = self.table
904 if table.LigCaretList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400905 indices = table.LigCaretList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400906 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i]
907 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400908 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400909 if not table.LigCaretList.LigGlyphCount:
910 table.LigCaretList = None
911 if table.MarkAttachClassDef:
912 table.MarkAttachClassDef.classDefs = {g:v for g,v in
913 table.MarkAttachClassDef.classDefs.iteritems()
914 if g in glyphs}
915 if not table.MarkAttachClassDef.classDefs:
916 table.MarkAttachClassDef = None
917 if table.GlyphClassDef:
918 table.GlyphClassDef.classDefs = {g:v for g,v in
919 table.GlyphClassDef.classDefs.iteritems()
920 if g in glyphs}
921 if not table.GlyphClassDef.classDefs:
922 table.GlyphClassDef = None
923 if table.AttachList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400924 indices = table.AttachList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400925 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i]
926 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400927 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400928 if not table.AttachList.GlyphCount:
929 table.AttachList = None
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400930 return bool(table.LigCaretList or
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400931 table.MarkAttachClassDef or
932 table.GlyphClassDef or
933 table.AttachList)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -0400934
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400935@_add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400936def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400937 # Prune unknown kern table types
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400938 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')]
939 return bool(self.kernTables)
Behdad Esfahbodd4e33a72013-07-24 18:51:05 -0400940
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400941@_add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400942def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400943 glyphs = s.glyphs_gsubed
944 for t in self.kernTables:
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400945 t.kernTable = {(a,b):v for (a,b),v in t.kernTable.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400946 if a in glyphs and b in glyphs}
947 self.kernTables = [t for t in self.kernTables if t.kernTable]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400948 return bool(self.kernTables)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -0400949
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400950@_add_method(fontTools.ttLib.getTableClass('hmtx'),
951 fontTools.ttLib.getTableClass('vmtx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400952def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400953 self.metrics = {g:v for g,v in self.metrics.iteritems() if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400954 return bool(self.metrics)
Behdad Esfahbodc7160442013-07-22 14:29:08 -0400955
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400956@_add_method(fontTools.ttLib.getTableClass('hdmx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400957def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400958 self.hdmx = {sz:{g:v for g,v in l.iteritems() if g in s.glyphs}
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400959 for sz,l in self.hdmx.iteritems()}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400960 return bool(self.hdmx)
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -0400961
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400962@_add_method(fontTools.ttLib.getTableClass('VORG'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400963def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400964 self.VOriginRecords = {g:v for g,v in self.VOriginRecords.iteritems()
965 if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400966 self.numVertOriginYMetrics = len(self.VOriginRecords)
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400967 return True # Never drop; has default metrics
Behdad Esfahbode45d6af2013-07-22 15:29:17 -0400968
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400969@_add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400970def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400971 if not options.glyph_names:
972 self.formatType = 3.0
973 return True
Behdad Esfahbod42648242013-07-23 12:56:06 -0400974
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400975@_add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400976def subset_glyphs(self, s):
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400977 self.extraNames = [] # This seems to do it
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400978 return True
Behdad Esfahbod653e9742013-07-22 15:17:12 -0400979
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400980@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400981def getComponentNamesFast(self, glyfTable):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400982 if struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400983 return [] # Not composite
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400984 data = self.data
985 i = 10
986 components = []
987 more = 1
988 while more:
989 flags, glyphID = struct.unpack(">HH", data[i:i+4])
990 i += 4
991 flags = int(flags)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400992 components.append(glyfTable.getGlyphName(int(glyphID)))
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -0400993
Behdad Esfahbod80c8a652013-08-14 12:55:42 -0400994 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -0400995 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -0400996 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
997 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
998 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
999 more = flags & 0x0020 # MORE_COMPONENTS
1000
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001001 return components
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001002
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001003@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001004def remapComponentsFast(self, indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001005 if struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001006 return # Not composite
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001007 data = bytearray(self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001008 i = 10
1009 more = 1
1010 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001011 flags =(data[i] << 8) | data[i+1]
1012 glyphID =(data[i+2] << 8) | data[i+3]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001013 # Remap
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001014 glyphID = indices.index(glyphID)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001015 data[i+2] = glyphID >> 8
1016 data[i+3] = glyphID & 0xFF
1017 i += 4
1018 flags = int(flags)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001019
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001020 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001021 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001022 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1023 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1024 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1025 more = flags & 0x0020 # MORE_COMPONENTS
1026
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001027 self.data = str(data)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001028
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001029@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001030def dropInstructionsFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001031 numContours = struct.unpack(">h", self.data[:2])[0]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001032 data = bytearray(self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001033 i = 10
1034 if numContours >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001035 i += 2 * numContours # endPtsOfContours
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001036 instructionLen =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001037 # Zero it
1038 data[i] = data [i+1] = 0
1039 i += 2
1040 if instructionLen:
1041 # Splice it out
1042 data = data[:i] + data[i+instructionLen:]
1043 else:
1044 more = 1
1045 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001046 flags =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001047 # Turn instruction flag off
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001048 flags &= ~0x0100 # WE_HAVE_INSTRUCTIONS
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001049 data[i+0] = flags >> 8
1050 data[i+1] = flags & 0xFF
1051 i += 4
1052 flags = int(flags)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001053
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001054 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001055 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001056 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1057 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1058 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1059 more = flags & 0x0020 # MORE_COMPONENTS
1060
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001061 # Cut off
1062 data = data[:i]
1063 if len(data) % 4:
1064 # add pad bytes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001065 nPadBytes = 4 -(len(data) % 4)
1066 for i in range(nPadBytes):
1067 data.append(0)
1068 self.data = str(data)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001069
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001070@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001071def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001072 decompose = s.glyphs
1073 # I don't know if component glyphs can be composite themselves.
1074 # We handle them anyway.
1075 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001076 components = set()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001077 for g in decompose:
1078 if g not in self.glyphs:
1079 continue
1080 gl = self.glyphs[g]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001081 if hasattr(gl, "data"):
1082 for c in gl.getComponentNamesFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001083 if c not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001084 components.add(c)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001085 else:
1086 # TTX seems to expand gid0..3 always
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001087 if gl.isComposite():
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001088 for c in gl.components:
1089 if c.glyphName not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001090 components.add(c.glyphName)
1091 components = set(c for c in components if c not in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001092 if not components:
1093 break
1094 decompose = components
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001095 s.glyphs.update(components)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001096
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001097@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001098def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001099 self.glyphs = {g:v for g,v in self.glyphs.iteritems() if g in s.glyphs}
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001100 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001101 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001102 if hasattr(v, "data"):
1103 v.remapComponentsFast(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001104 else:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001105 pass # No need
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001106 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001107 return bool(self.glyphs)
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001108
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001109@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001110def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001111 if not options.hinting:
1112 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001113 if hasattr(v, "data"):
1114 v.dropInstructionsFast()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001115 else:
1116 v.program = fontTools.ttLib.tables.ttProgram.Program()
1117 v.program.fromBytecode([])
1118 return True
Behdad Esfahboded98c612013-07-23 12:37:41 -04001119
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001120@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001121def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001122 cff = self.cff
1123 # CFF table should have one font only
1124 cff.fontNames = cff.fontNames[:1]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001125 return bool(cff.fontNames)
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001126
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001127@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001128def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001129 cff = self.cff
1130 for fontname in cff.keys():
1131 font = cff[fontname]
1132 cs = font.CharStrings
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001133
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001134 # Load all glyphs
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001135 for g in font.charset:
1136 if g not in s.glyphs: continue
1137 c,sel = cs.getItemAndSelector(g)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001138
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001139 if cs.charStringsAreIndexed:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001140 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001141 csi = cs.charStringsIndex
1142 csi.items = [csi.items[i] for i in indices]
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001143 csi.count = len(csi.items)
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001144 del csi.file, csi.offsets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001145 if hasattr(font, "FDSelect"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001146 sel = font.FDSelect
1147 sel.format = None
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001148 sel.gidArray = [sel.gidArray[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001149 cs.charStrings = {g:indices.index(v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001150 for g,v in cs.charStrings.iteritems()
1151 if g in s.glyphs}
1152 else:
1153 cs.charStrings = {g:v
1154 for g,v in cs.charStrings.iteritems()
1155 if g in s.glyphs}
1156 font.charset = [g for g in font.charset if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001157 font.numGlyphs = len(font.charset)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001158
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001159 return any(cff[fontname].numGlyphs for fontname in cff.keys())
1160
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001161@_add_method(fontTools.misc.psCharStrings.T2CharString)
1162def subset_subroutines(self, subrs, gsubrs):
1163 p = self.program
1164 for i in range(1, len(p)):
1165 if p[i] == 'callsubr':
1166 assert type(p[i-1]) is int
1167 p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias
1168 elif p[i] == 'callgsubr':
1169 assert type(p[i-1]) is int
1170 p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias
1171
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001172@_add_method(fontTools.ttLib.getTableClass('CFF '))
1173def prune_post_subset(self, options):
1174 cff = self.cff
1175
1176 class _MarkingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1177
1178 def __init__(self, localSubrs, globalSubrs):
1179 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1180 localSubrs,
1181 globalSubrs)
1182 for subrs in [localSubrs, globalSubrs]:
1183 if subrs and not hasattr(subrs, "_used"):
1184 subrs._used = set()
1185
1186 def op_callsubr(self, index):
1187 self.localSubrs._used.add(self.operandStack[-1]+self.localBias)
1188 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
1189
1190 def op_callgsubr(self, index):
1191 self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias)
1192 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
1193
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001194 class _NonrecursingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1195
1196 def __init__(self, localSubrs, globalSubrs):
1197 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1198 localSubrs,
1199 globalSubrs)
1200
1201 def op_callsubr(self, index):
1202 self.pop()
1203
1204 def op_callgsubr(self, index):
1205 self.pop()
1206
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001207 for fontname in cff.keys():
1208 font = cff[fontname]
1209 cs = font.CharStrings
1210
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001211 # Drop unused FontDictionaries
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001212 if hasattr(font, "FDSelect"):
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001213 sel = font.FDSelect
1214 indices = _uniq_sort(sel.gidArray)
1215 sel.gidArray = [indices.index (ss) for ss in sel.gidArray]
1216 arr = font.FDArray
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001217 arr.items = [arr[i] for i in indices]
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001218 arr.count = len(arr.items)
1219 del arr.file, arr.offsets
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001220
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001221 # Mark all used subroutines
1222 for g in font.charset:
1223 c,sel = cs.getItemAndSelector(g)
1224 subrs = getattr(c.private, "Subrs", [])
1225 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs)
1226 decompiler.execute(c)
1227
1228 # Renumber subroutines to remove unused ones
1229 all_subrs = [font.GlobalSubrs]
1230 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs'))
1231 # Prepare
1232 for subrs in all_subrs:
1233 if not subrs: continue
1234 if not hasattr(subrs, '_used'):
1235 subrs._used = set()
1236 subrs._used = _uniq_sort(subrs._used)
1237 subrs._old_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs)
1238 subrs._new_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs._used)
Behdad Esfahboded107712013-08-14 19:54:13 -04001239 # Renumber glyph charstrings
1240 for g in font.charset:
1241 c,sel = cs.getItemAndSelector(g)
1242 subrs = getattr(c.private, "Subrs", [])
1243 c.subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001244 # Renumber subroutines themselves
1245 for subrs in all_subrs:
1246 if not subrs: continue
1247 decompiler = _NonrecursingT2Decompiler(subrs, font.GlobalSubrs)
1248 for i in range (subrs.count):
1249 if i not in subrs._used: continue
1250 decompiler.reset()
1251 decompiler.execute(subrs[i])
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001252 subrs[i].subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001253 # Cleanup
1254 for subrs in all_subrs:
1255 if not subrs: continue
1256 subrs.items = [subrs.items[i] for i in subrs._used]
1257 del subrs.file, subrs.offsets
1258 del subrs._used, subrs._old_bias, subrs._new_bias
1259
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001260 if not options.hinting:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001261 pass # TODO(behdad) Drop hints
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001262
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001263 return True
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001264
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001265@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001266def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001267 tables = [t for t in self.tables
1268 if t.platformID == 3 and t.platEncID in [1, 10]]
1269 for u in s.unicodes_requested:
1270 found = False
1271 for table in tables:
1272 if u in table.cmap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001273 s.glyphs.add(table.cmap[u])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001274 found = True
1275 break
1276 if not found:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001277 s.log("No glyph for Unicode value %s; skipping." % u)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001278
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001279@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001280def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001281 if not options.legacy_cmap:
1282 # Drop non-Unicode / non-Symbol cmaps
1283 self.tables = [t for t in self.tables
1284 if t.platformID == 3 and t.platEncID in [0, 1, 10]]
1285 if not options.symbol_cmap:
1286 self.tables = [t for t in self.tables
1287 if t.platformID == 3 and t.platEncID in [1, 10]]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001288 # TODO(behdad) Only keep one subtable?
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001289 # For now, drop format=0 which can't be subset_glyphs easily?
1290 self.tables = [t for t in self.tables if t.format != 0]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001291 return bool(self.tables)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001292
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001293@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001294def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001295 s.glyphs = s.glyphs_cmaped
1296 for t in self.tables:
1297 # For reasons I don't understand I need this here
1298 # to force decompilation of the cmap format 14.
1299 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001300 getattr(t, "asdf")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001301 except AttributeError:
1302 pass
1303 if t.format == 14:
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001304 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None).
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001305 t.uvsDict = {v:[(u,g) for u,g in l if g in s.glyphs]
1306 for v,l in t.uvsDict.iteritems()}
1307 t.uvsDict = {v:l for v,l in t.uvsDict.iteritems() if l}
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001308 else:
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001309 t.cmap = {u:g for u,g in t.cmap.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001310 if g in s.glyphs_requested or u in s.unicodes_requested}
1311 self.tables = [t for t in self.tables
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001312 if (t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001313 # TODO(behdad) Convert formats when needed.
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001314 # In particular, if we have a format=12 without non-BMP
1315 # characters, either drop format=12 one or convert it
1316 # to format=4 if there's not one.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001317 return bool(self.tables)
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001318
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001319@_add_method(fontTools.ttLib.getTableClass('name'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001320def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001321 if '*' not in options.name_IDs:
1322 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1323 if not options.name_legacy:
1324 self.names = [n for n in self.names
1325 if n.platformID == 3 and n.platEncID == 1]
1326 if '*' not in options.name_languages:
1327 self.names = [n for n in self.names if n.langID in options.name_languages]
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001328 return True # Retain even if empty
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001329
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001330
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001331# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange?
1332# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
Behdad Esfahbod10195332013-08-14 19:55:24 -04001333# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001334# TODO(behdad) Text direction considerations.
1335# TODO(behdad) Text script / language considerations.
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001336# TODO(behdad) Drop GPOS Device records if not hinting?
1337# TODO(behdad) Move font name loading hack to Subsetter?
Behdad Esfahbod56ebd042013-07-22 13:02:24 -04001338
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001339
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001340class Options(object):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001341
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001342 class UnknownOptionError(Exception):
1343 pass
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001344
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001345 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC',
1346 'PCLT', 'LTSH']
1347 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
1348 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
1349 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1350 'loca', 'name', 'cvt ', 'fpgm', 'prep']
1351 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001352
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001353 # Based on HarfBuzz shapers
1354 _layout_features_groups = {
1355 # Default shaper
1356 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1357 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
1358 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
1359 'ltr': ['ltra', 'ltrm'],
1360 'rtl': ['rtla', 'rtlm'],
1361 # Complex shapers
1362 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1363 'cswh', 'mset'],
1364 'hangul': ['ljmo', 'vjmo', 'tjmo'],
1365 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'],
1366 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1367 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1368 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
1369 }
1370 _layout_features_default = _uniq_sort(sum(
1371 _layout_features_groups.itervalues(), []))
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001372
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001373 drop_tables = _drop_tables_default
1374 no_subset_tables = _no_subset_tables_default
1375 hinting_tables = _hinting_tables_default
1376 layout_features = _layout_features_default
1377 hinting = False
1378 glyph_names = False
1379 legacy_cmap = False
1380 symbol_cmap = False
1381 name_IDs = [1, 2] # Family and Style
1382 name_legacy = False
1383 name_languages = [0x0409] # English
1384 mandatory_glyphs = True # First four for TrueType, .notdef for CFF
1385 recalc_bboxes = False # Slows us down
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001386
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001387 def __init__(self, **kwargs):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001388
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001389 self.set(**kwargs)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001390
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001391 def set(self, **kwargs):
1392 for k,v in kwargs.iteritems():
1393 if not hasattr(self, k):
1394 raise self.UnknownOptionError("Unknown option '%s'" % k)
1395 setattr(self, k, v)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001396
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001397 def parse_opts(self, argv, ignore_unknown=False):
1398 ret = []
1399 opts = {}
1400 for a in argv:
1401 orig_a = a
1402 if not a.startswith('--'):
1403 ret.append(a)
1404 continue
1405 a = a[2:]
1406 i = a.find('=')
1407 if i == -1:
1408 if a.startswith("no-"):
1409 k = a[3:]
1410 v = False
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001411 else:
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001412 k = a
1413 v = True
1414 else:
1415 k = a[:i]
1416 v = a[i+1:]
1417 k = k.replace('-', '_')
1418 if not hasattr(self, k):
1419 if ignore_unknown == True or k in ignore_unknown:
1420 ret.append(orig_a)
1421 continue
1422 else:
1423 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001424
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001425 ov = getattr(self, k)
1426 if isinstance(ov, bool):
1427 v = bool(v)
1428 elif isinstance(ov, int):
1429 v = int(v)
1430 elif isinstance(ov, list):
1431 v = v.split(',')
1432 v = [int(x, 0) if x[0] in range(10) else x for x in v]
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001433
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001434 opts[k] = v
1435 self.set(**opts)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001436
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001437 return ret
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001438
1439
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001440class Subsetter(object):
1441
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001442 def __init__(self, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001443
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001444 if not log:
1445 log = Logger()
1446 if not options:
1447 options = Options()
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001448
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001449 self.options = options
1450 self.log = log
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001451 self.unicodes_requested = set()
1452 self.glyphs_requested = set()
1453 self.glyphs = set()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001454
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001455 def populate(self, glyphs=[], unicodes=[], text=""):
1456 self.unicodes_requested.update(unicodes)
1457 if isinstance(text, str):
1458 text = text.decode("utf8")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001459 for u in text:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001460 self.unicodes_requested.add(ord(u))
1461 self.glyphs_requested.update(glyphs)
1462 self.glyphs.update(glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001463
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001464 def _prune_pre_subset(self, font):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001465
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001466 for tag in font.keys():
1467 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001468
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001469 if(tag in self.options.drop_tables or
1470 (tag in self.options.hinting_tables and not self.options.hinting)):
1471 self.log(tag, "dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001472 del font[tag]
1473 continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001474
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001475 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001476
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001477 if hasattr(clazz, 'prune_pre_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001478 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001479 retain = table.prune_pre_subset(self.options)
1480 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001481 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001482 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001483 del font[tag]
1484 continue
1485 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001486 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001487
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001488 def _closure_glyphs(self, font):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001489
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001490 self.glyphs = self.glyphs_requested.copy()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001491
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001492 if 'cmap' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001493 font['cmap'].closure_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001494 self.glyphs_cmaped = self.glyphs
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001495
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001496 if self.options.mandatory_glyphs:
1497 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001498 for i in range(4):
1499 self.glyphs.add(font.getGlyphName(i))
1500 self.log("Added first four glyphs to subset")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001501 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001502 self.glyphs.add('.notdef')
1503 self.log("Added .notdef glyph to subset")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001504
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001505 if 'GSUB' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001506 self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1507 len(self.glyphs))
1508 self.log.glyphs(self.glyphs, font=font)
1509 font['GSUB'].closure_glyphs(self)
1510 self.log("Closed glyph list over 'GSUB': %d glyphs after" %
1511 len(self.glyphs))
1512 self.log.glyphs(self.glyphs, font=font)
1513 self.log.lapse("close glyph list over 'GSUB'")
1514 self.glyphs_gsubed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001515
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001516 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001517 self.log("Closing glyph list over 'glyf': %d glyphs before" %
1518 len(self.glyphs))
1519 self.log.glyphs(self.glyphs, font=font)
1520 font['glyf'].closure_glyphs(self)
1521 self.log("Closed glyph list over 'glyf': %d glyphs after" %
1522 len(self.glyphs))
1523 self.log.glyphs(self.glyphs, font=font)
1524 self.log.lapse("close glyph list over 'glyf'")
1525 self.glyphs_glyfed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001526
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001527 self.glyphs_all = self.glyphs.copy()
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001528
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001529 self.log("Retaining %d glyphs: " % len(self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001530
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001531 def _subset_glyphs(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001532 for tag in font.keys():
1533 if tag == 'GlyphOrder': continue
1534 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001535
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001536 if tag in self.options.no_subset_tables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001537 self.log(tag, "subsetting not needed")
1538 elif hasattr(clazz, 'subset_glyphs'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001539 table = font[tag]
1540 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001541 retain = table.subset_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001542 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001543 self.log.lapse("subset '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001544 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001545 self.log(tag, "subsetted to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001546 del font[tag]
1547 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001548 self.log(tag, "subsetted")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001549 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001550 self.log(tag, "NOT subset; don't know how to subset; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001551 del font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001552
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001553 glyphOrder = font.getGlyphOrder()
1554 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001555 font.setGlyphOrder(glyphOrder)
1556 font._buildReverseGlyphOrderDict()
1557 self.log.lapse("subset GlyphOrder")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001558
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001559 def _prune_post_subset(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001560 for tag in font.keys():
1561 if tag == 'GlyphOrder': continue
1562 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001563 if hasattr(clazz, 'prune_post_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001564 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001565 retain = table.prune_post_subset(self.options)
1566 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001567 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001568 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001569 del font[tag]
1570 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001571 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001572
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001573 def subset(self, font):
Behdad Esfahbod756af492013-08-01 12:05:26 -04001574
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001575 font.recalcBBoxes = self.options.recalc_bboxes
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001576
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001577 self._prune_pre_subset(font)
1578 self._closure_glyphs(font)
1579 self._subset_glyphs(font)
1580 self._prune_post_subset(font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001581
Behdad Esfahbod756af492013-08-01 12:05:26 -04001582
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04001583class Logger(object):
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001584
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001585 def __init__(self, verbose=False, xml=False, timing=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001586 self.verbose = verbose
1587 self.xml = xml
1588 self.timing = timing
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001589 self.last_time = self.start_time = time.time()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001590
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001591 def parse_opts(self, argv):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001592 argv = argv[:]
1593 for v in ['verbose', 'xml', 'timing']:
1594 if "--"+v in argv:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001595 setattr(self, v, True)
1596 argv.remove("--"+v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001597 return argv
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001598
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001599 def __call__(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001600 if not self.verbose:
1601 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001602 print ' '.join(str(x) for x in things)
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001603
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001604 def lapse(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001605 if not self.timing:
1606 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001607 new_time = time.time()
1608 print "Took %0.3fs to %s" %(new_time - self.last_time,
1609 ' '.join(str(x) for x in things))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001610 self.last_time = new_time
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001611
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001612 def glyphs(self, glyphs, font=None):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001613 self("Names: ", sorted(glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001614 if font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001615 reverseGlyphMap = font.getReverseGlyphMap()
1616 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001617
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001618 def font(self, font, file=sys.stdout):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001619 if not self.xml:
1620 return
1621 import xmlWriter
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001622 writer = xmlWriter.XMLWriter(file)
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001623 font.disassembleInstructions = False # Work around ttx bug
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001624 for tag in font.keys():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001625 writer.begintag(tag)
1626 writer.newline()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001627 font[tag].toXML(writer, font)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001628 writer.endtag(tag)
1629 writer.newline()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001630
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001631
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001632def load_font(fontfile, dont_load_glyph_names=False):
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001633
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001634 # TODO(behdad) Option for ignoreDecompileErrors?
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001635
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001636 font = fontTools.ttx.TTFont(fontfile)
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001637
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001638 # Hack:
1639 #
1640 # If we don't need glyph names, change 'post' class to not try to
1641 # load them. It avoid lots of headache with broken fonts as well
1642 # as loading time.
1643 #
1644 # Ideally ttLib should provide a way to ask it to skip loading
1645 # glyph names. But it currently doesn't provide such a thing.
1646 #
1647 if dont_load_glyph_names:
1648 post = fontTools.ttLib.getTableClass('post')
1649 saved = post.decode_format_2_0
1650 post.decode_format_2_0 = post.decode_format_3_0
1651 f = font['post']
1652 if f.formatType == 2.0:
1653 f.formatType = 3.0
1654 post.decode_format_2_0 = saved
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001655
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001656 return font
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001657
1658
Behdad Esfahbod58280972013-08-13 20:46:52 -04001659# Cleanup module space
1660l = locals()
1661for k,v in l.items():
Behdad Esfahbod11763302013-08-14 15:33:08 -04001662 if v == None:
1663 del l[k]
Behdad Esfahbodcc4fc4a2013-08-13 20:53:25 -04001664del k, v, l
Behdad Esfahbod58280972013-08-13 20:46:52 -04001665
1666
Behdad Esfahbodc0ded942013-08-14 13:53:33 -04001667def main(args=None):
Behdad Esfahbod23b2ee92013-08-13 20:29:10 -04001668
Behdad Esfahbodc0ded942013-08-14 13:53:33 -04001669 if args == None: args = sys.argv
Behdad Esfahbod3c472002013-08-14 13:52:27 -04001670 arg0, args = args[0], args[1:]
Behdad Esfahbod610b0552013-07-23 14:52:18 -04001671
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001672 log = Logger()
1673 args = log.parse_opts(args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04001674
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001675 options = Options()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001676 args = options.parse_opts(args, ignore_unknown=['text'])
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001677
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001678 if len(args) < 2:
Behdad Esfahbod3c472002013-08-14 13:52:27 -04001679 print >>sys.stderr, "usage: %s font-file glyph..." % arg0
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001680 sys.exit(1)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001681
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001682 fontfile = args[0]
1683 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001684
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001685 dont_load_glyph_names =(not options.glyph_names and
1686 all(any(g.startswith(p)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001687 for p in ['gid', 'glyph', 'uni', 'U+'])
1688 for g in args))
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001689
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001690 font = load_font(fontfile, dont_load_glyph_names=dont_load_glyph_names)
1691 subsetter = Subsetter(options=options, log=log)
1692 log.lapse("load font")
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001693
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001694 names = font.getGlyphNames()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001695 log.lapse("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001696
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001697 glyphs = []
1698 unicodes = []
1699 text = ""
1700 for g in args:
1701 if g in names:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001702 glyphs.append(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001703 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001704 if g.startswith('--text='):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001705 text += g[7:]
1706 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001707 if g.startswith('uni') or g.startswith('U+'):
1708 if g.startswith('uni') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001709 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001710 elif g.startswith('U+') and len(g) > 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001711 g = g[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001712 u = int(g, 16)
1713 unicodes.append(u)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001714 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001715 if g.startswith('gid') or g.startswith('glyph'):
1716 if g.startswith('gid') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001717 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001718 elif g.startswith('glyph') and len(g) > 5:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001719 g = g[5:]
1720 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001721 glyphs.append(font.getGlyphName(int(g), requireReal=1))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001722 except ValueError:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001723 raise Exception("Invalid glyph identifier: %s" % g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001724 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001725 raise Exception("Invalid glyph identifier: %s" % g)
1726 log.lapse("compile glyph list")
1727 log("Unicodes:", unicodes)
1728 log("Glyphs:", glyphs)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001729
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001730 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
1731 subsetter.subset(font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04001732
Behdad Esfahbod34426c12013-08-14 18:30:09 -04001733 outfile = fontfile + '.subset'
1734
1735 font.save(outfile)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001736 log.lapse("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001737
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001738 log.last_time = log.start_time
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001739 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001740
Behdad Esfahbod34426c12013-08-14 18:30:09 -04001741 if log.verbose:
1742 import os
1743 log("Input font: %d bytes" % os.path.getsize(fontfile))
1744 log("Subset font: %d bytes" % os.path.getsize(outfile))
1745
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001746 log.font(font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001747
Behdad Esfahbodc56bf482013-08-13 20:13:33 -04001748 font.close()
1749
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001750if __name__ == '__main__':
Behdad Esfahbod23b2ee92013-08-13 20:29:10 -04001751 main()