blob: c3eb2527f5d1f0d25961e4b37085daacfd827f6c [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 Esfahboda030a0d2013-11-27 17:46:15 -050010from __future__ import print_function, division
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 Esfahbod9e856ea2013-08-13 19:50:38 -0400104 if self.Format in [1, 2]:
Behdad Esfahbod6890d052013-11-27 06:26:35 -0500105 s.glyphs.update(v for g,v in self.mapping.items() if g in cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400106 else:
107 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400108
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400109@_add_method(otTables.SingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400110def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400111 if self.Format in [1, 2]:
Behdad Esfahbod6890d052013-11-27 06:26:35 -0500112 self.mapping = dict((g,v) for g,v in self.mapping.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400113 if g in s.glyphs and v in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400114 return bool(self.mapping)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400115 else:
116 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400117
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400118@_add_method(otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400119def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500120 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400121 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400122 indices = self.Coverage.intersect(cur_glyphs)
Behdad Esfahboda9bfec12013-08-16 16:21:25 -0400123 _set_update(s.glyphs, *(self.Sequence[i].Substitute for i in indices))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400124 else:
125 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400126
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400127@_add_method(otTables.MultipleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400128def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400129 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400130 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400131 self.Sequence = [self.Sequence[i] for i in indices]
132 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400133 indices = [i for i,seq in enumerate(self.Sequence)
134 if all(sub in s.glyphs for sub in seq.Substitute)]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400135 self.Sequence = [self.Sequence[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400136 self.Coverage.remap(indices)
137 self.SequenceCount = len(self.Sequence)
138 return bool(self.SequenceCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400139 else:
140 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400141
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400142@_add_method(otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400143def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500144 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400145 if self.Format == 1:
Behdad Esfahbod6890d052013-11-27 06:26:35 -0500146 _set_update(s.glyphs, *(vlist for g,vlist in self.alternates.items()
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -0400147 if g in cur_glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400148 else:
149 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400150
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400151@_add_method(otTables.AlternateSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400152def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400153 if self.Format == 1:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400154 self.alternates = dict((g,vlist)
Behdad Esfahbod6890d052013-11-27 06:26:35 -0500155 for g,vlist in self.alternates.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400156 if g in s.glyphs and
157 all(v in s.glyphs for v in vlist))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400158 return bool(self.alternates)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400159 else:
160 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400161
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400162@_add_method(otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400163def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500164 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400165 if self.Format == 1:
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -0400166 _set_update(s.glyphs, *([seq.LigGlyph for seq in seqs
167 if all(c in s.glyphs for c in seq.Component)]
Behdad Esfahbod6890d052013-11-27 06:26:35 -0500168 for g,seqs in self.ligatures.items()
Behdad Esfahbod42d4f2b2013-08-16 16:16:22 -0400169 if g in cur_glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400170 else:
171 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400172
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400173@_add_method(otTables.LigatureSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400174def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400175 if self.Format == 1:
Behdad Esfahbod6890d052013-11-27 06:26:35 -0500176 self.ligatures = dict((g,v) for g,v in self.ligatures.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -0400177 if g in s.glyphs)
178 self.ligatures = dict((g,[seq for seq in seqs
179 if seq.LigGlyph in s.glyphs and
180 all(c in s.glyphs for c in seq.Component)])
Behdad Esfahbod6890d052013-11-27 06:26:35 -0500181 for g,seqs in self.ligatures.items())
182 self.ligatures = dict((g,v) for g,v in self.ligatures.items() if v)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400183 return bool(self.ligatures)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400184 else:
185 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400186
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400187@_add_method(otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400188def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500189 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400190 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400191 indices = self.Coverage.intersect(cur_glyphs)
192 if(not indices or
193 not all(c.intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400194 for c in self.LookAheadCoverage + self.BacktrackCoverage)):
195 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400196 s.glyphs.update(self.Substitute[i] for i in indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400197 else:
198 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400199
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400200@_add_method(otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400201def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400202 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400203 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400204 self.Substitute = [self.Substitute[i] for i in indices]
205 # Now drop rules generating glyphs we don't want
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400206 indices = [i for i,sub in enumerate(self.Substitute)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400207 if sub in s.glyphs]
208 self.Substitute = [self.Substitute[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400209 self.Coverage.remap(indices)
210 self.GlyphCount = len(self.Substitute)
211 return bool(self.GlyphCount and
212 all(c.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400213 for c in self.LookAheadCoverage+self.BacktrackCoverage))
214 else:
215 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400216
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400217@_add_method(otTables.SinglePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400218def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400219 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400220 return len(self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400221 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400222 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400223 self.Value = [self.Value[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400224 self.ValueCount = len(self.Value)
225 return bool(self.ValueCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400226 else:
227 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400228
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400229@_add_method(otTables.SinglePos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400230def prune_post_subset(self, options):
231 if not options.hinting:
232 # Drop device tables
233 self.ValueFormat &= ~0x00F0
234 return True
235
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400236@_add_method(otTables.PairPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400237def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400238 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400239 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400240 self.PairSet = [self.PairSet[i] for i in indices]
241 for p in self.PairSet:
242 p.PairValueRecord = [r for r in p.PairValueRecord
243 if r.SecondGlyph in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400244 p.PairValueCount = len(p.PairValueRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400245 self.PairSet = [p for p in self.PairSet if p.PairValueCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400246 self.PairSetCount = len(self.PairSet)
247 return bool(self.PairSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400248 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400249 class1_map = self.ClassDef1.subset(s.glyphs, remap=True)
250 class2_map = self.ClassDef2.subset(s.glyphs, remap=True)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400251 self.Class1Record = [self.Class1Record[i] for i in class1_map]
252 for c in self.Class1Record:
253 c.Class2Record = [c.Class2Record[i] for i in class2_map]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400254 self.Class1Count = len(class1_map)
255 self.Class2Count = len(class2_map)
256 return bool(self.Class1Count and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400257 self.Class2Count and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400258 self.Coverage.subset(s.glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400259 else:
260 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400261
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400262@_add_method(otTables.PairPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400263def prune_post_subset(self, options):
264 if not options.hinting:
265 # Drop device tables
266 self.ValueFormat1 &= ~0x00F0
267 self.ValueFormat2 &= ~0x00F0
268 return True
269
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400270@_add_method(otTables.CursivePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400271def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400272 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400273 indices = self.Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400274 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400275 self.EntryExitCount = len(self.EntryExitRecord)
276 return bool(self.EntryExitCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400277 else:
278 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400279
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400280@_add_method(otTables.Anchor)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400281def prune_hints(self):
282 # Drop device tables / contour anchor point
283 self.Format = 1
284
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400285@_add_method(otTables.CursivePos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400286def prune_post_subset(self, options):
287 if not options.hinting:
288 for rec in self.EntryExitRecord:
289 if rec.EntryAnchor: rec.EntryAnchor.prune_hints()
290 if rec.ExitAnchor: rec.ExitAnchor.prune_hints()
291 return True
292
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400293@_add_method(otTables.MarkBasePos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400294def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400295 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400296 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400297 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
298 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400299 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
300 base_indices = self.BaseCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400301 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i]
302 for i in base_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400303 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400304 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400305 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400306 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400307 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400308 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400309 for b in self.BaseArray.BaseRecord:
310 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400311 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400312 self.MarkArray.MarkCount and
313 self.BaseArray.BaseCount)
314 else:
315 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400316
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400317@_add_method(otTables.MarkBasePos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400318def prune_post_subset(self, options):
319 if not options.hinting:
320 for m in self.MarkArray.MarkRecord:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200321 if m.MarkAnchor:
322 m.MarkAnchor.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400323 for b in self.BaseArray.BaseRecord:
324 for a in b.BaseAnchor:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200325 if a:
326 a.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400327 return True
328
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400329@_add_method(otTables.MarkLigPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400330def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400331 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400332 mark_indices = self.MarkCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400333 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i]
334 for i in mark_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400335 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
336 ligature_indices = self.LigatureCoverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400337 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i]
338 for i in ligature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400339 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400340 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400341 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400342 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400343 for m in self.MarkArray.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400344 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400345 for l in self.LigatureArray.LigatureAttach:
346 for c in l.ComponentRecord:
347 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400348 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400349 self.MarkArray.MarkCount and
350 self.LigatureArray.LigatureCount)
351 else:
352 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400353
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400354@_add_method(otTables.MarkLigPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400355def prune_post_subset(self, options):
356 if not options.hinting:
357 for m in self.MarkArray.MarkRecord:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200358 if m.MarkAnchor:
359 m.MarkAnchor.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400360 for l in self.LigatureArray.LigatureAttach:
361 for c in l.ComponentRecord:
362 for a in c.LigatureAnchor:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200363 if a:
364 a.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400365 return True
366
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400367@_add_method(otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400368def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400369 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400370 mark1_indices = self.Mark1Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400371 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i]
372 for i in mark1_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400373 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
374 mark2_indices = self.Mark2Coverage.subset(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400375 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i]
376 for i in mark2_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400377 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400378 # Prune empty classes
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400379 class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400380 self.ClassCount = len(class_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400381 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400382 m.Class = class_indices.index(m.Class)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400383 for b in self.Mark2Array.Mark2Record:
384 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400385 return bool(self.ClassCount and
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400386 self.Mark1Array.MarkCount and
387 self.Mark2Array.MarkCount)
388 else:
389 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400390
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400391@_add_method(otTables.MarkMarkPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400392def prune_post_subset(self, options):
393 if not options.hinting:
394 # Drop device tables or contour anchor point
395 for m in self.Mark1Array.MarkRecord:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200396 if m.MarkAnchor:
397 m.MarkAnchor.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400398 for b in self.Mark2Array.Mark2Record:
Behdad Esfahbod0ec17d92013-09-15 18:30:41 -0400399 for m in b.Mark2Anchor:
Behdad Esfahbode1a010c2013-10-09 15:57:22 +0200400 if m:
401 m.prune_hints()
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400402 return True
403
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400404@_add_method(otTables.SingleSubst,
405 otTables.MultipleSubst,
406 otTables.AlternateSubst,
407 otTables.LigatureSubst,
408 otTables.ReverseChainSingleSubst,
409 otTables.SinglePos,
410 otTables.PairPos,
411 otTables.CursivePos,
412 otTables.MarkBasePos,
413 otTables.MarkLigPos,
414 otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400415def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400416 pass
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400417
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400418@_add_method(otTables.SingleSubst,
419 otTables.MultipleSubst,
420 otTables.AlternateSubst,
421 otTables.LigatureSubst,
422 otTables.ReverseChainSingleSubst,
423 otTables.SinglePos,
424 otTables.PairPos,
425 otTables.CursivePos,
426 otTables.MarkBasePos,
427 otTables.MarkLigPos,
428 otTables.MarkMarkPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400429def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400430 return []
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400431
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400432@_add_method(otTables.SingleSubst,
433 otTables.MultipleSubst,
434 otTables.AlternateSubst,
435 otTables.LigatureSubst,
436 otTables.ContextSubst,
437 otTables.ChainContextSubst,
438 otTables.ReverseChainSingleSubst,
439 otTables.SinglePos,
440 otTables.PairPos,
441 otTables.CursivePos,
442 otTables.MarkBasePos,
443 otTables.MarkLigPos,
444 otTables.MarkMarkPos,
445 otTables.ContextPos,
446 otTables.ChainContextPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400447def prune_pre_subset(self, options):
448 return True
449
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400450@_add_method(otTables.SingleSubst,
451 otTables.MultipleSubst,
452 otTables.AlternateSubst,
453 otTables.LigatureSubst,
454 otTables.ReverseChainSingleSubst,
455 otTables.ContextSubst,
456 otTables.ChainContextSubst,
457 otTables.ContextPos,
458 otTables.ChainContextPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400459def prune_post_subset(self, options):
460 return True
461
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400462@_add_method(otTables.SingleSubst,
463 otTables.AlternateSubst,
464 otTables.ReverseChainSingleSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400465def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400466 return False
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400467
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400468@_add_method(otTables.MultipleSubst,
469 otTables.LigatureSubst,
470 otTables.ContextSubst,
471 otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400472def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400473 return True
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400474
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400475@_add_method(otTables.ContextSubst,
476 otTables.ChainContextSubst,
477 otTables.ContextPos,
478 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400479def __classify_context(self):
Behdad Esfahbodb178dca2013-07-23 22:51:50 -0400480
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -0400481 class ContextHelper(object):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400482 def __init__(self, klass, Format):
483 if klass.__name__.endswith('Subst'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400484 Typ = 'Sub'
485 Type = 'Subst'
486 else:
487 Typ = 'Pos'
488 Type = 'Pos'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400489 if klass.__name__.startswith('Chain'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400490 Chain = 'Chain'
491 else:
492 Chain = ''
493 ChainTyp = Chain+Typ
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400494
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400495 self.Typ = Typ
496 self.Type = Type
497 self.Chain = Chain
498 self.ChainTyp = ChainTyp
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400499
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400500 self.LookupRecord = Type+'LookupRecord'
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400501
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400502 if Format == 1:
503 Coverage = lambda r: r.Coverage
504 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400505 ContextData = lambda r:(None,)
506 ChainContextData = lambda r:(None, None, None)
507 RuleData = lambda r:(r.Input,)
508 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400509 SetRuleData = None
510 ChainSetRuleData = None
511 elif Format == 2:
512 Coverage = lambda r: r.Coverage
513 ChainCoverage = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400514 ContextData = lambda r:(r.ClassDef,)
515 ChainContextData = lambda r:(r.LookAheadClassDef,
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400516 r.InputClassDef,
517 r.BacktrackClassDef)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400518 RuleData = lambda r:(r.Class,)
519 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack)
520 def SetRuleData(r, d):(r.Class,) = d
521 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400522 elif Format == 3:
523 Coverage = lambda r: r.Coverage[0]
524 ChainCoverage = lambda r: r.InputCoverage[0]
525 ContextData = None
526 ChainContextData = None
527 RuleData = lambda r: r.Coverage
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400528 ChainRuleData = lambda r:(r.LookAheadCoverage +
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400529 r.InputCoverage +
530 r.BacktrackCoverage)
531 SetRuleData = None
532 ChainSetRuleData = None
533 else:
534 assert 0, "unknown format: %s" % Format
Behdad Esfahbod452ab6c2013-07-23 22:57:43 -0400535
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400536 if Chain:
537 self.Coverage = ChainCoverage
538 self.ContextData = ChainContextData
539 self.RuleData = ChainRuleData
540 self.SetRuleData = ChainSetRuleData
541 else:
542 self.Coverage = Coverage
543 self.ContextData = ContextData
544 self.RuleData = RuleData
545 self.SetRuleData = SetRuleData
Behdad Esfahbod9e735722013-07-23 16:35:23 -0400546
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400547 if Format == 1:
548 self.Rule = ChainTyp+'Rule'
549 self.RuleCount = ChainTyp+'RuleCount'
550 self.RuleSet = ChainTyp+'RuleSet'
551 self.RuleSetCount = ChainTyp+'RuleSetCount'
552 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
553 elif Format == 2:
554 self.Rule = ChainTyp+'ClassRule'
555 self.RuleCount = ChainTyp+'ClassRuleCount'
556 self.RuleSet = ChainTyp+'ClassSet'
557 self.RuleSetCount = ChainTyp+'ClassSetCount'
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400558 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r)
Behdad Esfahbod89987002013-07-23 23:07:42 -0400559
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400560 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
Behdad Esfahbod98b60752013-10-14 17:49:19 +0200561 self.ClassDefIndex = 1 if Chain else 0
Behdad Esfahbod11763302013-08-14 15:33:08 -0400562 self.Input = 'Input' if Chain else 'Class'
Behdad Esfahbod27108392013-07-23 16:40:47 -0400563
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400564 if self.Format not in [1, 2, 3]:
Behdad Esfahbod318adc02013-08-13 20:09:28 -0400565 return None # Don't shoot the messenger; let it go
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400566 if not hasattr(self.__class__, "__ContextHelpers"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400567 self.__class__.__ContextHelpers = {}
568 if self.Format not in self.__class__.__ContextHelpers:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400569 helper = ContextHelper(self.__class__, self.Format)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400570 self.__class__.__ContextHelpers[self.Format] = helper
571 return self.__class__.__ContextHelpers[self.Format]
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400572
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400573@_add_method(otTables.ContextSubst,
574 otTables.ChainContextSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400575def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500576 if cur_glyphs is None: cur_glyphs = s.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400577 c = self.__classify_context()
Behdad Esfahbod1ab2dbf2013-07-23 17:17:21 -0400578
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400579 indices = c.Coverage(self).intersect(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400580 if not indices:
581 return []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400582 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs);
Behdad Esfahbod1d4fa132013-08-08 22:59:32 -0400583
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400584 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400585 ContextData = c.ContextData(self)
586 rss = getattr(self, c.RuleSet)
Behdad Esfahbod11174452013-11-18 20:20:49 -0500587 rssCount = getattr(self, c.RuleSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400588 for i in indices:
Behdad Esfahbod11174452013-11-18 20:20:49 -0500589 if i >= rssCount or not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400590 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400591 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400592 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
593 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400594 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400595 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400596 if not ll: continue
597 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400598 if chaos:
599 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400600 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400601 if seqi == 0:
602 pos_glyphs = set([c.Coverage(self).glyphs[i]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400603 else:
Behdad Esfahbodd3fdcc72013-08-14 17:59:31 -0400604 pos_glyphs = set([r.Input[seqi - 1]])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400605 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400606 chaos = chaos or lookup.may_have_non_1to1()
607 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400608 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400609 ClassDef = getattr(self, c.ClassDef)
610 indices = ClassDef.intersect(cur_glyphs)
611 ContextData = c.ContextData(self)
612 rss = getattr(self, c.RuleSet)
Behdad Esfahbod11174452013-11-18 20:20:49 -0500613 rssCount = getattr(self, c.RuleSetCount)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400614 for i in indices:
Behdad Esfahbod11174452013-11-18 20:20:49 -0500615 if i >= rssCount or not rss[i]: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400616 for r in getattr(rss[i], c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400617 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400618 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
619 for cd,klist in zip(ContextData, c.RuleData(r))):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400620 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400621 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400622 if not ll: continue
623 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400624 if chaos:
625 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400626 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400627 if seqi == 0:
628 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400629 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400630 pos_glyphs = ClassDef.intersect_class(s.glyphs,
Behdad Esfahbod11763302013-08-14 15:33:08 -0400631 getattr(r, c.Input)[seqi - 1])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400632 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400633 chaos = chaos or lookup.may_have_non_1to1()
634 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400635 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400636 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400637 return []
638 r = self
639 chaos = False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400640 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400641 if not ll: continue
642 seqi = ll.SequenceIndex
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400643 if chaos:
644 pos_glyphs = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400645 else:
Behdad Esfahbod348f8582013-08-20 11:50:04 -0400646 if seqi == 0:
647 pos_glyphs = cur_glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400648 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400649 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400650 lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400651 chaos = chaos or lookup.may_have_non_1to1()
652 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400653 else:
654 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod00776972013-07-23 15:33:00 -0400655
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400656@_add_method(otTables.ContextSubst,
657 otTables.ContextPos,
658 otTables.ChainContextSubst,
659 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400660def subset_glyphs(self, s):
661 c = self.__classify_context()
Behdad Esfahbodd8c7e102013-07-23 17:07:06 -0400662
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400663 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400664 indices = self.Coverage.subset(s.glyphs)
665 rss = getattr(self, c.RuleSet)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400666 rss = [rss[i] for i in indices]
667 for rs in rss:
668 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400669 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400670 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400671 if r and all(all(g in s.glyphs for g in glist)
672 for glist in c.RuleData(r))]
673 setattr(rs, c.Rule, ss)
674 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400675 # Prune empty subrulesets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400676 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)]
677 setattr(self, c.RuleSet, rss)
678 setattr(self, c.RuleSetCount, len(rss))
679 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400680 elif self.Format == 2:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400681 if not self.Coverage.subset(s.glyphs):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400682 return False
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400683 ContextData = c.ContextData(self)
684 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData]
Behdad Esfahbod98b60752013-10-14 17:49:19 +0200685
686 # Keep rulesets for class numbers that survived.
687 indices = klass_maps[c.ClassDefIndex]
688 rss = getattr(self, c.RuleSet)
689 rssCount = getattr(self, c.RuleSetCount)
690 rss = [rss[i] for i in indices if i < rssCount]
691 del rssCount
692 # Delete, but not renumber, unreachable rulesets.
693 indices = getattr(self, c.ClassDef).intersect(self.Coverage.glyphs)
694 rss = [rss if i in indices else None for i,rss in enumerate(rss)]
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -0500695 while rss and rss[-1] is None:
Behdad Esfahbod98b60752013-10-14 17:49:19 +0200696 del rss[-1]
697
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400698 for rs in rss:
699 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400700 ss = getattr(rs, c.Rule)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400701 ss = [r for r in ss
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400702 if r and all(all(k in klass_map for k in klist)
703 for klass_map,klist in zip(klass_maps, c.RuleData(r)))]
704 setattr(rs, c.Rule, ss)
705 setattr(rs, c.RuleCount, len(ss))
Behdad Esfahbode9a3bd62013-07-23 22:41:11 -0400706
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400707 # Remap rule classes
708 for r in ss:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400709 c.SetRuleData(r, [[klass_map.index(k) for k in klist]
710 for klass_map,klist in zip(klass_maps, c.RuleData(r))])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400711 return bool(rss)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400712 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400713 return all(x.subset(s.glyphs) for x in c.RuleData(self))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400714 else:
715 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400716
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400717@_add_method(otTables.ContextSubst,
718 otTables.ChainContextSubst,
719 otTables.ContextPos,
720 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400721def subset_lookups(self, lookup_indices):
722 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400723
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400724 if self.Format in [1, 2]:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400725 for rs in getattr(self, c.RuleSet):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400726 if not rs: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400727 for r in getattr(rs, c.Rule):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400728 if not r: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400729 setattr(r, c.LookupRecord,
730 [ll for ll in getattr(r, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400731 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400732 for ll in getattr(r, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400733 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400734 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400735 elif self.Format == 3:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400736 setattr(self, c.LookupRecord,
737 [ll for ll in getattr(self, c.LookupRecord)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400738 if ll and ll.LookupListIndex in lookup_indices])
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400739 for ll in getattr(self, c.LookupRecord):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400740 if not ll: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400741 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400742 else:
743 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400744
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400745@_add_method(otTables.ContextSubst,
746 otTables.ChainContextSubst,
747 otTables.ContextPos,
748 otTables.ChainContextPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400749def collect_lookups(self):
750 c = self.__classify_context()
Behdad Esfahbod44c2b3c2013-07-23 16:00:32 -0400751
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400752 if self.Format in [1, 2]:
753 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400754 for rs in getattr(self, c.RuleSet) if rs
755 for r in getattr(rs, c.Rule) if r
756 for ll in getattr(r, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400757 elif self.Format == 3:
758 return [ll.LookupListIndex
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400759 for ll in getattr(self, c.LookupRecord) if ll]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400760 else:
761 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod59dfc132013-07-23 15:39:20 -0400762
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400763@_add_method(otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400764def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400765 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400766 self.ExtSubTable.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400767 else:
768 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400769
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400770@_add_method(otTables.ExtensionSubst)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400771def may_have_non_1to1(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400772 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400773 return self.ExtSubTable.may_have_non_1to1()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400774 else:
775 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400776
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400777@_add_method(otTables.ExtensionSubst,
778 otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400779def prune_pre_subset(self, options):
780 if self.Format == 1:
781 return self.ExtSubTable.prune_pre_subset(options)
782 else:
783 assert 0, "unknown format: %s" % self.Format
784
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400785@_add_method(otTables.ExtensionSubst,
786 otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400787def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400788 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400789 return self.ExtSubTable.subset_glyphs(s)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400790 else:
791 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod54660612013-07-21 18:16:55 -0400792
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400793@_add_method(otTables.ExtensionSubst,
794 otTables.ExtensionPos)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400795def prune_post_subset(self, options):
796 if self.Format == 1:
797 return self.ExtSubTable.prune_post_subset(options)
798 else:
799 assert 0, "unknown format: %s" % self.Format
800
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400801@_add_method(otTables.ExtensionSubst,
802 otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400803def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400804 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400805 return self.ExtSubTable.subset_lookups(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400806 else:
807 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400808
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400809@_add_method(otTables.ExtensionSubst,
810 otTables.ExtensionPos)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400811def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400812 if self.Format == 1:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400813 return self.ExtSubTable.collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400814 else:
815 assert 0, "unknown format: %s" % self.Format
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400816
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400817@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400818def closure_glyphs(self, s, cur_glyphs=None):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400819 for st in self.SubTable:
820 if not st: continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400821 st.closure_glyphs(s, cur_glyphs)
Behdad Esfahbod610b0552013-07-23 14:52:18 -0400822
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400823@_add_method(otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400824def prune_pre_subset(self, options):
825 ret = False
826 for st in self.SubTable:
827 if not st: continue
828 if st.prune_pre_subset(options): ret = True
829 return ret
830
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400831@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400832def subset_glyphs(self, s):
833 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)]
834 self.SubTableCount = len(self.SubTable)
835 return bool(self.SubTableCount)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -0400836
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400837@_add_method(otTables.Lookup)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400838def prune_post_subset(self, options):
839 ret = False
840 for st in self.SubTable:
841 if not st: continue
842 if st.prune_post_subset(options): ret = True
843 return ret
844
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400845@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400846def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400847 for s in self.SubTable:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400848 s.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400849
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400850@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400851def collect_lookups(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400852 return _uniq_sort(sum((st.collect_lookups() for st in self.SubTable
853 if st), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400854
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400855@_add_method(otTables.Lookup)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400856def may_have_non_1to1(self):
857 return any(st.may_have_non_1to1() for st in self.SubTable if st)
Behdad Esfahbodaeacc152013-08-12 20:24:33 -0400858
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400859@_add_method(otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400860def prune_pre_subset(self, options):
861 ret = False
862 for l in self.Lookup:
863 if not l: continue
864 if l.prune_pre_subset(options): ret = True
865 return ret
866
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400867@_add_method(otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400868def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400869 "Returns the indices of nonempty lookups."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400870 return [i for i,l in enumerate(self.Lookup) if l and l.subset_glyphs(s)]
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400871
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400872@_add_method(otTables.LookupList)
Behdad Esfahbodd77f1572013-08-15 19:24:36 -0400873def prune_post_subset(self, options):
874 ret = False
875 for l in self.Lookup:
876 if not l: continue
877 if l.prune_post_subset(options): ret = True
878 return ret
879
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400880@_add_method(otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400881def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400882 self.Lookup = [self.Lookup[i] for i in lookup_indices
883 if i < self.LookupCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400884 self.LookupCount = len(self.Lookup)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400885 for l in self.Lookup:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400886 l.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400887
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400888@_add_method(otTables.LookupList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400889def closure_lookups(self, lookup_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400890 lookup_indices = _uniq_sort(lookup_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400891 recurse = lookup_indices
892 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400893 recurse_lookups = sum((self.Lookup[i].collect_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400894 for i in recurse if i < self.LookupCount), [])
895 recurse_lookups = [l for l in recurse_lookups
896 if l not in lookup_indices and l < self.LookupCount]
897 if not recurse_lookups:
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400898 return _uniq_sort(lookup_indices)
899 recurse_lookups = _uniq_sort(recurse_lookups)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400900 lookup_indices.extend(recurse_lookups)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400901 recurse = recurse_lookups
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400902
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400903@_add_method(otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400904def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400905 self.LookupListIndex = [l for l in self.LookupListIndex
906 if l in lookup_indices]
907 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400908 self.LookupListIndex = [lookup_indices.index(l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400909 for l in self.LookupListIndex]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400910 self.LookupCount = len(self.LookupListIndex)
Behdad Esfahbodd214f202013-11-26 17:42:13 -0500911 return self.LookupCount or self.FeatureParams
Behdad Esfahbod54660612013-07-21 18:16:55 -0400912
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400913@_add_method(otTables.Feature)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400914def collect_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400915 return self.LookupListIndex[:]
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_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400919 "Returns the indices of nonempty features."
Behdad Esfahbod4734be52013-08-14 19:47:42 -0400920 feature_indices = [i for i,f in enumerate(self.FeatureRecord)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400921 if f.Feature.subset_lookups(lookup_indices)]
922 self.subset_features(feature_indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400923 return feature_indices
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400924
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400925@_add_method(otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400926def collect_lookups(self, feature_indices):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400927 return _uniq_sort(sum((self.FeatureRecord[i].Feature.collect_lookups()
928 for i in feature_indices
Behdad Esfahbod1ee298d2013-08-13 20:07:09 -0400929 if i < self.FeatureCount), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400930
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400931@_add_method(otTables.FeatureList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400932def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400933 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400934 self.FeatureCount = len(self.FeatureRecord)
935 return bool(self.FeatureCount)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -0400936
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400937@_add_method(otTables.DefaultLangSys,
938 otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400939def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400940 if self.ReqFeatureIndex in feature_indices:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400941 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400942 else:
943 self.ReqFeatureIndex = 65535
944 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
945 # Now map them.
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400946 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400947 if f in feature_indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400948 self.FeatureCount = len(self.FeatureIndex)
949 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400950
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400951@_add_method(otTables.DefaultLangSys,
952 otTables.LangSys)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400953def collect_features(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400954 feature_indices = self.FeatureIndex[:]
955 if self.ReqFeatureIndex != 65535:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400956 feature_indices.append(self.ReqFeatureIndex)
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400957 return _uniq_sort(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400958
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400959@_add_method(otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400960def subset_features(self, feature_indices):
961 if(self.DefaultLangSys and
962 not self.DefaultLangSys.subset_features(feature_indices)):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400963 self.DefaultLangSys = None
964 self.LangSysRecord = [l for l in self.LangSysRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400965 if l.LangSys.subset_features(feature_indices)]
966 self.LangSysCount = len(self.LangSysRecord)
967 return bool(self.LangSysCount or self.DefaultLangSys)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400968
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400969@_add_method(otTables.Script)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400970def collect_features(self):
971 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400972 if self.DefaultLangSys:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400973 feature_indices.append(self.DefaultLangSys.collect_features())
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400974 return _uniq_sort(sum(feature_indices, []))
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400975
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400976@_add_method(otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400977def subset_features(self, feature_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400978 self.ScriptRecord = [s for s in self.ScriptRecord
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400979 if s.Script.subset_features(feature_indices)]
980 self.ScriptCount = len(self.ScriptRecord)
981 return bool(self.ScriptCount)
Behdad Esfahbod77cda412013-07-22 11:46:50 -0400982
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400983@_add_method(otTables.ScriptList)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400984def collect_features(self):
Behdad Esfahbod22f5cfc2013-08-13 20:25:37 -0400985 return _uniq_sort(sum((s.Script.collect_features()
986 for s in self.ScriptRecord), []))
Behdad Esfahbod78661bb2013-07-23 10:23:42 -0400987
Behdad Esfahbod46d260f2013-09-19 20:36:49 -0400988@_add_method(ttLib.getTableClass('GSUB'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -0400989def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -0400990 s.table = self.table
Behdad Esfahbod19d7cf22013-12-04 21:13:11 -0500991 if self.table.ScriptList:
992 feature_indices = self.table.ScriptList.collect_features()
993 else:
994 feature_indices = []
Behdad Esfahbod7e972472013-11-15 17:57:15 -0500995 if self.table.FeatureList:
996 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
997 else:
998 lookup_indices = []
999 if self.table.LookupList:
1000 while True:
1001 orig_glyphs = s.glyphs.copy()
1002 for i in lookup_indices:
1003 if i >= self.table.LookupList.LookupCount: continue
1004 if not self.table.LookupList.Lookup[i]: continue
1005 self.table.LookupList.Lookup[i].closure_glyphs(s)
1006 if orig_glyphs == s.glyphs:
1007 break
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001008 del s.table
Behdad Esfahbod610b0552013-07-23 14:52:18 -04001009
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001010@_add_method(ttLib.getTableClass('GSUB'),
1011 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001012def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001013 s.glyphs = s.glyphs_gsubed
Behdad Esfahbod7e972472013-11-15 17:57:15 -05001014 if self.table.LookupList:
1015 lookup_indices = self.table.LookupList.subset_glyphs(s)
1016 else:
1017 lookup_indices = []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001018 self.subset_lookups(lookup_indices)
1019 self.prune_lookups()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001020 return True
Behdad Esfahbod02b92062013-07-21 18:40:59 -04001021
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001022@_add_method(ttLib.getTableClass('GSUB'),
1023 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001024def subset_lookups(self, lookup_indices):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001025 """Retrains specified lookups, then removes empty features, language
1026 systems, and scripts."""
Behdad Esfahbod7e972472013-11-15 17:57:15 -05001027 if self.table.LookupList:
1028 self.table.LookupList.subset_lookups(lookup_indices)
1029 if self.table.FeatureList:
1030 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices)
1031 else:
1032 feature_indices = []
Behdad Esfahbod19d7cf22013-12-04 21:13:11 -05001033 if self.table.ScriptList:
1034 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod77cda412013-07-22 11:46:50 -04001035
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001036@_add_method(ttLib.getTableClass('GSUB'),
1037 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001038def prune_lookups(self):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001039 "Remove unreferenced lookups"
Behdad Esfahbod19d7cf22013-12-04 21:13:11 -05001040 if self.table.ScriptList:
1041 feature_indices = self.table.ScriptList.collect_features()
1042 else:
1043 feature_indices = []
Behdad Esfahbod7e972472013-11-15 17:57:15 -05001044 if self.table.FeatureList:
1045 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
1046 else:
1047 lookup_indices = []
1048 if self.table.LookupList:
1049 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices)
1050 else:
1051 lookup_indices = []
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001052 self.subset_lookups(lookup_indices)
Behdad Esfahbod78661bb2013-07-23 10:23:42 -04001053
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001054@_add_method(ttLib.getTableClass('GSUB'),
1055 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001056def subset_feature_tags(self, feature_tags):
Behdad Esfahbod7e972472013-11-15 17:57:15 -05001057 if self.table.FeatureList:
1058 feature_indices = [i for i,f in
1059 enumerate(self.table.FeatureList.FeatureRecord)
1060 if f.FeatureTag in feature_tags]
1061 self.table.FeatureList.subset_features(feature_indices)
1062 else:
1063 feature_indices = []
Behdad Esfahbod19d7cf22013-12-04 21:13:11 -05001064 if self.table.ScriptList:
1065 self.table.ScriptList.subset_features(feature_indices)
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001066
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001067@_add_method(ttLib.getTableClass('GSUB'),
1068 ttLib.getTableClass('GPOS'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001069def prune_pre_subset(self, options):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001070 if '*' not in options.layout_features:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001071 self.subset_feature_tags(options.layout_features)
1072 self.prune_lookups()
Behdad Esfahbod7e972472013-11-15 17:57:15 -05001073 if self.table.LookupList:
1074 self.table.LookupList.prune_pre_subset(options);
Behdad Esfahbodd77f1572013-08-15 19:24:36 -04001075 return True
1076
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001077@_add_method(ttLib.getTableClass('GSUB'),
1078 ttLib.getTableClass('GPOS'))
Behdad Esfahbodd77f1572013-08-15 19:24:36 -04001079def prune_post_subset(self, options):
Behdad Esfahbod9fe4eef2013-11-25 04:28:37 -05001080 table = self.table
1081 if table.ScriptList and not table.ScriptList.ScriptRecord:
1082 table.ScriptList = None
1083 if table.FeatureList and not table.FeatureList.FeatureRecord:
1084 table.FeatureList = None
1085 if table.LookupList:
1086 table.LookupList.prune_post_subset(options);
1087 if not table.LookupList.Lookup:
1088 table.LookupList = None
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001089 return True
Behdad Esfahbod356c42e2013-07-23 12:10:46 -04001090
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001091@_add_method(ttLib.getTableClass('GDEF'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001092def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001093 glyphs = s.glyphs_gsubed
1094 table = self.table
1095 if table.LigCaretList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001096 indices = table.LigCaretList.Coverage.subset(glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001097 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i]
1098 for i in indices]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001099 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001100 if table.MarkAttachClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001101 table.MarkAttachClassDef.classDefs = dict((g,v) for g,v in
1102 table.MarkAttachClassDef.
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001103 classDefs.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001104 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001105 if table.GlyphClassDef:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001106 table.GlyphClassDef.classDefs = dict((g,v) for g,v in
1107 table.GlyphClassDef.
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001108 classDefs.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001109 if g in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001110 if table.AttachList:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001111 indices = table.AttachList.Coverage.subset(glyphs)
Behdad Esfahbod98769432013-11-19 14:40:57 -05001112 GlyphCount = table.AttachList.GlyphCount
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001113 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i]
Behdad Esfahbod98769432013-11-19 14:40:57 -05001114 for i in indices
1115 if i < GlyphCount]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001116 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint)
Behdad Esfahbod5aea27d2013-11-25 04:19:42 -05001117 if hasattr(table, "MarkGlyphSetsDef") and table.MarkGlyphSetsDef:
1118 for coverage in table.MarkGlyphSetsDef.Coverage:
1119 coverage.subset(glyphs)
Behdad Esfahbod05da9702013-11-25 05:23:07 -05001120 # TODO: The following is disabled. If enabling, we need to go fixup all
1121 # lookups that use MarkFilteringSet and map their set.
1122 #indices = table.MarkGlyphSetsDef.Coverage = [c for c in table.MarkGlyphSetsDef.Coverage if c.glyphs]
Behdad Esfahbod5aea27d2013-11-25 04:19:42 -05001123 return True
1124
1125@_add_method(ttLib.getTableClass('GDEF'))
1126def prune_post_subset(self, options):
1127 table = self.table
1128 if table.LigCaretList and not table.LigCaretList.LigGlyphCount:
1129 table.LigCaretList = None
1130 if table.MarkAttachClassDef and not table.MarkAttachClassDef.classDefs:
1131 table.MarkAttachClassDef = None
1132 if table.GlyphClassDef and not table.GlyphClassDef.classDefs:
1133 table.GlyphClassDef = None
1134 if table.AttachList and not table.AttachList.GlyphCount:
1135 table.AttachList = None
1136 if hasattr(table, "MarkGlyphSetsDef") and table.MarkGlyphSetsDef and not table.MarkGlyphSetsDef.Coverage:
1137 table.MarkGlyphSetsDef = None
Behdad Esfahboda030a0d2013-11-27 17:46:15 -05001138 if table.Version == 0x00010002/0x10000:
Behdad Esfahbod5aea27d2013-11-25 04:19:42 -05001139 table.Version = 1.0
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001140 return bool(table.LigCaretList or
Behdad Esfahbod5aea27d2013-11-25 04:19:42 -05001141 table.MarkAttachClassDef or
1142 table.GlyphClassDef or
1143 table.AttachList or
Behdad Esfahboda030a0d2013-11-27 17:46:15 -05001144 (table.Version >= 0x00010002/0x10000 and table.MarkGlyphSetsDef))
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001145
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001146@_add_method(ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001147def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001148 # Prune unknown kern table types
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001149 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')]
1150 return bool(self.kernTables)
Behdad Esfahbodd4e33a72013-07-24 18:51:05 -04001151
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001152@_add_method(ttLib.getTableClass('kern'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001153def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001154 glyphs = s.glyphs_gsubed
1155 for t in self.kernTables:
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001156 t.kernTable = dict(((a,b),v) for (a,b),v in t.kernTable.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001157 if a in glyphs and b in glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001158 self.kernTables = [t for t in self.kernTables if t.kernTable]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001159 return bool(self.kernTables)
Behdad Esfahbodefb984a2013-07-21 22:26:16 -04001160
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001161@_add_method(ttLib.getTableClass('vmtx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001162def subset_glyphs(self, s):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001163 self.metrics = dict((g,v) for g,v in self.metrics.items() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001164 return bool(self.metrics)
Behdad Esfahbodc7160442013-07-22 14:29:08 -04001165
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001166@_add_method(ttLib.getTableClass('hmtx'))
1167def subset_glyphs(self, s):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001168 self.metrics = dict((g,v) for g,v in self.metrics.items() if g in s.glyphs)
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001169 return True # Required table
1170
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001171@_add_method(ttLib.getTableClass('hdmx'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001172def subset_glyphs(self, s):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001173 self.hdmx = dict((sz,dict((g,v) for g,v in l.items() if g in s.glyphs))
1174 for sz,l in self.hdmx.items())
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001175 return bool(self.hdmx)
Behdad Esfahbod75e14fc2013-07-22 14:49:54 -04001176
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001177@_add_method(ttLib.getTableClass('VORG'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001178def subset_glyphs(self, s):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001179 self.VOriginRecords = dict((g,v) for g,v in self.VOriginRecords.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001180 if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001181 self.numVertOriginYMetrics = len(self.VOriginRecords)
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001182 return True # Never drop; has default metrics
Behdad Esfahbode45d6af2013-07-22 15:29:17 -04001183
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001184@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001185def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001186 if not options.glyph_names:
1187 self.formatType = 3.0
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001188 return True # Required table
Behdad Esfahbod42648242013-07-23 12:56:06 -04001189
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001190@_add_method(ttLib.getTableClass('post'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001191def subset_glyphs(self, s):
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001192 self.extraNames = [] # This seems to do it
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001193 return True # Required table
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001194
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001195@_add_method(ttLib.getTableModule('glyf').Glyph)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001196def remapComponentsFast(self, indices):
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001197 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001198 return # Not composite
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001199 data = array.array("B", self.data)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001200 i = 10
1201 more = 1
1202 while more:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001203 flags =(data[i] << 8) | data[i+1]
1204 glyphID =(data[i+2] << 8) | data[i+3]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001205 # Remap
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001206 glyphID = indices.index(glyphID)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001207 data[i+2] = glyphID >> 8
1208 data[i+3] = glyphID & 0xFF
1209 i += 4
1210 flags = int(flags)
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001211
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001212 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS
Behdad Esfahbod574ce792013-08-13 20:51:44 -04001213 else: i += 2
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04001214 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE
1215 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE
1216 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
1217 more = flags & 0x0020 # MORE_COMPONENTS
1218
Behdad Esfahbodd816b7f2013-08-16 16:18:40 -04001219 self.data = data.tostring()
Behdad Esfahbod4cf7a802013-07-24 16:08:35 -04001220
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001221@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001222def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001223 decompose = s.glyphs
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001224 while True:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001225 components = set()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001226 for g in decompose:
1227 if g not in self.glyphs:
1228 continue
1229 gl = self.glyphs[g]
Behdad Esfahbod043108c2013-09-27 12:59:47 -04001230 for c in gl.getComponentNames(self):
Behdad Esfahbod626107c2013-09-20 14:10:31 -04001231 if c not in s.glyphs:
1232 components.add(c)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001233 components = set(c for c in components if c not in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001234 if not components:
1235 break
1236 decompose = components
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001237 s.glyphs.update(components)
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001238
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001239@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001240def prune_pre_subset(self, options):
1241 if options.notdef_glyph and not options.notdef_outline:
1242 g = self[self.glyphOrder[0]]
1243 # Yay, easy!
1244 g.__dict__.clear()
1245 g.data = ""
1246 return True
1247
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001248@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001249def subset_glyphs(self, s):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001250 self.glyphs = dict((g,v) for g,v in self.glyphs.items() if g in s.glyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001251 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001252 for v in self.glyphs.values():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001253 if hasattr(v, "data"):
1254 v.remapComponentsFast(indices)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001255 else:
Behdad Esfahbod318adc02013-08-13 20:09:28 -04001256 pass # No need
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001257 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs]
Behdad Esfahbodb69b6712013-08-29 18:17:31 -04001258 # Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset.
1259 return True
Behdad Esfahbod861d9152013-07-22 16:47:24 -04001260
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001261@_add_method(ttLib.getTableClass('glyf'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001262def prune_post_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001263 if not options.hinting:
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001264 for v in self.glyphs.values():
Behdad Esfahbod626107c2013-09-20 14:10:31 -04001265 v.removeHinting()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001266 return True
Behdad Esfahboded98c612013-07-23 12:37:41 -04001267
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001268@_add_method(ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001269def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001270 cff = self.cff
Behdad Esfahbode0622072013-09-10 14:33:19 -04001271 # CFF table must have one font only
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001272 cff.fontNames = cff.fontNames[:1]
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001273
1274 if options.notdef_glyph and not options.notdef_outline:
1275 for fontname in cff.keys():
1276 font = cff[fontname]
1277 c,_ = font.CharStrings.getItemAndSelector('.notdef')
Behdad Esfahbod21582e92013-09-12 16:47:52 -04001278 # XXX we should preserve the glyph width
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001279 c.bytecode = '\x0e' # endchar
1280 c.program = None
1281
Behdad Esfahbod50f83ef2013-08-29 18:18:17 -04001282 return True # bool(cff.fontNames)
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001283
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001284@_add_method(ttLib.getTableClass('CFF '))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001285def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001286 cff = self.cff
1287 for fontname in cff.keys():
1288 font = cff[fontname]
1289 cs = font.CharStrings
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001290
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001291 # Load all glyphs
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001292 for g in font.charset:
1293 if g not in s.glyphs: continue
1294 c,sel = cs.getItemAndSelector(g)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001295
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001296 if cs.charStringsAreIndexed:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001297 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs]
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001298 csi = cs.charStringsIndex
1299 csi.items = [csi.items[i] for i in indices]
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001300 csi.count = len(csi.items)
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001301 del csi.file, csi.offsets
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001302 if hasattr(font, "FDSelect"):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001303 sel = font.FDSelect
1304 sel.format = None
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001305 sel.gidArray = [sel.gidArray[i] for i in indices]
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001306 cs.charStrings = dict((g,indices.index(v))
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001307 for g,v in cs.charStrings.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001308 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001309 else:
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001310 cs.charStrings = dict((g,v)
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001311 for g,v in cs.charStrings.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001312 if g in s.glyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001313 font.charset = [g for g in font.charset if g in s.glyphs]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001314 font.numGlyphs = len(font.charset)
Behdad Esfahbod9290fb42013-08-14 17:48:31 -04001315
Behdad Esfahbod50f83ef2013-08-29 18:18:17 -04001316 return True # any(cff[fontname].numGlyphs for fontname in cff.keys())
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001317
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001318@_add_method(psCharStrings.T2CharString)
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001319def subset_subroutines(self, subrs, gsubrs):
1320 p = self.program
Behdad Esfahbode0622072013-09-10 14:33:19 -04001321 assert len(p)
Behdad Esfahbodb466efe2013-11-27 03:34:35 -05001322 for i in range(1, len(p)):
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001323 if p[i] == 'callsubr':
Behdad Esfahbodc2e2e832013-11-27 04:15:27 -05001324 assert isinstance(p[i-1], int)
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001325 p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias
1326 elif p[i] == 'callgsubr':
Behdad Esfahbodc2e2e832013-11-27 04:15:27 -05001327 assert isinstance(p[i-1], int)
Behdad Esfahbodb4ea9532013-08-14 19:37:39 -04001328 p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias
1329
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001330@_add_method(psCharStrings.T2CharString)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001331def drop_hints(self):
1332 hints = self._hints
1333
1334 if hints.has_hint:
1335 self.program = self.program[hints.last_hint:]
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001336 if hasattr(self, 'width'):
1337 # Insert width back if needed
1338 if self.width != self.private.defaultWidthX:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001339 self.program.insert(0, self.width - self.private.nominalWidthX)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001340
1341 if hints.has_hintmask:
1342 i = 0
1343 p = self.program
1344 while i < len(p):
1345 if p[i] in ['hintmask', 'cntrmask']:
1346 assert i + 1 <= len(p)
1347 del p[i:i+2]
1348 continue
1349 i += 1
1350
Behdad Esfahbod2a70f4a2013-10-28 15:18:07 +01001351 # TODO: we currently don't drop calls to "empty" subroutines.
1352
Behdad Esfahbode0622072013-09-10 14:33:19 -04001353 assert len(self.program)
1354
1355 del self._hints
1356
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001357class _MarkingT2Decompiler(psCharStrings.SimpleT2Decompiler):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001358
1359 def __init__(self, localSubrs, globalSubrs):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001360 psCharStrings.SimpleT2Decompiler.__init__(self,
1361 localSubrs,
1362 globalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001363 for subrs in [localSubrs, globalSubrs]:
1364 if subrs and not hasattr(subrs, "_used"):
1365 subrs._used = set()
1366
1367 def op_callsubr(self, index):
1368 self.localSubrs._used.add(self.operandStack[-1]+self.localBias)
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001369 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001370
1371 def op_callgsubr(self, index):
1372 self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias)
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001373 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001374
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001375class _DehintingT2Decompiler(psCharStrings.SimpleT2Decompiler):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001376
Behdad Esfahbod1f262892013-11-28 14:26:39 -05001377 class Hints(object):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001378 def __init__(self):
1379 # Whether calling this charstring produces any hint stems
1380 self.has_hint = False
1381 # Index to start at to drop all hints
1382 self.last_hint = 0
1383 # Index up to which we know more hints are possible. Only
1384 # relevant if status is 0 or 1.
1385 self.last_checked = 0
1386 # The status means:
1387 # 0: after dropping hints, this charstring is empty
1388 # 1: after dropping hints, there may be more hints continuing after this
1389 # 2: no more hints possible after this charstring
1390 self.status = 0
1391 # Has hintmask instructions; not recursive
1392 self.has_hintmask = False
1393 pass
1394
1395 def __init__(self, css, localSubrs, globalSubrs):
1396 self._css = css
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001397 psCharStrings.SimpleT2Decompiler.__init__(self,
1398 localSubrs,
1399 globalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001400
1401 def execute(self, charString):
1402 old_hints = charString._hints if hasattr(charString, '_hints') else None
1403 charString._hints = self.Hints()
1404
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001405 psCharStrings.SimpleT2Decompiler.execute(self, charString)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001406
1407 hints = charString._hints
1408
1409 if hints.has_hint or hints.has_hintmask:
1410 self._css.add(charString)
1411
1412 if hints.status != 2:
1413 # Check from last_check, make sure we didn't have any operators.
Behdad Esfahbodb466efe2013-11-27 03:34:35 -05001414 for i in range(hints.last_checked, len(charString.program) - 1):
Behdad Esfahbodc2e2e832013-11-27 04:15:27 -05001415 if isinstance(charString.program[i], str):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001416 hints.status = 2
1417 break;
1418 else:
1419 hints.status = 1 # There's *something* here
1420 hints.last_checked = len(charString.program)
1421
1422 if old_hints:
1423 assert hints.__dict__ == old_hints.__dict__
1424
1425 def op_callsubr(self, index):
1426 subr = self.localSubrs[self.operandStack[-1]+self.localBias]
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001427 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001428 self.processSubr(index, subr)
1429
1430 def op_callgsubr(self, index):
1431 subr = self.globalSubrs[self.operandStack[-1]+self.globalBias]
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001432 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001433 self.processSubr(index, subr)
1434
1435 def op_hstem(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001436 psCharStrings.SimpleT2Decompiler.op_hstem(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001437 self.processHint(index)
1438 def op_vstem(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001439 psCharStrings.SimpleT2Decompiler.op_vstem(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001440 self.processHint(index)
1441 def op_hstemhm(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001442 psCharStrings.SimpleT2Decompiler.op_hstemhm(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001443 self.processHint(index)
1444 def op_vstemhm(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001445 psCharStrings.SimpleT2Decompiler.op_vstemhm(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001446 self.processHint(index)
1447 def op_hintmask(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001448 psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001449 self.processHintmask(index)
1450 def op_cntrmask(self, index):
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001451 psCharStrings.SimpleT2Decompiler.op_cntrmask(self, index)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001452 self.processHintmask(index)
1453
1454 def processHintmask(self, index):
1455 cs = self.callingStack[-1]
1456 hints = cs._hints
1457 hints.has_hintmask = True
1458 if hints.status != 2 and hints.has_hint:
1459 # Check from last_check, see if we may be an implicit vstem
Behdad Esfahbodb466efe2013-11-27 03:34:35 -05001460 for i in range(hints.last_checked, index - 1):
Behdad Esfahbodc2e2e832013-11-27 04:15:27 -05001461 if isinstance(cs.program[i], str):
Behdad Esfahbod84763142013-09-10 19:00:48 -04001462 hints.status = 2
Behdad Esfahbode0622072013-09-10 14:33:19 -04001463 break;
Behdad Esfahbod84763142013-09-10 19:00:48 -04001464 if hints.status != 2:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001465 # We are an implicit vstem
1466 hints.last_hint = index + 1
Behdad Esfahbod84763142013-09-10 19:00:48 -04001467 hints.status = 0
Behdad Esfahbod2a70f4a2013-10-28 15:18:07 +01001468 hints.last_checked = index + 1
Behdad Esfahbode0622072013-09-10 14:33:19 -04001469
1470 def processHint(self, index):
1471 cs = self.callingStack[-1]
1472 hints = cs._hints
1473 hints.has_hint = True
1474 hints.last_hint = index
1475 hints.last_checked = index
1476
1477 def processSubr(self, index, subr):
1478 cs = self.callingStack[-1]
1479 hints = cs._hints
1480 subr_hints = subr._hints
1481
1482 if subr_hints.has_hint:
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001483 if hints.status != 2:
1484 hints.has_hint = True
Behdad Esfahbod99536852013-09-12 00:23:11 -04001485 hints.last_checked = index
1486 hints.status = subr_hints.status
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001487 # Decide where to chop off from
1488 if subr_hints.status == 0:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001489 hints.last_hint = index
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001490 else:
Behdad Esfahbod99536852013-09-12 00:23:11 -04001491 hints.last_hint = index - 2 # Leave the subr call in
Behdad Esfahbode0622072013-09-10 14:33:19 -04001492 else:
Behdad Esfahbod285d7b82013-09-10 20:30:47 -04001493 # In my understanding, this is a font bug. Ie. it has hint stems
1494 # *after* path construction. I've seen this in widespread fonts.
1495 # Best to ignore the hints I suppose...
1496 pass
1497 #assert 0
Behdad Esfahbode0622072013-09-10 14:33:19 -04001498 else:
1499 hints.status = max(hints.status, subr_hints.status)
1500 if hints.status != 2:
1501 # Check from last_check, make sure we didn't have
1502 # any operators.
Behdad Esfahbodb466efe2013-11-27 03:34:35 -05001503 for i in range(hints.last_checked, index - 1):
Behdad Esfahbodc2e2e832013-11-27 04:15:27 -05001504 if isinstance(cs.program[i], str):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001505 hints.status = 2
1506 break;
1507 hints.last_checked = index
Behdad Esfahbod2a70f4a2013-10-28 15:18:07 +01001508 if hints.status != 2:
1509 # Decide where to chop off from
1510 if subr_hints.status == 0:
1511 hints.last_hint = index
1512 else:
1513 hints.last_hint = index - 2 # Leave the subr call in
Behdad Esfahbode0622072013-09-10 14:33:19 -04001514
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001515@_add_method(ttLib.getTableClass('CFF '))
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001516def prune_post_subset(self, options):
1517 cff = self.cff
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001518 for fontname in cff.keys():
1519 font = cff[fontname]
1520 cs = font.CharStrings
1521
Behdad Esfahbode0622072013-09-10 14:33:19 -04001522
1523 #
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001524 # Drop unused FontDictionaries
Behdad Esfahbode0622072013-09-10 14:33:19 -04001525 #
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001526 if hasattr(font, "FDSelect"):
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001527 sel = font.FDSelect
1528 indices = _uniq_sort(sel.gidArray)
1529 sel.gidArray = [indices.index (ss) for ss in sel.gidArray]
1530 arr = font.FDArray
Behdad Esfahbod3c20a132013-08-14 19:39:00 -04001531 arr.items = [arr[i] for i in indices]
Behdad Esfahbodd315c912013-08-14 18:18:51 -04001532 arr.count = len(arr.items)
1533 del arr.file, arr.offsets
Behdad Esfahbod1a4e72e2013-08-13 15:46:37 -04001534
Behdad Esfahbode0622072013-09-10 14:33:19 -04001535
1536 #
1537 # Drop hints if not needed
1538 #
1539 if not options.hinting:
1540
1541 #
1542 # This can be tricky, but doesn't have to. What we do is:
1543 #
1544 # - Run all used glyph charstrings and recurse into subroutines,
1545 # - For each charstring (including subroutines), if it has any
1546 # of the hint stem operators, we mark it as such. Upon returning,
1547 # for each charstring we note all the subroutine calls it makes
1548 # that (recursively) contain a stem,
1549 # - Dropping hinting then consists of the following two ops:
1550 # * Drop the piece of the program in each charstring before the
1551 # last call to a stem op or a stem-calling subroutine,
1552 # * Drop all hintmask operations.
1553 # - It's trickier... A hintmask right after hints and a few numbers
1554 # will act as an implicit vstemhm. As such, we track whether
1555 # we have seen any non-hint operators so far and do the right
1556 # thing, recursively... Good luck understanding that :(
1557 #
1558 css = set()
1559 for g in font.charset:
1560 c,sel = cs.getItemAndSelector(g)
1561 # Make sure it's decompiled. We want our "decompiler" to walk
1562 # the program, not the bytecode.
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001563 c.draw(basePen.NullPen())
Behdad Esfahbode0622072013-09-10 14:33:19 -04001564 subrs = getattr(c.private, "Subrs", [])
1565 decompiler = _DehintingT2Decompiler(css, subrs, c.globalSubrs)
1566 decompiler.execute(c)
1567 for charstring in css:
1568 charstring.drop_hints()
1569
Behdad Esfahbod16fc3232013-09-30 15:09:27 -04001570 # Drop font-wide hinting values
1571 all_privs = []
1572 if hasattr(font, 'FDSelect'):
1573 all_privs.extend(fd.Private for fd in font.FDArray)
1574 else:
1575 all_privs.append(font.Private)
1576 for priv in all_privs:
Behdad Esfahbod4d99d142013-10-28 13:15:08 +01001577 for k in ['BlueValues', 'OtherBlues', 'FamilyBlues', 'FamilyOtherBlues',
1578 'BlueScale', 'BlueShift', 'BlueFuzz',
1579 'StemSnapH', 'StemSnapV', 'StdHW', 'StdVW']:
Behdad Esfahbod16fc3232013-09-30 15:09:27 -04001580 if hasattr(priv, k):
1581 setattr(priv, k, None)
1582
Behdad Esfahbode0622072013-09-10 14:33:19 -04001583
1584 #
1585 # Renumber subroutines to remove unused ones
1586 #
1587
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001588 # Mark all used subroutines
1589 for g in font.charset:
1590 c,sel = cs.getItemAndSelector(g)
1591 subrs = getattr(c.private, "Subrs", [])
1592 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs)
1593 decompiler.execute(c)
1594
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001595 all_subrs = [font.GlobalSubrs]
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001596 if hasattr(font, 'FDSelect'):
Behdad Esfahbode0622072013-09-10 14:33:19 -04001597 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs') and fd.Private.Subrs)
1598 elif hasattr(font.Private, 'Subrs') and font.Private.Subrs:
Behdad Esfahbodcbcaccf2013-08-30 16:21:38 -04001599 all_subrs.append(font.Private.Subrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001600
1601 subrs = set(subrs) # Remove duplicates
1602
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001603 # Prepare
1604 for subrs in all_subrs:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001605 if not hasattr(subrs, '_used'):
1606 subrs._used = set()
1607 subrs._used = _uniq_sort(subrs._used)
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001608 subrs._old_bias = psCharStrings.calcSubrBias(subrs)
1609 subrs._new_bias = psCharStrings.calcSubrBias(subrs._used)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001610
Behdad Esfahboded107712013-08-14 19:54:13 -04001611 # Renumber glyph charstrings
1612 for g in font.charset:
1613 c,sel = cs.getItemAndSelector(g)
1614 subrs = getattr(c.private, "Subrs", [])
1615 c.subset_subroutines (subrs, font.GlobalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001616
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001617 # Renumber subroutines themselves
1618 for subrs in all_subrs:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001619
1620 if subrs == font.GlobalSubrs:
1621 if not hasattr(font, 'FDSelect') and hasattr(font.Private, 'Subrs'):
1622 local_subrs = font.Private.Subrs
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001623 else:
Behdad Esfahbode0622072013-09-10 14:33:19 -04001624 local_subrs = []
1625 else:
1626 local_subrs = subrs
1627
1628 subrs.items = [subrs.items[i] for i in subrs._used]
1629 subrs.count = len(subrs.items)
1630 del subrs.file
1631 if hasattr(subrs, 'offsets'):
1632 del subrs.offsets
1633
Behdad Esfahbodb466efe2013-11-27 03:34:35 -05001634 for i in range (subrs.count):
Behdad Esfahbod83f1f5c2013-08-30 16:20:08 -04001635 subrs[i].subset_subroutines (local_subrs, font.GlobalSubrs)
Behdad Esfahbode0622072013-09-10 14:33:19 -04001636
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001637 # Cleanup
1638 for subrs in all_subrs:
Behdad Esfahbod2f3a4b92013-08-14 19:18:50 -04001639 del subrs._used, subrs._old_bias, subrs._new_bias
1640
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001641 return True
Behdad Esfahbod2b677c82013-07-23 13:37:13 -04001642
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001643@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001644def closure_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001645 tables = [t for t in self.tables
1646 if t.platformID == 3 and t.platEncID in [1, 10]]
1647 for u in s.unicodes_requested:
1648 found = False
1649 for table in tables:
1650 if u in table.cmap:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001651 s.glyphs.add(table.cmap[u])
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001652 found = True
1653 break
1654 if not found:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001655 s.log("No glyph for Unicode value %s; skipping." % u)
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001656
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001657@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001658def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001659 if not options.legacy_cmap:
1660 # Drop non-Unicode / non-Symbol cmaps
1661 self.tables = [t for t in self.tables
1662 if t.platformID == 3 and t.platEncID in [0, 1, 10]]
1663 if not options.symbol_cmap:
1664 self.tables = [t for t in self.tables
1665 if t.platformID == 3 and t.platEncID in [1, 10]]
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001666 # TODO(behdad) Only keep one subtable?
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001667 # For now, drop format=0 which can't be subset_glyphs easily?
1668 self.tables = [t for t in self.tables if t.format != 0]
Behdad Esfahbodfd92d4c2013-09-19 19:43:09 -04001669 self.numSubTables = len(self.tables)
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001670 return True # Required table
Behdad Esfahbodabb50a12013-07-23 12:58:37 -04001671
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001672@_add_method(ttLib.getTableClass('cmap'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001673def subset_glyphs(self, s):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001674 s.glyphs = s.glyphs_cmaped
1675 for t in self.tables:
1676 # For reasons I don't understand I need this here
1677 # to force decompilation of the cmap format 14.
1678 try:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001679 getattr(t, "asdf")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001680 except AttributeError:
1681 pass
1682 if t.format == 14:
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001683 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None).
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001684 t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs])
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001685 for v,l in t.uvsDict.items())
1686 t.uvsDict = dict((v,l) for v,l in t.uvsDict.items() if l)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001687 else:
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001688 t.cmap = dict((u,g) for u,g in t.cmap.items()
Behdad Esfahbodd73f2252013-08-16 10:58:25 -04001689 if g in s.glyphs_requested or u in s.unicodes_requested)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001690 self.tables = [t for t in self.tables
Behdad Esfahbod4734be52013-08-14 19:47:42 -04001691 if (t.cmap if t.format != 14 else t.uvsDict)]
Behdad Esfahbodfd92d4c2013-09-19 19:43:09 -04001692 self.numSubTables = len(self.tables)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001693 # TODO(behdad) Convert formats when needed.
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001694 # In particular, if we have a format=12 without non-BMP
1695 # characters, either drop format=12 one or convert it
1696 # to format=4 if there's not one.
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001697 return True # Required table
Behdad Esfahbod61addb42013-07-23 11:03:49 -04001698
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001699@_add_method(ttLib.getTableClass('name'))
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001700def prune_pre_subset(self, options):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001701 if '*' not in options.name_IDs:
1702 self.names = [n for n in self.names if n.nameID in options.name_IDs]
1703 if not options.name_legacy:
1704 self.names = [n for n in self.names
1705 if n.platformID == 3 and n.platEncID == 1]
1706 if '*' not in options.name_languages:
1707 self.names = [n for n in self.names if n.langID in options.name_languages]
Behdad Esfahboda6241e62013-10-28 13:09:25 +01001708 return True # Required table
Behdad Esfahbod653e9742013-07-22 15:17:12 -04001709
Behdad Esfahbod8c646f62013-07-22 15:06:23 -04001710
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001711# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange?
Behdad Esfahbod26560d22013-10-26 22:03:35 +02001712# TODO(behdad) Drop AAT tables.
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001713# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
Behdad Esfahbod852e8a52013-08-29 18:19:22 -04001714# TODO(behdad) Drop empty GSUB/GPOS, and GDEF if no GSUB/GPOS left
1715# TODO(behdad) Drop GDEF subitems if unused by lookups
Behdad Esfahbod10195332013-08-14 19:55:24 -04001716# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF)
Behdad Esfahbod71f7c742013-08-13 20:16:16 -04001717# TODO(behdad) Text direction considerations.
1718# TODO(behdad) Text script / language considerations.
Behdad Esfahbodcc8fc782013-11-26 22:53:04 -05001719# TODO(behdad) Optionally drop 'kern' table if GPOS available
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04001720
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001721class Options(object):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001722
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001723 class UnknownOptionError(Exception):
1724 pass
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001725
Behdad Esfahboda17743f2013-08-28 17:14:53 -04001726 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ',
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001727 'PCLT', 'LTSH']
1728 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
1729 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color
1730 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2',
1731 'loca', 'name', 'cvt ', 'fpgm', 'prep']
1732 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX']
Behdad Esfahbod26d9ee72013-08-13 16:55:01 -04001733
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001734 # Based on HarfBuzz shapers
1735 _layout_features_groups = {
1736 # Default shaper
1737 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
1738 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
1739 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
1740 'ltr': ['ltra', 'ltrm'],
1741 'rtl': ['rtla', 'rtlm'],
1742 # Complex shapers
1743 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
1744 'cswh', 'mset'],
1745 'hangul': ['ljmo', 'vjmo', 'tjmo'],
Behdad Esfahbod3977d3e2013-10-14 17:49:12 +02001746 'tibetan': ['abvs', 'blws', 'abvm', 'blwm'],
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001747 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
1748 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
1749 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
1750 }
1751 _layout_features_default = _uniq_sort(sum(
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001752 iter(_layout_features_groups.values()), []))
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001753
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001754 drop_tables = _drop_tables_default
1755 no_subset_tables = _no_subset_tables_default
1756 hinting_tables = _hinting_tables_default
1757 layout_features = _layout_features_default
Behdad Esfahbodfe6bc4c2013-11-02 11:10:23 +00001758 hinting = True
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001759 glyph_names = False
1760 legacy_cmap = False
1761 symbol_cmap = False
1762 name_IDs = [1, 2] # Family and Style
1763 name_legacy = False
1764 name_languages = [0x0409] # English
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001765 notdef_glyph = True # gid0 for TrueType / .notdef for CFF
Behdad Esfahbod2d82c322013-08-29 18:02:48 -04001766 notdef_outline = False # No need for notdef to have an outline really
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001767 recommended_glyphs = False # gid1, gid2, gid3 for TrueType
Behdad Esfahbode911de12013-08-16 12:42:34 -04001768 recalc_bounds = False # Recalculate font bounding boxes
Behdad Esfahbod03d78da2013-08-29 16:42:00 -04001769 canonical_order = False # Order tables as recommended
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04001770 flavor = None # May be 'woff'
Behdad Esfahbod9eeeb4e2013-08-13 16:58:50 -04001771
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001772 def __init__(self, **kwargs):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001773
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001774 self.set(**kwargs)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001775
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001776 def set(self, **kwargs):
Behdad Esfahbod6890d052013-11-27 06:26:35 -05001777 for k,v in kwargs.items():
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001778 if not hasattr(self, k):
Behdad Esfahbodac10d812013-09-03 18:29:58 -04001779 raise self.UnknownOptionError("Unknown option '%s'" % k)
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001780 setattr(self, k, v)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001781
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001782 def parse_opts(self, argv, ignore_unknown=False):
1783 ret = []
1784 opts = {}
1785 for a in argv:
1786 orig_a = a
1787 if not a.startswith('--'):
1788 ret.append(a)
1789 continue
1790 a = a[2:]
1791 i = a.find('=')
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001792 op = '='
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001793 if i == -1:
1794 if a.startswith("no-"):
1795 k = a[3:]
1796 v = False
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001797 else:
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001798 k = a
1799 v = True
1800 else:
1801 k = a[:i]
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001802 if k[-1] in "-+":
1803 op = k[-1]+'=' # Ops is '-=' or '+=' now.
1804 k = k[:-1]
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001805 v = a[i+1:]
1806 k = k.replace('-', '_')
1807 if not hasattr(self, k):
Behdad Esfahbod9e6ef942013-12-04 16:31:44 -05001808 if ignore_unknown is True or k in ignore_unknown:
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001809 ret.append(orig_a)
1810 continue
1811 else:
1812 raise self.UnknownOptionError("Unknown option '%s'" % a)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001813
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001814 ov = getattr(self, k)
1815 if isinstance(ov, bool):
1816 v = bool(v)
1817 elif isinstance(ov, int):
1818 v = int(v)
1819 elif isinstance(ov, list):
Behdad Esfahbod0fc55022013-08-15 19:06:48 -04001820 vv = v.split(',')
1821 if vv == ['']:
1822 vv = []
Behdad Esfahbod87c8c502013-08-16 14:44:09 -04001823 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 -04001824 if op == '=':
1825 v = vv
1826 elif op == '+=':
1827 v = ov
1828 v.extend(vv)
1829 elif op == '-=':
1830 v = ov
1831 for x in vv:
1832 if x in v:
1833 v.remove(x)
1834 else:
Behdad Esfahbod153ec402013-12-04 01:15:46 -05001835 assert False
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001836
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001837 opts[k] = v
1838 self.set(**opts)
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001839
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001840 return ret
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001841
1842
Behdad Esfahbod5d4f99d2013-08-13 20:57:59 -04001843class Subsetter(object):
1844
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001845 def __init__(self, options=None, log=None):
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001846
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001847 if not log:
1848 log = Logger()
1849 if not options:
1850 options = Options()
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04001851
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001852 self.options = options
1853 self.log = log
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001854 self.unicodes_requested = set()
1855 self.glyphs_requested = set()
1856 self.glyphs = set()
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001857
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001858 def populate(self, glyphs=[], unicodes=[], text=""):
1859 self.unicodes_requested.update(unicodes)
Behdad Esfahbodb21c9d32013-11-27 18:09:08 -05001860 if isinstance(text, bytes):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001861 text = text.decode("utf8")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001862 for u in text:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001863 self.unicodes_requested.add(ord(u))
1864 self.glyphs_requested.update(glyphs)
1865 self.glyphs.update(glyphs)
Behdad Esfahbod3d513b72013-07-31 14:11:40 -04001866
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001867 def _prune_pre_subset(self, font):
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001868
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001869 for tag in font.keys():
1870 if tag == 'GlyphOrder': continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001871
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001872 if(tag in self.options.drop_tables or
1873 (tag in self.options.hinting_tables and not self.options.hinting)):
1874 self.log(tag, "dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001875 del font[tag]
1876 continue
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001877
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001878 clazz = ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001879
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001880 if hasattr(clazz, 'prune_pre_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001881 table = font[tag]
Behdad Esfahbod010c5f92013-09-10 20:54:46 -04001882 self.log.lapse("load '%s'" % tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001883 retain = table.prune_pre_subset(self.options)
1884 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001885 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001886 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001887 del font[tag]
1888 continue
1889 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001890 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001891
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001892 def _closure_glyphs(self, font):
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001893
Behdad Esfahbod05a28622013-12-04 23:05:59 -05001894 realGlyphs = set(font.getGlyphOrder())
1895
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001896 self.glyphs = self.glyphs_requested.copy()
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001897
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001898 if 'cmap' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001899 font['cmap'].closure_glyphs(self)
Behdad Esfahbod05a28622013-12-04 23:05:59 -05001900 self.glyphs.intersection_update(realGlyphs)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001901 self.glyphs_cmaped = self.glyphs
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001902
Behdad Esfahbod04f3a192013-08-29 16:56:06 -04001903 if self.options.notdef_glyph:
1904 if 'glyf' in font:
1905 self.glyphs.add(font.getGlyphName(0))
1906 self.log("Added gid0 to subset")
1907 else:
1908 self.glyphs.add('.notdef')
1909 self.log("Added .notdef to subset")
1910 if self.options.recommended_glyphs:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001911 if 'glyf' in font:
Behdad Esfahbod05a28622013-12-04 23:05:59 -05001912 for i in min(range(4), len(font.getGlyphOrder())):
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001913 self.glyphs.add(font.getGlyphName(i))
1914 self.log("Added first four glyphs to subset")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001915
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001916 if 'GSUB' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001917 self.log("Closing glyph list over 'GSUB': %d glyphs before" %
1918 len(self.glyphs))
1919 self.log.glyphs(self.glyphs, font=font)
1920 font['GSUB'].closure_glyphs(self)
Behdad Esfahbod05a28622013-12-04 23:05:59 -05001921 self.glyphs.intersection_update(realGlyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001922 self.log("Closed glyph list over 'GSUB': %d glyphs after" %
1923 len(self.glyphs))
1924 self.log.glyphs(self.glyphs, font=font)
1925 self.log.lapse("close glyph list over 'GSUB'")
1926 self.glyphs_gsubed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001927
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001928 if 'glyf' in font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001929 self.log("Closing glyph list over 'glyf': %d glyphs before" %
1930 len(self.glyphs))
1931 self.log.glyphs(self.glyphs, font=font)
1932 font['glyf'].closure_glyphs(self)
Behdad Esfahbod05a28622013-12-04 23:05:59 -05001933 self.glyphs.intersection_update(realGlyphs)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001934 self.log("Closed glyph list over 'glyf': %d glyphs after" %
1935 len(self.glyphs))
1936 self.log.glyphs(self.glyphs, font=font)
1937 self.log.lapse("close glyph list over 'glyf'")
1938 self.glyphs_glyfed = self.glyphs.copy()
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001939
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001940 self.glyphs_all = self.glyphs.copy()
Behdad Esfahbod8c8ff452013-07-31 19:47:37 -04001941
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001942 self.log("Retaining %d glyphs: " % len(self.glyphs_all))
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001943
Behdad Esfahbodebcad972013-12-04 23:00:52 -05001944 del self.glyphs
1945
1946
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001947 def _subset_glyphs(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001948 for tag in font.keys():
1949 if tag == 'GlyphOrder': continue
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001950 clazz = ttLib.getTableClass(tag)
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001951
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001952 if tag in self.options.no_subset_tables:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001953 self.log(tag, "subsetting not needed")
1954 elif hasattr(clazz, 'subset_glyphs'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001955 table = font[tag]
1956 self.glyphs = self.glyphs_all
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001957 retain = table.subset_glyphs(self)
Behdad Esfahbodebcad972013-12-04 23:00:52 -05001958 del self.glyphs
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001959 self.log.lapse("subset '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001960 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001961 self.log(tag, "subsetted to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001962 del font[tag]
1963 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001964 self.log(tag, "subsetted")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001965 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001966 self.log(tag, "NOT subset; don't know how to subset; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001967 del font[tag]
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001968
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001969 glyphOrder = font.getGlyphOrder()
1970 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001971 font.setGlyphOrder(glyphOrder)
1972 font._buildReverseGlyphOrderDict()
1973 self.log.lapse("subset GlyphOrder")
Behdad Esfahbod2fb90e22013-07-31 20:04:08 -04001974
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001975 def _prune_post_subset(self, font):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001976 for tag in font.keys():
1977 if tag == 'GlyphOrder': continue
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04001978 clazz = ttLib.getTableClass(tag)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001979 if hasattr(clazz, 'prune_post_subset'):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001980 table = font[tag]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001981 retain = table.prune_post_subset(self.options)
1982 self.log.lapse("prune '%s'" % tag)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001983 if not retain:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001984 self.log(tag, "pruned to empty; dropped")
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04001985 del font[tag]
1986 else:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001987 self.log(tag, "pruned")
Behdad Esfahbod063a2db2013-07-31 15:22:02 -04001988
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001989 def subset(self, font):
Behdad Esfahbod756af492013-08-01 12:05:26 -04001990
Behdad Esfahbodb7f460b2013-08-13 20:48:33 -04001991 self._prune_pre_subset(font)
1992 self._closure_glyphs(font)
1993 self._subset_glyphs(font)
1994 self._prune_post_subset(font)
Behdad Esfahbod98259f22013-07-31 20:16:24 -04001995
Behdad Esfahbod756af492013-08-01 12:05:26 -04001996
Behdad Esfahbod3d4c4712013-08-13 20:10:17 -04001997class Logger(object):
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04001998
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04001999 def __init__(self, verbose=False, xml=False, timing=False):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002000 self.verbose = verbose
2001 self.xml = xml
2002 self.timing = timing
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002003 self.last_time = self.start_time = time.time()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002004
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002005 def parse_opts(self, argv):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002006 argv = argv[:]
2007 for v in ['verbose', 'xml', 'timing']:
2008 if "--"+v in argv:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002009 setattr(self, v, True)
2010 argv.remove("--"+v)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002011 return argv
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002012
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002013 def __call__(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002014 if not self.verbose:
2015 return
Behdad Esfahbod4cd467c2013-11-27 04:57:06 -05002016 print(' '.join(str(x) for x in things))
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002017
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002018 def lapse(self, *things):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002019 if not self.timing:
2020 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002021 new_time = time.time()
Behdad Esfahbod4cd467c2013-11-27 04:57:06 -05002022 print("Took %0.3fs to %s" %(new_time - self.last_time,
2023 ' '.join(str(x) for x in things)))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002024 self.last_time = new_time
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002025
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04002026 def glyphs(self, glyphs, font=None):
Behdad Esfahbod57fb7262013-12-04 21:56:53 -05002027 if not self.verbose:
2028 return
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002029 self("Names: ", sorted(glyphs))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002030 if font:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002031 reverseGlyphMap = font.getReverseGlyphMap()
2032 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs))
Behdad Esfahbodf5497842013-08-08 21:57:02 -04002033
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002034 def font(self, font, file=sys.stdout):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002035 if not self.xml:
2036 return
Behdad Esfahbod28fc4982013-09-18 19:01:16 -04002037 from fontTools.misc import xmlWriter
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002038 writer = xmlWriter.XMLWriter(file)
Behdad Esfahbod45a84602013-08-19 14:44:49 -04002039 font.disassembleInstructions = False # Work around ttLib bug
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002040 for tag in font.keys():
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002041 writer.begintag(tag)
2042 writer.newline()
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002043 font[tag].toXML(writer, font)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002044 writer.endtag(tag)
2045 writer.newline()
Behdad Esfahboddf3d7572013-07-31 15:03:43 -04002046
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002047
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002048def load_font(fontFile,
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04002049 options,
Behdad Esfahbod6bd43242013-12-04 21:34:05 -05002050 allowVID=False,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002051 checkChecksums=False,
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002052 dontLoadGlyphNames=False):
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002053
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04002054 font = ttLib.TTFont(fontFile,
Behdad Esfahbod6bd43242013-12-04 21:34:05 -05002055 allowVID=allowVID,
2056 checkChecksums=checkChecksums,
2057 recalcBBoxes=options.recalc_bounds)
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002058
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002059 # Hack:
2060 #
2061 # If we don't need glyph names, change 'post' class to not try to
2062 # load them. It avoid lots of headache with broken fonts as well
2063 # as loading time.
2064 #
2065 # Ideally ttLib should provide a way to ask it to skip loading
2066 # glyph names. But it currently doesn't provide such a thing.
2067 #
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002068 if dontLoadGlyphNames:
Behdad Esfahbod46d260f2013-09-19 20:36:49 -04002069 post = ttLib.getTableClass('post')
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002070 saved = post.decode_format_2_0
2071 post.decode_format_2_0 = post.decode_format_3_0
2072 f = font['post']
2073 if f.formatType == 2.0:
2074 f.formatType = 3.0
2075 post.decode_format_2_0 = saved
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002076
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002077 return font
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002078
Behdad Esfahbode911de12013-08-16 12:42:34 -04002079def save_font(font, outfile, options):
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04002080 if options.flavor and not hasattr(font, 'flavor'):
2081 raise Exception("fonttools version does not support flavors.")
2082 font.flavor = options.flavor
Behdad Esfahbode911de12013-08-16 12:42:34 -04002083 font.save(outfile, reorderTables=options.canonical_order)
Behdad Esfahbod41de4cc2013-08-15 12:09:55 -04002084
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002085def main(args):
Behdad Esfahbod610b0552013-07-23 14:52:18 -04002086
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002087 log = Logger()
2088 args = log.parse_opts(args)
Behdad Esfahbod4ae81712013-07-22 11:57:13 -04002089
Behdad Esfahbod80c8a652013-08-14 12:55:42 -04002090 options = Options()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002091 args = options.parse_opts(args, ignore_unknown=['text'])
Behdad Esfahbod97e17b82013-07-31 15:59:21 -04002092
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002093 if len(args) < 2:
Behdad Esfahbodcfeafd72013-11-27 17:27:35 -05002094 print("usage: pyftsubset font-file glyph... [--text=ABC]... [--option=value]...", file=sys.stderr)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002095 sys.exit(1)
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002096
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002097 fontfile = args[0]
2098 args = args[1:]
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002099
Behdad Esfahbod85da2682013-08-15 12:17:21 -04002100 dontLoadGlyphNames =(not options.glyph_names and
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002101 all(any(g.startswith(p)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002102 for p in ['gid', 'glyph', 'uni', 'U+'])
2103 for g in args))
Behdad Esfahbodf6b668e2013-08-13 12:20:59 -04002104
Behdad Esfahbodadc47fd2013-08-15 18:29:25 -04002105 font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002106 subsetter = Subsetter(options=options, log=log)
2107 log.lapse("load font")
Behdad Esfahbod02b92062013-07-21 18:40:59 -04002108
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002109 names = font.getGlyphNames()
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002110 log.lapse("loading glyph names")
Behdad Esfahbode7f5a892013-07-31 19:58:59 -04002111
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002112 glyphs = []
2113 unicodes = []
2114 text = ""
2115 for g in args:
Behdad Esfahbod2be33d92013-09-10 19:28:59 -04002116 if g == '*':
2117 glyphs.extend(font.getGlyphOrder())
2118 continue
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002119 if g in names:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002120 glyphs.append(g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002121 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002122 if g.startswith('--text='):
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002123 text += g[7:]
2124 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002125 if g.startswith('uni') or g.startswith('U+'):
2126 if g.startswith('uni') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002127 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002128 elif g.startswith('U+') and len(g) > 2:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002129 g = g[2:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002130 u = int(g, 16)
2131 unicodes.append(u)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002132 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002133 if g.startswith('gid') or g.startswith('glyph'):
2134 if g.startswith('gid') and len(g) > 3:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002135 g = g[3:]
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002136 elif g.startswith('glyph') and len(g) > 5:
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002137 g = g[5:]
2138 try:
Behdad Esfahboddc873722013-12-04 21:28:50 -05002139 glyphs.append(font.getGlyphName(int(g), requireReal=True))
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002140 except ValueError:
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002141 raise Exception("Invalid glyph identifier: %s" % g)
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002142 continue
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002143 raise Exception("Invalid glyph identifier: %s" % g)
2144 log.lapse("compile glyph list")
2145 log("Unicodes:", unicodes)
2146 log("Glyphs:", glyphs)
Behdad Esfahbod6df089a2013-07-31 19:27:14 -04002147
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002148 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text)
2149 subsetter.subset(font)
Behdad Esfahbodd1d41bc2013-07-21 23:15:32 -04002150
Behdad Esfahbod34426c12013-08-14 18:30:09 -04002151 outfile = fontfile + '.subset'
2152
Behdad Esfahbodc6c3bb82013-08-15 17:46:20 -04002153 save_font (font, outfile, options)
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002154 log.lapse("compile and save font")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04002155
Behdad Esfahbod9e856ea2013-08-13 19:50:38 -04002156 log.last_time = log.start_time
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002157 log.lapse("make one with everything(TOTAL TIME)")
Behdad Esfahbodde71dca2013-07-24 12:40:54 -04002158
Behdad Esfahbod34426c12013-08-14 18:30:09 -04002159 if log.verbose:
2160 import os
2161 log("Input font: %d bytes" % os.path.getsize(fontfile))
2162 log("Subset font: %d bytes" % os.path.getsize(outfile))
2163
Behdad Esfahbod77a2b282013-08-13 19:53:30 -04002164 log.font(font)
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04002165
Behdad Esfahbodc56bf482013-08-13 20:13:33 -04002166 font.close()
2167
Behdad Esfahbod39a39ac2013-08-22 18:10:17 -04002168
2169__all__ = [
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002170 'Options',
2171 'Subsetter',
2172 'Logger',
2173 'load_font',
2174 'save_font',
2175 'main'
Behdad Esfahbod39a39ac2013-08-22 18:10:17 -04002176]
2177
Behdad Esfahbod8c486d82013-07-24 13:34:47 -04002178if __name__ == '__main__':
Behdad Esfahbodb69400f2013-08-29 18:40:53 -04002179 main(sys.argv[1:])