blob: 45bd45735533292bb27e310d7bbec1db598c3f0a [file] [log] [blame]
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04001# Copyright 2013 Google, Inc. All Rights Reserved.
2#
Behdad Esfahbod0fe6a512013-07-23 11:17:35 -04003# Google Author(s): Behdad Esfahbod
Behdad Esfahbod616d36e2013-08-13 20:02:59 -04004
5"""Python OpenType Layout Subsetter.
6
7Later grown into full OpenType subsetter, supporting all standard tables.
8"""
9
Behdad Esfahbod1ae29592014-01-14 15:07:50 +080010from __future__ import print_function, division, absolute_import
Behdad Esfahbodcfeafd72013-11-27 17:27:35 -050011from fontTools.misc.py23 import *
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040012from fontTools import ttLib
13from fontTools.ttLib.tables import otTables
14from fontTools.misc import psCharStrings
15from fontTools.pens import basePen
Behdad Esfahbodcfeafd72013-11-27 17:27:35 -050016import sys
17import struct
18import time
19import array
Behdad Esfahbod54660612013-07-21 18:16:55 -040020
Behdad Esfahbod54660612013-07-21 18:16:55 -040021
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040022def _add_method(*clazzes):
Behdad Esfahbod616d36e2013-08-13 20:02:59 -040023 """Returns a decorator function that adds a new method to one or
24 more classes."""
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040025 def wrapper(method):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040026 for clazz in clazzes:
27 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.'
Behdad Esfahbod553c3bb2013-11-27 02:24:11 -050028 assert not hasattr(clazz, method.__name__), \
Behdad Esfahbodd77f1572013-08-15 19:24:36 -040029 "Oops, class '%s' has method '%s'." % (clazz.__name__,
Behdad Esfahbod553c3bb2013-11-27 02:24:11 -050030 method.__name__)
31 setattr(clazz, method.__name__, method)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040032 return None
33 return wrapper
Behdad Esfahbod54660612013-07-21 18:16:55 -040034
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040035def _uniq_sort(l):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040036 return sorted(set(l))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040037
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -040038def _set_update(s, *others):
39 # Jython's set.update only takes one other argument.
40 # Emulate real set.update...
41 for other in others:
42 s.update(other)
43
Behdad Esfahbod78661bb2013-07-23 10:23:42 -040044
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040045@_add_method(otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040046def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040047 "Returns ascending list of matching coverage values."
Behdad Esfahbod4734be52013-08-14 19:47:42 -040048 return [i for i,g in enumerate(self.glyphs) if g in glyphs]
Behdad Esfahbod610b0552013-07-23 14:52:18 -040049
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040050@_add_method(otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040051def intersect_glyphs(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040052 "Returns set of intersecting glyphs."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040053 return set(g for g in self.glyphs if g in glyphs)
Behdad Esfahbod849d25c2013-08-12 19:24:24 -040054
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040055@_add_method(otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040056def subset(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040057 "Returns ascending list of remaining coverage values."
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040058 indices = self.intersect(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040059 self.glyphs = [g for g in self.glyphs if g in glyphs]
60 return indices
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -040061
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040062@_add_method(otTables.Coverage)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040063def remap(self, coverage_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040064 "Remaps coverage."
65 self.glyphs = [self.glyphs[i] for i in coverage_map]
Behdad Esfahbod14374262013-08-08 22:26:49 -040066
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040067@_add_method(otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040068def intersect(self, glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040069 "Returns ascending list of matching class values."
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040070 return _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040071 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod6890d052013-11-27 06:26:35 -050072 [v for g,v in self.classDefs.items() if g in glyphs])
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040073
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040074@_add_method(otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040075def intersect_class(self, glyphs, klass):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040076 "Returns set of glyphs matching class."
77 if klass == 0:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040078 return set(g for g in glyphs if g not in self.classDefs)
Behdad Esfahbod6890d052013-11-27 06:26:35 -050079 return set(g for g,v in self.classDefs.items()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040080 if v == klass and g in glyphs)
Behdad Esfahbodb8d55882013-07-23 22:17:39 -040081
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040082@_add_method(otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040083def subset(self, glyphs, remap=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040084 "Returns ascending list of remaining classes."
Behdad Esfahbod6890d052013-11-27 06:26:35 -050085 self.classDefs = dict((g,v) for g,v in self.classDefs.items() if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040086 # Note: while class 0 has the special meaning of "not matched",
87 # if no glyph will ever /not match/, we can optimize class 0 out too.
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -040088 indices = _uniq_sort(
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040089 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
Behdad Esfahbod6890d052013-11-27 06:26:35 -050090 list(self.classDefs.values()))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040091 if remap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040092 self.remap(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040093 return indices
Behdad Esfahbod4aa6ce32013-07-22 12:15:36 -040094
Behdad Esfahbod46d260f2013-09-19 20:36:49 -040095@_add_method(otTables.ClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -040096def remap(self, class_map):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -040097 "Remaps classes."
Behdad Esfahbodd73f2252013-08-16 10:58:25 -040098 self.classDefs = dict((g,class_map.index(v))
Behdad Esfahbod6890d052013-11-27 06:26:35 -050099 for g,v in self.classDefs.items())
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400100
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400101@_add_method(otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400102def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500103 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod45ed5722013-12-17 06:01:08 -0500104 s.glyphs.update(v for g,v in self.mapping.items() if g in cur_glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400105
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400106@_add_method(otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400107def subset_glyphs(self, s):
Behdad Esfahbod45ed5722013-12-17 06:01:08 -0500108 self.mapping = dict((g,v) for g,v in self.mapping.items()
109 if g in s.glyphs and v in s.glyphs)
110 return bool(self.mapping)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400111
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400112@_add_method(otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400113def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500114 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod45ed5722013-12-17 06:01:08 -0500115 indices = self.Coverage.intersect(cur_glyphs)
116 _set_update(s.glyphs, *(self.Sequence[i].Substitute for i in indices))
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400117
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400118@_add_method(otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400119def subset_glyphs(self, s):
Behdad Esfahbod45ed5722013-12-17 06:01:08 -0500120 indices = self.Coverage.subset(s.glyphs)
121 self.Sequence = [self.Sequence[i] for i in indices]
122 # Now drop rules generating glyphs we don't want
123 indices = [i for i,seq in enumerate(self.Sequence)
124 if all(sub in s.glyphs for sub in seq.Substitute)]
125 self.Sequence = [self.Sequence[i] for i in indices]
126 self.Coverage.remap(indices)
127 self.SequenceCount = len(self.Sequence)
128 return bool(self.SequenceCount)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400129
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400130@_add_method(otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400131def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500132 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod45ed5722013-12-17 06:01:08 -0500133 _set_update(s.glyphs, *(vlist for g,vlist in self.alternates.items()
134 if g in cur_glyphs))
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400135
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400136@_add_method(otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400137def subset_glyphs(self, s):
Behdad Esfahbod45ed5722013-12-17 06:01:08 -0500138 self.alternates = dict((g,vlist)
139 for g,vlist in self.alternates.items()
140 if g in s.glyphs and
141 all(v in s.glyphs for v in vlist))
142 return bool(self.alternates)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400143
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400144@_add_method(otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400145def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500146 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod45ed5722013-12-17 06:01:08 -0500147 _set_update(s.glyphs, *([seq.LigGlyph for seq in seqs
148 if all(c in s.glyphs for c in seq.Component)]
149 for g,seqs in self.ligatures.items()
150 if g in cur_glyphs))
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400151
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400152@_add_method(otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400153def subset_glyphs(self, s):
Behdad Esfahbod45ed5722013-12-17 06:01:08 -0500154 self.ligatures = dict((g,v) for g,v in self.ligatures.items()
155 if g in s.glyphs)
156 self.ligatures = dict((g,[seq for seq in seqs
157 if seq.LigGlyph in s.glyphs and
158 all(c in s.glyphs for c in seq.Component)])
159 for g,seqs in self.ligatures.items())
160 self.ligatures = dict((g,v) for g,v in self.ligatures.items() if v)
161 return bool(self.ligatures)
Behdad Esfahbod54660612013-07-21 18:16:55 -0400162
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400163@_add_method(otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400164def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500165 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400166 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400167 indices = self.Coverage.intersect(cur_glyphs)
168 if(not indices or
169 not all(c.intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400170 for c in self.LookAheadCoverage + self.BacktrackCoverage)):
171 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400172 s.glyphs.update(self.Substitute[i] for i in indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400173 else:
174 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400175
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400176@_add_method(otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400177def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400178 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400179 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400180 self.Substitute = [self.Substitute[i] for i in indices]
181 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400182 indices = [i for i,sub in enumerate(self.Substitute)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400183 if sub in s.glyphs]
184 self.Substitute = [self.Substitute[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400185 self.Coverage.remap(indices)
186 self.GlyphCount = len(self.Substitute)
187 return bool(self.GlyphCount and
188 all(c.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400189 for c in self.LookAheadCoverage+self.BacktrackCoverage))
190 else:
191 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400192
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400193@_add_method(otTables.SinglePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400194def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400195 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400196 return len(self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400197 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400198 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400199 self.Value = [self.Value[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400200 self.ValueCount = len(self.Value)
201 return bool(self.ValueCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400202 else:
203 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400204
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400205@_add_method(otTables.SinglePos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400206def prune_post_subset(self, options):
207 if not options.hinting:
208 # Drop device tables
209 self.ValueFormat &= ~0x00F0
210 return True
211
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400212@_add_method(otTables.PairPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400213def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400214 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400215 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400216 self.PairSet = [self.PairSet[i] for i in indices]
217 for p in self.PairSet:
218 p.PairValueRecord = [r for r in p.PairValueRecord
219 if r.SecondGlyph in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400220 p.PairValueCount = len(p.PairValueRecord)
Behdad Esfahbod11631b42014-05-13 14:31:42 -0400221 # Remove empty pairsets
222 indices = [i for i,p in enumerate(self.PairSet) if p.PairValueCount]
223 self.Coverage.remap(indices)
224 self.PairSet = [self.PairSet[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400225 self.PairSetCount = len(self.PairSet)
226 return bool(self.PairSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400227 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400228 class1_map = self.ClassDef1.subset(s.glyphs, remap=True)
229 class2_map = self.ClassDef2.subset(s.glyphs, remap=True)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400230 self.Class1Record = [self.Class1Record[i] for i in class1_map]
231 for c in self.Class1Record:
232 c.Class2Record = [c.Class2Record[i] for i in class2_map]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400233 self.Class1Count = len(class1_map)
234 self.Class2Count = len(class2_map)
235 return bool(self.Class1Count and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400236 self.Class2Count and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400237 self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400238 else:
239 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400240
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400241@_add_method(otTables.PairPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400242def prune_post_subset(self, options):
243 if not options.hinting:
244 # Drop device tables
245 self.ValueFormat1 &= ~0x00F0
246 self.ValueFormat2 &= ~0x00F0
247 return True
248
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400249@_add_method(otTables.CursivePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400250def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400251 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400252 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400253 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400254 self.EntryExitCount = len(self.EntryExitRecord)
255 return bool(self.EntryExitCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400256 else:
257 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400258
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400259@_add_method(otTables.Anchor)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400260def prune_hints(self):
261 # Drop device tables / contour anchor point
Behdad Esfahbod6c51f502013-12-15 23:12:26 -0500262 self.ensureDecompiled()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400263 self.Format = 1
264
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400265@_add_method(otTables.CursivePos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400266def prune_post_subset(self, options):
267 if not options.hinting:
268 for rec in self.EntryExitRecord:
269 if rec.EntryAnchor: rec.EntryAnchor.prune_hints()
270 if rec.ExitAnchor: rec.ExitAnchor.prune_hints()
271 return True
272
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400273@_add_method(otTables.MarkBasePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400274def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400275 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400276 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400277 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
278 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400279 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
280 base_indices = self.BaseCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400281 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i]
282 for i in base_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400283 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400284 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400285 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400286 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400287 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400288 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400289 for b in self.BaseArray.BaseRecord:
290 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400291 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400292 self.MarkArray.MarkCount and
293 self.BaseArray.BaseCount)
294 else:
295 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400296
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400297@_add_method(otTables.MarkBasePos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400298def prune_post_subset(self, options):
299 if not options.hinting:
300 for m in self.MarkArray.MarkRecord:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200301 if m.MarkAnchor:
302 m.MarkAnchor.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400303 for b in self.BaseArray.BaseRecord:
304 for a in b.BaseAnchor:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200305 if a:
306 a.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400307 return True
308
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400309@_add_method(otTables.MarkLigPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400310def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400311 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400312 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400313 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
314 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400315 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
316 ligature_indices = self.LigatureCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400317 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i]
318 for i in ligature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400319 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400320 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400321 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400322 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400323 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400324 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400325 for l in self.LigatureArray.LigatureAttach:
326 for c in l.ComponentRecord:
327 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400328 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400329 self.MarkArray.MarkCount and
330 self.LigatureArray.LigatureCount)
331 else:
332 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400333
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400334@_add_method(otTables.MarkLigPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400335def prune_post_subset(self, options):
336 if not options.hinting:
337 for m in self.MarkArray.MarkRecord:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200338 if m.MarkAnchor:
339 m.MarkAnchor.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400340 for l in self.LigatureArray.LigatureAttach:
341 for c in l.ComponentRecord:
342 for a in c.LigatureAnchor:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200343 if a:
344 a.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400345 return True
346
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400347@_add_method(otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400348def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400349 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400350 mark1_indices = self.Mark1Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400351 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i]
352 for i in mark1_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400353 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
354 mark2_indices = self.Mark2Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400355 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i]
356 for i in mark2_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400357 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400358 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400359 class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400360 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400361 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400362 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400363 for b in self.Mark2Array.Mark2Record:
364 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400365 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400366 self.Mark1Array.MarkCount and
367 self.Mark2Array.MarkCount)
368 else:
369 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400370
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400371@_add_method(otTables.MarkMarkPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400372def prune_post_subset(self, options):
373 if not options.hinting:
374 # Drop device tables or contour anchor point
375 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200376 if m.MarkAnchor:
377 m.MarkAnchor.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400378 for b in self.Mark2Array.Mark2Record:
Behdad Esfahbod0ec17d92013-09-15 18:30:41 -0400379 for m in b.Mark2Anchor:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200380 if m:
381 m.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400382 return True
383
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400384@_add_method(otTables.SingleSubst,
385 otTables.MultipleSubst,
386 otTables.AlternateSubst,
387 otTables.LigatureSubst,
388 otTables.ReverseChainSingleSubst,
389 otTables.SinglePos,
390 otTables.PairPos,
391 otTables.CursivePos,
392 otTables.MarkBasePos,
393 otTables.MarkLigPos,
394 otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400395def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400396 pass
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400397
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400398@_add_method(otTables.SingleSubst,
399 otTables.MultipleSubst,
400 otTables.AlternateSubst,
401 otTables.LigatureSubst,
402 otTables.ReverseChainSingleSubst,
403 otTables.SinglePos,
404 otTables.PairPos,
405 otTables.CursivePos,
406 otTables.MarkBasePos,
407 otTables.MarkLigPos,
408 otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400409def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400410 return []
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400411
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400412@_add_method(otTables.SingleSubst,
413 otTables.MultipleSubst,
414 otTables.AlternateSubst,
415 otTables.LigatureSubst,
416 otTables.ContextSubst,
417 otTables.ChainContextSubst,
418 otTables.ReverseChainSingleSubst,
419 otTables.SinglePos,
420 otTables.PairPos,
421 otTables.CursivePos,
422 otTables.MarkBasePos,
423 otTables.MarkLigPos,
424 otTables.MarkMarkPos,
425 otTables.ContextPos,
426 otTables.ChainContextPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400427def prune_pre_subset(self, options):
428 return True
429
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400430@_add_method(otTables.SingleSubst,
431 otTables.MultipleSubst,
432 otTables.AlternateSubst,
433 otTables.LigatureSubst,
434 otTables.ReverseChainSingleSubst,
435 otTables.ContextSubst,
436 otTables.ChainContextSubst,
437 otTables.ContextPos,
438 otTables.ChainContextPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400439def prune_post_subset(self, options):
440 return True
441
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400442@_add_method(otTables.SingleSubst,
443 otTables.AlternateSubst,
444 otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400445def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400446 return False
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400447
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400448@_add_method(otTables.MultipleSubst,
449 otTables.LigatureSubst,
450 otTables.ContextSubst,
451 otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400452def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400453 return True
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400454
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400455@_add_method(otTables.ContextSubst,
456 otTables.ChainContextSubst,
457 otTables.ContextPos,
458 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400459def __classify_context(self):
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400460
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -0400461 class ContextHelper(object):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400462 def __init__(self, klass, Format):
463 if klass.__name__.endswith('Subst'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400464 Typ = 'Sub'
465 Type = 'Subst'
466 else:
467 Typ = 'Pos'
468 Type = 'Pos'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400469 if klass.__name__.startswith('Chain'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400470 Chain = 'Chain'
471 else:
472 Chain = ''
473 ChainTyp = Chain+Typ
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400474
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400475 self.Typ = Typ
476 self.Type = Type
477 self.Chain = Chain
478 self.ChainTyp = ChainTyp
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400479
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400480 self.LookupRecord = Type+'LookupRecord'
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400481
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400482 if Format == 1:
483 Coverage = lambda r: r.Coverage
484 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400485 ContextData = lambda r:(None,)
486 ChainContextData = lambda r:(None, None, None)
487 RuleData = lambda r:(r.Input,)
488 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400489 SetRuleData = None
490 ChainSetRuleData = None
491 elif Format == 2:
492 Coverage = lambda r: r.Coverage
493 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400494 ContextData = lambda r:(r.ClassDef,)
495 ChainContextData = lambda r:(r.LookAheadClassDef,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400496 r.InputClassDef,
497 r.BacktrackClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400498 RuleData = lambda r:(r.Class,)
499 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack)
500 def SetRuleData(r, d):(r.Class,) = d
501 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400502 elif Format == 3:
503 Coverage = lambda r: r.Coverage[0]
504 ChainCoverage = lambda r: r.InputCoverage[0]
505 ContextData = None
506 ChainContextData = None
507 RuleData = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400508 ChainRuleData = lambda r:(r.LookAheadCoverage +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400509 r.InputCoverage +
510 r.BacktrackCoverage)
511 SetRuleData = None
512 ChainSetRuleData = None
513 else:
514 assert 0, "unknown format: %s" % Format
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400515
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400516 if Chain:
517 self.Coverage = ChainCoverage
518 self.ContextData = ChainContextData
519 self.RuleData = ChainRuleData
520 self.SetRuleData = ChainSetRuleData
521 else:
522 self.Coverage = Coverage
523 self.ContextData = ContextData
524 self.RuleData = RuleData
525 self.SetRuleData = SetRuleData
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400526
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400527 if Format == 1:
528 self.Rule = ChainTyp+'Rule'
529 self.RuleCount = ChainTyp+'RuleCount'
530 self.RuleSet = ChainTyp+'RuleSet'
531 self.RuleSetCount = ChainTyp+'RuleSetCount'
532 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
533 elif Format == 2:
534 self.Rule = ChainTyp+'ClassRule'
535 self.RuleCount = ChainTyp+'ClassRuleCount'
536 self.RuleSet = ChainTyp+'ClassSet'
537 self.RuleSetCount = ChainTyp+'ClassSetCount'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400538 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r)
Behdad Esfahbod89987002013-07-23 23:07:42 -0400539
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400540 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
Behdad Esfahbod98b60752013-10-14 17:49:19 +0200541 self.ClassDefIndex = 1 if Chain else 0
Behdad Esfahbod11763302013-08-14 15:33:08 -0400542 self.Input = 'Input' if Chain else 'Class'
Behdad Esfahbod27108392013-07-23 16:40:47 -0400543
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400544 if self.Format not in [1, 2, 3]:
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400545 return None # Don't shoot the messenger; let it go
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400546 if not hasattr(self.__class__, "__ContextHelpers"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400547 self.__class__.__ContextHelpers = {}
548 if self.Format not in self.__class__.__ContextHelpers:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400549 helper = ContextHelper(self.__class__, self.Format)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400550 self.__class__.__ContextHelpers[self.Format] = helper
551 return self.__class__.__ContextHelpers[self.Format]
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400552
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400553@_add_method(otTables.ContextSubst,
554 otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400555def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500556 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400557 c = self.__classify_context()
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400558
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400559 indices = c.Coverage(self).intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400560 if not indices:
561 return []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400562 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs);
Behdad Esfahbod1d4fa132013-08-08 22:59:32 -0400563
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400564 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400565 ContextData = c.ContextData(self)
566 rss = getattr(self, c.RuleSet)
Behdad Esfahbod11174452013-11-18 20:20:49 -0500567 rssCount = getattr(self, c.RuleSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400568 for i in indices:
Behdad Esfahbod11174452013-11-18 20:20:49 -0500569 if i >= rssCount or not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400570 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400571 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400572 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
573 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400574 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400575 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400576 if not ll: continue
577 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400578 if chaos:
579 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400580 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400581 if seqi == 0:
582 pos_glyphs = set([c.Coverage(self).glyphs[i]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400583 else:
Behdad Esfahbodd3fdcc72013-08-14 17:59:31 -0400584 pos_glyphs = set([r.Input[seqi - 1]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400585 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400586 chaos = chaos or lookup.may_have_non_1to1()
587 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400588 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400589 ClassDef = getattr(self, c.ClassDef)
590 indices = ClassDef.intersect(cur_glyphs)
591 ContextData = c.ContextData(self)
592 rss = getattr(self, c.RuleSet)
Behdad Esfahbod11174452013-11-18 20:20:49 -0500593 rssCount = getattr(self, c.RuleSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400594 for i in indices:
Behdad Esfahbod11174452013-11-18 20:20:49 -0500595 if i >= rssCount or not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400596 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400597 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400598 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
599 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400600 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400601 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400602 if not ll: continue
603 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400604 if chaos:
605 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400606 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400607 if seqi == 0:
608 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400609 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400610 pos_glyphs = ClassDef.intersect_class(s.glyphs,
Behdad Esfahbod11763302013-08-14 15:33:08 -0400611 getattr(r, c.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 == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400616 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400617 return []
618 r = self
619 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400620 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400621 if not ll: continue
622 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400623 if chaos:
624 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400625 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400626 if seqi == 0:
627 pos_glyphs = cur_glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400628 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400629 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400630 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400631 chaos = chaos or lookup.may_have_non_1to1()
632 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400633 else:
634 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod00776972013-07-23 15:33:00 -0400635
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400636@_add_method(otTables.ContextSubst,
637 otTables.ContextPos,
638 otTables.ChainContextSubst,
639 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400640def subset_glyphs(self, s):
641 c = self.__classify_context()
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400642
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400643 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400644 indices = self.Coverage.subset(s.glyphs)
645 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400646 rss = [rss[i] for i in indices]
647 for rs in rss:
648 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400649 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400650 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400651 if r and all(all(g in s.glyphs for g in glist)
652 for glist in c.RuleData(r))]
653 setattr(rs, c.Rule, ss)
654 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400655 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400656 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
657 setattr(self, c.RuleSet, rss)
658 setattr(self, c.RuleSetCount, len(rss))
659 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400660 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400661 if not self.Coverage.subset(s.glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400662 return False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400663 ContextData = c.ContextData(self)
664 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData]
Behdad Esfahbod98b60752013-10-14 17:49:19 +0200665
666 # Keep rulesets for class numbers that survived.
667 indices = klass_maps[c.ClassDefIndex]
668 rss = getattr(self, c.RuleSet)
669 rssCount = getattr(self, c.RuleSetCount)
670 rss = [rss[i] for i in indices if i < rssCount]
671 del rssCount
672 # Delete, but not renumber, unreachable rulesets.
673 indices = getattr(self, c.ClassDef).intersect(self.Coverage.glyphs)
674 rss = [rss if i in indices else None for i,rss in enumerate(rss)]
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500675 while rss and rss[-1] is None:
Behdad Esfahbod98b60752013-10-14 17:49:19 +0200676 del rss[-1]
677
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400678 for rs in rss:
679 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400680 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400681 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400682 if r and all(all(k in klass_map for k in klist)
683 for klass_map,klist in zip(klass_maps, c.RuleData(r)))]
684 setattr(rs, c.Rule, ss)
685 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400686
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400687 # Remap rule classes
688 for r in ss:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400689 c.SetRuleData(r, [[klass_map.index(k) for k in klist]
690 for klass_map,klist in zip(klass_maps, c.RuleData(r))])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400691 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400692 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400693 return all(x.subset(s.glyphs) for x in c.RuleData(self))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400694 else:
695 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400696
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400697@_add_method(otTables.ContextSubst,
698 otTables.ChainContextSubst,
699 otTables.ContextPos,
700 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400701def subset_lookups(self, lookup_indices):
702 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400703
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400704 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400705 for rs in getattr(self, c.RuleSet):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400706 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400707 for r in getattr(rs, c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400708 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400709 setattr(r, c.LookupRecord,
710 [ll for ll in getattr(r, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400711 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400712 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400713 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400714 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400715 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400716 setattr(self, c.LookupRecord,
717 [ll for ll in getattr(self, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400718 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400719 for ll in getattr(self, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400720 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400721 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400722 else:
723 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400724
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400725@_add_method(otTables.ContextSubst,
726 otTables.ChainContextSubst,
727 otTables.ContextPos,
728 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400729def collect_lookups(self):
730 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400731
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400732 if self.Format in [1, 2]:
733 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400734 for rs in getattr(self, c.RuleSet) if rs
735 for r in getattr(rs, c.Rule) if r
736 for ll in getattr(r, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400737 elif self.Format == 3:
738 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400739 for ll in getattr(self, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400740 else:
741 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400742
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400743@_add_method(otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400744def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400745 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400746 self.ExtSubTable.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400747 else:
748 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400749
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400750@_add_method(otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400751def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400752 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400753 return self.ExtSubTable.may_have_non_1to1()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400754 else:
755 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400756
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400757@_add_method(otTables.ExtensionSubst,
758 otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400759def prune_pre_subset(self, options):
760 if self.Format == 1:
761 return self.ExtSubTable.prune_pre_subset(options)
762 else:
763 assert 0, "unknown format: %s" % self.Format
764
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400765@_add_method(otTables.ExtensionSubst,
766 otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400767def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400768 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400769 return self.ExtSubTable.subset_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400770 else:
771 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400772
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400773@_add_method(otTables.ExtensionSubst,
774 otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400775def prune_post_subset(self, options):
776 if self.Format == 1:
777 return self.ExtSubTable.prune_post_subset(options)
778 else:
779 assert 0, "unknown format: %s" % self.Format
780
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400781@_add_method(otTables.ExtensionSubst,
782 otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400783def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400784 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400785 return self.ExtSubTable.subset_lookups(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400786 else:
787 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400788
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400789@_add_method(otTables.ExtensionSubst,
790 otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400791def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400792 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400793 return self.ExtSubTable.collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400794 else:
795 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400796
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400797@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400798def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400799 for st in self.SubTable:
800 if not st: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400801 st.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400802
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400803@_add_method(otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400804def prune_pre_subset(self, options):
805 ret = False
806 for st in self.SubTable:
807 if not st: continue
808 if st.prune_pre_subset(options): ret = True
809 return ret
810
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400811@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400812def subset_glyphs(self, s):
813 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)]
814 self.SubTableCount = len(self.SubTable)
815 return bool(self.SubTableCount)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400816
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400817@_add_method(otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400818def prune_post_subset(self, options):
819 ret = False
820 for st in self.SubTable:
821 if not st: continue
822 if st.prune_post_subset(options): ret = True
823 return ret
824
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400825@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400826def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400827 for s in self.SubTable:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400828 s.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400829
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400830@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400831def collect_lookups(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400832 return _uniq_sort(sum((st.collect_lookups() for st in self.SubTable
833 if st), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400834
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400835@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400836def may_have_non_1to1(self):
837 return any(st.may_have_non_1to1() for st in self.SubTable if st)
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400838
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400839@_add_method(otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400840def prune_pre_subset(self, options):
841 ret = False
842 for l in self.Lookup:
843 if not l: continue
844 if l.prune_pre_subset(options): ret = True
845 return ret
846
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400847@_add_method(otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400848def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400849 "Returns the indices of nonempty lookups."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400850 return [i for i,l in enumerate(self.Lookup) if l and l.subset_glyphs(s)]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400851
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400852@_add_method(otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400853def prune_post_subset(self, options):
854 ret = False
855 for l in self.Lookup:
856 if not l: continue
857 if l.prune_post_subset(options): ret = True
858 return ret
859
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400860@_add_method(otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400861def subset_lookups(self, lookup_indices):
Behdad Esfahbod6c51f502013-12-15 23:12:26 -0500862 self.ensureDecompiled()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400863 self.Lookup = [self.Lookup[i] for i in lookup_indices
864 if i < self.LookupCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400865 self.LookupCount = len(self.Lookup)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400866 for l in self.Lookup:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400867 l.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400868
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400869@_add_method(otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400870def closure_lookups(self, lookup_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400871 lookup_indices = _uniq_sort(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400872 recurse = lookup_indices
873 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400874 recurse_lookups = sum((self.Lookup[i].collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400875 for i in recurse if i < self.LookupCount), [])
876 recurse_lookups = [l for l in recurse_lookups
877 if l not in lookup_indices and l < self.LookupCount]
878 if not recurse_lookups:
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400879 return _uniq_sort(lookup_indices)
880 recurse_lookups = _uniq_sort(recurse_lookups)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400881 lookup_indices.extend(recurse_lookups)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400882 recurse = recurse_lookups
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400883
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400884@_add_method(otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400885def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400886 self.LookupListIndex = [l for l in self.LookupListIndex
887 if l in lookup_indices]
888 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400889 self.LookupListIndex = [lookup_indices.index(l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400890 for l in self.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400891 self.LookupCount = len(self.LookupListIndex)
Behdad Esfahbodd214f202013-11-26 17:42:13 -0500892 return self.LookupCount or self.FeatureParams
Behdad Esfahbod54660612013-07-21 18:16:55 -0400893
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400894@_add_method(otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400895def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400896 return self.LookupListIndex[:]
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400897
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400898@_add_method(otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400899def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400900 "Returns the indices of nonempty features."
Behdad Esfahbod0be386e2013-12-16 20:52:52 -0500901 # Note: Never ever drop feature 'pref', even if it's empty.
902 # HarfBuzz chooses shaper for Khmer based on presence of this
903 # feature. See thread at:
904 # http://lists.freedesktop.org/archives/harfbuzz/2012-November/002660.html
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400905 feature_indices = [i for i,f in enumerate(self.FeatureRecord)
Behdad Esfahbod0be386e2013-12-16 20:52:52 -0500906 if (f.Feature.subset_lookups(lookup_indices) or
907 f.FeatureTag == 'pref')]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400908 self.subset_features(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400909 return feature_indices
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400910
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400911@_add_method(otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400912def collect_lookups(self, feature_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400913 return _uniq_sort(sum((self.FeatureRecord[i].Feature.collect_lookups()
914 for i in feature_indices
Behdad Esfahbod1ee298d2013-08-13 20:07:09 -0400915 if i < self.FeatureCount), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400916
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400917@_add_method(otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400918def subset_features(self, feature_indices):
Behdad Esfahbod6c51f502013-12-15 23:12:26 -0500919 self.ensureDecompiled()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400920 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400921 self.FeatureCount = len(self.FeatureRecord)
922 return bool(self.FeatureCount)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400923
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400924@_add_method(otTables.DefaultLangSys,
925 otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400926def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400927 if self.ReqFeatureIndex in feature_indices:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400928 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400929 else:
930 self.ReqFeatureIndex = 65535
931 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
932 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400933 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400934 if f in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400935 self.FeatureCount = len(self.FeatureIndex)
936 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400937
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400938@_add_method(otTables.DefaultLangSys,
939 otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400940def collect_features(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400941 feature_indices = self.FeatureIndex[:]
942 if self.ReqFeatureIndex != 65535:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400943 feature_indices.append(self.ReqFeatureIndex)
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400944 return _uniq_sort(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400945
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400946@_add_method(otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400947def subset_features(self, feature_indices):
948 if(self.DefaultLangSys and
949 not self.DefaultLangSys.subset_features(feature_indices)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400950 self.DefaultLangSys = None
951 self.LangSysRecord = [l for l in self.LangSysRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400952 if l.LangSys.subset_features(feature_indices)]
953 self.LangSysCount = len(self.LangSysRecord)
954 return bool(self.LangSysCount or self.DefaultLangSys)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400955
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400956@_add_method(otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400957def collect_features(self):
958 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400959 if self.DefaultLangSys:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400960 feature_indices.append(self.DefaultLangSys.collect_features())
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400961 return _uniq_sort(sum(feature_indices, []))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400962
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400963@_add_method(otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400964def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400965 self.ScriptRecord = [s for s in self.ScriptRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400966 if s.Script.subset_features(feature_indices)]
967 self.ScriptCount = len(self.ScriptRecord)
968 return bool(self.ScriptCount)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400969
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400970@_add_method(otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400971def collect_features(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400972 return _uniq_sort(sum((s.Script.collect_features()
973 for s in self.ScriptRecord), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400974
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400975@_add_method(ttLib.getTableClass('GSUB'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400976def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400977 s.table = self.table
Behdad Esfahbod19d7cf22013-12-04 21:13:11 -0500978 if self.table.ScriptList:
979 feature_indices = self.table.ScriptList.collect_features()
980 else:
981 feature_indices = []
Behdad Esfahbod7e972472013-11-15 17:57:15 -0500982 if self.table.FeatureList:
983 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
984 else:
985 lookup_indices = []
986 if self.table.LookupList:
987 while True:
988 orig_glyphs = s.glyphs.copy()
989 for i in lookup_indices:
990 if i >= self.table.LookupList.LookupCount: continue
991 if not self.table.LookupList.Lookup[i]: continue
992 self.table.LookupList.Lookup[i].closure_glyphs(s)
993 if orig_glyphs == s.glyphs:
994 break
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400995 del s.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400996
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400997@_add_method(ttLib.getTableClass('GSUB'),
998 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400999def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001000 s.glyphs = s.glyphs_gsubed
Behdad Esfahbod7e972472013-11-15 17:57:15 -05001001 if self.table.LookupList:
1002 lookup_indices = self.table.LookupList.subset_glyphs(s)
1003 else:
1004 lookup_indices = []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001005 self.subset_lookups(lookup_indices)
1006 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001007 return True
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001008
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001009@_add_method(ttLib.getTableClass('GSUB'),
1010 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001011def subset_lookups(self, lookup_indices):
Behdad Esfahbodbaa97d62013-12-06 21:11:22 -05001012 """Retains specified lookups, then removes empty features, language
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001013 systems, and scripts."""
Behdad Esfahbod7e972472013-11-15 17:57:15 -05001014 if self.table.LookupList:
1015 self.table.LookupList.subset_lookups(lookup_indices)
1016 if self.table.FeatureList:
1017 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices)
1018 else:
1019 feature_indices = []
Behdad Esfahbod19d7cf22013-12-04 21:13:11 -05001020 if self.table.ScriptList:
1021 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -04001022
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001023@_add_method(ttLib.getTableClass('GSUB'),
1024 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001025def prune_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001026 "Remove unreferenced lookups"
Behdad Esfahbod19d7cf22013-12-04 21:13:11 -05001027 if self.table.ScriptList:
1028 feature_indices = self.table.ScriptList.collect_features()
1029 else:
1030 feature_indices = []
Behdad Esfahbod7e972472013-11-15 17:57:15 -05001031 if self.table.FeatureList:
1032 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
1033 else:
1034 lookup_indices = []
1035 if self.table.LookupList:
1036 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices)
1037 else:
1038 lookup_indices = []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001039 self.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -04001040
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001041@_add_method(ttLib.getTableClass('GSUB'),
1042 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001043def subset_feature_tags(self, feature_tags):
Behdad Esfahbod7e972472013-11-15 17:57:15 -05001044 if self.table.FeatureList:
1045 feature_indices = [i for i,f in
1046 enumerate(self.table.FeatureList.FeatureRecord)
1047 if f.FeatureTag in feature_tags]
1048 self.table.FeatureList.subset_features(feature_indices)
1049 else:
1050 feature_indices = []
Behdad Esfahbod19d7cf22013-12-04 21:13:11 -05001051 if self.table.ScriptList:
1052 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001053
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001054@_add_method(ttLib.getTableClass('GSUB'),
1055 ttLib.getTableClass('GPOS'))
Behdad Esfahbodec9436d2013-12-06 21:58:41 -05001056def prune_features(self):
1057 "Remove unreferenced featurs"
1058 if self.table.ScriptList:
1059 feature_indices = self.table.ScriptList.collect_features()
1060 else:
1061 feature_indices = []
1062 if self.table.FeatureList:
1063 self.table.FeatureList.subset_features(feature_indices)
1064 if self.table.ScriptList:
1065 self.table.ScriptList.subset_features(feature_indices)
1066
1067@_add_method(ttLib.getTableClass('GSUB'),
1068 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001069def prune_pre_subset(self, options):
Behdad Esfahbod9d2481b2013-12-06 21:42:36 -05001070 # Drop undesired features
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001071 if '*' not in options.layout_features:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001072 self.subset_feature_tags(options.layout_features)
Behdad Esfahbod9d2481b2013-12-06 21:42:36 -05001073 # Drop unreferenced lookups
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001074 self.prune_lookups()
Behdad Esfahbod9d2481b2013-12-06 21:42:36 -05001075 # Prune lookups themselves
Behdad Esfahbod7e972472013-11-15 17:57:15 -05001076 if self.table.LookupList:
1077 self.table.LookupList.prune_pre_subset(options);
Behdad Esfahbodd77f1572013-08-15 19:24:36 -04001078 return True
1079
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001080@_add_method(ttLib.getTableClass('GSUB'),
1081 ttLib.getTableClass('GPOS'))
Behdad Esfahbod3db5e112013-12-07 12:54:44 -05001082def remove_redundant_langsys(self):
1083 table = self.table
1084 if not table.ScriptList or not table.FeatureList:
1085 return
1086
1087 features = table.FeatureList.FeatureRecord
1088
1089 for s in table.ScriptList.ScriptRecord:
1090 d = s.Script.DefaultLangSys
1091 if not d:
1092 continue
1093 for lr in s.Script.LangSysRecord[:]:
1094 l = lr.LangSys
1095 # Compare d and l
1096 if len(d.FeatureIndex) != len(l.FeatureIndex):
1097 continue
1098 if (d.ReqFeatureIndex == 65535) != (l.ReqFeatureIndex == 65535):
1099 continue
1100
1101 if d.ReqFeatureIndex != 65535:
1102 if features[d.ReqFeatureIndex] != features[l.ReqFeatureIndex]:
1103 continue
1104
1105 for i in range(len(d.FeatureIndex)):
1106 if features[d.FeatureIndex[i]] != features[l.FeatureIndex[i]]:
Behdad Esfahbod3db5e112013-12-07 12:54:44 -05001107 break
1108 else:
1109 # LangSys and default are equal; delete LangSys
1110 s.Script.LangSysRecord.remove(lr)
1111
1112@_add_method(ttLib.getTableClass('GSUB'),
1113 ttLib.getTableClass('GPOS'))
Behdad Esfahbodd77f1572013-08-15 19:24:36 -04001114def prune_post_subset(self, options):
Behdad Esfahbod9fe4eef2013-11-25 04:28:37 -05001115 table = self.table
Behdad Esfahbodec9436d2013-12-06 21:58:41 -05001116
1117 # LookupList looks good. Just prune lookups themselves
Behdad Esfahbod9fe4eef2013-11-25 04:28:37 -05001118 if table.LookupList:
1119 table.LookupList.prune_post_subset(options);
Behdad Esfahbod92af6a52013-12-10 17:51:32 -05001120 # XXX Next two lines disabled because OTS is stupid and
1121 # doesn't like NULL offsetse here.
1122 #if not table.LookupList.Lookup:
1123 # table.LookupList = None
Behdad Esfahbodec9436d2013-12-06 21:58:41 -05001124
1125 if not table.LookupList:
1126 table.FeatureList = None
1127
1128 if table.FeatureList:
Behdad Esfahbod3db5e112013-12-07 12:54:44 -05001129 self.remove_redundant_langsys()
Behdad Esfahbodec9436d2013-12-06 21:58:41 -05001130 # Remove unreferenced features
1131 self.prune_features()
1132
Behdad Esfahbod92af6a52013-12-10 17:51:32 -05001133 # XXX Next two lines disabled because OTS is stupid and
1134 # doesn't like NULL offsetse here.
1135 #if table.FeatureList and not table.FeatureList.FeatureRecord:
1136 # table.FeatureList = None
Behdad Esfahbodec9436d2013-12-06 21:58:41 -05001137
1138 # Never drop scripts themselves as them just being available
1139 # holds semantic significance.
Behdad Esfahbod92af6a52013-12-10 17:51:32 -05001140 # XXX Next two lines disabled because OTS is stupid and
1141 # doesn't like NULL offsetse here.
1142 #if table.ScriptList and not table.ScriptList.ScriptRecord:
1143 # table.ScriptList = None
Behdad Esfahbodec9436d2013-12-06 21:58:41 -05001144
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001145 return True
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001146
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001147@_add_method(ttLib.getTableClass('GDEF'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001148def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001149 glyphs = s.glyphs_gsubed
1150 table = self.table
1151 if table.LigCaretList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001152 indices = table.LigCaretList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001153 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i]
1154 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001155 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001156 if table.MarkAttachClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001157 table.MarkAttachClassDef.classDefs = dict((g,v) for g,v in
1158 table.MarkAttachClassDef.
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001159 classDefs.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001160 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001161 if table.GlyphClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001162 table.GlyphClassDef.classDefs = dict((g,v) for g,v in
1163 table.GlyphClassDef.
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001164 classDefs.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001165 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001166 if table.AttachList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001167 indices = table.AttachList.Coverage.subset(glyphs)
Behdad Esfahbod98769432013-11-19 14:40:57 -05001168 GlyphCount = table.AttachList.GlyphCount
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001169 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i]
Behdad Esfahbod98769432013-11-19 14:40:57 -05001170 for i in indices
1171 if i < GlyphCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001172 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint)
Behdad Esfahbod5aea27d2013-11-25 04:19:42 -05001173 if hasattr(table, "MarkGlyphSetsDef") and table.MarkGlyphSetsDef:
1174 for coverage in table.MarkGlyphSetsDef.Coverage:
1175 coverage.subset(glyphs)
Behdad Esfahbod05da9702013-11-25 05:23:07 -05001176 # TODO: The following is disabled. If enabling, we need to go fixup all
1177 # lookups that use MarkFilteringSet and map their set.
1178 #indices = table.MarkGlyphSetsDef.Coverage = [c for c in table.MarkGlyphSetsDef.Coverage if c.glyphs]
Behdad Esfahbod5aea27d2013-11-25 04:19:42 -05001179 return True
1180
1181@_add_method(ttLib.getTableClass('GDEF'))
1182def prune_post_subset(self, options):
1183 table = self.table
Behdad Esfahbodfa95e872013-12-15 22:02:20 -05001184 # XXX check these against OTS
Behdad Esfahbod5aea27d2013-11-25 04:19:42 -05001185 if table.LigCaretList and not table.LigCaretList.LigGlyphCount:
1186 table.LigCaretList = None
1187 if table.MarkAttachClassDef and not table.MarkAttachClassDef.classDefs:
1188 table.MarkAttachClassDef = None
1189 if table.GlyphClassDef and not table.GlyphClassDef.classDefs:
1190 table.GlyphClassDef = None
1191 if table.AttachList and not table.AttachList.GlyphCount:
1192 table.AttachList = None
1193 if hasattr(table, "MarkGlyphSetsDef") and table.MarkGlyphSetsDef and not table.MarkGlyphSetsDef.Coverage:
1194 table.MarkGlyphSetsDef = None
Behdad Esfahboda030a0d2013-11-27 17:46:15 -05001195 if table.Version == 0x00010002/0x10000:
Behdad Esfahbod5aea27d2013-11-25 04:19:42 -05001196 table.Version = 1.0
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001197 return bool(table.LigCaretList or
Behdad Esfahbod5aea27d2013-11-25 04:19:42 -05001198 table.MarkAttachClassDef or
1199 table.GlyphClassDef or
1200 table.AttachList or
Behdad Esfahboda030a0d2013-11-27 17:46:15 -05001201 (table.Version >= 0x00010002/0x10000 and table.MarkGlyphSetsDef))
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001202
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001203@_add_method(ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001204def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001205 # Prune unknown kern table types
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001206 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')]
1207 return bool(self.kernTables)
Behdad Esfahbodd4e33a72013-07-24 18:51:05 -04001208
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001209@_add_method(ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001210def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001211 glyphs = s.glyphs_gsubed
1212 for t in self.kernTables:
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001213 t.kernTable = dict(((a,b),v) for (a,b),v in t.kernTable.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001214 if a in glyphs and b in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001215 self.kernTables = [t for t in self.kernTables if t.kernTable]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001216 return bool(self.kernTables)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001217
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001218@_add_method(ttLib.getTableClass('vmtx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001219def subset_glyphs(self, s):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001220 self.metrics = dict((g,v) for g,v in self.metrics.items() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001221 return bool(self.metrics)
Behdad Esfahbodc7160442013-07-22 14:29:08 -04001222
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001223@_add_method(ttLib.getTableClass('hmtx'))
1224def subset_glyphs(self, s):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001225 self.metrics = dict((g,v) for g,v in self.metrics.items() if g in s.glyphs)
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001226 return True # Required table
1227
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001228@_add_method(ttLib.getTableClass('hdmx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001229def subset_glyphs(self, s):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001230 self.hdmx = dict((sz,dict((g,v) for g,v in l.items() if g in s.glyphs))
1231 for sz,l in self.hdmx.items())
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001232 return bool(self.hdmx)
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -04001233
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001234@_add_method(ttLib.getTableClass('VORG'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001235def subset_glyphs(self, s):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001236 self.VOriginRecords = dict((g,v) for g,v in self.VOriginRecords.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001237 if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001238 self.numVertOriginYMetrics = len(self.VOriginRecords)
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001239 return True # Never drop; has default metrics
Behdad Esfahbode45d6af2013-07-22 15:29:17 -04001240
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001241@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001242def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001243 if not options.glyph_names:
1244 self.formatType = 3.0
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001245 return True # Required table
Behdad Esfahbod42648242013-07-23 12:56:06 -04001246
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001247@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001248def subset_glyphs(self, s):
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001249 self.extraNames = [] # This seems to do it
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001250 return True # Required table
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001251
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001252@_add_method(ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001253def remapComponentsFast(self, indices):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001254 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001255 return # Not composite
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001256 data = array.array("B", self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001257 i = 10
1258 more = 1
1259 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001260 flags =(data[i] << 8) | data[i+1]
1261 glyphID =(data[i+2] << 8) | data[i+3]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001262 # Remap
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001263 glyphID = indices.index(glyphID)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001264 data[i+2] = glyphID >> 8
1265 data[i+3] = glyphID & 0xFF
1266 i += 4
1267 flags = int(flags)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001268
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001269 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001270 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001271 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1272 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1273 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1274 more = flags & 0x0020 # MORE_COMPONENTS
1275
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001276 self.data = data.tostring()
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001277
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001278@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001279def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001280 decompose = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001281 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001282 components = set()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001283 for g in decompose:
1284 if g not in self.glyphs:
1285 continue
1286 gl = self.glyphs[g]
Behdad Esfahbod043108c2013-09-27 12:59:47 -04001287 for c in gl.getComponentNames(self):
Behdad Esfahbod626107c2013-09-20 14:10:31 -04001288 if c not in s.glyphs:
1289 components.add(c)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001290 components = set(c for c in components if c not in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001291 if not components:
1292 break
1293 decompose = components
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001294 s.glyphs.update(components)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001295
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001296@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001297def prune_pre_subset(self, options):
1298 if options.notdef_glyph and not options.notdef_outline:
1299 g = self[self.glyphOrder[0]]
1300 # Yay, easy!
1301 g.__dict__.clear()
1302 g.data = ""
1303 return True
1304
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001305@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001306def subset_glyphs(self, s):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001307 self.glyphs = dict((g,v) for g,v in self.glyphs.items() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001308 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001309 for v in self.glyphs.values():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001310 if hasattr(v, "data"):
1311 v.remapComponentsFast(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001312 else:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001313 pass # No need
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001314 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbodb69b6712013-08-29 18:17:31 -04001315 # Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset.
1316 return True
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001317
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001318@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001319def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001320 if not options.hinting:
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001321 for v in self.glyphs.values():
Behdad Esfahbod626107c2013-09-20 14:10:31 -04001322 v.removeHinting()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001323 return True
Behdad Esfahboded98c612013-07-23 12:37:41 -04001324
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001325@_add_method(ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001326def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001327 cff = self.cff
Behdad Esfahbode0622072013-09-10 14:33:19 -04001328 # CFF table must have one font only
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001329 cff.fontNames = cff.fontNames[:1]
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001330
1331 if options.notdef_glyph and not options.notdef_outline:
1332 for fontname in cff.keys():
1333 font = cff[fontname]
1334 c,_ = font.CharStrings.getItemAndSelector('.notdef')
Behdad Esfahbod21582e92013-09-12 16:47:52 -04001335 # XXX we should preserve the glyph width
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001336 c.bytecode = '\x0e' # endchar
1337 c.program = None
1338
Behdad Esfahbod50f83ef2013-08-29 18:18:17 -04001339 return True # bool(cff.fontNames)
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001340
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001341@_add_method(ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001342def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001343 cff = self.cff
1344 for fontname in cff.keys():
1345 font = cff[fontname]
1346 cs = font.CharStrings
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001347
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001348 # Load all glyphs
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001349 for g in font.charset:
1350 if g not in s.glyphs: continue
1351 c,sel = cs.getItemAndSelector(g)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001352
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001353 if cs.charStringsAreIndexed:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001354 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001355 csi = cs.charStringsIndex
1356 csi.items = [csi.items[i] for i in indices]
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001357 csi.count = len(csi.items)
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001358 del csi.file, csi.offsets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001359 if hasattr(font, "FDSelect"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001360 sel = font.FDSelect
1361 sel.format = None
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001362 sel.gidArray = [sel.gidArray[i] for i in indices]
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001363 cs.charStrings = dict((g,indices.index(v))
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001364 for g,v in cs.charStrings.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001365 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001366 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001367 cs.charStrings = dict((g,v)
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001368 for g,v in cs.charStrings.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001369 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001370 font.charset = [g for g in font.charset if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001371 font.numGlyphs = len(font.charset)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001372
Behdad Esfahbod50f83ef2013-08-29 18:18:17 -04001373 return True # any(cff[fontname].numGlyphs for fontname in cff.keys())
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001374
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001375@_add_method(psCharStrings.T2CharString)
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001376def subset_subroutines(self, subrs, gsubrs):
1377 p = self.program
Behdad Esfahbode0622072013-09-10 14:33:19 -04001378 assert len(p)
Behdad Esfahbodb466efe2013-11-27 03:34:35 -05001379 for i in range(1, len(p)):
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001380 if p[i] == 'callsubr':
Behdad Esfahbodc2e2e832013-11-27 04:15:27 -05001381 assert isinstance(p[i-1], int)
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001382 p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias
1383 elif p[i] == 'callgsubr':
Behdad Esfahbodc2e2e832013-11-27 04:15:27 -05001384 assert isinstance(p[i-1], int)
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001385 p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias
1386
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001387@_add_method(psCharStrings.T2CharString)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001388def drop_hints(self):
1389 hints = self._hints
1390
1391 if hints.has_hint:
1392 self.program = self.program[hints.last_hint:]
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001393 if hasattr(self, 'width'):
1394 # Insert width back if needed
1395 if self.width != self.private.defaultWidthX:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001396 self.program.insert(0, self.width - self.private.nominalWidthX)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001397
1398 if hints.has_hintmask:
1399 i = 0
1400 p = self.program
1401 while i < len(p):
1402 if p[i] in ['hintmask', 'cntrmask']:
1403 assert i + 1 <= len(p)
1404 del p[i:i+2]
1405 continue
1406 i += 1
1407
Behdad Esfahbod2a70f4a2013-10-28 15:18:07 +01001408 # TODO: we currently don't drop calls to "empty" subroutines.
1409
Behdad Esfahbode0622072013-09-10 14:33:19 -04001410 assert len(self.program)
1411
1412 del self._hints
1413
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001414class _MarkingT2Decompiler(psCharStrings.SimpleT2Decompiler):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001415
1416 def __init__(self, localSubrs, globalSubrs):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001417 psCharStrings.SimpleT2Decompiler.__init__(self,
1418 localSubrs,
1419 globalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001420 for subrs in [localSubrs, globalSubrs]:
1421 if subrs and not hasattr(subrs, "_used"):
1422 subrs._used = set()
1423
1424 def op_callsubr(self, index):
1425 self.localSubrs._used.add(self.operandStack[-1]+self.localBias)
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001426 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001427
1428 def op_callgsubr(self, index):
1429 self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias)
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001430 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001431
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001432class _DehintingT2Decompiler(psCharStrings.SimpleT2Decompiler):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001433
Behdad Esfahbod1f262892013-11-28 14:26:39 -05001434 class Hints(object):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001435 def __init__(self):
1436 # Whether calling this charstring produces any hint stems
1437 self.has_hint = False
1438 # Index to start at to drop all hints
1439 self.last_hint = 0
1440 # Index up to which we know more hints are possible. Only
1441 # relevant if status is 0 or 1.
1442 self.last_checked = 0
1443 # The status means:
1444 # 0: after dropping hints, this charstring is empty
1445 # 1: after dropping hints, there may be more hints continuing after this
1446 # 2: no more hints possible after this charstring
1447 self.status = 0
1448 # Has hintmask instructions; not recursive
1449 self.has_hintmask = False
1450 pass
1451
1452 def __init__(self, css, localSubrs, globalSubrs):
1453 self._css = css
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001454 psCharStrings.SimpleT2Decompiler.__init__(self,
1455 localSubrs,
1456 globalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001457
1458 def execute(self, charString):
1459 old_hints = charString._hints if hasattr(charString, '_hints') else None
1460 charString._hints = self.Hints()
1461
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001462 psCharStrings.SimpleT2Decompiler.execute(self, charString)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001463
1464 hints = charString._hints
1465
1466 if hints.has_hint or hints.has_hintmask:
1467 self._css.add(charString)
1468
1469 if hints.status != 2:
1470 # Check from last_check, make sure we didn't have any operators.
Behdad Esfahbodb466efe2013-11-27 03:34:35 -05001471 for i in range(hints.last_checked, len(charString.program) - 1):
Behdad Esfahbodc2e2e832013-11-27 04:15:27 -05001472 if isinstance(charString.program[i], str):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001473 hints.status = 2
1474 break;
1475 else:
1476 hints.status = 1 # There's *something* here
1477 hints.last_checked = len(charString.program)
1478
1479 if old_hints:
1480 assert hints.__dict__ == old_hints.__dict__
1481
1482 def op_callsubr(self, index):
1483 subr = self.localSubrs[self.operandStack[-1]+self.localBias]
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001484 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001485 self.processSubr(index, subr)
1486
1487 def op_callgsubr(self, index):
1488 subr = self.globalSubrs[self.operandStack[-1]+self.globalBias]
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001489 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001490 self.processSubr(index, subr)
1491
1492 def op_hstem(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001493 psCharStrings.SimpleT2Decompiler.op_hstem(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001494 self.processHint(index)
1495 def op_vstem(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001496 psCharStrings.SimpleT2Decompiler.op_vstem(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001497 self.processHint(index)
1498 def op_hstemhm(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001499 psCharStrings.SimpleT2Decompiler.op_hstemhm(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001500 self.processHint(index)
1501 def op_vstemhm(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001502 psCharStrings.SimpleT2Decompiler.op_vstemhm(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001503 self.processHint(index)
1504 def op_hintmask(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001505 psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001506 self.processHintmask(index)
1507 def op_cntrmask(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001508 psCharStrings.SimpleT2Decompiler.op_cntrmask(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001509 self.processHintmask(index)
1510
1511 def processHintmask(self, index):
1512 cs = self.callingStack[-1]
1513 hints = cs._hints
1514 hints.has_hintmask = True
1515 if hints.status != 2 and hints.has_hint:
1516 # Check from last_check, see if we may be an implicit vstem
Behdad Esfahbodb466efe2013-11-27 03:34:35 -05001517 for i in range(hints.last_checked, index - 1):
Behdad Esfahbodc2e2e832013-11-27 04:15:27 -05001518 if isinstance(cs.program[i], str):
Behdad Esfahbod84763142013-09-10 19:00:48 -04001519 hints.status = 2
Behdad Esfahbode0622072013-09-10 14:33:19 -04001520 break;
Behdad Esfahbod84763142013-09-10 19:00:48 -04001521 if hints.status != 2:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001522 # We are an implicit vstem
1523 hints.last_hint = index + 1
Behdad Esfahbod84763142013-09-10 19:00:48 -04001524 hints.status = 0
Behdad Esfahbod2a70f4a2013-10-28 15:18:07 +01001525 hints.last_checked = index + 1
Behdad Esfahbode0622072013-09-10 14:33:19 -04001526
1527 def processHint(self, index):
1528 cs = self.callingStack[-1]
1529 hints = cs._hints
1530 hints.has_hint = True
1531 hints.last_hint = index
1532 hints.last_checked = index
1533
1534 def processSubr(self, index, subr):
1535 cs = self.callingStack[-1]
1536 hints = cs._hints
1537 subr_hints = subr._hints
1538
1539 if subr_hints.has_hint:
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001540 if hints.status != 2:
1541 hints.has_hint = True
Behdad Esfahbod99536852013-09-12 00:23:11 -04001542 hints.last_checked = index
1543 hints.status = subr_hints.status
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001544 # Decide where to chop off from
1545 if subr_hints.status == 0:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001546 hints.last_hint = index
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001547 else:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001548 hints.last_hint = index - 2 # Leave the subr call in
Behdad Esfahbode0622072013-09-10 14:33:19 -04001549 else:
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001550 # In my understanding, this is a font bug. Ie. it has hint stems
1551 # *after* path construction. I've seen this in widespread fonts.
1552 # Best to ignore the hints I suppose...
1553 pass
1554 #assert 0
Behdad Esfahbode0622072013-09-10 14:33:19 -04001555 else:
1556 hints.status = max(hints.status, subr_hints.status)
1557 if hints.status != 2:
1558 # Check from last_check, make sure we didn't have
1559 # any operators.
Behdad Esfahbodb466efe2013-11-27 03:34:35 -05001560 for i in range(hints.last_checked, index - 1):
Behdad Esfahbodc2e2e832013-11-27 04:15:27 -05001561 if isinstance(cs.program[i], str):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001562 hints.status = 2
1563 break;
1564 hints.last_checked = index
Behdad Esfahbod2a70f4a2013-10-28 15:18:07 +01001565 if hints.status != 2:
1566 # Decide where to chop off from
1567 if subr_hints.status == 0:
1568 hints.last_hint = index
1569 else:
1570 hints.last_hint = index - 2 # Leave the subr call in
Behdad Esfahbode0622072013-09-10 14:33:19 -04001571
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001572@_add_method(ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001573def prune_post_subset(self, options):
1574 cff = self.cff
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001575 for fontname in cff.keys():
1576 font = cff[fontname]
1577 cs = font.CharStrings
1578
Behdad Esfahbode0622072013-09-10 14:33:19 -04001579
1580 #
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001581 # Drop unused FontDictionaries
Behdad Esfahbode0622072013-09-10 14:33:19 -04001582 #
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001583 if hasattr(font, "FDSelect"):
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001584 sel = font.FDSelect
1585 indices = _uniq_sort(sel.gidArray)
1586 sel.gidArray = [indices.index (ss) for ss in sel.gidArray]
1587 arr = font.FDArray
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001588 arr.items = [arr[i] for i in indices]
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001589 arr.count = len(arr.items)
1590 del arr.file, arr.offsets
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001591
Behdad Esfahbode0622072013-09-10 14:33:19 -04001592
1593 #
1594 # Drop hints if not needed
1595 #
1596 if not options.hinting:
1597
1598 #
1599 # This can be tricky, but doesn't have to. What we do is:
1600 #
1601 # - Run all used glyph charstrings and recurse into subroutines,
1602 # - For each charstring (including subroutines), if it has any
1603 # of the hint stem operators, we mark it as such. Upon returning,
1604 # for each charstring we note all the subroutine calls it makes
1605 # that (recursively) contain a stem,
1606 # - Dropping hinting then consists of the following two ops:
1607 # * Drop the piece of the program in each charstring before the
1608 # last call to a stem op or a stem-calling subroutine,
1609 # * Drop all hintmask operations.
1610 # - It's trickier... A hintmask right after hints and a few numbers
1611 # will act as an implicit vstemhm. As such, we track whether
1612 # we have seen any non-hint operators so far and do the right
1613 # thing, recursively... Good luck understanding that :(
1614 #
1615 css = set()
1616 for g in font.charset:
1617 c,sel = cs.getItemAndSelector(g)
1618 # Make sure it's decompiled. We want our "decompiler" to walk
1619 # the program, not the bytecode.
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001620 c.draw(basePen.NullPen())
Behdad Esfahbode0622072013-09-10 14:33:19 -04001621 subrs = getattr(c.private, "Subrs", [])
1622 decompiler = _DehintingT2Decompiler(css, subrs, c.globalSubrs)
1623 decompiler.execute(c)
1624 for charstring in css:
1625 charstring.drop_hints()
1626
Behdad Esfahbod16fc3232013-09-30 15:09:27 -04001627 # Drop font-wide hinting values
1628 all_privs = []
1629 if hasattr(font, 'FDSelect'):
1630 all_privs.extend(fd.Private for fd in font.FDArray)
1631 else:
1632 all_privs.append(font.Private)
1633 for priv in all_privs:
Behdad Esfahbod4d99d142013-10-28 13:15:08 +01001634 for k in ['BlueValues', 'OtherBlues', 'FamilyBlues', 'FamilyOtherBlues',
1635 'BlueScale', 'BlueShift', 'BlueFuzz',
1636 'StemSnapH', 'StemSnapV', 'StdHW', 'StdVW']:
Behdad Esfahbod16fc3232013-09-30 15:09:27 -04001637 if hasattr(priv, k):
1638 setattr(priv, k, None)
1639
Behdad Esfahbode0622072013-09-10 14:33:19 -04001640
1641 #
1642 # Renumber subroutines to remove unused ones
1643 #
1644
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001645 # Mark all used subroutines
1646 for g in font.charset:
1647 c,sel = cs.getItemAndSelector(g)
1648 subrs = getattr(c.private, "Subrs", [])
1649 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs)
1650 decompiler.execute(c)
1651
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001652 all_subrs = [font.GlobalSubrs]
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001653 if hasattr(font, 'FDSelect'):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001654 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs') and fd.Private.Subrs)
1655 elif hasattr(font.Private, 'Subrs') and font.Private.Subrs:
Behdad Esfahbodcbcaccf2013-08-30 16:21:38 -04001656 all_subrs.append(font.Private.Subrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001657
1658 subrs = set(subrs) # Remove duplicates
1659
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001660 # Prepare
1661 for subrs in all_subrs:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001662 if not hasattr(subrs, '_used'):
1663 subrs._used = set()
1664 subrs._used = _uniq_sort(subrs._used)
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001665 subrs._old_bias = psCharStrings.calcSubrBias(subrs)
1666 subrs._new_bias = psCharStrings.calcSubrBias(subrs._used)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001667
Behdad Esfahboded107712013-08-14 19:54:13 -04001668 # Renumber glyph charstrings
1669 for g in font.charset:
1670 c,sel = cs.getItemAndSelector(g)
1671 subrs = getattr(c.private, "Subrs", [])
1672 c.subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001673
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001674 # Renumber subroutines themselves
1675 for subrs in all_subrs:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001676
1677 if subrs == font.GlobalSubrs:
1678 if not hasattr(font, 'FDSelect') and hasattr(font.Private, 'Subrs'):
1679 local_subrs = font.Private.Subrs
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001680 else:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001681 local_subrs = []
1682 else:
1683 local_subrs = subrs
1684
1685 subrs.items = [subrs.items[i] for i in subrs._used]
1686 subrs.count = len(subrs.items)
1687 del subrs.file
1688 if hasattr(subrs, 'offsets'):
1689 del subrs.offsets
1690
Behdad Esfahbodb466efe2013-11-27 03:34:35 -05001691 for i in range (subrs.count):
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001692 subrs[i].subset_subroutines (local_subrs, font.GlobalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001693
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001694 # Cleanup
1695 for subrs in all_subrs:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001696 del subrs._used, subrs._old_bias, subrs._new_bias
1697
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001698 return True
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001699
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001700@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001701def closure_glyphs(self, s):
Behdad Esfahbod2007a492014-03-12 12:26:41 -07001702 tables = [t for t in self.tables if t.isUnicode()]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001703 for u in s.unicodes_requested:
Behdad Esfahbod3e193542014-05-13 21:08:51 -04001704 found = False
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001705 for table in tables:
Behdad Esfahbode8988812014-05-09 17:48:02 -04001706 if table.format == 14:
1707 for l in table.uvsDict.values():
1708 # TODO(behdad) Speed this up!
1709 gids = [g for uc,g in l if u == uc and g is not None]
1710 s.glyphs.update(gids)
1711 # Intentionally not setting found=True here.
1712 else:
1713 if u in table.cmap:
1714 s.glyphs.add(table.cmap[u])
1715 found = True
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001716 if not found:
Behdad Esfahbode8988812014-05-09 17:48:02 -04001717 s.log("No default glyph for Unicode %04X found." % u)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001718
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001719@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001720def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001721 if not options.legacy_cmap:
1722 # Drop non-Unicode / non-Symbol cmaps
Behdad Esfahbod2007a492014-03-12 12:26:41 -07001723 self.tables = [t for t in self.tables if t.isUnicode() or t.isSymbol()]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001724 if not options.symbol_cmap:
Behdad Esfahbod2007a492014-03-12 12:26:41 -07001725 self.tables = [t for t in self.tables if not t.isSymbol()]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001726 # TODO(behdad) Only keep one subtable?
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001727 # For now, drop format=0 which can't be subset_glyphs easily?
1728 self.tables = [t for t in self.tables if t.format != 0]
Behdad Esfahbodfd92d4c2013-09-19 19:43:09 -04001729 self.numSubTables = len(self.tables)
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001730 return True # Required table
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001731
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001732@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001733def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001734 s.glyphs = s.glyphs_cmaped
1735 for t in self.tables:
1736 # For reasons I don't understand I need this here
1737 # to force decompilation of the cmap format 14.
1738 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001739 getattr(t, "asdf")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001740 except AttributeError:
1741 pass
1742 if t.format == 14:
Behdad Esfahbode8988812014-05-09 17:48:02 -04001743 # TODO(behdad) We drop all the default-UVS mappings for glyphs_requested.
1744 # I don't think we care about that...
1745 t.uvsDict = dict((v,[(u,g) for u,g in l
1746 if g in s.glyphs or u in s.unicodes_requested])
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001747 for v,l in t.uvsDict.items())
1748 t.uvsDict = dict((v,l) for v,l in t.uvsDict.items() if l)
Behdad Esfahbod11580c52014-03-13 17:34:35 -07001749 elif t.isUnicode():
1750 t.cmap = dict((u,g) for u,g in t.cmap.items()
1751 if g in s.glyphs_requested or u in s.unicodes_requested)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001752 else:
Behdad Esfahbod11580c52014-03-13 17:34:35 -07001753 t.cmap = dict((u,g) for u,g in t.cmap.items()
1754 if g in s.glyphs_requested)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001755 self.tables = [t for t in self.tables
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001756 if (t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbodfd92d4c2013-09-19 19:43:09 -04001757 self.numSubTables = len(self.tables)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001758 # TODO(behdad) Convert formats when needed.
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001759 # In particular, if we have a format=12 without non-BMP
1760 # characters, either drop format=12 one or convert it
1761 # to format=4 if there's not one.
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001762 return True # Required table
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001763
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001764@_add_method(ttLib.getTableClass('name'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001765def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001766 if '*' not in options.name_IDs:
1767 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1768 if not options.name_legacy:
Behdad Esfahboda08b1b12014-03-12 12:33:40 -07001769 self.names = [n for n in self.names if n.isUnicode()]
1770 # TODO(behdad) Option to keep only one platform's
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001771 if '*' not in options.name_languages:
Behdad Esfahboda08b1b12014-03-12 12:33:40 -07001772 # TODO(behdad) This is Windows-platform specific!
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001773 self.names = [n for n in self.names if n.langID in options.name_languages]
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001774 return True # Required table
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001775
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001776
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001777# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange?
Behdad Esfahbod26560d22013-10-26 22:03:35 +02001778# TODO(behdad) Drop AAT tables.
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001779# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
Behdad Esfahbod852e8a52013-08-29 18:19:22 -04001780# TODO(behdad) Drop empty GSUB/GPOS, and GDEF if no GSUB/GPOS left
1781# TODO(behdad) Drop GDEF subitems if unused by lookups
Behdad Esfahbod10195332013-08-14 19:55:24 -04001782# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001783# TODO(behdad) Text direction considerations.
1784# TODO(behdad) Text script / language considerations.
Behdad Esfahbodcc8fc782013-11-26 22:53:04 -05001785# TODO(behdad) Optionally drop 'kern' table if GPOS available
Behdad Esfahbodfa95e872013-12-15 22:02:20 -05001786# TODO(behdad) Implement --unicode='*' to choose all cmap'ed
1787# TODO(behdad) Drop old-spec Indic scripts
1788
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001789
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001790class Options(object):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001791
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001792 class UnknownOptionError(Exception):
1793 pass
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001794
Behdad Esfahboda17743f2013-08-28 17:14:53 -04001795 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ',
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001796 'PCLT', 'LTSH']
1797 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
1798 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
1799 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1800 'loca', 'name', 'cvt ', 'fpgm', 'prep']
1801 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001802
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001803 # Based on HarfBuzz shapers
1804 _layout_features_groups = {
1805 # Default shaper
1806 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1807 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
1808 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
1809 'ltr': ['ltra', 'ltrm'],
1810 'rtl': ['rtla', 'rtlm'],
1811 # Complex shapers
1812 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1813 'cswh', 'mset'],
1814 'hangul': ['ljmo', 'vjmo', 'tjmo'],
Behdad Esfahbod3977d3e2013-10-14 17:49:12 +02001815 'tibetan': ['abvs', 'blws', 'abvm', 'blwm'],
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001816 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1817 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1818 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
1819 }
1820 _layout_features_default = _uniq_sort(sum(
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001821 iter(_layout_features_groups.values()), []))
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001822
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001823 drop_tables = _drop_tables_default
1824 no_subset_tables = _no_subset_tables_default
1825 hinting_tables = _hinting_tables_default
1826 layout_features = _layout_features_default
Behdad Esfahbodfe6bc4c2013-11-02 11:10:23 +00001827 hinting = True
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001828 glyph_names = False
1829 legacy_cmap = False
1830 symbol_cmap = False
1831 name_IDs = [1, 2] # Family and Style
1832 name_legacy = False
1833 name_languages = [0x0409] # English
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001834 notdef_glyph = True # gid0 for TrueType / .notdef for CFF
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001835 notdef_outline = False # No need for notdef to have an outline really
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001836 recommended_glyphs = False # gid1, gid2, gid3 for TrueType
Behdad Esfahbode911de12013-08-16 12:42:34 -04001837 recalc_bounds = False # Recalculate font bounding boxes
Behdad Esfahbodf09164a2014-05-01 15:16:14 -07001838 recalc_timestamp = False # Recalculate font modified timestamp
Behdad Esfahbod03d78da2013-08-29 16:42:00 -04001839 canonical_order = False # Order tables as recommended
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001840 flavor = None # May be 'woff'
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001841
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001842 def __init__(self, **kwargs):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001843
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001844 self.set(**kwargs)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001845
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001846 def set(self, **kwargs):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001847 for k,v in kwargs.items():
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001848 if not hasattr(self, k):
Behdad Esfahbodac10d812013-09-03 18:29:58 -04001849 raise self.UnknownOptionError("Unknown option '%s'" % k)
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001850 setattr(self, k, v)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001851
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001852 def parse_opts(self, argv, ignore_unknown=False):
1853 ret = []
1854 opts = {}
1855 for a in argv:
1856 orig_a = a
1857 if not a.startswith('--'):
1858 ret.append(a)
1859 continue
1860 a = a[2:]
1861 i = a.find('=')
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001862 op = '='
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001863 if i == -1:
1864 if a.startswith("no-"):
1865 k = a[3:]
1866 v = False
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001867 else:
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001868 k = a
1869 v = True
1870 else:
1871 k = a[:i]
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001872 if k[-1] in "-+":
1873 op = k[-1]+'=' # Ops is '-=' or '+=' now.
1874 k = k[:-1]
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001875 v = a[i+1:]
1876 k = k.replace('-', '_')
1877 if not hasattr(self, k):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -05001878 if ignore_unknown is True or k in ignore_unknown:
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001879 ret.append(orig_a)
1880 continue
1881 else:
1882 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001883
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001884 ov = getattr(self, k)
1885 if isinstance(ov, bool):
1886 v = bool(v)
1887 elif isinstance(ov, int):
1888 v = int(v)
1889 elif isinstance(ov, list):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001890 vv = v.split(',')
1891 if vv == ['']:
1892 vv = []
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001893 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 -04001894 if op == '=':
1895 v = vv
1896 elif op == '+=':
1897 v = ov
1898 v.extend(vv)
1899 elif op == '-=':
1900 v = ov
1901 for x in vv:
1902 if x in v:
1903 v.remove(x)
1904 else:
Behdad Esfahbod153ec402013-12-04 01:15:46 -05001905 assert False
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001906
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001907 opts[k] = v
1908 self.set(**opts)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001909
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001910 return ret
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001911
1912
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001913class Subsetter(object):
1914
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001915 def __init__(self, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001916
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001917 if not log:
1918 log = Logger()
1919 if not options:
1920 options = Options()
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001921
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001922 self.options = options
1923 self.log = log
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001924 self.unicodes_requested = set()
1925 self.glyphs_requested = set()
1926 self.glyphs = set()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001927
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001928 def populate(self, glyphs=[], unicodes=[], text=""):
1929 self.unicodes_requested.update(unicodes)
Behdad Esfahbodb21c9d32013-11-27 18:09:08 -05001930 if isinstance(text, bytes):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001931 text = text.decode("utf8")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001932 for u in text:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001933 self.unicodes_requested.add(ord(u))
1934 self.glyphs_requested.update(glyphs)
1935 self.glyphs.update(glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001936
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001937 def _prune_pre_subset(self, font):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001938
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001939 for tag in font.keys():
1940 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001941
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001942 if(tag in self.options.drop_tables or
1943 (tag in self.options.hinting_tables and not self.options.hinting)):
1944 self.log(tag, "dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001945 del font[tag]
1946 continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001947
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001948 clazz = ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001949
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001950 if hasattr(clazz, 'prune_pre_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001951 table = font[tag]
Behdad Esfahbod010c5f92013-09-10 20:54:46 -04001952 self.log.lapse("load '%s'" % tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001953 retain = table.prune_pre_subset(self.options)
1954 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001955 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001956 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001957 del font[tag]
1958 continue
1959 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001960 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001961
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001962 def _closure_glyphs(self, font):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001963
Behdad Esfahbod05a28622013-12-04 23:05:59 -05001964 realGlyphs = set(font.getGlyphOrder())
1965
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001966 self.glyphs = self.glyphs_requested.copy()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001967
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001968 if 'cmap' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001969 font['cmap'].closure_glyphs(self)
Behdad Esfahbod05a28622013-12-04 23:05:59 -05001970 self.glyphs.intersection_update(realGlyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001971 self.glyphs_cmaped = self.glyphs
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001972
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001973 if self.options.notdef_glyph:
1974 if 'glyf' in font:
1975 self.glyphs.add(font.getGlyphName(0))
1976 self.log("Added gid0 to subset")
1977 else:
1978 self.glyphs.add('.notdef')
1979 self.log("Added .notdef to subset")
1980 if self.options.recommended_glyphs:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001981 if 'glyf' in font:
Behdad Esfahbod10a3fff2013-12-08 15:22:32 -05001982 for i in range(min(4, len(font.getGlyphOrder()))):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001983 self.glyphs.add(font.getGlyphName(i))
1984 self.log("Added first four glyphs to subset")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001985
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001986 if 'GSUB' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001987 self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1988 len(self.glyphs))
1989 self.log.glyphs(self.glyphs, font=font)
1990 font['GSUB'].closure_glyphs(self)
Behdad Esfahbod05a28622013-12-04 23:05:59 -05001991 self.glyphs.intersection_update(realGlyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001992 self.log("Closed glyph list over 'GSUB': %d glyphs after" %
1993 len(self.glyphs))
1994 self.log.glyphs(self.glyphs, font=font)
1995 self.log.lapse("close glyph list over 'GSUB'")
1996 self.glyphs_gsubed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001997
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001998 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001999 self.log("Closing glyph list over 'glyf': %d glyphs before" %
2000 len(self.glyphs))
2001 self.log.glyphs(self.glyphs, font=font)
2002 font['glyf'].closure_glyphs(self)
Behdad Esfahbod05a28622013-12-04 23:05:59 -05002003 self.glyphs.intersection_update(realGlyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002004 self.log("Closed glyph list over 'glyf': %d glyphs after" %
2005 len(self.glyphs))
2006 self.log.glyphs(self.glyphs, font=font)
2007 self.log.lapse("close glyph list over 'glyf'")
2008 self.glyphs_glyfed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04002009
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002010 self.glyphs_all = self.glyphs.copy()
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04002011
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002012 self.log("Retaining %d glyphs: " % len(self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04002013
Behdad Esfahbodebcad972013-12-04 23:00:52 -05002014 del self.glyphs
2015
2016
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04002017 def _subset_glyphs(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002018 for tag in font.keys():
2019 if tag == 'GlyphOrder': continue
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04002020 clazz = ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04002021
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002022 if tag in self.options.no_subset_tables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002023 self.log(tag, "subsetting not needed")
2024 elif hasattr(clazz, 'subset_glyphs'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002025 table = font[tag]
2026 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002027 retain = table.subset_glyphs(self)
Behdad Esfahbodebcad972013-12-04 23:00:52 -05002028 del self.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002029 self.log.lapse("subset '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002030 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002031 self.log(tag, "subsetted to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002032 del font[tag]
2033 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002034 self.log(tag, "subsetted")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002035 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002036 self.log(tag, "NOT subset; don't know how to subset; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002037 del font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04002038
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002039 glyphOrder = font.getGlyphOrder()
2040 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002041 font.setGlyphOrder(glyphOrder)
2042 font._buildReverseGlyphOrderDict()
2043 self.log.lapse("subset GlyphOrder")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04002044
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04002045 def _prune_post_subset(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002046 for tag in font.keys():
2047 if tag == 'GlyphOrder': continue
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04002048 clazz = ttLib.getTableClass(tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002049 if hasattr(clazz, 'prune_post_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002050 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002051 retain = table.prune_post_subset(self.options)
2052 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002053 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002054 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002055 del font[tag]
2056 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002057 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04002058
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002059 def subset(self, font):
Behdad Esfahbod756af492013-08-01 12:05:26 -04002060
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04002061 self._prune_pre_subset(font)
2062 self._closure_glyphs(font)
2063 self._subset_glyphs(font)
2064 self._prune_post_subset(font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04002065
Behdad Esfahbod756af492013-08-01 12:05:26 -04002066
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04002067class Logger(object):
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002068
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002069 def __init__(self, verbose=False, xml=False, timing=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002070 self.verbose = verbose
2071 self.xml = xml
2072 self.timing = timing
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002073 self.last_time = self.start_time = time.time()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002074
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002075 def parse_opts(self, argv):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002076 argv = argv[:]
2077 for v in ['verbose', 'xml', 'timing']:
2078 if "--"+v in argv:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002079 setattr(self, v, True)
2080 argv.remove("--"+v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002081 return argv
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002082
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002083 def __call__(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002084 if not self.verbose:
2085 return
Behdad Esfahbod4cd467c2013-11-27 04:57:06 -05002086 print(' '.join(str(x) for x in things))
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002087
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002088 def lapse(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002089 if not self.timing:
2090 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002091 new_time = time.time()
Behdad Esfahbod4cd467c2013-11-27 04:57:06 -05002092 print("Took %0.3fs to %s" %(new_time - self.last_time,
2093 ' '.join(str(x) for x in things)))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002094 self.last_time = new_time
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002095
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04002096 def glyphs(self, glyphs, font=None):
Behdad Esfahbod57fb7262013-12-04 21:56:53 -05002097 if not self.verbose:
2098 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002099 self("Names: ", sorted(glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002100 if font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002101 reverseGlyphMap = font.getReverseGlyphMap()
2102 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04002103
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002104 def font(self, font, file=sys.stdout):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002105 if not self.xml:
2106 return
Behdad Esfahbod28fc4982013-09-18 19:01:16 -04002107 from fontTools.misc import xmlWriter
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002108 writer = xmlWriter.XMLWriter(file)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002109 for tag in font.keys():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002110 writer.begintag(tag)
2111 writer.newline()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002112 font[tag].toXML(writer, font)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002113 writer.endtag(tag)
2114 writer.newline()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002115
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002116
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002117def load_font(fontFile,
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04002118 options,
Behdad Esfahbod6bd43242013-12-04 21:34:05 -05002119 allowVID=False,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002120 checkChecksums=False,
Behdad Esfahbod283fb262013-12-16 00:50:48 -05002121 dontLoadGlyphNames=False,
2122 lazy=True):
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002123
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04002124 font = ttLib.TTFont(fontFile,
Behdad Esfahbod6bd43242013-12-04 21:34:05 -05002125 allowVID=allowVID,
2126 checkChecksums=checkChecksums,
Behdad Esfahbod283fb262013-12-16 00:50:48 -05002127 recalcBBoxes=options.recalc_bounds,
Behdad Esfahbodf09164a2014-05-01 15:16:14 -07002128 recalcTimestamp=options.recalc_timestamp,
Behdad Esfahbod283fb262013-12-16 00:50:48 -05002129 lazy=lazy)
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002130
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002131 # Hack:
2132 #
2133 # If we don't need glyph names, change 'post' class to not try to
2134 # load them. It avoid lots of headache with broken fonts as well
2135 # as loading time.
2136 #
2137 # Ideally ttLib should provide a way to ask it to skip loading
2138 # glyph names. But it currently doesn't provide such a thing.
2139 #
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002140 if dontLoadGlyphNames:
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04002141 post = ttLib.getTableClass('post')
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002142 saved = post.decode_format_2_0
2143 post.decode_format_2_0 = post.decode_format_3_0
2144 f = font['post']
2145 if f.formatType == 2.0:
2146 f.formatType = 3.0
2147 post.decode_format_2_0 = saved
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002148
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002149 return font
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002150
Behdad Esfahbode911de12013-08-16 12:42:34 -04002151def save_font(font, outfile, options):
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04002152 if options.flavor and not hasattr(font, 'flavor'):
2153 raise Exception("fonttools version does not support flavors.")
2154 font.flavor = options.flavor
Behdad Esfahbode911de12013-08-16 12:42:34 -04002155 font.save(outfile, reorderTables=options.canonical_order)
Behdad Esfahbod41de4cc2013-08-15 12:09:55 -04002156
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002157def main(args):
Behdad Esfahbod610b0552013-07-23 14:52:18 -04002158
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002159 log = Logger()
2160 args = log.parse_opts(args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04002161
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04002162 options = Options()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002163 args = options.parse_opts(args, ignore_unknown=['text'])
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04002164
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002165 if len(args) < 2:
Behdad Esfahbodcfeafd72013-11-27 17:27:35 -05002166 print("usage: pyftsubset font-file glyph... [--text=ABC]... [--option=value]...", file=sys.stderr)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002167 sys.exit(1)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002168
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002169 fontfile = args[0]
2170 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002171
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002172 dontLoadGlyphNames =(not options.glyph_names and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002173 all(any(g.startswith(p)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002174 for p in ['gid', 'glyph', 'uni', 'U+'])
2175 for g in args))
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002176
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04002177 font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002178 log.lapse("load font")
Behdad Esfahbodb640f742013-09-19 20:12:56 -04002179 subsetter = Subsetter(options=options, log=log)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002180
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002181 names = font.getGlyphNames()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002182 log.lapse("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04002183
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002184 glyphs = []
2185 unicodes = []
2186 text = ""
2187 for g in args:
Behdad Esfahbod2be33d92013-09-10 19:28:59 -04002188 if g == '*':
2189 glyphs.extend(font.getGlyphOrder())
2190 continue
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002191 if g in names:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002192 glyphs.append(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002193 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002194 if g.startswith('--text='):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002195 text += g[7:]
2196 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002197 if g.startswith('uni') or g.startswith('U+'):
2198 if g.startswith('uni') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002199 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002200 elif g.startswith('U+') and len(g) > 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002201 g = g[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002202 u = int(g, 16)
2203 unicodes.append(u)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002204 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002205 if g.startswith('gid') or g.startswith('glyph'):
2206 if g.startswith('gid') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002207 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002208 elif g.startswith('glyph') and len(g) > 5:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002209 g = g[5:]
2210 try:
Behdad Esfahboddc873722013-12-04 21:28:50 -05002211 glyphs.append(font.getGlyphName(int(g), requireReal=True))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002212 except ValueError:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002213 raise Exception("Invalid glyph identifier: %s" % g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002214 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002215 raise Exception("Invalid glyph identifier: %s" % g)
2216 log.lapse("compile glyph list")
2217 log("Unicodes:", unicodes)
2218 log("Glyphs:", glyphs)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04002219
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002220 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
2221 subsetter.subset(font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04002222
Behdad Esfahbod34426c12013-08-14 18:30:09 -04002223 outfile = fontfile + '.subset'
2224
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04002225 save_font (font, outfile, options)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002226 log.lapse("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04002227
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002228 log.last_time = log.start_time
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002229 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04002230
Behdad Esfahbod34426c12013-08-14 18:30:09 -04002231 if log.verbose:
2232 import os
2233 log("Input font: %d bytes" % os.path.getsize(fontfile))
2234 log("Subset font: %d bytes" % os.path.getsize(outfile))
2235
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002236 log.font(font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04002237
Behdad Esfahbodc56bf482013-08-13 20:13:33 -04002238 font.close()
2239
Behdad Esfahbod39a39ac2013-08-22 18:10:17 -04002240
2241__all__ = [
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002242 'Options',
2243 'Subsetter',
2244 'Logger',
2245 'load_font',
2246 'save_font',
2247 'main'
Behdad Esfahbod39a39ac2013-08-22 18:10:17 -04002248]
2249
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04002250if __name__ == '__main__':
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002251 main(sys.argv[1:])