blob: 4892e1da3736d48461dd3c73a718fe33432822d4 [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 Esfahbod9e856ea2013-08-13 19:50:38 -04001139 if 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 Esfahbod9e856ea2013-08-13 19:50:38 -04001162 if 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 Esfahbod9e856ea2013-08-13 19:50:38 -04001188 numContours = struct.unpack(">h", self.data[:2])[0]
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001189 data = array.array("B", self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001190 i = 10
1191 if numContours >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001192 i += 2 * numContours # endPtsOfContours
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001193 instructionLen =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001194 # Zero it
1195 data[i] = data [i+1] = 0
1196 i += 2
1197 if instructionLen:
1198 # Splice it out
1199 data = data[:i] + data[i+instructionLen:]
1200 else:
1201 more = 1
1202 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001203 flags =(data[i] << 8) | data[i+1]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001204 # Turn instruction flag off
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001205 flags &= ~0x0100 # WE_HAVE_INSTRUCTIONS
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001206 data[i+0] = flags >> 8
1207 data[i+1] = flags & 0xFF
1208 i += 4
1209 flags = int(flags)
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001210
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001211 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001212 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001213 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1214 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1215 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1216 more = flags & 0x0020 # MORE_COMPONENTS
1217
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001218 # Cut off
1219 data = data[:i]
1220 if len(data) % 4:
1221 # add pad bytes
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001222 nPadBytes = 4 -(len(data) % 4)
1223 for i in range(nPadBytes):
1224 data.append(0)
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001225 self.data = data.tostring()
Behdad Esfahbod6ec88542013-07-24 16:52:47 -04001226
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001227@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001228def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001229 decompose = s.glyphs
1230 # I don't know if component glyphs can be composite themselves.
1231 # We handle them anyway.
1232 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001233 components = set()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001234 for g in decompose:
1235 if g not in self.glyphs:
1236 continue
1237 gl = self.glyphs[g]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001238 if hasattr(gl, "data"):
1239 for c in gl.getComponentNamesFast(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001240 if c not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001241 components.add(c)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001242 else:
1243 # TTX seems to expand gid0..3 always
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001244 if gl.isComposite():
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001245 for c in gl.components:
1246 if c.glyphName not in s.glyphs:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001247 components.add(c.glyphName)
1248 components = set(c for c in components if c not in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001249 if not components:
1250 break
1251 decompose = components
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001252 s.glyphs.update(components)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001253
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001254@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001255def subset_glyphs(self, s):
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001256 self.glyphs = dict((g,v) for g,v in self.glyphs.iteritems() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001257 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001258 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001259 if hasattr(v, "data"):
1260 v.remapComponentsFast(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001261 else:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001262 pass # No need
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001263 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001264 return bool(self.glyphs)
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001265
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001266@_add_method(fontTools.ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001267def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001268 if not options.hinting:
1269 for v in self.glyphs.itervalues():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001270 if hasattr(v, "data"):
1271 v.dropInstructionsFast()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001272 else:
1273 v.program = fontTools.ttLib.tables.ttProgram.Program()
1274 v.program.fromBytecode([])
1275 return True
Behdad Esfahboded98c612013-07-23 12:37:41 -04001276
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001277@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001278def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001279 cff = self.cff
1280 # CFF table should have one font only
1281 cff.fontNames = cff.fontNames[:1]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001282 return bool(cff.fontNames)
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001283
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001284@_add_method(fontTools.ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001285def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001286 cff = self.cff
1287 for fontname in cff.keys():
1288 font = cff[fontname]
1289 cs = font.CharStrings
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001290
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001291 # Load all glyphs
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001292 for g in font.charset:
1293 if g not in s.glyphs: continue
1294 c,sel = cs.getItemAndSelector(g)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001295
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001296 if cs.charStringsAreIndexed:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001297 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001298 csi = cs.charStringsIndex
1299 csi.items = [csi.items[i] for i in indices]
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001300 csi.count = len(csi.items)
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001301 del csi.file, csi.offsets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001302 if hasattr(font, "FDSelect"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001303 sel = font.FDSelect
1304 sel.format = None
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001305 sel.gidArray = [sel.gidArray[i] for i in indices]
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001306 cs.charStrings = dict((g,indices.index(v))
1307 for g,v in cs.charStrings.iteritems()
1308 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001309 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001310 cs.charStrings = dict((g,v)
1311 for g,v in cs.charStrings.iteritems()
1312 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001313 font.charset = [g for g in font.charset if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001314 font.numGlyphs = len(font.charset)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001315
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001316 return any(cff[fontname].numGlyphs for fontname in cff.keys())
1317
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001318@_add_method(fontTools.misc.psCharStrings.T2CharString)
1319def subset_subroutines(self, subrs, gsubrs):
1320 p = self.program
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001321 for i in xrange(1, len(p)):
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001322 if p[i] == 'callsubr':
1323 assert type(p[i-1]) is int
1324 p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias
1325 elif p[i] == 'callgsubr':
1326 assert type(p[i-1]) is int
1327 p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias
1328
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001329@_add_method(fontTools.ttLib.getTableClass('CFF '))
1330def prune_post_subset(self, options):
1331 cff = self.cff
1332
1333 class _MarkingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1334
1335 def __init__(self, localSubrs, globalSubrs):
1336 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1337 localSubrs,
1338 globalSubrs)
1339 for subrs in [localSubrs, globalSubrs]:
1340 if subrs and not hasattr(subrs, "_used"):
1341 subrs._used = set()
1342
1343 def op_callsubr(self, index):
1344 self.localSubrs._used.add(self.operandStack[-1]+self.localBias)
1345 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
1346
1347 def op_callgsubr(self, index):
1348 self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias)
1349 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
1350
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001351 class _NonrecursingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler):
1352
1353 def __init__(self, localSubrs, globalSubrs):
1354 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self,
1355 localSubrs,
1356 globalSubrs)
1357
1358 def op_callsubr(self, index):
1359 self.pop()
1360
1361 def op_callgsubr(self, index):
1362 self.pop()
1363
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001364 for fontname in cff.keys():
1365 font = cff[fontname]
1366 cs = font.CharStrings
1367
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001368 # Drop unused FontDictionaries
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001369 if hasattr(font, "FDSelect"):
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001370 sel = font.FDSelect
1371 indices = _uniq_sort(sel.gidArray)
1372 sel.gidArray = [indices.index (ss) for ss in sel.gidArray]
1373 arr = font.FDArray
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001374 arr.items = [arr[i] for i in indices]
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001375 arr.count = len(arr.items)
1376 del arr.file, arr.offsets
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001377
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001378 # Mark all used subroutines
1379 for g in font.charset:
1380 c,sel = cs.getItemAndSelector(g)
1381 subrs = getattr(c.private, "Subrs", [])
1382 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs)
1383 decompiler.execute(c)
1384
1385 # Renumber subroutines to remove unused ones
1386 all_subrs = [font.GlobalSubrs]
1387 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs'))
1388 # Prepare
1389 for subrs in all_subrs:
1390 if not subrs: continue
1391 if not hasattr(subrs, '_used'):
1392 subrs._used = set()
1393 subrs._used = _uniq_sort(subrs._used)
1394 subrs._old_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs)
1395 subrs._new_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs._used)
Behdad Esfahboded107712013-08-14 19:54:13 -04001396 # Renumber glyph charstrings
1397 for g in font.charset:
1398 c,sel = cs.getItemAndSelector(g)
1399 subrs = getattr(c.private, "Subrs", [])
1400 c.subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001401 # Renumber subroutines themselves
1402 for subrs in all_subrs:
1403 if not subrs: continue
1404 decompiler = _NonrecursingT2Decompiler(subrs, font.GlobalSubrs)
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001405 for i in xrange (subrs.count):
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001406 if i not in subrs._used: continue
1407 decompiler.reset()
1408 decompiler.execute(subrs[i])
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001409 subrs[i].subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001410 # Cleanup
1411 for subrs in all_subrs:
1412 if not subrs: continue
1413 subrs.items = [subrs.items[i] for i in subrs._used]
1414 del subrs.file, subrs.offsets
1415 del subrs._used, subrs._old_bias, subrs._new_bias
1416
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001417 if not options.hinting:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001418 pass # TODO(behdad) Drop hints
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001419
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001420 return True
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001421
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001422@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001423def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001424 tables = [t for t in self.tables
1425 if t.platformID == 3 and t.platEncID in [1, 10]]
1426 for u in s.unicodes_requested:
1427 found = False
1428 for table in tables:
1429 if u in table.cmap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001430 s.glyphs.add(table.cmap[u])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001431 found = True
1432 break
1433 if not found:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001434 s.log("No glyph for Unicode value %s; skipping." % u)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001435
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001436@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001437def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001438 if not options.legacy_cmap:
1439 # Drop non-Unicode / non-Symbol cmaps
1440 self.tables = [t for t in self.tables
1441 if t.platformID == 3 and t.platEncID in [0, 1, 10]]
1442 if not options.symbol_cmap:
1443 self.tables = [t for t in self.tables
1444 if t.platformID == 3 and t.platEncID in [1, 10]]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001445 # TODO(behdad) Only keep one subtable?
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001446 # For now, drop format=0 which can't be subset_glyphs easily?
1447 self.tables = [t for t in self.tables if t.format != 0]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001448 return bool(self.tables)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001449
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001450@_add_method(fontTools.ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001451def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001452 s.glyphs = s.glyphs_cmaped
1453 for t in self.tables:
1454 # For reasons I don't understand I need this here
1455 # to force decompilation of the cmap format 14.
1456 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001457 getattr(t, "asdf")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001458 except AttributeError:
1459 pass
1460 if t.format == 14:
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001461 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None).
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001462 t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs])
1463 for v,l in t.uvsDict.iteritems())
1464 t.uvsDict = dict((v,l) for v,l in t.uvsDict.iteritems() if l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001465 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001466 t.cmap = dict((u,g) for u,g in t.cmap.iteritems()
1467 if g in s.glyphs_requested or u in s.unicodes_requested)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001468 self.tables = [t for t in self.tables
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001469 if (t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001470 # TODO(behdad) Convert formats when needed.
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001471 # In particular, if we have a format=12 without non-BMP
1472 # characters, either drop format=12 one or convert it
1473 # to format=4 if there's not one.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001474 return bool(self.tables)
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001475
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -04001476@_add_method(fontTools.ttLib.getTableClass('name'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001477def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001478 if '*' not in options.name_IDs:
1479 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1480 if not options.name_legacy:
1481 self.names = [n for n in self.names
1482 if n.platformID == 3 and n.platEncID == 1]
1483 if '*' not in options.name_languages:
1484 self.names = [n for n in self.names if n.langID in options.name_languages]
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001485 return True # Retain even if empty
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001486
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001487
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001488# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange?
1489# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
Behdad Esfahbod10195332013-08-14 19:55:24 -04001490# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001491# TODO(behdad) Text direction considerations.
1492# TODO(behdad) Text script / language considerations.
Behdad Esfahbod56ebd042013-07-22 13:02:24 -04001493
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001494
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001495class Options(object):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001496
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001497 class UnknownOptionError(Exception):
1498 pass
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001499
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001500 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC',
1501 'PCLT', 'LTSH']
1502 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
1503 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
1504 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1505 'loca', 'name', 'cvt ', 'fpgm', 'prep']
1506 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001507
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001508 # Based on HarfBuzz shapers
1509 _layout_features_groups = {
1510 # Default shaper
1511 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1512 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
1513 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
1514 'ltr': ['ltra', 'ltrm'],
1515 'rtl': ['rtla', 'rtlm'],
1516 # Complex shapers
1517 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1518 'cswh', 'mset'],
1519 'hangul': ['ljmo', 'vjmo', 'tjmo'],
1520 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'],
1521 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1522 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1523 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
1524 }
1525 _layout_features_default = _uniq_sort(sum(
1526 _layout_features_groups.itervalues(), []))
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001527
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001528 drop_tables = _drop_tables_default
1529 no_subset_tables = _no_subset_tables_default
1530 hinting_tables = _hinting_tables_default
1531 layout_features = _layout_features_default
1532 hinting = False
1533 glyph_names = False
1534 legacy_cmap = False
1535 symbol_cmap = False
1536 name_IDs = [1, 2] # Family and Style
1537 name_legacy = False
1538 name_languages = [0x0409] # English
1539 mandatory_glyphs = True # First four for TrueType, .notdef for CFF
Behdad Esfahbode911de12013-08-16 12:42:34 -04001540 recalc_bounds = False # Recalculate font bounding boxes
1541 canonical_order = True # Order tables as recommended
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001542 flavor = None # May be 'woff'
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001543
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001544 def __init__(self, **kwargs):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001545
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001546 self.set(**kwargs)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001547
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001548 def set(self, **kwargs):
1549 for k,v in kwargs.iteritems():
1550 if not hasattr(self, k):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001551 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001552 setattr(self, k, v)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001553
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001554 def parse_opts(self, argv, ignore_unknown=False):
1555 ret = []
1556 opts = {}
1557 for a in argv:
1558 orig_a = a
1559 if not a.startswith('--'):
1560 ret.append(a)
1561 continue
1562 a = a[2:]
1563 i = a.find('=')
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001564 op = '='
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001565 if i == -1:
1566 if a.startswith("no-"):
1567 k = a[3:]
1568 v = False
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001569 else:
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001570 k = a
1571 v = True
1572 else:
1573 k = a[:i]
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001574 if k[-1] in "-+":
1575 op = k[-1]+'=' # Ops is '-=' or '+=' now.
1576 k = k[:-1]
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001577 v = a[i+1:]
1578 k = k.replace('-', '_')
1579 if not hasattr(self, k):
1580 if ignore_unknown == True or k in ignore_unknown:
1581 ret.append(orig_a)
1582 continue
1583 else:
1584 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001585
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001586 ov = getattr(self, k)
1587 if isinstance(ov, bool):
1588 v = bool(v)
1589 elif isinstance(ov, int):
1590 v = int(v)
1591 elif isinstance(ov, list):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001592 vv = v.split(',')
1593 if vv == ['']:
1594 vv = []
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001595 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 -04001596 if op == '=':
1597 v = vv
1598 elif op == '+=':
1599 v = ov
1600 v.extend(vv)
1601 elif op == '-=':
1602 v = ov
1603 for x in vv:
1604 if x in v:
1605 v.remove(x)
1606 else:
1607 assert 0
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001608
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001609 opts[k] = v
1610 self.set(**opts)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001611
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001612 return ret
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001613
1614
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001615class Subsetter(object):
1616
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001617 def __init__(self, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001618
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001619 if not log:
1620 log = Logger()
1621 if not options:
1622 options = Options()
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001623
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001624 self.options = options
1625 self.log = log
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001626 self.unicodes_requested = set()
1627 self.glyphs_requested = set()
1628 self.glyphs = set()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001629
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001630 def populate(self, glyphs=[], unicodes=[], text=""):
1631 self.unicodes_requested.update(unicodes)
1632 if isinstance(text, str):
1633 text = text.decode("utf8")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001634 for u in text:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001635 self.unicodes_requested.add(ord(u))
1636 self.glyphs_requested.update(glyphs)
1637 self.glyphs.update(glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001638
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001639 def _prune_pre_subset(self, font):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001640
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001641 for tag in font.keys():
1642 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001643
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001644 if(tag in self.options.drop_tables or
1645 (tag in self.options.hinting_tables and not self.options.hinting)):
1646 self.log(tag, "dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001647 del font[tag]
1648 continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001649
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001650 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001651
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001652 if hasattr(clazz, 'prune_pre_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001653 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001654 retain = table.prune_pre_subset(self.options)
1655 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001656 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001657 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001658 del font[tag]
1659 continue
1660 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001661 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001662
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001663 def _closure_glyphs(self, font):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001664
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001665 self.glyphs = self.glyphs_requested.copy()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001666
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001667 if 'cmap' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001668 font['cmap'].closure_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001669 self.glyphs_cmaped = self.glyphs
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001670
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001671 if self.options.mandatory_glyphs:
1672 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001673 for i in range(4):
1674 self.glyphs.add(font.getGlyphName(i))
1675 self.log("Added first four glyphs to subset")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001676 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001677 self.glyphs.add('.notdef')
1678 self.log("Added .notdef glyph to subset")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001679
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001680 if 'GSUB' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001681 self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1682 len(self.glyphs))
1683 self.log.glyphs(self.glyphs, font=font)
1684 font['GSUB'].closure_glyphs(self)
1685 self.log("Closed glyph list over 'GSUB': %d glyphs after" %
1686 len(self.glyphs))
1687 self.log.glyphs(self.glyphs, font=font)
1688 self.log.lapse("close glyph list over 'GSUB'")
1689 self.glyphs_gsubed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001690
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001691 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001692 self.log("Closing glyph list over 'glyf': %d glyphs before" %
1693 len(self.glyphs))
1694 self.log.glyphs(self.glyphs, font=font)
1695 font['glyf'].closure_glyphs(self)
1696 self.log("Closed glyph list over 'glyf': %d glyphs after" %
1697 len(self.glyphs))
1698 self.log.glyphs(self.glyphs, font=font)
1699 self.log.lapse("close glyph list over 'glyf'")
1700 self.glyphs_glyfed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001701
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001702 self.glyphs_all = self.glyphs.copy()
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001703
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001704 self.log("Retaining %d glyphs: " % len(self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001705
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001706 def _subset_glyphs(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001707 for tag in font.keys():
1708 if tag == 'GlyphOrder': continue
1709 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001710
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001711 if tag in self.options.no_subset_tables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001712 self.log(tag, "subsetting not needed")
1713 elif hasattr(clazz, 'subset_glyphs'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001714 table = font[tag]
1715 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001716 retain = table.subset_glyphs(self)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001717 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001718 self.log.lapse("subset '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001719 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001720 self.log(tag, "subsetted to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001721 del font[tag]
1722 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001723 self.log(tag, "subsetted")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001724 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001725 self.log(tag, "NOT subset; don't know how to subset; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001726 del font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001727
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001728 glyphOrder = font.getGlyphOrder()
1729 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001730 font.setGlyphOrder(glyphOrder)
1731 font._buildReverseGlyphOrderDict()
1732 self.log.lapse("subset GlyphOrder")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001733
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001734 def _prune_post_subset(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001735 for tag in font.keys():
1736 if tag == 'GlyphOrder': continue
1737 clazz = fontTools.ttLib.getTableClass(tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001738 if hasattr(clazz, 'prune_post_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001739 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001740 retain = table.prune_post_subset(self.options)
1741 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001742 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001743 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001744 del font[tag]
1745 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001746 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001747
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001748 def subset(self, font):
Behdad Esfahbod756af492013-08-01 12:05:26 -04001749
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001750 self._prune_pre_subset(font)
1751 self._closure_glyphs(font)
1752 self._subset_glyphs(font)
1753 self._prune_post_subset(font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001754
Behdad Esfahbod756af492013-08-01 12:05:26 -04001755
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04001756class Logger(object):
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001757
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001758 def __init__(self, verbose=False, xml=False, timing=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001759 self.verbose = verbose
1760 self.xml = xml
1761 self.timing = timing
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001762 self.last_time = self.start_time = time.time()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001763
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001764 def parse_opts(self, argv):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001765 argv = argv[:]
1766 for v in ['verbose', 'xml', 'timing']:
1767 if "--"+v in argv:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001768 setattr(self, v, True)
1769 argv.remove("--"+v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001770 return argv
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001771
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001772 def __call__(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001773 if not self.verbose:
1774 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001775 print ' '.join(str(x) for x in things)
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001776
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001777 def lapse(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001778 if not self.timing:
1779 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001780 new_time = time.time()
1781 print "Took %0.3fs to %s" %(new_time - self.last_time,
1782 ' '.join(str(x) for x in things))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001783 self.last_time = new_time
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001784
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001785 def glyphs(self, glyphs, font=None):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001786 self("Names: ", sorted(glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001787 if font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001788 reverseGlyphMap = font.getReverseGlyphMap()
1789 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04001790
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001791 def font(self, font, file=sys.stdout):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001792 if not self.xml:
1793 return
1794 import xmlWriter
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001795 writer = xmlWriter.XMLWriter(file)
Behdad Esfahbod45a84602013-08-19 14:44:49 -04001796 font.disassembleInstructions = False # Work around ttLib bug
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001797 for tag in font.keys():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001798 writer.begintag(tag)
1799 writer.newline()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001800 font[tag].toXML(writer, font)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001801 writer.endtag(tag)
1802 writer.newline()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001803
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001804
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001805def load_font(fontFile,
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04001806 options,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001807 checkChecksums=False,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001808 dontLoadGlyphNames=False):
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001809
Behdad Esfahbod45a84602013-08-19 14:44:49 -04001810 font = fontTools.ttLib.TTFont(fontFile,
1811 checkChecksums=checkChecksums,
1812 recalcBBoxes=options.recalc_bounds)
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001813
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001814 # Hack:
1815 #
1816 # If we don't need glyph names, change 'post' class to not try to
1817 # load them. It avoid lots of headache with broken fonts as well
1818 # as loading time.
1819 #
1820 # Ideally ttLib should provide a way to ask it to skip loading
1821 # glyph names. But it currently doesn't provide such a thing.
1822 #
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001823 if dontLoadGlyphNames:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001824 post = fontTools.ttLib.getTableClass('post')
1825 saved = post.decode_format_2_0
1826 post.decode_format_2_0 = post.decode_format_3_0
1827 f = font['post']
1828 if f.formatType == 2.0:
1829 f.formatType = 3.0
1830 post.decode_format_2_0 = saved
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001831
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001832 return font
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001833
Behdad Esfahbode911de12013-08-16 12:42:34 -04001834def save_font(font, outfile, options):
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001835 if options.flavor and not hasattr(font, 'flavor'):
1836 raise Exception("fonttools version does not support flavors.")
1837 font.flavor = options.flavor
Behdad Esfahbode911de12013-08-16 12:42:34 -04001838 font.save(outfile, reorderTables=options.canonical_order)
Behdad Esfahbod41de4cc2013-08-15 12:09:55 -04001839
Behdad Esfahbodc0ded942013-08-14 13:53:33 -04001840def main(args=None):
Behdad Esfahbod23b2ee92013-08-13 20:29:10 -04001841
Behdad Esfahbodc0ded942013-08-14 13:53:33 -04001842 if args == None: args = sys.argv
Behdad Esfahbod3c472002013-08-14 13:52:27 -04001843 arg0, args = args[0], args[1:]
Behdad Esfahbod610b0552013-07-23 14:52:18 -04001844
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001845 log = Logger()
1846 args = log.parse_opts(args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04001847
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001848 options = Options()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001849 args = options.parse_opts(args, ignore_unknown=['text'])
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001850
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001851 if len(args) < 2:
Behdad Esfahbodf55013e2013-08-15 18:39:55 -04001852 print >>sys.stderr, "usage: %s font-file glyph... [--text=ABC]... [--option=value]..." % arg0
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001853 sys.exit(1)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001854
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001855 fontfile = args[0]
1856 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001857
Behdad Esfahbod85da2682013-08-15 12:17:21 -04001858 dontLoadGlyphNames =(not options.glyph_names and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001859 all(any(g.startswith(p)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001860 for p in ['gid', 'glyph', 'uni', 'U+'])
1861 for g in args))
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04001862
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04001863 font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001864 subsetter = Subsetter(options=options, log=log)
1865 log.lapse("load font")
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001866
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001867 names = font.getGlyphNames()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001868 log.lapse("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04001869
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001870 glyphs = []
1871 unicodes = []
1872 text = ""
1873 for g in args:
1874 if g in names:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001875 glyphs.append(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001876 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001877 if g.startswith('--text='):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001878 text += g[7:]
1879 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001880 if g.startswith('uni') or g.startswith('U+'):
1881 if g.startswith('uni') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001882 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001883 elif g.startswith('U+') and len(g) > 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001884 g = g[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001885 u = int(g, 16)
1886 unicodes.append(u)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001887 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001888 if g.startswith('gid') or g.startswith('glyph'):
1889 if g.startswith('gid') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001890 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001891 elif g.startswith('glyph') and len(g) > 5:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001892 g = g[5:]
1893 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001894 glyphs.append(font.getGlyphName(int(g), requireReal=1))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001895 except ValueError:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001896 raise Exception("Invalid glyph identifier: %s" % g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001897 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001898 raise Exception("Invalid glyph identifier: %s" % g)
1899 log.lapse("compile glyph list")
1900 log("Unicodes:", unicodes)
1901 log("Glyphs:", glyphs)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04001902
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001903 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
1904 subsetter.subset(font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04001905
Behdad Esfahbod34426c12013-08-14 18:30:09 -04001906 outfile = fontfile + '.subset'
1907
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001908 save_font (font, outfile, options)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001909 log.lapse("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001910
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001911 log.last_time = log.start_time
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001912 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04001913
Behdad Esfahbod34426c12013-08-14 18:30:09 -04001914 if log.verbose:
1915 import os
1916 log("Input font: %d bytes" % os.path.getsize(fontfile))
1917 log("Subset font: %d bytes" % os.path.getsize(outfile))
1918
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001919 log.font(font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001920
Behdad Esfahbodc56bf482013-08-13 20:13:33 -04001921 font.close()
1922
Behdad Esfahbod39a39ac2013-08-22 18:10:17 -04001923
1924__all__ = [
1925 'Options',
1926 'Subsetter',
1927 'Logger',
1928 'load_font',
1929 'save_font',
1930 'main'
1931]
1932
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001933if __name__ == '__main__':
Behdad Esfahbod23b2ee92013-08-13 20:29:10 -04001934 main()