blob: cbe412e462b2a35888d14ccdd3dec895364a99ae [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 Esfahbod22f5cfc2013-08-13 20:25:37 -040025import sys
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -040026import struct
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040027import time
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -040028import array
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040029
Behdad Esfahbod45a84602013-08-19 14:44:49 -040030import fontTools.ttLib
31import fontTools.ttLib.tables
32import fontTools.ttLib.tables.otTables
33import fontTools.cffLib
34import fontTools.misc.psCharStrings
Behdad Esfahbod54660612013-07-21 18:16:55 -040035
Behdad Esfahbod54660612013-07-21 18:16:55 -040036
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040037def _add_method(*clazzes):
Behdad Esfahbod616d36e2013-08-13 20:02:59 -040038 """Returns a decorator function that adds a new method to one or
39 more classes."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040040 def wrapper(method):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040041 for clazz in clazzes:
42 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
Behdad Esfahbode7a0d562013-08-16 10:56:30 -040043 assert not hasattr(clazz, method.func_name), \
Behdad Esfahbodd77f1572013-08-15 19:24:36 -040044 "Oops, class '%s' has method '%s'." % (clazz.__name__,
45 method.func_name)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040046 setattr(clazz, method.func_name, method)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040047 return None
48 return wrapper
Behdad Esfahbod54660612013-07-21 18:16:55 -040049
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040050def _uniq_sort(l):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040051 return sorted(set(l))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040052
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -040053def _set_update(s, *others):
54 # Jython's set.update only takes one other argument.
55 # Emulate real set.update...
56 for other in others:
57 s.update(other)
58
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040059
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040060@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040061def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040062 "Returns ascending list of matching coverage values."
Behdad Esfahbod4734be52013-08-14 19:47:42 -040063 return [i for i,g in enumerate(self.glyphs) if g in glyphs]
Behdad Esfahbod610b0552013-07-23 14:52:18 -040064
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040065@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040066def intersect_glyphs(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040067 "Returns set of intersecting glyphs."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040068 return set(g for g in self.glyphs if g in glyphs)
Behdad Esfahbod849d25c2013-08-12 19:24:24 -040069
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040070@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040071def subset(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040072 "Returns ascending list of remaining coverage values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040073 indices = self.intersect(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040074 self.glyphs = [g for g in self.glyphs if g in glyphs]
75 return indices
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040076
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040077@_add_method(fontTools.ttLib.tables.otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040078def remap(self, coverage_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040079 "Remaps coverage."
80 self.glyphs = [self.glyphs[i] for i in coverage_map]
Behdad Esfahbod14374262013-08-08 22:26:49 -040081
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040082@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040083def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040084 "Returns ascending list of matching class values."
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040085 return _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040086 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040087 [v for g,v in self.classDefs.iteritems() if 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 intersect_class(self, glyphs, klass):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040091 "Returns set of glyphs matching class."
92 if klass == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040093 return set(g for g in glyphs if g not in self.classDefs)
94 return set(g for g,v in self.classDefs.iteritems()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040095 if v == klass and g in glyphs)
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040096
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040097@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040098def subset(self, glyphs, remap=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040099 "Returns ascending list of remaining classes."
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400100 self.classDefs = dict((g,v) for g,v in self.classDefs.iteritems() if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400101 # Note: while class 0 has the special meaning of "not matched",
102 # if no glyph will ever /not match/, we can optimize class 0 out too.
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400103 indices = _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400104 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod4e5d9672013-08-14 19:49:53 -0400105 self.classDefs.values())
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400106 if remap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400107 self.remap(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400108 return indices
Behdad Esfahbod4aa6ce32013-07-22 12:15:36 -0400109
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400110@_add_method(fontTools.ttLib.tables.otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400111def remap(self, class_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400112 "Remaps classes."
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400113 self.classDefs = dict((g,class_map.index(v))
114 for g,v in self.classDefs.iteritems())
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400115
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400116@_add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400117def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400118 if cur_glyphs == None: cur_glyphs = s.glyphs
119 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400120 s.glyphs.update(v for g,v in self.mapping.iteritems() if g in cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400121 else:
122 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400123
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400124@_add_method(fontTools.ttLib.tables.otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400125def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400126 if self.Format in [1, 2]:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400127 self.mapping = dict((g,v) for g,v in self.mapping.iteritems()
128 if g in s.glyphs and v in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400129 return bool(self.mapping)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400130 else:
131 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400132
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400133@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400134def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400135 if cur_glyphs == None: cur_glyphs = s.glyphs
136 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400137 indices = self.Coverage.intersect(cur_glyphs)
Behdad Esfahboda9bfec12013-08-16 16:21:25 -0400138 _set_update(s.glyphs, *(self.Sequence[i].Substitute for i in indices))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400139 else:
140 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400141
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400142@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400143def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400144 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400145 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400146 self.Sequence = [self.Sequence[i] for i in indices]
147 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400148 indices = [i for i,seq in enumerate(self.Sequence)
149 if all(sub in s.glyphs for sub in seq.Substitute)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400150 self.Sequence = [self.Sequence[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400151 self.Coverage.remap(indices)
152 self.SequenceCount = len(self.Sequence)
153 return bool(self.SequenceCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400154 else:
155 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400156
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400157@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400158def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400159 if cur_glyphs == None: cur_glyphs = s.glyphs
160 if self.Format == 1:
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -0400161 _set_update(s.glyphs, *(vlist for g,vlist in self.alternates.iteritems()
162 if g in cur_glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400163 else:
164 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400165
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400166@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400167def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400168 if self.Format == 1:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400169 self.alternates = dict((g,vlist)
170 for g,vlist in self.alternates.iteritems()
171 if g in s.glyphs and
172 all(v in s.glyphs for v in vlist))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400173 return bool(self.alternates)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400174 else:
175 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400176
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400177@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400178def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400179 if cur_glyphs == None: cur_glyphs = s.glyphs
180 if self.Format == 1:
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -0400181 _set_update(s.glyphs, *([seq.LigGlyph for seq in seqs
182 if all(c in s.glyphs for c in seq.Component)]
183 for g,seqs in self.ligatures.iteritems()
184 if g in cur_glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400185 else:
186 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400187
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400188@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400189def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400190 if self.Format == 1:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400191 self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems()
192 if g in s.glyphs)
193 self.ligatures = dict((g,[seq for seq in seqs
194 if seq.LigGlyph in s.glyphs and
195 all(c in s.glyphs for c in seq.Component)])
196 for g,seqs in self.ligatures.iteritems())
197 self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems() if v)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400198 return bool(self.ligatures)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400199 else:
200 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400201
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400202@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400203def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400204 if cur_glyphs == None: cur_glyphs = s.glyphs
205 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400206 indices = self.Coverage.intersect(cur_glyphs)
207 if(not indices or
208 not all(c.intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400209 for c in self.LookAheadCoverage + self.BacktrackCoverage)):
210 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400211 s.glyphs.update(self.Substitute[i] for i in indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400212 else:
213 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400214
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400215@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400216def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400217 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400218 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400219 self.Substitute = [self.Substitute[i] for i in indices]
220 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400221 indices = [i for i,sub in enumerate(self.Substitute)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400222 if sub in s.glyphs]
223 self.Substitute = [self.Substitute[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400224 self.Coverage.remap(indices)
225 self.GlyphCount = len(self.Substitute)
226 return bool(self.GlyphCount and
227 all(c.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400228 for c in self.LookAheadCoverage+self.BacktrackCoverage))
229 else:
230 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400231
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400232@_add_method(fontTools.ttLib.tables.otTables.SinglePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400233def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400234 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400235 return len(self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400236 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400237 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400238 self.Value = [self.Value[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400239 self.ValueCount = len(self.Value)
240 return bool(self.ValueCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400241 else:
242 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400243
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400244@_add_method(fontTools.ttLib.tables.otTables.SinglePos)
245def prune_post_subset(self, options):
246 if not options.hinting:
247 # Drop device tables
248 self.ValueFormat &= ~0x00F0
249 return True
250
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400251@_add_method(fontTools.ttLib.tables.otTables.PairPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400252def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400253 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400254 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400255 self.PairSet = [self.PairSet[i] for i in indices]
256 for p in self.PairSet:
257 p.PairValueRecord = [r for r in p.PairValueRecord
258 if r.SecondGlyph in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400259 p.PairValueCount = len(p.PairValueRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400260 self.PairSet = [p for p in self.PairSet if p.PairValueCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400261 self.PairSetCount = len(self.PairSet)
262 return bool(self.PairSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400263 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400264 class1_map = self.ClassDef1.subset(s.glyphs, remap=True)
265 class2_map = self.ClassDef2.subset(s.glyphs, remap=True)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400266 self.Class1Record = [self.Class1Record[i] for i in class1_map]
267 for c in self.Class1Record:
268 c.Class2Record = [c.Class2Record[i] for i in class2_map]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400269 self.Class1Count = len(class1_map)
270 self.Class2Count = len(class2_map)
271 return bool(self.Class1Count and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400272 self.Class2Count and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400273 self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400274 else:
275 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400276
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400277@_add_method(fontTools.ttLib.tables.otTables.PairPos)
278def prune_post_subset(self, options):
279 if not options.hinting:
280 # Drop device tables
281 self.ValueFormat1 &= ~0x00F0
282 self.ValueFormat2 &= ~0x00F0
283 return True
284
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400285@_add_method(fontTools.ttLib.tables.otTables.CursivePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400286def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400287 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400288 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400289 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400290 self.EntryExitCount = len(self.EntryExitRecord)
291 return bool(self.EntryExitCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400292 else:
293 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400294
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400295@_add_method(fontTools.ttLib.tables.otTables.Anchor)
296def prune_hints(self):
297 # Drop device tables / contour anchor point
298 self.Format = 1
299
300@_add_method(fontTools.ttLib.tables.otTables.CursivePos)
301def prune_post_subset(self, options):
302 if not options.hinting:
303 for rec in self.EntryExitRecord:
304 if rec.EntryAnchor: rec.EntryAnchor.prune_hints()
305 if rec.ExitAnchor: rec.ExitAnchor.prune_hints()
306 return True
307
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400308@_add_method(fontTools.ttLib.tables.otTables.MarkBasePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400309def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400310 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400311 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400312 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
313 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400314 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
315 base_indices = self.BaseCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400316 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i]
317 for i in base_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400318 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400319 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400320 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400321 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400322 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400323 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400324 for b in self.BaseArray.BaseRecord:
325 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400326 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400327 self.MarkArray.MarkCount and
328 self.BaseArray.BaseCount)
329 else:
330 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400331
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400332@_add_method(fontTools.ttLib.tables.otTables.MarkBasePos)
333def prune_post_subset(self, options):
334 if not options.hinting:
335 for m in self.MarkArray.MarkRecord:
336 m.MarkAnchor.prune_hints()
337 for b in self.BaseArray.BaseRecord:
338 for a in b.BaseAnchor:
339 a.prune_hints()
340 return True
341
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400342@_add_method(fontTools.ttLib.tables.otTables.MarkLigPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400343def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400344 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400345 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400346 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
347 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400348 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
349 ligature_indices = self.LigatureCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400350 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i]
351 for i in ligature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400352 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400353 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400354 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400355 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400356 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400357 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400358 for l in self.LigatureArray.LigatureAttach:
359 for c in l.ComponentRecord:
360 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400361 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400362 self.MarkArray.MarkCount and
363 self.LigatureArray.LigatureCount)
364 else:
365 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400366
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400367@_add_method(fontTools.ttLib.tables.otTables.MarkLigPos)
368def prune_post_subset(self, options):
369 if not options.hinting:
370 for m in self.MarkArray.MarkRecord:
371 m.MarkAnchor.prune_hints()
372 for l in self.LigatureArray.LigatureAttach:
373 for c in l.ComponentRecord:
374 for a in c.LigatureAnchor:
375 a.prune_hints()
376 return True
377
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400378@_add_method(fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400379def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400380 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400381 mark1_indices = self.Mark1Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400382 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i]
383 for i in mark1_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400384 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
385 mark2_indices = self.Mark2Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400386 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i]
387 for i in mark2_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400388 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400389 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400390 class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400391 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400392 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400393 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400394 for b in self.Mark2Array.Mark2Record:
395 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400396 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400397 self.Mark1Array.MarkCount and
398 self.Mark2Array.MarkCount)
399 else:
400 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400401
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400402@_add_method(fontTools.ttLib.tables.otTables.MarkMarkPos)
403def prune_post_subset(self, options):
404 if not options.hinting:
405 # Drop device tables or contour anchor point
406 for m in self.Mark1Array.MarkRecord:
407 m.MarkAnchor.prune_hints()
408 for b in self.Mark2Array.Mark2Record:
409 for m in rec.Mark2Anchor:
410 m.prune_hints()
411 return True
412
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400413@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
414 fontTools.ttLib.tables.otTables.MultipleSubst,
415 fontTools.ttLib.tables.otTables.AlternateSubst,
416 fontTools.ttLib.tables.otTables.LigatureSubst,
417 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
418 fontTools.ttLib.tables.otTables.SinglePos,
419 fontTools.ttLib.tables.otTables.PairPos,
420 fontTools.ttLib.tables.otTables.CursivePos,
421 fontTools.ttLib.tables.otTables.MarkBasePos,
422 fontTools.ttLib.tables.otTables.MarkLigPos,
423 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400424def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400425 pass
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400426
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400427@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
428 fontTools.ttLib.tables.otTables.MultipleSubst,
429 fontTools.ttLib.tables.otTables.AlternateSubst,
430 fontTools.ttLib.tables.otTables.LigatureSubst,
431 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
432 fontTools.ttLib.tables.otTables.SinglePos,
433 fontTools.ttLib.tables.otTables.PairPos,
434 fontTools.ttLib.tables.otTables.CursivePos,
435 fontTools.ttLib.tables.otTables.MarkBasePos,
436 fontTools.ttLib.tables.otTables.MarkLigPos,
437 fontTools.ttLib.tables.otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400438def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400439 return []
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400440
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400441@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400442 fontTools.ttLib.tables.otTables.MultipleSubst,
443 fontTools.ttLib.tables.otTables.AlternateSubst,
444 fontTools.ttLib.tables.otTables.LigatureSubst,
445 fontTools.ttLib.tables.otTables.ContextSubst,
446 fontTools.ttLib.tables.otTables.ChainContextSubst,
447 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
448 fontTools.ttLib.tables.otTables.SinglePos,
449 fontTools.ttLib.tables.otTables.PairPos,
450 fontTools.ttLib.tables.otTables.CursivePos,
451 fontTools.ttLib.tables.otTables.MarkBasePos,
452 fontTools.ttLib.tables.otTables.MarkLigPos,
453 fontTools.ttLib.tables.otTables.MarkMarkPos,
454 fontTools.ttLib.tables.otTables.ContextPos,
455 fontTools.ttLib.tables.otTables.ChainContextPos)
456def prune_pre_subset(self, options):
457 return True
458
459@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
460 fontTools.ttLib.tables.otTables.MultipleSubst,
461 fontTools.ttLib.tables.otTables.AlternateSubst,
462 fontTools.ttLib.tables.otTables.LigatureSubst,
463 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst,
464 fontTools.ttLib.tables.otTables.ContextSubst,
465 fontTools.ttLib.tables.otTables.ChainContextSubst,
466 fontTools.ttLib.tables.otTables.ContextPos,
467 fontTools.ttLib.tables.otTables.ChainContextPos)
468def prune_post_subset(self, options):
469 return True
470
471@_add_method(fontTools.ttLib.tables.otTables.SingleSubst,
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400472 fontTools.ttLib.tables.otTables.AlternateSubst,
473 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400474def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400475 return False
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400476
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400477@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst,
478 fontTools.ttLib.tables.otTables.LigatureSubst,
479 fontTools.ttLib.tables.otTables.ContextSubst,
480 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400481def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400482 return True
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400483
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400484@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
485 fontTools.ttLib.tables.otTables.ChainContextSubst,
486 fontTools.ttLib.tables.otTables.ContextPos,
487 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400488def __classify_context(self):
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400489
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -0400490 class ContextHelper(object):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400491 def __init__(self, klass, Format):
492 if klass.__name__.endswith('Subst'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400493 Typ = 'Sub'
494 Type = 'Subst'
495 else:
496 Typ = 'Pos'
497 Type = 'Pos'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400498 if klass.__name__.startswith('Chain'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400499 Chain = 'Chain'
500 else:
501 Chain = ''
502 ChainTyp = Chain+Typ
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400503
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400504 self.Typ = Typ
505 self.Type = Type
506 self.Chain = Chain
507 self.ChainTyp = ChainTyp
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400508
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400509 self.LookupRecord = Type+'LookupRecord'
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400510
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400511 if Format == 1:
512 Coverage = lambda r: r.Coverage
513 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400514 ContextData = lambda r:(None,)
515 ChainContextData = lambda r:(None, None, None)
516 RuleData = lambda r:(r.Input,)
517 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400518 SetRuleData = None
519 ChainSetRuleData = None
520 elif Format == 2:
521 Coverage = lambda r: r.Coverage
522 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400523 ContextData = lambda r:(r.ClassDef,)
524 ChainContextData = lambda r:(r.LookAheadClassDef,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400525 r.InputClassDef,
526 r.BacktrackClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400527 RuleData = lambda r:(r.Class,)
528 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack)
529 def SetRuleData(r, d):(r.Class,) = d
530 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400531 elif Format == 3:
532 Coverage = lambda r: r.Coverage[0]
533 ChainCoverage = lambda r: r.InputCoverage[0]
534 ContextData = None
535 ChainContextData = None
536 RuleData = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400537 ChainRuleData = lambda r:(r.LookAheadCoverage +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400538 r.InputCoverage +
539 r.BacktrackCoverage)
540 SetRuleData = None
541 ChainSetRuleData = None
542 else:
543 assert 0, "unknown format: %s" % Format
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400544
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400545 if Chain:
546 self.Coverage = ChainCoverage
547 self.ContextData = ChainContextData
548 self.RuleData = ChainRuleData
549 self.SetRuleData = ChainSetRuleData
550 else:
551 self.Coverage = Coverage
552 self.ContextData = ContextData
553 self.RuleData = RuleData
554 self.SetRuleData = SetRuleData
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400555
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400556 if Format == 1:
557 self.Rule = ChainTyp+'Rule'
558 self.RuleCount = ChainTyp+'RuleCount'
559 self.RuleSet = ChainTyp+'RuleSet'
560 self.RuleSetCount = ChainTyp+'RuleSetCount'
561 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
562 elif Format == 2:
563 self.Rule = ChainTyp+'ClassRule'
564 self.RuleCount = ChainTyp+'ClassRuleCount'
565 self.RuleSet = ChainTyp+'ClassSet'
566 self.RuleSetCount = ChainTyp+'ClassSetCount'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400567 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r)
Behdad Esfahbod89987002013-07-23 23:07:42 -0400568
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400569 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
Behdad Esfahbod11763302013-08-14 15:33:08 -0400570 self.Input = 'Input' if Chain else 'Class'
Behdad Esfahbod27108392013-07-23 16:40:47 -0400571
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400572 if self.Format not in [1, 2, 3]:
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400573 return None # Don't shoot the messenger; let it go
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400574 if not hasattr(self.__class__, "__ContextHelpers"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400575 self.__class__.__ContextHelpers = {}
576 if self.Format not in self.__class__.__ContextHelpers:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400577 helper = ContextHelper(self.__class__, self.Format)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400578 self.__class__.__ContextHelpers[self.Format] = helper
579 return self.__class__.__ContextHelpers[self.Format]
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400580
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400581@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
582 fontTools.ttLib.tables.otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400583def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400584 if cur_glyphs == None: cur_glyphs = s.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400585 c = self.__classify_context()
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400586
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400587 indices = c.Coverage(self).intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400588 if not indices:
589 return []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400590 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs);
Behdad Esfahbod1d4fa132013-08-08 22:59:32 -0400591
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400592 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400593 ContextData = c.ContextData(self)
594 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400595 for i in indices:
596 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400597 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400598 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400599 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
600 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400601 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400602 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400603 if not ll: continue
604 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400605 if chaos:
606 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400607 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400608 if seqi == 0:
609 pos_glyphs = set([c.Coverage(self).glyphs[i]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400610 else:
Behdad Esfahbodd3fdcc72013-08-14 17:59:31 -0400611 pos_glyphs = set([r.Input[seqi - 1]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400612 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400613 chaos = chaos or lookup.may_have_non_1to1()
614 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400615 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400616 ClassDef = getattr(self, c.ClassDef)
617 indices = ClassDef.intersect(cur_glyphs)
618 ContextData = c.ContextData(self)
619 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400620 for i in indices:
621 if not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400622 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400623 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400624 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
625 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400626 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400627 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400628 if not ll: continue
629 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400630 if chaos:
631 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400632 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400633 if seqi == 0:
634 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400635 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400636 pos_glyphs = ClassDef.intersect_class(s.glyphs,
Behdad Esfahbod11763302013-08-14 15:33:08 -0400637 getattr(r, c.Input)[seqi - 1])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400638 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400639 chaos = chaos or lookup.may_have_non_1to1()
640 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400641 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400642 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400643 return []
644 r = self
645 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400646 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400647 if not ll: continue
648 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400649 if chaos:
650 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400651 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400652 if seqi == 0:
653 pos_glyphs = cur_glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400654 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400655 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400656 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400657 chaos = chaos or lookup.may_have_non_1to1()
658 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400659 else:
660 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod00776972013-07-23 15:33:00 -0400661
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400662@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
663 fontTools.ttLib.tables.otTables.ContextPos,
664 fontTools.ttLib.tables.otTables.ChainContextSubst,
665 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400666def subset_glyphs(self, s):
667 c = self.__classify_context()
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400668
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400669 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400670 indices = self.Coverage.subset(s.glyphs)
671 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400672 rss = [rss[i] for i in indices]
673 for rs in rss:
674 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400675 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400676 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400677 if r and all(all(g in s.glyphs for g in glist)
678 for glist in c.RuleData(r))]
679 setattr(rs, c.Rule, ss)
680 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400681 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400682 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
683 setattr(self, c.RuleSet, rss)
684 setattr(self, c.RuleSetCount, len(rss))
685 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400686 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400687 if not self.Coverage.subset(s.glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400688 return False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400689 indices = getattr(self, c.ClassDef).subset(self.Coverage.glyphs,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400690 remap=False)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400691 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400692 rss = [rss[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400693 ContextData = c.ContextData(self)
694 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400695 for rs in rss:
696 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400697 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400698 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400699 if r and all(all(k in klass_map for k in klist)
700 for klass_map,klist in zip(klass_maps, c.RuleData(r)))]
701 setattr(rs, c.Rule, ss)
702 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400703
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400704 # Remap rule classes
705 for r in ss:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400706 c.SetRuleData(r, [[klass_map.index(k) for k in klist]
707 for klass_map,klist in zip(klass_maps, c.RuleData(r))])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400708 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400709 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
710 setattr(self, c.RuleSet, rss)
711 setattr(self, c.RuleSetCount, len(rss))
712 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400713 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400714 return all(x.subset(s.glyphs) for x in c.RuleData(self))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400715 else:
716 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400717
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400718@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
719 fontTools.ttLib.tables.otTables.ChainContextSubst,
720 fontTools.ttLib.tables.otTables.ContextPos,
721 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400722def subset_lookups(self, lookup_indices):
723 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400724
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400725 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400726 for rs in getattr(self, c.RuleSet):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400727 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400728 for r in getattr(rs, c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400729 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400730 setattr(r, c.LookupRecord,
731 [ll for ll in getattr(r, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400732 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400733 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400734 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400735 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400736 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400737 setattr(self, c.LookupRecord,
738 [ll for ll in getattr(self, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400739 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400740 for ll in getattr(self, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400741 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400742 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400743 else:
744 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400745
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400746@_add_method(fontTools.ttLib.tables.otTables.ContextSubst,
747 fontTools.ttLib.tables.otTables.ChainContextSubst,
748 fontTools.ttLib.tables.otTables.ContextPos,
749 fontTools.ttLib.tables.otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400750def collect_lookups(self):
751 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400752
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400753 if self.Format in [1, 2]:
754 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400755 for rs in getattr(self, c.RuleSet) if rs
756 for r in getattr(rs, c.Rule) if r
757 for ll in getattr(r, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400758 elif self.Format == 3:
759 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400760 for ll in getattr(self, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400761 else:
762 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400763
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400764@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400765def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400766 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400767 self.ExtSubTable.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400768 else:
769 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400770
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400771@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400772def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400773 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400774 return self.ExtSubTable.may_have_non_1to1()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400775 else:
776 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400777
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400778@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
779 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400780def prune_pre_subset(self, options):
781 if self.Format == 1:
782 return self.ExtSubTable.prune_pre_subset(options)
783 else:
784 assert 0, "unknown format: %s" % self.Format
785
786@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
787 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400788def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400789 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400790 return self.ExtSubTable.subset_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400791 else:
792 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400793
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400794@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
795 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400796def prune_post_subset(self, options):
797 if self.Format == 1:
798 return self.ExtSubTable.prune_post_subset(options)
799 else:
800 assert 0, "unknown format: %s" % self.Format
801
802@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
803 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400804def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400805 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400806 return self.ExtSubTable.subset_lookups(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400807 else:
808 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400809
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400810@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst,
811 fontTools.ttLib.tables.otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400812def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400813 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400814 return self.ExtSubTable.collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400815 else:
816 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400817
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400818@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400819def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400820 for st in self.SubTable:
821 if not st: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400822 st.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400823
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400824@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400825def prune_pre_subset(self, options):
826 ret = False
827 for st in self.SubTable:
828 if not st: continue
829 if st.prune_pre_subset(options): ret = True
830 return ret
831
832@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400833def subset_glyphs(self, s):
834 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)]
835 self.SubTableCount = len(self.SubTable)
836 return bool(self.SubTableCount)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400837
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400838@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400839def prune_post_subset(self, options):
840 ret = False
841 for st in self.SubTable:
842 if not st: continue
843 if st.prune_post_subset(options): ret = True
844 return ret
845
846@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400847def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400848 for s in self.SubTable:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400849 s.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400850
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400851@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400852def collect_lookups(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400853 return _uniq_sort(sum((st.collect_lookups() for st in self.SubTable
854 if st), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400855
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400856@_add_method(fontTools.ttLib.tables.otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400857def may_have_non_1to1(self):
858 return any(st.may_have_non_1to1() for st in self.SubTable if st)
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400859
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400860@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400861def prune_pre_subset(self, options):
862 ret = False
863 for l in self.Lookup:
864 if not l: continue
865 if l.prune_pre_subset(options): ret = True
866 return ret
867
868@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400869def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400870 "Returns the indices of nonempty lookups."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400871 return [i for i,l in enumerate(self.Lookup) if l and l.subset_glyphs(s)]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400872
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400873@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400874def prune_post_subset(self, options):
875 ret = False
876 for l in self.Lookup:
877 if not l: continue
878 if l.prune_post_subset(options): ret = True
879 return ret
880
881@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400882def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400883 self.Lookup = [self.Lookup[i] for i in lookup_indices
884 if i < self.LookupCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400885 self.LookupCount = len(self.Lookup)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400886 for l in self.Lookup:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400887 l.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400888
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400889@_add_method(fontTools.ttLib.tables.otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400890def closure_lookups(self, lookup_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400891 lookup_indices = _uniq_sort(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400892 recurse = lookup_indices
893 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400894 recurse_lookups = sum((self.Lookup[i].collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400895 for i in recurse if i < self.LookupCount), [])
896 recurse_lookups = [l for l in recurse_lookups
897 if l not in lookup_indices and l < self.LookupCount]
898 if not recurse_lookups:
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400899 return _uniq_sort(lookup_indices)
900 recurse_lookups = _uniq_sort(recurse_lookups)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400901 lookup_indices.extend(recurse_lookups)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400902 recurse = recurse_lookups
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400903
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400904@_add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400905def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400906 self.LookupListIndex = [l for l in self.LookupListIndex
907 if l in lookup_indices]
908 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400909 self.LookupListIndex = [lookup_indices.index(l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400910 for l in self.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400911 self.LookupCount = len(self.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400912 return self.LookupCount
Behdad Esfahbod54660612013-07-21 18:16:55 -0400913
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400914@_add_method(fontTools.ttLib.tables.otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400915def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400916 return self.LookupListIndex[:]
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400917
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400918@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400919def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400920 "Returns the indices of nonempty features."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400921 feature_indices = [i for i,f in enumerate(self.FeatureRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400922 if f.Feature.subset_lookups(lookup_indices)]
923 self.subset_features(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400924 return feature_indices
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400925
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400926@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400927def collect_lookups(self, feature_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400928 return _uniq_sort(sum((self.FeatureRecord[i].Feature.collect_lookups()
929 for i in feature_indices
Behdad Esfahbod1ee298d2013-08-13 20:07:09 -0400930 if i < self.FeatureCount), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400931
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400932@_add_method(fontTools.ttLib.tables.otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400933def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400934 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400935 self.FeatureCount = len(self.FeatureRecord)
936 return bool(self.FeatureCount)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400937
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400938@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
939 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400940def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400941 if self.ReqFeatureIndex in feature_indices:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400942 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400943 else:
944 self.ReqFeatureIndex = 65535
945 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
946 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400947 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400948 if f in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400949 self.FeatureCount = len(self.FeatureIndex)
950 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400951
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400952@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys,
953 fontTools.ttLib.tables.otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400954def collect_features(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400955 feature_indices = self.FeatureIndex[:]
956 if self.ReqFeatureIndex != 65535:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400957 feature_indices.append(self.ReqFeatureIndex)
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400958 return _uniq_sort(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400959
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400960@_add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400961def subset_features(self, feature_indices):
962 if(self.DefaultLangSys and
963 not self.DefaultLangSys.subset_features(feature_indices)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400964 self.DefaultLangSys = None
965 self.LangSysRecord = [l for l in self.LangSysRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400966 if l.LangSys.subset_features(feature_indices)]
967 self.LangSysCount = len(self.LangSysRecord)
968 return bool(self.LangSysCount or self.DefaultLangSys)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400969
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400970@_add_method(fontTools.ttLib.tables.otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400971def collect_features(self):
972 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400973 if self.DefaultLangSys:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400974 feature_indices.append(self.DefaultLangSys.collect_features())
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400975 return _uniq_sort(sum(feature_indices, []))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400976
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400977@_add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400978def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400979 self.ScriptRecord = [s for s in self.ScriptRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400980 if s.Script.subset_features(feature_indices)]
981 self.ScriptCount = len(self.ScriptRecord)
982 return bool(self.ScriptCount)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400983
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400984@_add_method(fontTools.ttLib.tables.otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400985def collect_features(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400986 return _uniq_sort(sum((s.Script.collect_features()
987 for s in self.ScriptRecord), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400988
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400989@_add_method(fontTools.ttLib.getTableClass('GSUB'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400990def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400991 s.table = self.table
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400992 feature_indices = self.table.ScriptList.collect_features()
993 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400994 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400995 orig_glyphs = s.glyphs.copy()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400996 for i in lookup_indices:
997 if i >= self.table.LookupList.LookupCount: continue
998 if not self.table.LookupList.Lookup[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400999 self.table.LookupList.Lookup[i].closure_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001000 if orig_glyphs == s.glyphs:
1001 break
1002 del s.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -04001003
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001004@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1005 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001006def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001007 s.glyphs = s.glyphs_gsubed
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001008 lookup_indices = self.table.LookupList.subset_glyphs(s)
1009 self.subset_lookups(lookup_indices)
1010 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001011 return True
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001012
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001013@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1014 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001015def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001016 """Retrains specified lookups, then removes empty features, language
1017 systems, and scripts."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001018 self.table.LookupList.subset_lookups(lookup_indices)
1019 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices)
1020 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -04001021
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001022@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1023 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001024def prune_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001025 "Remove unreferenced lookups"
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001026 feature_indices = self.table.ScriptList.collect_features()
1027 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
1028 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices)
1029 self.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -04001030
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001031@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1032 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001033def subset_feature_tags(self, feature_tags):
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001034 feature_indices = [i for i,f in
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001035 enumerate(self.table.FeatureList.FeatureRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001036 if f.FeatureTag in feature_tags]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001037 self.table.FeatureList.subset_features(feature_indices)
1038 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001039
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001040@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1041 fontTools.ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001042def prune_pre_subset(self, options):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001043 if '*' not in options.layout_features:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001044 self.subset_feature_tags(options.layout_features)
1045 self.prune_lookups()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -04001046 self.table.LookupList.prune_pre_subset(options);
1047 return True
1048
1049@_add_method(fontTools.ttLib.getTableClass('GSUB'),
1050 fontTools.ttLib.getTableClass('GPOS'))
1051def prune_post_subset(self, options):
1052 self.table.LookupList.prune_post_subset(options);
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001053 return True
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001054
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001055@_add_method(fontTools.ttLib.getTableClass('GDEF'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001056def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001057 glyphs = s.glyphs_gsubed
1058 table = self.table
1059 if table.LigCaretList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001060 indices = table.LigCaretList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001061 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i]
1062 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001063 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001064 if not table.LigCaretList.LigGlyphCount:
1065 table.LigCaretList = None
1066 if table.MarkAttachClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001067 table.MarkAttachClassDef.classDefs = dict((g,v) for g,v in
1068 table.MarkAttachClassDef.
1069 classDefs.iteritems()
1070 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001071 if not table.MarkAttachClassDef.classDefs:
1072 table.MarkAttachClassDef = None
1073 if table.GlyphClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001074 table.GlyphClassDef.classDefs = dict((g,v) for g,v in
1075 table.GlyphClassDef.
1076 classDefs.iteritems()
1077 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001078 if not table.GlyphClassDef.classDefs:
1079 table.GlyphClassDef = None
1080 if table.AttachList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001081 indices = table.AttachList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001082 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i]
1083 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001084 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001085 if not table.AttachList.GlyphCount:
1086 table.AttachList = None
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001087 return bool(table.LigCaretList or
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001088 table.MarkAttachClassDef or
1089 table.GlyphClassDef or
1090 table.AttachList)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001091
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001092@_add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001093def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001094 # Prune unknown kern table types
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001095 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')]
1096 return bool(self.kernTables)
Behdad Esfahbodd4e33a72013-07-24 18:51:05 -04001097
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001098@_add_method(fontTools.ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001099def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001100 glyphs = s.glyphs_gsubed
1101 for t in self.kernTables:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001102 t.kernTable = dict(((a,b),v) for (a,b),v in t.kernTable.iteritems()
1103 if a in glyphs and b in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001104 self.kernTables = [t for t in self.kernTables if t.kernTable]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001105 return bool(self.kernTables)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001106
Behdad Esfahbode7a0d562013-08-16 10:56:30 -04001107@_add_method(fontTools.ttLib.getTableClass('vmtx'),
1108 fontTools.ttLib.getTableClass('hmtx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001109def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001110 self.metrics = dict((g,v) for g,v in self.metrics.iteritems() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001111 return bool(self.metrics)
Behdad Esfahbodc7160442013-07-22 14:29:08 -04001112
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001113@_add_method(fontTools.ttLib.getTableClass('hdmx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001114def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001115 self.hdmx = dict((sz,_dict((g,v) for g,v in l.iteritems() if g in s.glyphs))
1116 for sz,l in self.hdmx.iteritems())
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001117 return bool(self.hdmx)
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -04001118
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001119@_add_method(fontTools.ttLib.getTableClass('VORG'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001120def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001121 self.VOriginRecords = dict((g,v) for g,v in self.VOriginRecords.iteritems()
1122 if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001123 self.numVertOriginYMetrics = len(self.VOriginRecords)
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001124 return True # Never drop; has default metrics
Behdad Esfahbode45d6af2013-07-22 15:29:17 -04001125
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001126@_add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001127def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001128 if not options.glyph_names:
1129 self.formatType = 3.0
1130 return True
Behdad Esfahbod42648242013-07-23 12:56:06 -04001131
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001132@_add_method(fontTools.ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001133def subset_glyphs(self, s):
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001134 self.extraNames = [] # This seems to do it
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001135 return True
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001136
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001137@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001138def getComponentNamesFast(self, glyfTable):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001139 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001140 return [] # Not composite
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001141 data = self.data
1142 i = 10
1143 components = []
1144 more = 1
1145 while more:
1146 flags, glyphID = struct.unpack(">HH", data[i:i+4])
1147 i += 4
1148 flags = int(flags)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001149 components.append(glyfTable.getGlyphName(int(glyphID)))
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001150
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001151 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001152 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001153 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1154 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1155 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1156 more = flags & 0x0020 # MORE_COMPONENTS
1157
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001158 return components
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001159
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001160@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001161def remapComponentsFast(self, indices):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001162 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001163 return # Not composite
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001164 data = array.array("B", self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001165 i = 10
1166 more = 1
1167 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001168 flags =(data[i] << 8) | data[i+1]
1169 glyphID =(data[i+2] << 8) | data[i+3]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001170 # Remap
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001171 glyphID = indices.index(glyphID)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001172 data[i+2] = glyphID >> 8
1173 data[i+3] = glyphID & 0xFF
1174 i += 4
1175 flags = int(flags)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001176
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001177 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001178 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001179 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1180 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1181 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1182 more = flags & 0x0020 # MORE_COMPONENTS
1183
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001184 self.data = data.tostring()
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001185
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001186@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001187def dropInstructionsFast(self):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001188 if not self.data:
1189 return
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001190 numContours = struct.unpack(">h", self.data[:2])[0]
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001191 data = array.array("B", self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001192 i = 10
1193 if numContours >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001194 i += 2 * numContours # endPtsOfContours
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001195 instructionLen =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001196 # Zero it
1197 data[i] = data [i+1] = 0
1198 i += 2
1199 if instructionLen:
1200 # Splice it out
1201 data = data[:i] + data[i+instructionLen:]
1202 else:
1203 more = 1
1204 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001205 flags =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001206 # Turn instruction flag off
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001207 flags &= ~0x0100 # WE_HAVE_INSTRUCTIONS
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001208 data[i+0] = flags >> 8
1209 data[i+1] = flags & 0xFF
1210 i += 4
1211 flags = int(flags)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001212
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001213 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001214 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001215 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1216 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1217 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1218 more = flags & 0x0020 # MORE_COMPONENTS
1219
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001220 # Cut off
1221 data = data[:i]
1222 if len(data) % 4:
1223 # add pad bytes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001224 nPadBytes = 4 -(len(data) % 4)
1225 for i in range(nPadBytes):
1226 data.append(0)
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001227 self.data = data.tostring()
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001228
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001229@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001230def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001231 decompose = s.glyphs
1232 # I don't know if component glyphs can be composite themselves.
1233 # We handle them anyway.
1234 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001235 components = set()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001236 for g in decompose:
1237 if g not in self.glyphs:
1238 continue
1239 gl = self.glyphs[g]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001240 if hasattr(gl, "data"):
1241 for c in gl.getComponentNamesFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001242 if c not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001243 components.add(c)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001244 else:
1245 # TTX seems to expand gid0..3 always
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001246 if gl.isComposite():
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001247 for c in gl.components:
1248 if c.glyphName not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001249 components.add(c.glyphName)
1250 components = set(c for c in components if c not in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001251 if not components:
1252 break
1253 decompose = components
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001254 s.glyphs.update(components)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001255
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001256@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001257def prune_pre_subset(self, options):
1258 if options.notdef_glyph and not options.notdef_outline:
1259 g = self[self.glyphOrder[0]]
1260 # Yay, easy!
1261 g.__dict__.clear()
1262 g.data = ""
1263 return True
1264
1265@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001266def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001267 self.glyphs = dict((g,v) for g,v in self.glyphs.iteritems() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001268 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001269 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001270 if hasattr(v, "data"):
1271 v.remapComponentsFast(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001272 else:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001273 pass # No need
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001274 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbodb69b6712013-08-29 18:17:31 -04001275 # Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset.
1276 return True
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001277
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001278@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001279def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001280 if not options.hinting:
1281 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001282 if hasattr(v, "data"):
1283 v.dropInstructionsFast()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001284 else:
1285 v.program = fontTools.ttLib.tables.ttProgram.Program()
1286 v.program.fromBytecode([])
1287 return True
Behdad Esfahboded98c612013-07-23 12:37:41 -04001288
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001289@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001290def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001291 cff = self.cff
1292 # CFF table should have one font only
1293 cff.fontNames = cff.fontNames[:1]
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001294
1295 if options.notdef_glyph and not options.notdef_outline:
1296 for fontname in cff.keys():
1297 font = cff[fontname]
1298 c,_ = font.CharStrings.getItemAndSelector('.notdef')
1299 c.bytecode = '\x0e' # endchar
1300 c.program = None
1301
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001302 return bool(cff.fontNames)
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001303
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001304@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001305def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001306 cff = self.cff
1307 for fontname in cff.keys():
1308 font = cff[fontname]
1309 cs = font.CharStrings
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001310
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001311 # Load all glyphs
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001312 for g in font.charset:
1313 if g not in s.glyphs: continue
1314 c,sel = cs.getItemAndSelector(g)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001315
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001316 if cs.charStringsAreIndexed:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001317 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001318 csi = cs.charStringsIndex
1319 csi.items = [csi.items[i] for i in indices]
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001320 csi.count = len(csi.items)
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001321 del csi.file, csi.offsets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001322 if hasattr(font, "FDSelect"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001323 sel = font.FDSelect
1324 sel.format = None
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001325 sel.gidArray = [sel.gidArray[i] for i in indices]
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001326 cs.charStrings = dict((g,indices.index(v))
1327 for g,v in cs.charStrings.iteritems()
1328 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001329 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001330 cs.charStrings = dict((g,v)
1331 for g,v in cs.charStrings.iteritems()
1332 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001333 font.charset = [g for g in font.charset if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001334 font.numGlyphs = len(font.charset)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001335
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001336 return any(cff[fontname].numGlyphs for fontname in cff.keys())
1337
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001338@_add_method(fontTools.misc.psCharStrings.T2CharString)
1339def subset_subroutines(self, subrs, gsubrs):
1340 p = self.program
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001341 for i in xrange(1, len(p)):
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001342 if p[i] == 'callsubr':
1343 assert type(p[i-1]) is int
1344 p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias
1345 elif p[i] == 'callgsubr':
1346 assert type(p[i-1]) is int
1347 p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias
1348
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001349@_add_method(fontTools.ttLib.getTableClass('CFF '))
1350def prune_post_subset(self, options):
1351 cff = self.cff
1352
1353 class _MarkingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1354
1355 def __init__(self, localSubrs, globalSubrs):
1356 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1357 localSubrs,
1358 globalSubrs)
1359 for subrs in [localSubrs, globalSubrs]:
1360 if subrs and not hasattr(subrs, "_used"):
1361 subrs._used = set()
1362
1363 def op_callsubr(self, index):
1364 self.localSubrs._used.add(self.operandStack[-1]+self.localBias)
1365 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
1366
1367 def op_callgsubr(self, index):
1368 self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias)
1369 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
1370
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001371 class _NonrecursingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1372
1373 def __init__(self, localSubrs, globalSubrs):
1374 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1375 localSubrs,
1376 globalSubrs)
1377
1378 def op_callsubr(self, index):
1379 self.pop()
1380
1381 def op_callgsubr(self, index):
1382 self.pop()
1383
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001384 for fontname in cff.keys():
1385 font = cff[fontname]
1386 cs = font.CharStrings
1387
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001388 # Drop unused FontDictionaries
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001389 if hasattr(font, "FDSelect"):
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001390 sel = font.FDSelect
1391 indices = _uniq_sort(sel.gidArray)
1392 sel.gidArray = [indices.index (ss) for ss in sel.gidArray]
1393 arr = font.FDArray
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001394 arr.items = [arr[i] for i in indices]
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001395 arr.count = len(arr.items)
1396 del arr.file, arr.offsets
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001397
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001398 # Mark all used subroutines
1399 for g in font.charset:
1400 c,sel = cs.getItemAndSelector(g)
1401 subrs = getattr(c.private, "Subrs", [])
1402 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs)
1403 decompiler.execute(c)
1404
1405 # Renumber subroutines to remove unused ones
1406 all_subrs = [font.GlobalSubrs]
1407 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs'))
1408 # Prepare
1409 for subrs in all_subrs:
1410 if not subrs: continue
1411 if not hasattr(subrs, '_used'):
1412 subrs._used = set()
1413 subrs._used = _uniq_sort(subrs._used)
1414 subrs._old_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs)
1415 subrs._new_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs._used)
Behdad Esfahboded107712013-08-14 19:54:13 -04001416 # Renumber glyph charstrings
1417 for g in font.charset:
1418 c,sel = cs.getItemAndSelector(g)
1419 subrs = getattr(c.private, "Subrs", [])
1420 c.subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001421 # Renumber subroutines themselves
1422 for subrs in all_subrs:
1423 if not subrs: continue
1424 decompiler = _NonrecursingT2Decompiler(subrs, font.GlobalSubrs)
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001425 for i in xrange (subrs.count):
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001426 if i not in subrs._used: continue
1427 decompiler.reset()
1428 decompiler.execute(subrs[i])
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001429 subrs[i].subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001430 # Cleanup
1431 for subrs in all_subrs:
1432 if not subrs: continue
1433 subrs.items = [subrs.items[i] for i in subrs._used]
1434 del subrs.file, subrs.offsets
1435 del subrs._used, subrs._old_bias, subrs._new_bias
1436
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001437 if not options.hinting:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001438 pass # TODO(behdad) Drop hints
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001439
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001440 return True
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001441
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001442@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001443def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001444 tables = [t for t in self.tables
1445 if t.platformID == 3 and t.platEncID in [1, 10]]
1446 for u in s.unicodes_requested:
1447 found = False
1448 for table in tables:
1449 if u in table.cmap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001450 s.glyphs.add(table.cmap[u])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001451 found = True
1452 break
1453 if not found:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001454 s.log("No glyph for Unicode value %s; skipping." % u)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001455
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001456@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001457def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001458 if not options.legacy_cmap:
1459 # Drop non-Unicode / non-Symbol cmaps
1460 self.tables = [t for t in self.tables
1461 if t.platformID == 3 and t.platEncID in [0, 1, 10]]
1462 if not options.symbol_cmap:
1463 self.tables = [t for t in self.tables
1464 if t.platformID == 3 and t.platEncID in [1, 10]]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001465 # TODO(behdad) Only keep one subtable?
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001466 # For now, drop format=0 which can't be subset_glyphs easily?
1467 self.tables = [t for t in self.tables if t.format != 0]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001468 return bool(self.tables)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001469
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001470@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001471def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001472 s.glyphs = s.glyphs_cmaped
1473 for t in self.tables:
1474 # For reasons I don't understand I need this here
1475 # to force decompilation of the cmap format 14.
1476 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001477 getattr(t, "asdf")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001478 except AttributeError:
1479 pass
1480 if t.format == 14:
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001481 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None).
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001482 t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs])
1483 for v,l in t.uvsDict.iteritems())
1484 t.uvsDict = dict((v,l) for v,l in t.uvsDict.iteritems() if l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001485 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001486 t.cmap = dict((u,g) for u,g in t.cmap.iteritems()
1487 if g in s.glyphs_requested or u in s.unicodes_requested)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001488 self.tables = [t for t in self.tables
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001489 if (t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001490 # TODO(behdad) Convert formats when needed.
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001491 # In particular, if we have a format=12 without non-BMP
1492 # characters, either drop format=12 one or convert it
1493 # to format=4 if there's not one.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001494 return bool(self.tables)
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001495
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001496@_add_method(fontTools.ttLib.getTableClass('name'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001497def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001498 if '*' not in options.name_IDs:
1499 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1500 if not options.name_legacy:
1501 self.names = [n for n in self.names
1502 if n.platformID == 3 and n.platEncID == 1]
1503 if '*' not in options.name_languages:
1504 self.names = [n for n in self.names if n.langID in options.name_languages]
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001505 return True # Retain even if empty
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001506
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001507
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001508# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange?
1509# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
Behdad Esfahbod10195332013-08-14 19:55:24 -04001510# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001511# TODO(behdad) Text direction considerations.
1512# TODO(behdad) Text script / language considerations.
Behdad Esfahbod56ebd042013-07-22 13:02:24 -04001513
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001514
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001515class Options(object):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001516
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001517 class UnknownOptionError(Exception):
1518 pass
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001519
Behdad Esfahboda17743f2013-08-28 17:14:53 -04001520 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ',
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001521 'PCLT', 'LTSH']
1522 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
1523 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
1524 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1525 'loca', 'name', 'cvt ', 'fpgm', 'prep']
1526 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001527
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001528 # Based on HarfBuzz shapers
1529 _layout_features_groups = {
1530 # Default shaper
1531 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1532 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
1533 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
1534 'ltr': ['ltra', 'ltrm'],
1535 'rtl': ['rtla', 'rtlm'],
1536 # Complex shapers
1537 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1538 'cswh', 'mset'],
1539 'hangul': ['ljmo', 'vjmo', 'tjmo'],
1540 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'],
1541 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1542 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1543 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
1544 }
1545 _layout_features_default = _uniq_sort(sum(
1546 _layout_features_groups.itervalues(), []))
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001547
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001548 drop_tables = _drop_tables_default
1549 no_subset_tables = _no_subset_tables_default
1550 hinting_tables = _hinting_tables_default
1551 layout_features = _layout_features_default
1552 hinting = False
1553 glyph_names = False
1554 legacy_cmap = False
1555 symbol_cmap = False
1556 name_IDs = [1, 2] # Family and Style
1557 name_legacy = False
1558 name_languages = [0x0409] # English
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001559 notdef_glyph = True # gid0 for TrueType / .notdef for CFF
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001560 notdef_outline = False # No need for notdef to have an outline really
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001561 recommended_glyphs = False # gid1, gid2, gid3 for TrueType
Behdad Esfahbode911de12013-08-16 12:42:34 -04001562 recalc_bounds = False # Recalculate font bounding boxes
Behdad Esfahbod03d78da2013-08-29 16:42:00 -04001563 canonical_order = False # Order tables as recommended
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001564 flavor = None # May be 'woff'
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001565
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001566 def __init__(self, **kwargs):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001567
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001568 self.set(**kwargs)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001569
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001570 def set(self, **kwargs):
1571 for k,v in kwargs.iteritems():
1572 if not hasattr(self, k):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001573 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001574 setattr(self, k, v)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001575
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001576 def parse_opts(self, argv, ignore_unknown=False):
1577 ret = []
1578 opts = {}
1579 for a in argv:
1580 orig_a = a
1581 if not a.startswith('--'):
1582 ret.append(a)
1583 continue
1584 a = a[2:]
1585 i = a.find('=')
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001586 op = '='
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001587 if i == -1:
1588 if a.startswith("no-"):
1589 k = a[3:]
1590 v = False
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001591 else:
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001592 k = a
1593 v = True
1594 else:
1595 k = a[:i]
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001596 if k[-1] in "-+":
1597 op = k[-1]+'=' # Ops is '-=' or '+=' now.
1598 k = k[:-1]
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001599 v = a[i+1:]
1600 k = k.replace('-', '_')
1601 if not hasattr(self, k):
1602 if ignore_unknown == True or k in ignore_unknown:
1603 ret.append(orig_a)
1604 continue
1605 else:
1606 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001607
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001608 ov = getattr(self, k)
1609 if isinstance(ov, bool):
1610 v = bool(v)
1611 elif isinstance(ov, int):
1612 v = int(v)
1613 elif isinstance(ov, list):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001614 vv = v.split(',')
1615 if vv == ['']:
1616 vv = []
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001617 vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001618 if op == '=':
1619 v = vv
1620 elif op == '+=':
1621 v = ov
1622 v.extend(vv)
1623 elif op == '-=':
1624 v = ov
1625 for x in vv:
1626 if x in v:
1627 v.remove(x)
1628 else:
1629 assert 0
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001630
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001631 opts[k] = v
1632 self.set(**opts)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001633
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001634 return ret
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001635
1636
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001637class Subsetter(object):
1638
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001639 def __init__(self, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001640
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001641 if not log:
1642 log = Logger()
1643 if not options:
1644 options = Options()
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001645
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001646 self.options = options
1647 self.log = log
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001648 self.unicodes_requested = set()
1649 self.glyphs_requested = set()
1650 self.glyphs = set()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001651
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001652 def populate(self, glyphs=[], unicodes=[], text=""):
1653 self.unicodes_requested.update(unicodes)
1654 if isinstance(text, str):
1655 text = text.decode("utf8")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001656 for u in text:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001657 self.unicodes_requested.add(ord(u))
1658 self.glyphs_requested.update(glyphs)
1659 self.glyphs.update(glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001660
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001661 def _prune_pre_subset(self, font):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001662
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001663 for tag in font.keys():
1664 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001665
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001666 if(tag in self.options.drop_tables or
1667 (tag in self.options.hinting_tables and not self.options.hinting)):
1668 self.log(tag, "dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001669 del font[tag]
1670 continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001671
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001672 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001673
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001674 if hasattr(clazz, 'prune_pre_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001675 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001676 retain = table.prune_pre_subset(self.options)
1677 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001678 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001679 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001680 del font[tag]
1681 continue
1682 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001683 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001684
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001685 def _closure_glyphs(self, font):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001686
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001687 self.glyphs = self.glyphs_requested.copy()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001688
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001689 if 'cmap' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001690 font['cmap'].closure_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001691 self.glyphs_cmaped = self.glyphs
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001692
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001693 if self.options.notdef_glyph:
1694 if 'glyf' in font:
1695 self.glyphs.add(font.getGlyphName(0))
1696 self.log("Added gid0 to subset")
1697 else:
1698 self.glyphs.add('.notdef')
1699 self.log("Added .notdef to subset")
1700 if self.options.recommended_glyphs:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001701 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001702 for i in range(4):
1703 self.glyphs.add(font.getGlyphName(i))
1704 self.log("Added first four glyphs to subset")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001705
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001706 if 'GSUB' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001707 self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1708 len(self.glyphs))
1709 self.log.glyphs(self.glyphs, font=font)
1710 font['GSUB'].closure_glyphs(self)
1711 self.log("Closed glyph list over 'GSUB': %d glyphs after" %
1712 len(self.glyphs))
1713 self.log.glyphs(self.glyphs, font=font)
1714 self.log.lapse("close glyph list over 'GSUB'")
1715 self.glyphs_gsubed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001716
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001717 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001718 self.log("Closing glyph list over 'glyf': %d glyphs before" %
1719 len(self.glyphs))
1720 self.log.glyphs(self.glyphs, font=font)
1721 font['glyf'].closure_glyphs(self)
1722 self.log("Closed glyph list over 'glyf': %d glyphs after" %
1723 len(self.glyphs))
1724 self.log.glyphs(self.glyphs, font=font)
1725 self.log.lapse("close glyph list over 'glyf'")
1726 self.glyphs_glyfed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001727
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001728 self.glyphs_all = self.glyphs.copy()
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001729
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001730 self.log("Retaining %d glyphs: " % len(self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001731
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001732 def _subset_glyphs(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001733 for tag in font.keys():
1734 if tag == 'GlyphOrder': continue
1735 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001736
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001737 if tag in self.options.no_subset_tables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001738 self.log(tag, "subsetting not needed")
1739 elif hasattr(clazz, 'subset_glyphs'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001740 table = font[tag]
1741 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001742 retain = table.subset_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001743 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001744 self.log.lapse("subset '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001745 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001746 self.log(tag, "subsetted to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001747 del font[tag]
1748 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001749 self.log(tag, "subsetted")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001750 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001751 self.log(tag, "NOT subset; don't know how to subset; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001752 del font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001753
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001754 glyphOrder = font.getGlyphOrder()
1755 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001756 font.setGlyphOrder(glyphOrder)
1757 font._buildReverseGlyphOrderDict()
1758 self.log.lapse("subset GlyphOrder")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001759
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001760 def _prune_post_subset(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001761 for tag in font.keys():
1762 if tag == 'GlyphOrder': continue
1763 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001764 if hasattr(clazz, 'prune_post_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001765 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001766 retain = table.prune_post_subset(self.options)
1767 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001768 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001769 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001770 del font[tag]
1771 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001772 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001773
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001774 def subset(self, font):
Behdad Esfahbod756af492013-08-01 12:05:26 -04001775
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001776 self._prune_pre_subset(font)
1777 self._closure_glyphs(font)
1778 self._subset_glyphs(font)
1779 self._prune_post_subset(font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001780
Behdad Esfahbod756af492013-08-01 12:05:26 -04001781
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04001782class Logger(object):
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001783
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001784 def __init__(self, verbose=False, xml=False, timing=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001785 self.verbose = verbose
1786 self.xml = xml
1787 self.timing = timing
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001788 self.last_time = self.start_time = time.time()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001789
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001790 def parse_opts(self, argv):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001791 argv = argv[:]
1792 for v in ['verbose', 'xml', 'timing']:
1793 if "--"+v in argv:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001794 setattr(self, v, True)
1795 argv.remove("--"+v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001796 return argv
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001797
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001798 def __call__(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001799 if not self.verbose:
1800 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001801 print ' '.join(str(x) for x in things)
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001802
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001803 def lapse(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001804 if not self.timing:
1805 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001806 new_time = time.time()
1807 print "Took %0.3fs to %s" %(new_time - self.last_time,
1808 ' '.join(str(x) for x in things))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001809 self.last_time = new_time
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001810
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001811 def glyphs(self, glyphs, font=None):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001812 self("Names: ", sorted(glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001813 if font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001814 reverseGlyphMap = font.getReverseGlyphMap()
1815 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001816
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001817 def font(self, font, file=sys.stdout):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001818 if not self.xml:
1819 return
1820 import xmlWriter
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001821 writer = xmlWriter.XMLWriter(file)
Behdad Esfahbod45a84602013-08-19 14:44:49 -04001822 font.disassembleInstructions = False # Work around ttLib bug
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001823 for tag in font.keys():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001824 writer.begintag(tag)
1825 writer.newline()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001826 font[tag].toXML(writer, font)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001827 writer.endtag(tag)
1828 writer.newline()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001829
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001830
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001831def load_font(fontFile,
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04001832 options,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001833 checkChecksums=False,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001834 dontLoadGlyphNames=False):
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001835
Behdad Esfahbod45a84602013-08-19 14:44:49 -04001836 font = fontTools.ttLib.TTFont(fontFile,
1837 checkChecksums=checkChecksums,
1838 recalcBBoxes=options.recalc_bounds)
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001839
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001840 # Hack:
1841 #
1842 # If we don't need glyph names, change 'post' class to not try to
1843 # load them. It avoid lots of headache with broken fonts as well
1844 # as loading time.
1845 #
1846 # Ideally ttLib should provide a way to ask it to skip loading
1847 # glyph names. But it currently doesn't provide such a thing.
1848 #
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001849 if dontLoadGlyphNames:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001850 post = fontTools.ttLib.getTableClass('post')
1851 saved = post.decode_format_2_0
1852 post.decode_format_2_0 = post.decode_format_3_0
1853 f = font['post']
1854 if f.formatType == 2.0:
1855 f.formatType = 3.0
1856 post.decode_format_2_0 = saved
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001857
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001858 return font
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001859
Behdad Esfahbode911de12013-08-16 12:42:34 -04001860def save_font(font, outfile, options):
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001861 if options.flavor and not hasattr(font, 'flavor'):
1862 raise Exception("fonttools version does not support flavors.")
1863 font.flavor = options.flavor
Behdad Esfahbode911de12013-08-16 12:42:34 -04001864 font.save(outfile, reorderTables=options.canonical_order)
Behdad Esfahbod41de4cc2013-08-15 12:09:55 -04001865
Behdad Esfahbodc0ded942013-08-14 13:53:33 -04001866def main(args=None):
Behdad Esfahbod23b2ee92013-08-13 20:29:10 -04001867
Behdad Esfahbodc0ded942013-08-14 13:53:33 -04001868 if args == None: args = sys.argv
Behdad Esfahbod3c472002013-08-14 13:52:27 -04001869 arg0, args = args[0], args[1:]
Behdad Esfahbod610b0552013-07-23 14:52:18 -04001870
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001871 log = Logger()
1872 args = log.parse_opts(args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04001873
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001874 options = Options()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001875 args = options.parse_opts(args, ignore_unknown=['text'])
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001876
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001877 if len(args) < 2:
Behdad Esfahbodf55013e2013-08-15 18:39:55 -04001878 print >>sys.stderr, "usage: %s font-file glyph... [--text=ABC]... [--option=value]..." % arg0
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001879 sys.exit(1)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001880
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001881 fontfile = args[0]
1882 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001883
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001884 dontLoadGlyphNames =(not options.glyph_names and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001885 all(any(g.startswith(p)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001886 for p in ['gid', 'glyph', 'uni', 'U+'])
1887 for g in args))
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001888
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04001889 font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001890 subsetter = Subsetter(options=options, log=log)
1891 log.lapse("load font")
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001892
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001893 names = font.getGlyphNames()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001894 log.lapse("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001895
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001896 glyphs = []
1897 unicodes = []
1898 text = ""
1899 for g in args:
1900 if g in names:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001901 glyphs.append(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001902 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001903 if g.startswith('--text='):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001904 text += g[7:]
1905 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001906 if g.startswith('uni') or g.startswith('U+'):
1907 if g.startswith('uni') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001908 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001909 elif g.startswith('U+') and len(g) > 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001910 g = g[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001911 u = int(g, 16)
1912 unicodes.append(u)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001913 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001914 if g.startswith('gid') or g.startswith('glyph'):
1915 if g.startswith('gid') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001916 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001917 elif g.startswith('glyph') and len(g) > 5:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001918 g = g[5:]
1919 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001920 glyphs.append(font.getGlyphName(int(g), requireReal=1))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001921 except ValueError:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001922 raise Exception("Invalid glyph identifier: %s" % g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001923 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001924 raise Exception("Invalid glyph identifier: %s" % g)
1925 log.lapse("compile glyph list")
1926 log("Unicodes:", unicodes)
1927 log("Glyphs:", glyphs)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001928
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001929 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
1930 subsetter.subset(font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04001931
Behdad Esfahbod34426c12013-08-14 18:30:09 -04001932 outfile = fontfile + '.subset'
1933
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001934 save_font (font, outfile, options)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001935 log.lapse("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001936
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001937 log.last_time = log.start_time
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001938 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001939
Behdad Esfahbod34426c12013-08-14 18:30:09 -04001940 if log.verbose:
1941 import os
1942 log("Input font: %d bytes" % os.path.getsize(fontfile))
1943 log("Subset font: %d bytes" % os.path.getsize(outfile))
1944
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001945 log.font(font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001946
Behdad Esfahbodc56bf482013-08-13 20:13:33 -04001947 font.close()
1948
Behdad Esfahbod39a39ac2013-08-22 18:10:17 -04001949
1950__all__ = [
1951 'Options',
1952 'Subsetter',
1953 'Logger',
1954 'load_font',
1955 'save_font',
1956 'main'
1957]
1958
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001959if __name__ == '__main__':
Behdad Esfahbod23b2ee92013-08-13 20:29:10 -04001960 main()