• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2013 Google, Inc. All Rights Reserved.
2#
3# Google Author(s): Behdad Esfahbod
4
5from __future__ import print_function, division, absolute_import
6from fontTools.misc.py23 import *
7from fontTools.misc.fixedTools import otRound
8from fontTools import ttLib
9from fontTools.ttLib.tables import otTables
10from fontTools.pens.basePen import NullPen
11from fontTools.misc.loggingTools import Timer
12from fontTools.subset.cff import *
13from fontTools.varLib import varStore
14import sys
15import struct
16import array
17import logging
18from collections import Counter
19from types import MethodType
20
21__usage__ = "pyftsubset font-file [glyph...] [--option=value]..."
22
23__doc__="""\
24pyftsubset -- OpenType font subsetter and optimizer
25
26  pyftsubset is an OpenType font subsetter and optimizer, based on fontTools.
27  It accepts any TT- or CFF-flavored OpenType (.otf or .ttf) or WOFF (.woff)
28  font file. The subsetted glyph set is based on the specified glyphs
29  or characters, and specified OpenType layout features.
30
31  The tool also performs some size-reducing optimizations, aimed for using
32  subset fonts as webfonts.  Individual optimizations can be enabled or
33  disabled, and are enabled by default when they are safe.
34
35Usage:
36  """+__usage__+"""
37
38  At least one glyph or one of --gids, --gids-file, --glyphs, --glyphs-file,
39  --text, --text-file, --unicodes, or --unicodes-file, must be specified.
40
41Arguments:
42  font-file
43    The input font file.
44  glyph
45    Specify one or more glyph identifiers to include in the subset. Must be
46    PS glyph names, or the special string '*' to keep the entire glyph set.
47
48Initial glyph set specification:
49  These options populate the initial glyph set. Same option can appear
50  multiple times, and the results are accummulated.
51  --gids=<NNN>[,<NNN>...]
52      Specify comma/whitespace-separated list of glyph IDs or ranges as
53      decimal numbers.  For example, --gids=10-12,14 adds glyphs with
54      numbers 10, 11, 12, and 14.
55  --gids-file=<path>
56      Like --gids but reads from a file. Anything after a '#' on any line
57      is ignored as comments.
58  --glyphs=<glyphname>[,<glyphname>...]
59      Specify comma/whitespace-separated PS glyph names to add to the subset.
60      Note that only PS glyph names are accepted, not gidNNN, U+XXXX, etc
61      that are accepted on the command line.  The special string '*' will keep
62      the entire glyph set.
63  --glyphs-file=<path>
64      Like --glyphs but reads from a file. Anything after a '#' on any line
65      is ignored as comments.
66  --text=<text>
67      Specify characters to include in the subset, as UTF-8 string.
68  --text-file=<path>
69      Like --text but reads from a file. Newline character are not added to
70      the subset.
71  --unicodes=<XXXX>[,<XXXX>...]
72      Specify comma/whitespace-separated list of Unicode codepoints or
73      ranges as hex numbers, optionally prefixed with 'U+', 'u', etc.
74      For example, --unicodes=41-5a,61-7a adds ASCII letters, so does
75      the more verbose --unicodes=U+0041-005A,U+0061-007A.
76      The special strings '*' will choose all Unicode characters mapped
77      by the font.
78  --unicodes-file=<path>
79      Like --unicodes, but reads from a file. Anything after a '#' on any
80      line in the file is ignored as comments.
81  --ignore-missing-glyphs
82      Do not fail if some requested glyphs or gids are not available in
83      the font.
84  --no-ignore-missing-glyphs
85      Stop and fail if some requested glyphs or gids are not available
86      in the font. [default]
87  --ignore-missing-unicodes [default]
88      Do not fail if some requested Unicode characters (including those
89      indirectly specified using --text or --text-file) are not available
90      in the font.
91  --no-ignore-missing-unicodes
92      Stop and fail if some requested Unicode characters are not available
93      in the font.
94      Note the default discrepancy between ignoring missing glyphs versus
95      unicodes.  This is for historical reasons and in the future
96      --no-ignore-missing-unicodes might become default.
97
98Other options:
99  For the other options listed below, to see the current value of the option,
100  pass a value of '?' to it, with or without a '='.
101  Examples:
102    $ pyftsubset --glyph-names?
103    Current setting for 'glyph-names' is: False
104    $ ./pyftsubset --name-IDs=?
105    Current setting for 'name-IDs' is: [0, 1, 2, 3, 4, 5, 6]
106    $ ./pyftsubset --hinting? --no-hinting --hinting?
107    Current setting for 'hinting' is: True
108    Current setting for 'hinting' is: False
109
110Output options:
111  --output-file=<path>
112      The output font file. If not specified, the subsetted font
113      will be saved in as font-file.subset.
114  --flavor=<type>
115      Specify flavor of output font file. May be 'woff' or 'woff2'.
116      Note that WOFF2 requires the Brotli Python extension, available
117      at https://github.com/google/brotli
118  --with-zopfli
119      Use the Google Zopfli algorithm to compress WOFF. The output is 3-8 %
120      smaller than pure zlib, but the compression speed is much slower.
121      The Zopfli Python bindings are available at:
122      https://pypi.python.org/pypi/zopfli
123
124Glyph set expansion:
125  These options control how additional glyphs are added to the subset.
126  --retain-gids
127      Retain glyph indices; just empty glyphs not needed in-place.
128  --notdef-glyph
129      Add the '.notdef' glyph to the subset (ie, keep it). [default]
130  --no-notdef-glyph
131      Drop the '.notdef' glyph unless specified in the glyph set. This
132      saves a few bytes, but is not possible for Postscript-flavored
133      fonts, as those require '.notdef'. For TrueType-flavored fonts,
134      this works fine as long as no unsupported glyphs are requested
135      from the font.
136  --notdef-outline
137      Keep the outline of '.notdef' glyph. The '.notdef' glyph outline is
138      used when glyphs not supported by the font are to be shown. It is not
139      needed otherwise.
140  --no-notdef-outline
141      When including a '.notdef' glyph, remove its outline. This saves
142      a few bytes. [default]
143  --recommended-glyphs
144      Add glyphs 0, 1, 2, and 3 to the subset, as recommended for
145      TrueType-flavored fonts: '.notdef', 'NULL' or '.null', 'CR', 'space'.
146      Some legacy software might require this, but no modern system does.
147  --no-recommended-glyphs
148      Do not add glyphs 0, 1, 2, and 3 to the subset, unless specified in
149      glyph set. [default]
150  --no-layout-closure
151      Do not expand glyph set to add glyphs produced by OpenType layout
152      features.  Instead, OpenType layout features will be subset to only
153      rules that are relevant to the otherwise-specified glyph set.
154  --layout-features[+|-]=<feature>[,<feature>...]
155      Specify (=), add to (+=) or exclude from (-=) the comma-separated
156      set of OpenType layout feature tags that will be preserved.
157      Glyph variants used by the preserved features are added to the
158      specified subset glyph set. By default, 'calt', 'ccmp', 'clig', 'curs',
159      'dnom', 'frac', 'kern', 'liga', 'locl', 'mark', 'mkmk', 'numr', 'rclt',
160      'rlig', 'rvrn', and all features required for script shaping are
161      preserved. To see the full list, try '--layout-features=?'.
162      Use '*' to keep all features.
163      Multiple --layout-features options can be provided if necessary.
164      Examples:
165        --layout-features+=onum,pnum,ss01
166            * Keep the default set of features and 'onum', 'pnum', 'ss01'.
167        --layout-features-='mark','mkmk'
168            * Keep the default set of features but drop 'mark' and 'mkmk'.
169        --layout-features='kern'
170            * Only keep the 'kern' feature, drop all others.
171        --layout-features=''
172            * Drop all features.
173        --layout-features='*'
174            * Keep all features.
175        --layout-features+=aalt --layout-features-=vrt2
176            * Keep default set of features plus 'aalt', but drop 'vrt2'.
177  --layout-scripts[+|-]=<script>[,<script>...]
178      Specify (=), add to (+=) or exclude from (-=) the comma-separated
179      set of OpenType layout script tags that will be preserved.  By
180      default all scripts are retained ('*').
181
182Hinting options:
183  --hinting
184      Keep hinting [default]
185  --no-hinting
186      Drop glyph-specific hinting and font-wide hinting tables, as well
187      as remove hinting-related bits and pieces from other tables (eg. GPOS).
188      See --hinting-tables for list of tables that are dropped by default.
189      Instructions and hints are stripped from 'glyf' and 'CFF ' tables
190      respectively. This produces (sometimes up to 30%) smaller fonts that
191      are suitable for extremely high-resolution systems, like high-end
192      mobile devices and retina displays.
193
194Optimization options:
195  --desubroutinize
196      Remove CFF use of subroutinizes.  Subroutinization is a way to make CFF
197      fonts smaller.  For small subsets however, desubroutinizing might make
198      the font smaller.  It has even been reported that desubroutinized CFF
199      fonts compress better (produce smaller output) WOFF and WOFF2 fonts.
200      Also see note under --no-hinting.
201  --no-desubroutinize [default]
202      Leave CFF subroutinizes as is, only throw away unused subroutinizes.
203
204Font table options:
205  --drop-tables[+|-]=<table>[,<table>...]
206      Specify (=), add to (+=) or exclude from (-=) the comma-separated
207      set of tables that will be be dropped.
208      By default, the following tables are dropped:
209      'BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ', 'PCLT', 'LTSH'
210      and Graphite tables: 'Feat', 'Glat', 'Gloc', 'Silf', 'Sill'
211      and color tables: 'sbix'.
212      The tool will attempt to subset the remaining tables.
213      Examples:
214        --drop-tables-='SVG '
215            * Drop the default set of tables but keep 'SVG '.
216        --drop-tables+=GSUB
217            * Drop the default set of tables and 'GSUB'.
218        --drop-tables=DSIG
219            * Only drop the 'DSIG' table, keep all others.
220        --drop-tables=
221            * Keep all tables.
222  --no-subset-tables+=<table>[,<table>...]
223      Add to the set of tables that will not be subsetted.
224      By default, the following tables are included in this list, as
225      they do not need subsetting (ignore the fact that 'loca' is listed
226      here): 'gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2', 'loca', 'name',
227      'cvt ', 'fpgm', 'prep', 'VMDX', 'DSIG', 'CPAL', 'MVAR', 'cvar', 'STAT'.
228      By default, tables that the tool does not know how to subset and are not
229      specified here will be dropped from the font, unless --passthrough-tables
230      option is passed.
231      Example:
232         --no-subset-tables+=FFTM
233            * Keep 'FFTM' table in the font by preventing subsetting.
234  --passthrough-tables
235      Do not drop tables that the tool does not know how to subset.
236  --no-passthrough-tables
237      Tables that the tool does not know how to subset and are not specified
238      in --no-subset-tables will be dropped from the font. [default]
239  --hinting-tables[-]=<table>[,<table>...]
240      Specify (=), add to (+=) or exclude from (-=) the list of font-wide
241      hinting tables that will be dropped if --no-hinting is specified,
242      Examples:
243        --hinting-tables-='VDMX'
244            * Drop font-wide hinting tables except 'VDMX'.
245        --hinting-tables=''
246            * Keep all font-wide hinting tables (but strip hints from glyphs).
247  --legacy-kern
248      Keep TrueType 'kern' table even when OpenType 'GPOS' is available.
249  --no-legacy-kern
250      Drop TrueType 'kern' table if OpenType 'GPOS' is available. [default]
251
252Font naming options:
253  These options control what is retained in the 'name' table. For numerical
254  codes, see: http://www.microsoft.com/typography/otspec/name.htm
255  --name-IDs[+|-]=<nameID>[,<nameID>...]
256      Specify (=), add to (+=) or exclude from (-=) the set of 'name' table
257      entry nameIDs that will be preserved. By default, only nameIDs between 0
258      and 6 are preserved, the rest are dropped. Use '*' to keep all entries.
259      Examples:
260        --name-IDs+=7,8,9
261            * Also keep Trademark, Manufacturer and Designer name entries.
262        --name-IDs=''
263            * Drop all 'name' table entries.
264        --name-IDs='*'
265            * keep all 'name' table entries
266  --name-legacy
267      Keep legacy (non-Unicode) 'name' table entries (0.x, 1.x etc.).
268      XXX Note: This might be needed for some fonts that have no Unicode name
269      entires for English. See: https://github.com/fonttools/fonttools/issues/146
270  --no-name-legacy
271      Drop legacy (non-Unicode) 'name' table entries [default]
272  --name-languages[+|-]=<langID>[,<langID>]
273      Specify (=), add to (+=) or exclude from (-=) the set of 'name' table
274      langIDs that will be preserved. By default only records with langID
275      0x0409 (English) are preserved. Use '*' to keep all langIDs.
276  --obfuscate-names
277      Make the font unusable as a system font by replacing name IDs 1, 2, 3, 4,
278      and 6 with dummy strings (it is still fully functional as webfont).
279
280Glyph naming and encoding options:
281  --glyph-names
282      Keep PS glyph names in TT-flavored fonts. In general glyph names are
283      not needed for correct use of the font. However, some PDF generators
284      and PDF viewers might rely on glyph names to extract Unicode text
285      from PDF documents.
286  --no-glyph-names
287      Drop PS glyph names in TT-flavored fonts, by using 'post' table
288      version 3.0. [default]
289  --legacy-cmap
290      Keep the legacy 'cmap' subtables (0.x, 1.x, 4.x etc.).
291  --no-legacy-cmap
292      Drop the legacy 'cmap' subtables. [default]
293  --symbol-cmap
294      Keep the 3.0 symbol 'cmap'.
295  --no-symbol-cmap
296      Drop the 3.0 symbol 'cmap'. [default]
297
298Other font-specific options:
299  --recalc-bounds
300      Recalculate font bounding boxes.
301  --no-recalc-bounds
302      Keep original font bounding boxes. This is faster and still safe
303      for all practical purposes. [default]
304  --recalc-timestamp
305      Set font 'modified' timestamp to current time.
306  --no-recalc-timestamp
307      Do not modify font 'modified' timestamp. [default]
308  --canonical-order
309      Order tables as recommended in the OpenType standard. This is not
310      required by the standard, nor by any known implementation.
311  --no-canonical-order
312      Keep original order of font tables. This is faster. [default]
313  --prune-unicode-ranges
314      Update the 'OS/2 ulUnicodeRange*' bits after subsetting. The Unicode
315      ranges defined in the OpenType specification v1.7 are intersected with
316      the Unicode codepoints specified in the font's Unicode 'cmap' subtables:
317      when no overlap is found, the bit will be switched off. However, it will
318      *not* be switched on if an intersection is found.  [default]
319  --no-prune-unicode-ranges
320      Don't change the 'OS/2 ulUnicodeRange*' bits.
321  --recalc-average-width
322      Update the 'OS/2 xAvgCharWidth' field after subsetting.
323  --no-recalc-average-width
324      Don't change the 'OS/2 xAvgCharWidth' field. [default]
325  --font-number=<number>
326      Select font number for TrueType Collection (.ttc/.otc), starting from 0.
327
328Application options:
329  --verbose
330      Display verbose information of the subsetting process.
331  --timing
332      Display detailed timing information of the subsetting process.
333  --xml
334      Display the TTX XML representation of subsetted font.
335
336Example:
337  Produce a subset containing the characters ' !"#$%' without performing
338  size-reducing optimizations:
339
340  $ pyftsubset font.ttf --unicodes="U+0020-0025" \\
341    --layout-features='*' --glyph-names --symbol-cmap --legacy-cmap \\
342    --notdef-glyph --notdef-outline --recommended-glyphs \\
343    --name-IDs='*' --name-legacy --name-languages='*'
344"""
345
346
347log = logging.getLogger("fontTools.subset")
348
349def _log_glyphs(self, glyphs, font=None):
350	self.info("Glyph names: %s", sorted(glyphs))
351	if font:
352		reverseGlyphMap = font.getReverseGlyphMap()
353		self.info("Glyph IDs:   %s", sorted(reverseGlyphMap[g] for g in glyphs))
354
355# bind "glyphs" function to 'log' object
356log.glyphs = MethodType(_log_glyphs, log)
357
358# I use a different timing channel so I can configure it separately from the
359# main module's logger
360timer = Timer(logger=logging.getLogger("fontTools.subset.timer"))
361
362
363def _add_method(*clazzes):
364	"""Returns a decorator function that adds a new method to one or
365	more classes."""
366	def wrapper(method):
367		done = []
368		for clazz in clazzes:
369			if clazz in done: continue # Support multiple names of a clazz
370			done.append(clazz)
371			assert clazz.__name__ != 'DefaultTable', \
372					'Oops, table class not found.'
373			assert not hasattr(clazz, method.__name__), \
374					"Oops, class '%s' has method '%s'." % (clazz.__name__,
375									       method.__name__)
376			setattr(clazz, method.__name__, method)
377		return None
378	return wrapper
379
380def _uniq_sort(l):
381	return sorted(set(l))
382
383def _dict_subset(d, glyphs):
384	return {g:d[g] for g in glyphs}
385
386
387@_add_method(otTables.Coverage)
388def intersect(self, glyphs):
389	"""Returns ascending list of matching coverage values."""
390	return [i for i,g in enumerate(self.glyphs) if g in glyphs]
391
392@_add_method(otTables.Coverage)
393def intersect_glyphs(self, glyphs):
394	"""Returns set of intersecting glyphs."""
395	return set(g for g in self.glyphs if g in glyphs)
396
397@_add_method(otTables.Coverage)
398def subset(self, glyphs):
399	"""Returns ascending list of remaining coverage values."""
400	indices = self.intersect(glyphs)
401	self.glyphs = [g for g in self.glyphs if g in glyphs]
402	return indices
403
404@_add_method(otTables.Coverage)
405def remap(self, coverage_map):
406	"""Remaps coverage."""
407	self.glyphs = [self.glyphs[i] for i in coverage_map]
408
409@_add_method(otTables.ClassDef)
410def intersect(self, glyphs):
411	"""Returns ascending list of matching class values."""
412	return _uniq_sort(
413		 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
414			[v for g,v in self.classDefs.items() if g in glyphs])
415
416@_add_method(otTables.ClassDef)
417def intersect_class(self, glyphs, klass):
418	"""Returns set of glyphs matching class."""
419	if klass == 0:
420		return set(g for g in glyphs if g not in self.classDefs)
421	return set(g for g,v in self.classDefs.items()
422		     if v == klass and g in glyphs)
423
424@_add_method(otTables.ClassDef)
425def subset(self, glyphs, remap=False):
426	"""Returns ascending list of remaining classes."""
427	self.classDefs = {g:v for g,v in self.classDefs.items() if g in glyphs}
428	# Note: while class 0 has the special meaning of "not matched",
429	# if no glyph will ever /not match/, we can optimize class 0 out too.
430	indices = _uniq_sort(
431		 ([0] if any(g not in self.classDefs for g in glyphs) else []) +
432			list(self.classDefs.values()))
433	if remap:
434		self.remap(indices)
435	return indices
436
437@_add_method(otTables.ClassDef)
438def remap(self, class_map):
439	"""Remaps classes."""
440	self.classDefs = {g:class_map.index(v) for g,v in self.classDefs.items()}
441
442@_add_method(otTables.SingleSubst)
443def closure_glyphs(self, s, cur_glyphs):
444	s.glyphs.update(v for g,v in self.mapping.items() if g in cur_glyphs)
445
446@_add_method(otTables.SingleSubst)
447def subset_glyphs(self, s):
448	self.mapping = {g:v for g,v in self.mapping.items()
449					if g in s.glyphs and v in s.glyphs}
450	return bool(self.mapping)
451
452@_add_method(otTables.MultipleSubst)
453def closure_glyphs(self, s, cur_glyphs):
454	for glyph, subst in self.mapping.items():
455		if glyph in cur_glyphs:
456			s.glyphs.update(subst)
457
458@_add_method(otTables.MultipleSubst)
459def subset_glyphs(self, s):
460	self.mapping = {g:v for g,v in self.mapping.items()
461					if g in s.glyphs and all(sub in s.glyphs for sub in v)}
462	return bool(self.mapping)
463
464@_add_method(otTables.AlternateSubst)
465def closure_glyphs(self, s, cur_glyphs):
466	s.glyphs.update(*(vlist for g,vlist in self.alternates.items()
467				if g in cur_glyphs))
468
469@_add_method(otTables.AlternateSubst)
470def subset_glyphs(self, s):
471	self.alternates = {g:[v for v in vlist if v in s.glyphs]
472					   for g,vlist in self.alternates.items()
473					   if g in s.glyphs and
474					   any(v in s.glyphs for v in vlist)}
475	return bool(self.alternates)
476
477@_add_method(otTables.LigatureSubst)
478def closure_glyphs(self, s, cur_glyphs):
479	s.glyphs.update(*([seq.LigGlyph for seq in seqs
480				        if all(c in s.glyphs for c in seq.Component)]
481			  for g,seqs in self.ligatures.items()
482			  if g in cur_glyphs))
483
484@_add_method(otTables.LigatureSubst)
485def subset_glyphs(self, s):
486	self.ligatures = {g:v for g,v in self.ligatures.items()
487					  if g in s.glyphs}
488	self.ligatures = {g:[seq for seq in seqs
489				 if seq.LigGlyph in s.glyphs and
490					all(c in s.glyphs for c in seq.Component)]
491			  for g,seqs in self.ligatures.items()}
492	self.ligatures = {g:v for g,v in self.ligatures.items() if v}
493	return bool(self.ligatures)
494
495@_add_method(otTables.ReverseChainSingleSubst)
496def closure_glyphs(self, s, cur_glyphs):
497	if self.Format == 1:
498		indices = self.Coverage.intersect(cur_glyphs)
499		if(not indices or
500		   not all(c.intersect(s.glyphs)
501				   for c in self.LookAheadCoverage + self.BacktrackCoverage)):
502			return
503		s.glyphs.update(self.Substitute[i] for i in indices)
504	else:
505		assert 0, "unknown format: %s" % self.Format
506
507@_add_method(otTables.ReverseChainSingleSubst)
508def subset_glyphs(self, s):
509	if self.Format == 1:
510		indices = self.Coverage.subset(s.glyphs)
511		self.Substitute = [self.Substitute[i] for i in indices]
512		# Now drop rules generating glyphs we don't want
513		indices = [i for i,sub in enumerate(self.Substitute)
514				 if sub in s.glyphs]
515		self.Substitute = [self.Substitute[i] for i in indices]
516		self.Coverage.remap(indices)
517		self.GlyphCount = len(self.Substitute)
518		return bool(self.GlyphCount and
519			    all(c.subset(s.glyphs)
520				for c in self.LookAheadCoverage+self.BacktrackCoverage))
521	else:
522		assert 0, "unknown format: %s" % self.Format
523
524@_add_method(otTables.SinglePos)
525def subset_glyphs(self, s):
526	if self.Format == 1:
527		return len(self.Coverage.subset(s.glyphs))
528	elif self.Format == 2:
529		indices = self.Coverage.subset(s.glyphs)
530		values = self.Value
531		count = len(values)
532		self.Value = [values[i] for i in indices if i < count]
533		self.ValueCount = len(self.Value)
534		return bool(self.ValueCount)
535	else:
536		assert 0, "unknown format: %s" % self.Format
537
538@_add_method(otTables.SinglePos)
539def prune_post_subset(self, font, options):
540	if not options.hinting:
541		# Drop device tables
542		self.ValueFormat &= ~0x00F0
543	return True
544
545@_add_method(otTables.PairPos)
546def subset_glyphs(self, s):
547	if self.Format == 1:
548		indices = self.Coverage.subset(s.glyphs)
549		pairs = self.PairSet
550		count = len(pairs)
551		self.PairSet = [pairs[i] for i in indices if i < count]
552		for p in self.PairSet:
553			p.PairValueRecord = [r for r in p.PairValueRecord if r.SecondGlyph in s.glyphs]
554			p.PairValueCount = len(p.PairValueRecord)
555		# Remove empty pairsets
556		indices = [i for i,p in enumerate(self.PairSet) if p.PairValueCount]
557		self.Coverage.remap(indices)
558		self.PairSet = [self.PairSet[i] for i in indices]
559		self.PairSetCount = len(self.PairSet)
560		return bool(self.PairSetCount)
561	elif self.Format == 2:
562		class1_map = [c for c in self.ClassDef1.subset(s.glyphs, remap=True) if c < self.Class1Count]
563		class2_map = [c for c in self.ClassDef2.subset(s.glyphs, remap=True) if c < self.Class2Count]
564		self.Class1Record = [self.Class1Record[i] for i in class1_map]
565		for c in self.Class1Record:
566			c.Class2Record = [c.Class2Record[i] for i in class2_map]
567		self.Class1Count = len(class1_map)
568		self.Class2Count = len(class2_map)
569		return bool(self.Class1Count and
570					self.Class2Count and
571					self.Coverage.subset(s.glyphs))
572	else:
573		assert 0, "unknown format: %s" % self.Format
574
575@_add_method(otTables.PairPos)
576def prune_post_subset(self, font, options):
577	if not options.hinting:
578		# Drop device tables
579		self.ValueFormat1 &= ~0x00F0
580		self.ValueFormat2 &= ~0x00F0
581	return True
582
583@_add_method(otTables.CursivePos)
584def subset_glyphs(self, s):
585	if self.Format == 1:
586		indices = self.Coverage.subset(s.glyphs)
587		records = self.EntryExitRecord
588		count = len(records)
589		self.EntryExitRecord = [records[i] for i in indices if i < count]
590		self.EntryExitCount = len(self.EntryExitRecord)
591		return bool(self.EntryExitCount)
592	else:
593		assert 0, "unknown format: %s" % self.Format
594
595@_add_method(otTables.Anchor)
596def prune_hints(self):
597	# Drop device tables / contour anchor point
598	self.ensureDecompiled()
599	self.Format = 1
600
601@_add_method(otTables.CursivePos)
602def prune_post_subset(self, font, options):
603	if not options.hinting:
604		for rec in self.EntryExitRecord:
605			if rec.EntryAnchor: rec.EntryAnchor.prune_hints()
606			if rec.ExitAnchor: rec.ExitAnchor.prune_hints()
607	return True
608
609@_add_method(otTables.MarkBasePos)
610def subset_glyphs(self, s):
611	if self.Format == 1:
612		mark_indices = self.MarkCoverage.subset(s.glyphs)
613		self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i] for i in mark_indices]
614		self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
615		base_indices = self.BaseCoverage.subset(s.glyphs)
616		self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i] for i in base_indices]
617		self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
618		# Prune empty classes
619		class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
620		self.ClassCount = len(class_indices)
621		for m in self.MarkArray.MarkRecord:
622			m.Class = class_indices.index(m.Class)
623		for b in self.BaseArray.BaseRecord:
624			b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices]
625		return bool(self.ClassCount and
626					self.MarkArray.MarkCount and
627					self.BaseArray.BaseCount)
628	else:
629		assert 0, "unknown format: %s" % self.Format
630
631@_add_method(otTables.MarkBasePos)
632def prune_post_subset(self, font, options):
633		if not options.hinting:
634			for m in self.MarkArray.MarkRecord:
635				if m.MarkAnchor:
636					m.MarkAnchor.prune_hints()
637			for b in self.BaseArray.BaseRecord:
638				for a in b.BaseAnchor:
639					if a:
640						a.prune_hints()
641		return True
642
643@_add_method(otTables.MarkLigPos)
644def subset_glyphs(self, s):
645	if self.Format == 1:
646		mark_indices = self.MarkCoverage.subset(s.glyphs)
647		self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i] for i in mark_indices]
648		self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
649		ligature_indices = self.LigatureCoverage.subset(s.glyphs)
650		self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i] for i in ligature_indices]
651		self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
652		# Prune empty classes
653		class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
654		self.ClassCount = len(class_indices)
655		for m in self.MarkArray.MarkRecord:
656			m.Class = class_indices.index(m.Class)
657		for l in self.LigatureArray.LigatureAttach:
658			for c in l.ComponentRecord:
659				c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices]
660		return bool(self.ClassCount and
661					self.MarkArray.MarkCount and
662					self.LigatureArray.LigatureCount)
663	else:
664		assert 0, "unknown format: %s" % self.Format
665
666@_add_method(otTables.MarkLigPos)
667def prune_post_subset(self, font, options):
668		if not options.hinting:
669			for m in self.MarkArray.MarkRecord:
670				if m.MarkAnchor:
671					m.MarkAnchor.prune_hints()
672			for l in self.LigatureArray.LigatureAttach:
673				for c in l.ComponentRecord:
674					for a in c.LigatureAnchor:
675						if a:
676							a.prune_hints()
677		return True
678
679@_add_method(otTables.MarkMarkPos)
680def subset_glyphs(self, s):
681	if self.Format == 1:
682		mark1_indices = self.Mark1Coverage.subset(s.glyphs)
683		self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i] for i in mark1_indices]
684		self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
685		mark2_indices = self.Mark2Coverage.subset(s.glyphs)
686		self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i] for i in mark2_indices]
687		self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
688		# Prune empty classes
689		class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
690		self.ClassCount = len(class_indices)
691		for m in self.Mark1Array.MarkRecord:
692			m.Class = class_indices.index(m.Class)
693		for b in self.Mark2Array.Mark2Record:
694			b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices]
695		return bool(self.ClassCount and
696					self.Mark1Array.MarkCount and
697					self.Mark2Array.MarkCount)
698	else:
699		assert 0, "unknown format: %s" % self.Format
700
701@_add_method(otTables.MarkMarkPos)
702def prune_post_subset(self, font, options):
703		if not options.hinting:
704			# Drop device tables or contour anchor point
705			for m in self.Mark1Array.MarkRecord:
706				if m.MarkAnchor:
707					m.MarkAnchor.prune_hints()
708			for b in self.Mark2Array.Mark2Record:
709				for m in b.Mark2Anchor:
710					if m:
711						m.prune_hints()
712		return True
713
714@_add_method(otTables.SingleSubst,
715			 otTables.MultipleSubst,
716			 otTables.AlternateSubst,
717			 otTables.LigatureSubst,
718			 otTables.ReverseChainSingleSubst,
719			 otTables.SinglePos,
720			 otTables.PairPos,
721			 otTables.CursivePos,
722			 otTables.MarkBasePos,
723			 otTables.MarkLigPos,
724			 otTables.MarkMarkPos)
725def subset_lookups(self, lookup_indices):
726	pass
727
728@_add_method(otTables.SingleSubst,
729			 otTables.MultipleSubst,
730			 otTables.AlternateSubst,
731			 otTables.LigatureSubst,
732			 otTables.ReverseChainSingleSubst,
733			 otTables.SinglePos,
734			 otTables.PairPos,
735			 otTables.CursivePos,
736			 otTables.MarkBasePos,
737			 otTables.MarkLigPos,
738			 otTables.MarkMarkPos)
739def collect_lookups(self):
740	return []
741
742@_add_method(otTables.SingleSubst,
743			 otTables.MultipleSubst,
744			 otTables.AlternateSubst,
745			 otTables.LigatureSubst,
746			 otTables.ReverseChainSingleSubst,
747			 otTables.ContextSubst,
748			 otTables.ChainContextSubst,
749			 otTables.ContextPos,
750			 otTables.ChainContextPos)
751def prune_post_subset(self, font, options):
752	return True
753
754@_add_method(otTables.SingleSubst,
755			 otTables.AlternateSubst,
756			 otTables.ReverseChainSingleSubst)
757def may_have_non_1to1(self):
758	return False
759
760@_add_method(otTables.MultipleSubst,
761			 otTables.LigatureSubst,
762			 otTables.ContextSubst,
763			 otTables.ChainContextSubst)
764def may_have_non_1to1(self):
765	return True
766
767@_add_method(otTables.ContextSubst,
768			 otTables.ChainContextSubst,
769			 otTables.ContextPos,
770			 otTables.ChainContextPos)
771def __subset_classify_context(self):
772
773	class ContextHelper(object):
774		def __init__(self, klass, Format):
775			if klass.__name__.endswith('Subst'):
776				Typ = 'Sub'
777				Type = 'Subst'
778			else:
779				Typ = 'Pos'
780				Type = 'Pos'
781			if klass.__name__.startswith('Chain'):
782				Chain = 'Chain'
783				InputIdx = 1
784				DataLen = 3
785			else:
786				Chain = ''
787				InputIdx = 0
788				DataLen = 1
789			ChainTyp = Chain+Typ
790
791			self.Typ = Typ
792			self.Type = Type
793			self.Chain = Chain
794			self.ChainTyp = ChainTyp
795			self.InputIdx = InputIdx
796			self.DataLen = DataLen
797
798			self.LookupRecord = Type+'LookupRecord'
799
800			if Format == 1:
801				Coverage = lambda r: r.Coverage
802				ChainCoverage = lambda r: r.Coverage
803				ContextData = lambda r:(None,)
804				ChainContextData = lambda r:(None, None, None)
805				SetContextData = None
806				SetChainContextData = None
807				RuleData = lambda r:(r.Input,)
808				ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
809				def SetRuleData(r, d):
810					(r.Input,) = d
811					(r.GlyphCount,) = (len(x)+1 for x in d)
812				def ChainSetRuleData(r, d):
813					(r.Backtrack, r.Input, r.LookAhead) = d
814					(r.BacktrackGlyphCount,r.InputGlyphCount,r.LookAheadGlyphCount,) = (len(d[0]),len(d[1])+1,len(d[2]))
815			elif Format == 2:
816				Coverage = lambda r: r.Coverage
817				ChainCoverage = lambda r: r.Coverage
818				ContextData = lambda r:(r.ClassDef,)
819				ChainContextData = lambda r:(r.BacktrackClassDef,
820							     r.InputClassDef,
821							     r.LookAheadClassDef)
822				def SetContextData(r, d):
823					(r.ClassDef,) = d
824				def SetChainContextData(r, d):
825					(r.BacktrackClassDef,
826					 r.InputClassDef,
827					 r.LookAheadClassDef) = d
828				RuleData = lambda r:(r.Class,)
829				ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead)
830				def SetRuleData(r, d):
831					(r.Class,) = d
832					(r.GlyphCount,) = (len(x)+1 for x in d)
833				def ChainSetRuleData(r, d):
834					(r.Backtrack, r.Input, r.LookAhead) = d
835					(r.BacktrackGlyphCount,r.InputGlyphCount,r.LookAheadGlyphCount,) = (len(d[0]),len(d[1])+1,len(d[2]))
836			elif Format == 3:
837				Coverage = lambda r: r.Coverage[0]
838				ChainCoverage = lambda r: r.InputCoverage[0]
839				ContextData = None
840				ChainContextData = None
841				SetContextData = None
842				SetChainContextData = None
843				RuleData = lambda r: r.Coverage
844				ChainRuleData = lambda r:(r.BacktrackCoverage +
845							  r.InputCoverage +
846							  r.LookAheadCoverage)
847				def SetRuleData(r, d):
848					(r.Coverage,) = d
849					(r.GlyphCount,) = (len(x) for x in d)
850				def ChainSetRuleData(r, d):
851					(r.BacktrackCoverage, r.InputCoverage, r.LookAheadCoverage) = d
852					(r.BacktrackGlyphCount,r.InputGlyphCount,r.LookAheadGlyphCount,) = (len(x) for x in d)
853			else:
854				assert 0, "unknown format: %s" % Format
855
856			if Chain:
857				self.Coverage = ChainCoverage
858				self.ContextData = ChainContextData
859				self.SetContextData = SetChainContextData
860				self.RuleData = ChainRuleData
861				self.SetRuleData = ChainSetRuleData
862			else:
863				self.Coverage = Coverage
864				self.ContextData = ContextData
865				self.SetContextData = SetContextData
866				self.RuleData = RuleData
867				self.SetRuleData = SetRuleData
868
869			if Format == 1:
870				self.Rule = ChainTyp+'Rule'
871				self.RuleCount = ChainTyp+'RuleCount'
872				self.RuleSet = ChainTyp+'RuleSet'
873				self.RuleSetCount = ChainTyp+'RuleSetCount'
874				self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else []
875			elif Format == 2:
876				self.Rule = ChainTyp+'ClassRule'
877				self.RuleCount = ChainTyp+'ClassRuleCount'
878				self.RuleSet = ChainTyp+'ClassSet'
879				self.RuleSetCount = ChainTyp+'ClassSetCount'
880				self.Intersect = lambda glyphs, c, r: (c.intersect_class(glyphs, r) if c
881								       else (set(glyphs) if r == 0 else set()))
882
883				self.ClassDef = 'InputClassDef' if Chain else 'ClassDef'
884				self.ClassDefIndex = 1 if Chain else 0
885				self.Input = 'Input' if Chain else 'Class'
886
887	if self.Format not in [1, 2, 3]:
888		return None	# Don't shoot the messenger; let it go
889	if not hasattr(self.__class__, "__ContextHelpers"):
890		self.__class__.__ContextHelpers = {}
891	if self.Format not in self.__class__.__ContextHelpers:
892		helper = ContextHelper(self.__class__, self.Format)
893		self.__class__.__ContextHelpers[self.Format] = helper
894	return self.__class__.__ContextHelpers[self.Format]
895
896@_add_method(otTables.ContextSubst,
897			 otTables.ChainContextSubst)
898def closure_glyphs(self, s, cur_glyphs):
899	c = self.__subset_classify_context()
900
901	indices = c.Coverage(self).intersect(cur_glyphs)
902	if not indices:
903		return []
904	cur_glyphs = c.Coverage(self).intersect_glyphs(cur_glyphs)
905
906	if self.Format == 1:
907		ContextData = c.ContextData(self)
908		rss = getattr(self, c.RuleSet)
909		rssCount = getattr(self, c.RuleSetCount)
910		for i in indices:
911			if i >= rssCount or not rss[i]: continue
912			for r in getattr(rss[i], c.Rule):
913				if not r: continue
914				if not all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
915					   for cd,klist in zip(ContextData, c.RuleData(r))):
916					continue
917				chaos = set()
918				for ll in getattr(r, c.LookupRecord):
919					if not ll: continue
920					seqi = ll.SequenceIndex
921					if seqi in chaos:
922						# TODO Can we improve this?
923						pos_glyphs = None
924					else:
925						if seqi == 0:
926							pos_glyphs = frozenset([c.Coverage(self).glyphs[i]])
927						else:
928							pos_glyphs = frozenset([r.Input[seqi - 1]])
929					lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
930					chaos.add(seqi)
931					if lookup.may_have_non_1to1():
932						chaos.update(range(seqi, len(r.Input)+2))
933					lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
934	elif self.Format == 2:
935		ClassDef = getattr(self, c.ClassDef)
936		indices = ClassDef.intersect(cur_glyphs)
937		ContextData = c.ContextData(self)
938		rss = getattr(self, c.RuleSet)
939		rssCount = getattr(self, c.RuleSetCount)
940		for i in indices:
941			if i >= rssCount or not rss[i]: continue
942			for r in getattr(rss[i], c.Rule):
943				if not r: continue
944				if not all(all(c.Intersect(s.glyphs, cd, k) for k in klist)
945						   for cd,klist in zip(ContextData, c.RuleData(r))):
946					continue
947				chaos = set()
948				for ll in getattr(r, c.LookupRecord):
949					if not ll: continue
950					seqi = ll.SequenceIndex
951					if seqi in chaos:
952						# TODO Can we improve this?
953						pos_glyphs = None
954					else:
955						if seqi == 0:
956							pos_glyphs = frozenset(ClassDef.intersect_class(cur_glyphs, i))
957						else:
958							pos_glyphs = frozenset(ClassDef.intersect_class(s.glyphs, getattr(r, c.Input)[seqi - 1]))
959					lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
960					chaos.add(seqi)
961					if lookup.may_have_non_1to1():
962						chaos.update(range(seqi, len(getattr(r, c.Input))+2))
963					lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
964	elif self.Format == 3:
965		if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
966			return []
967		r = self
968		chaos = set()
969		for ll in getattr(r, c.LookupRecord):
970			if not ll: continue
971			seqi = ll.SequenceIndex
972			if seqi in chaos:
973				# TODO Can we improve this?
974				pos_glyphs = None
975			else:
976				if seqi == 0:
977					pos_glyphs = frozenset(cur_glyphs)
978				else:
979					pos_glyphs = frozenset(r.InputCoverage[seqi].intersect_glyphs(s.glyphs))
980			lookup = s.table.LookupList.Lookup[ll.LookupListIndex]
981			chaos.add(seqi)
982			if lookup.may_have_non_1to1():
983				chaos.update(range(seqi, len(r.InputCoverage)+1))
984			lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
985	else:
986		assert 0, "unknown format: %s" % self.Format
987
988@_add_method(otTables.ContextSubst,
989			 otTables.ContextPos,
990			 otTables.ChainContextSubst,
991			 otTables.ChainContextPos)
992def subset_glyphs(self, s):
993	c = self.__subset_classify_context()
994
995	if self.Format == 1:
996		indices = self.Coverage.subset(s.glyphs)
997		rss = getattr(self, c.RuleSet)
998		rssCount = getattr(self, c.RuleSetCount)
999		rss = [rss[i] for i in indices if i < rssCount]
1000		for rs in rss:
1001			if not rs: continue
1002			ss = getattr(rs, c.Rule)
1003			ss = [r for r in ss
1004				  if r and all(all(g in s.glyphs for g in glist)
1005					       for glist in c.RuleData(r))]
1006			setattr(rs, c.Rule, ss)
1007			setattr(rs, c.RuleCount, len(ss))
1008		# Prune empty rulesets
1009		indices = [i for i,rs in enumerate(rss) if rs and getattr(rs, c.Rule)]
1010		self.Coverage.remap(indices)
1011		rss = [rss[i] for i in indices]
1012		setattr(self, c.RuleSet, rss)
1013		setattr(self, c.RuleSetCount, len(rss))
1014		return bool(rss)
1015	elif self.Format == 2:
1016		if not self.Coverage.subset(s.glyphs):
1017			return False
1018		ContextData = c.ContextData(self)
1019		klass_maps = [x.subset(s.glyphs, remap=True) if x else None for x in ContextData]
1020
1021		# Keep rulesets for class numbers that survived.
1022		indices = klass_maps[c.ClassDefIndex]
1023		rss = getattr(self, c.RuleSet)
1024		rssCount = getattr(self, c.RuleSetCount)
1025		rss = [rss[i] for i in indices if i < rssCount]
1026		del rssCount
1027		# Delete, but not renumber, unreachable rulesets.
1028		indices = getattr(self, c.ClassDef).intersect(self.Coverage.glyphs)
1029		rss = [rss if i in indices else None for i,rss in enumerate(rss)]
1030
1031		for rs in rss:
1032			if not rs: continue
1033			ss = getattr(rs, c.Rule)
1034			ss = [r for r in ss
1035				  if r and all(all(k in klass_map for k in klist)
1036					       for klass_map,klist in zip(klass_maps, c.RuleData(r)))]
1037			setattr(rs, c.Rule, ss)
1038			setattr(rs, c.RuleCount, len(ss))
1039
1040			# Remap rule classes
1041			for r in ss:
1042				c.SetRuleData(r, [[klass_map.index(k) for k in klist]
1043						  for klass_map,klist in zip(klass_maps, c.RuleData(r))])
1044
1045		# Prune empty rulesets
1046		rss = [rs if rs and getattr(rs, c.Rule) else None for rs in rss]
1047		while rss and rss[-1] is None:
1048			del rss[-1]
1049		setattr(self, c.RuleSet, rss)
1050		setattr(self, c.RuleSetCount, len(rss))
1051
1052		# TODO: We can do a second round of remapping class values based
1053		# on classes that are actually used in at least one rule.	Right
1054		# now we subset classes to c.glyphs only.	Or better, rewrite
1055		# the above to do that.
1056
1057		return bool(rss)
1058	elif self.Format == 3:
1059		return all(x.subset(s.glyphs) for x in c.RuleData(self))
1060	else:
1061		assert 0, "unknown format: %s" % self.Format
1062
1063@_add_method(otTables.ContextSubst,
1064			 otTables.ChainContextSubst,
1065			 otTables.ContextPos,
1066			 otTables.ChainContextPos)
1067def subset_lookups(self, lookup_indices):
1068	c = self.__subset_classify_context()
1069
1070	if self.Format in [1, 2]:
1071		for rs in getattr(self, c.RuleSet):
1072			if not rs: continue
1073			for r in getattr(rs, c.Rule):
1074				if not r: continue
1075				setattr(r, c.LookupRecord,
1076					[ll for ll in getattr(r, c.LookupRecord)
1077					 if ll and ll.LookupListIndex in lookup_indices])
1078				for ll in getattr(r, c.LookupRecord):
1079					if not ll: continue
1080					ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
1081	elif self.Format == 3:
1082		setattr(self, c.LookupRecord,
1083				[ll for ll in getattr(self, c.LookupRecord)
1084				 if ll and ll.LookupListIndex in lookup_indices])
1085		for ll in getattr(self, c.LookupRecord):
1086			if not ll: continue
1087			ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex)
1088	else:
1089		assert 0, "unknown format: %s" % self.Format
1090
1091@_add_method(otTables.ContextSubst,
1092			 otTables.ChainContextSubst,
1093			 otTables.ContextPos,
1094			 otTables.ChainContextPos)
1095def collect_lookups(self):
1096	c = self.__subset_classify_context()
1097
1098	if self.Format in [1, 2]:
1099		return [ll.LookupListIndex
1100			for rs in getattr(self, c.RuleSet) if rs
1101			for r in getattr(rs, c.Rule) if r
1102			for ll in getattr(r, c.LookupRecord) if ll]
1103	elif self.Format == 3:
1104		return [ll.LookupListIndex
1105			for ll in getattr(self, c.LookupRecord) if ll]
1106	else:
1107		assert 0, "unknown format: %s" % self.Format
1108
1109@_add_method(otTables.ExtensionSubst)
1110def closure_glyphs(self, s, cur_glyphs):
1111	if self.Format == 1:
1112		self.ExtSubTable.closure_glyphs(s, cur_glyphs)
1113	else:
1114		assert 0, "unknown format: %s" % self.Format
1115
1116@_add_method(otTables.ExtensionSubst)
1117def may_have_non_1to1(self):
1118	if self.Format == 1:
1119		return self.ExtSubTable.may_have_non_1to1()
1120	else:
1121		assert 0, "unknown format: %s" % self.Format
1122
1123@_add_method(otTables.ExtensionSubst,
1124			 otTables.ExtensionPos)
1125def subset_glyphs(self, s):
1126	if self.Format == 1:
1127		return self.ExtSubTable.subset_glyphs(s)
1128	else:
1129		assert 0, "unknown format: %s" % self.Format
1130
1131@_add_method(otTables.ExtensionSubst,
1132			 otTables.ExtensionPos)
1133def prune_post_subset(self, font, options):
1134	if self.Format == 1:
1135		return self.ExtSubTable.prune_post_subset(font, options)
1136	else:
1137		assert 0, "unknown format: %s" % self.Format
1138
1139@_add_method(otTables.ExtensionSubst,
1140			 otTables.ExtensionPos)
1141def subset_lookups(self, lookup_indices):
1142	if self.Format == 1:
1143		return self.ExtSubTable.subset_lookups(lookup_indices)
1144	else:
1145		assert 0, "unknown format: %s" % self.Format
1146
1147@_add_method(otTables.ExtensionSubst,
1148			 otTables.ExtensionPos)
1149def collect_lookups(self):
1150	if self.Format == 1:
1151		return self.ExtSubTable.collect_lookups()
1152	else:
1153		assert 0, "unknown format: %s" % self.Format
1154
1155@_add_method(otTables.Lookup)
1156def closure_glyphs(self, s, cur_glyphs=None):
1157	if cur_glyphs is None:
1158		cur_glyphs = frozenset(s.glyphs)
1159
1160	# Memoize
1161	key = id(self)
1162	doneLookups = s._doneLookups
1163	count,covered = doneLookups.get(key, (0, None))
1164	if count != len(s.glyphs):
1165		count,covered = doneLookups[key] = (len(s.glyphs), set())
1166	if cur_glyphs.issubset(covered):
1167		return
1168	covered.update(cur_glyphs)
1169
1170	for st in self.SubTable:
1171		if not st: continue
1172		st.closure_glyphs(s, cur_glyphs)
1173
1174@_add_method(otTables.Lookup)
1175def subset_glyphs(self, s):
1176	self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)]
1177	self.SubTableCount = len(self.SubTable)
1178	return bool(self.SubTableCount)
1179
1180@_add_method(otTables.Lookup)
1181def prune_post_subset(self, font, options):
1182	ret = False
1183	for st in self.SubTable:
1184		if not st: continue
1185		if st.prune_post_subset(font, options): ret = True
1186	return ret
1187
1188@_add_method(otTables.Lookup)
1189def subset_lookups(self, lookup_indices):
1190	for s in self.SubTable:
1191		s.subset_lookups(lookup_indices)
1192
1193@_add_method(otTables.Lookup)
1194def collect_lookups(self):
1195	return sum((st.collect_lookups() for st in self.SubTable if st), [])
1196
1197@_add_method(otTables.Lookup)
1198def may_have_non_1to1(self):
1199	return any(st.may_have_non_1to1() for st in self.SubTable if st)
1200
1201@_add_method(otTables.LookupList)
1202def subset_glyphs(self, s):
1203	"""Returns the indices of nonempty lookups."""
1204	return [i for i,l in enumerate(self.Lookup) if l and l.subset_glyphs(s)]
1205
1206@_add_method(otTables.LookupList)
1207def prune_post_subset(self, font, options):
1208	ret = False
1209	for l in self.Lookup:
1210		if not l: continue
1211		if l.prune_post_subset(font, options): ret = True
1212	return ret
1213
1214@_add_method(otTables.LookupList)
1215def subset_lookups(self, lookup_indices):
1216	self.ensureDecompiled()
1217	self.Lookup = [self.Lookup[i] for i in lookup_indices
1218				   if i < self.LookupCount]
1219	self.LookupCount = len(self.Lookup)
1220	for l in self.Lookup:
1221		l.subset_lookups(lookup_indices)
1222
1223@_add_method(otTables.LookupList)
1224def neuter_lookups(self, lookup_indices):
1225	"""Sets lookups not in lookup_indices to None."""
1226	self.ensureDecompiled()
1227	self.Lookup = [l if i in lookup_indices else None for i,l in enumerate(self.Lookup)]
1228
1229@_add_method(otTables.LookupList)
1230def closure_lookups(self, lookup_indices):
1231	"""Returns sorted index of all lookups reachable from lookup_indices."""
1232	lookup_indices = _uniq_sort(lookup_indices)
1233	recurse = lookup_indices
1234	while True:
1235		recurse_lookups = sum((self.Lookup[i].collect_lookups()
1236				       for i in recurse if i < self.LookupCount), [])
1237		recurse_lookups = [l for l in recurse_lookups
1238				     if l not in lookup_indices and l < self.LookupCount]
1239		if not recurse_lookups:
1240			return _uniq_sort(lookup_indices)
1241		recurse_lookups = _uniq_sort(recurse_lookups)
1242		lookup_indices.extend(recurse_lookups)
1243		recurse = recurse_lookups
1244
1245@_add_method(otTables.Feature)
1246def subset_lookups(self, lookup_indices):
1247	""""Returns True if feature is non-empty afterwards."""
1248	self.LookupListIndex = [l for l in self.LookupListIndex
1249				  if l in lookup_indices]
1250	# Now map them.
1251	self.LookupListIndex = [lookup_indices.index(l)
1252				for l in self.LookupListIndex]
1253	self.LookupCount = len(self.LookupListIndex)
1254	return self.LookupCount or self.FeatureParams
1255
1256@_add_method(otTables.FeatureList)
1257def subset_lookups(self, lookup_indices):
1258	"""Returns the indices of nonempty features."""
1259	# Note: Never ever drop feature 'pref', even if it's empty.
1260	# HarfBuzz chooses shaper for Khmer based on presence of this
1261	# feature.	See thread at:
1262	# http://lists.freedesktop.org/archives/harfbuzz/2012-November/002660.html
1263	return [i for i,f in enumerate(self.FeatureRecord)
1264			if (f.Feature.subset_lookups(lookup_indices) or
1265				f.FeatureTag == 'pref')]
1266
1267@_add_method(otTables.FeatureList)
1268def collect_lookups(self, feature_indices):
1269	return sum((self.FeatureRecord[i].Feature.LookupListIndex
1270				for i in feature_indices
1271				if i < self.FeatureCount), [])
1272
1273@_add_method(otTables.FeatureList)
1274def subset_features(self, feature_indices):
1275	self.ensureDecompiled()
1276	self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices]
1277	self.FeatureCount = len(self.FeatureRecord)
1278	return bool(self.FeatureCount)
1279
1280@_add_method(otTables.FeatureTableSubstitution)
1281def subset_lookups(self, lookup_indices):
1282	"""Returns the indices of nonempty features."""
1283	return [r.FeatureIndex for r in self.SubstitutionRecord
1284			if r.Feature.subset_lookups(lookup_indices)]
1285
1286@_add_method(otTables.FeatureVariations)
1287def subset_lookups(self, lookup_indices):
1288	"""Returns the indices of nonempty features."""
1289	return sum((f.FeatureTableSubstitution.subset_lookups(lookup_indices)
1290				for f in self.FeatureVariationRecord), [])
1291
1292@_add_method(otTables.FeatureVariations)
1293def collect_lookups(self, feature_indices):
1294	return sum((r.Feature.LookupListIndex
1295				for vr in self.FeatureVariationRecord
1296				for r in vr.FeatureTableSubstitution.SubstitutionRecord
1297				if r.FeatureIndex in feature_indices), [])
1298
1299@_add_method(otTables.FeatureTableSubstitution)
1300def subset_features(self, feature_indices):
1301	self.ensureDecompiled()
1302	self.SubstitutionRecord = [r for r in self.SubstitutionRecord
1303				     if r.FeatureIndex in feature_indices]
1304	self.SubstitutionCount = len(self.SubstitutionRecord)
1305	return bool(self.SubstitutionCount)
1306
1307@_add_method(otTables.FeatureVariations)
1308def subset_features(self, feature_indices):
1309	self.ensureDecompiled()
1310	self.FeaturVariationRecord = [r for r in self.FeatureVariationRecord
1311					if r.FeatureTableSubstitution.subset_features(feature_indices)]
1312	self.FeatureVariationCount = len(self.FeatureVariationRecord)
1313	return bool(self.FeatureVariationCount)
1314
1315@_add_method(otTables.DefaultLangSys,
1316			 otTables.LangSys)
1317def subset_features(self, feature_indices):
1318	if self.ReqFeatureIndex in feature_indices:
1319		self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex)
1320	else:
1321		self.ReqFeatureIndex = 65535
1322	self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices]
1323	# Now map them.
1324	self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex
1325						      if f in feature_indices]
1326	self.FeatureCount = len(self.FeatureIndex)
1327	return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
1328
1329@_add_method(otTables.DefaultLangSys,
1330			 otTables.LangSys)
1331def collect_features(self):
1332	feature_indices = self.FeatureIndex[:]
1333	if self.ReqFeatureIndex != 65535:
1334		feature_indices.append(self.ReqFeatureIndex)
1335	return _uniq_sort(feature_indices)
1336
1337@_add_method(otTables.Script)
1338def subset_features(self, feature_indices, keepEmptyDefaultLangSys=False):
1339	if(self.DefaultLangSys and
1340	   not self.DefaultLangSys.subset_features(feature_indices) and
1341	   not keepEmptyDefaultLangSys):
1342		self.DefaultLangSys = None
1343	self.LangSysRecord = [l for l in self.LangSysRecord
1344				if l.LangSys.subset_features(feature_indices)]
1345	self.LangSysCount = len(self.LangSysRecord)
1346	return bool(self.LangSysCount or self.DefaultLangSys)
1347
1348@_add_method(otTables.Script)
1349def collect_features(self):
1350	feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
1351	if self.DefaultLangSys:
1352		feature_indices.append(self.DefaultLangSys.collect_features())
1353	return _uniq_sort(sum(feature_indices, []))
1354
1355@_add_method(otTables.ScriptList)
1356def subset_features(self, feature_indices, retain_empty):
1357	# https://bugzilla.mozilla.org/show_bug.cgi?id=1331737#c32
1358	self.ScriptRecord = [s for s in self.ScriptRecord
1359			       if s.Script.subset_features(feature_indices, s.ScriptTag=='DFLT') or
1360				  retain_empty]
1361	self.ScriptCount = len(self.ScriptRecord)
1362	return bool(self.ScriptCount)
1363
1364@_add_method(otTables.ScriptList)
1365def collect_features(self):
1366	return _uniq_sort(sum((s.Script.collect_features()
1367			       for s in self.ScriptRecord), []))
1368
1369# CBLC will inherit it
1370@_add_method(ttLib.getTableClass('EBLC'))
1371def subset_glyphs(self, s):
1372	for strike in self.strikes:
1373		for indexSubTable in strike.indexSubTables:
1374			indexSubTable.names = [n for n in indexSubTable.names if n in s.glyphs]
1375		strike.indexSubTables = [i for i in strike.indexSubTables if i.names]
1376	self.strikes = [s for s in self.strikes if s.indexSubTables]
1377
1378	return True
1379
1380# CBDT will inherit it
1381@_add_method(ttLib.getTableClass('EBDT'))
1382def subset_glyphs(self, s):
1383  self.strikeData = [{g: strike[g] for g in s.glyphs if g in strike}
1384					 for strike in self.strikeData]
1385  return True
1386
1387@_add_method(ttLib.getTableClass('GSUB'))
1388def closure_glyphs(self, s):
1389	s.table = self.table
1390	if self.table.ScriptList:
1391		feature_indices = self.table.ScriptList.collect_features()
1392	else:
1393		feature_indices = []
1394	if self.table.FeatureList:
1395		lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
1396	else:
1397		lookup_indices = []
1398	if getattr(self.table, 'FeatureVariations', None):
1399		lookup_indices += self.table.FeatureVariations.collect_lookups(feature_indices)
1400	lookup_indices = _uniq_sort(lookup_indices)
1401	if self.table.LookupList:
1402		s._doneLookups = {}
1403		while True:
1404			orig_glyphs = frozenset(s.glyphs)
1405			for i in lookup_indices:
1406				if i >= self.table.LookupList.LookupCount: continue
1407				if not self.table.LookupList.Lookup[i]: continue
1408				self.table.LookupList.Lookup[i].closure_glyphs(s)
1409			if orig_glyphs == s.glyphs:
1410				break
1411		del s._doneLookups
1412	del s.table
1413
1414@_add_method(ttLib.getTableClass('GSUB'),
1415	     ttLib.getTableClass('GPOS'))
1416def subset_glyphs(self, s):
1417	s.glyphs = s.glyphs_gsubed
1418	if self.table.LookupList:
1419		lookup_indices = self.table.LookupList.subset_glyphs(s)
1420	else:
1421		lookup_indices = []
1422	self.subset_lookups(lookup_indices)
1423	return True
1424
1425@_add_method(ttLib.getTableClass('GSUB'),
1426	     ttLib.getTableClass('GPOS'))
1427def retain_empty_scripts(self):
1428	# https://github.com/fonttools/fonttools/issues/518
1429	# https://bugzilla.mozilla.org/show_bug.cgi?id=1080739#c15
1430	return self.__class__ == ttLib.getTableClass('GSUB')
1431
1432@_add_method(ttLib.getTableClass('GSUB'),
1433	     ttLib.getTableClass('GPOS'))
1434def subset_lookups(self, lookup_indices):
1435	"""Retains specified lookups, then removes empty features, language
1436	systems, and scripts."""
1437	if self.table.LookupList:
1438		self.table.LookupList.subset_lookups(lookup_indices)
1439	if self.table.FeatureList:
1440		feature_indices = self.table.FeatureList.subset_lookups(lookup_indices)
1441	else:
1442		feature_indices = []
1443	if getattr(self.table, 'FeatureVariations', None):
1444		feature_indices += self.table.FeatureVariations.subset_lookups(lookup_indices)
1445	feature_indices = _uniq_sort(feature_indices)
1446	if self.table.FeatureList:
1447		self.table.FeatureList.subset_features(feature_indices)
1448	if getattr(self.table, 'FeatureVariations', None):
1449		self.table.FeatureVariations.subset_features(feature_indices)
1450	if self.table.ScriptList:
1451		self.table.ScriptList.subset_features(feature_indices, self.retain_empty_scripts())
1452
1453@_add_method(ttLib.getTableClass('GSUB'),
1454	     ttLib.getTableClass('GPOS'))
1455def neuter_lookups(self, lookup_indices):
1456	"""Sets lookups not in lookup_indices to None."""
1457	if self.table.LookupList:
1458		self.table.LookupList.neuter_lookups(lookup_indices)
1459
1460@_add_method(ttLib.getTableClass('GSUB'),
1461	     ttLib.getTableClass('GPOS'))
1462def prune_lookups(self, remap=True):
1463	"""Remove (default) or neuter unreferenced lookups"""
1464	if self.table.ScriptList:
1465		feature_indices = self.table.ScriptList.collect_features()
1466	else:
1467		feature_indices = []
1468	if self.table.FeatureList:
1469		lookup_indices = self.table.FeatureList.collect_lookups(feature_indices)
1470	else:
1471		lookup_indices = []
1472	if getattr(self.table, 'FeatureVariations', None):
1473		lookup_indices += self.table.FeatureVariations.collect_lookups(feature_indices)
1474	lookup_indices = _uniq_sort(lookup_indices)
1475	if self.table.LookupList:
1476		lookup_indices = self.table.LookupList.closure_lookups(lookup_indices)
1477	else:
1478		lookup_indices = []
1479	if remap:
1480		self.subset_lookups(lookup_indices)
1481	else:
1482		self.neuter_lookups(lookup_indices)
1483
1484@_add_method(ttLib.getTableClass('GSUB'),
1485	     ttLib.getTableClass('GPOS'))
1486def subset_feature_tags(self, feature_tags):
1487	if self.table.FeatureList:
1488		feature_indices = \
1489			[i for i,f in enumerate(self.table.FeatureList.FeatureRecord)
1490			 if f.FeatureTag in feature_tags]
1491		self.table.FeatureList.subset_features(feature_indices)
1492		if getattr(self.table, 'FeatureVariations', None):
1493			self.table.FeatureVariations.subset_features(feature_indices)
1494	else:
1495		feature_indices = []
1496	if self.table.ScriptList:
1497		self.table.ScriptList.subset_features(feature_indices, self.retain_empty_scripts())
1498
1499@_add_method(ttLib.getTableClass('GSUB'),
1500	     ttLib.getTableClass('GPOS'))
1501def subset_script_tags(self, script_tags):
1502	if self.table.ScriptList:
1503		self.table.ScriptList.ScriptRecord = \
1504			[s for s in self.table.ScriptList.ScriptRecord
1505			 if s.ScriptTag in script_tags]
1506		self.table.ScriptList.ScriptCount = len(self.table.ScriptList.ScriptRecord)
1507
1508@_add_method(ttLib.getTableClass('GSUB'),
1509			 ttLib.getTableClass('GPOS'))
1510def prune_features(self):
1511	"""Remove unreferenced features"""
1512	if self.table.ScriptList:
1513		feature_indices = self.table.ScriptList.collect_features()
1514	else:
1515		feature_indices = []
1516	if self.table.FeatureList:
1517		self.table.FeatureList.subset_features(feature_indices)
1518	if getattr(self.table, 'FeatureVariations', None):
1519		self.table.FeatureVariations.subset_features(feature_indices)
1520	if self.table.ScriptList:
1521		self.table.ScriptList.subset_features(feature_indices, self.retain_empty_scripts())
1522
1523@_add_method(ttLib.getTableClass('GSUB'),
1524	     ttLib.getTableClass('GPOS'))
1525def prune_pre_subset(self, font, options):
1526	# Drop undesired features
1527	if '*' not in options.layout_scripts:
1528		self.subset_script_tags(options.layout_scripts)
1529	if '*' not in options.layout_features:
1530		self.subset_feature_tags(options.layout_features)
1531	# Neuter unreferenced lookups
1532	self.prune_lookups(remap=False)
1533	return True
1534
1535@_add_method(ttLib.getTableClass('GSUB'),
1536	     ttLib.getTableClass('GPOS'))
1537def remove_redundant_langsys(self):
1538	table = self.table
1539	if not table.ScriptList or not table.FeatureList:
1540		return
1541
1542	features = table.FeatureList.FeatureRecord
1543
1544	for s in table.ScriptList.ScriptRecord:
1545		d = s.Script.DefaultLangSys
1546		if not d:
1547			continue
1548		for lr in s.Script.LangSysRecord[:]:
1549			l = lr.LangSys
1550			# Compare d and l
1551			if len(d.FeatureIndex) != len(l.FeatureIndex):
1552				continue
1553			if (d.ReqFeatureIndex == 65535) != (l.ReqFeatureIndex == 65535):
1554				continue
1555
1556			if d.ReqFeatureIndex != 65535:
1557				if features[d.ReqFeatureIndex] != features[l.ReqFeatureIndex]:
1558					continue
1559
1560			for i in range(len(d.FeatureIndex)):
1561				if features[d.FeatureIndex[i]] != features[l.FeatureIndex[i]]:
1562					break
1563			else:
1564				# LangSys and default are equal; delete LangSys
1565				s.Script.LangSysRecord.remove(lr)
1566
1567@_add_method(ttLib.getTableClass('GSUB'),
1568	     ttLib.getTableClass('GPOS'))
1569def prune_post_subset(self, font, options):
1570	table = self.table
1571
1572	self.prune_lookups() # XXX Is this actually needed?!
1573
1574	if table.LookupList:
1575		table.LookupList.prune_post_subset(font, options)
1576		# XXX Next two lines disabled because OTS is stupid and
1577		# doesn't like NULL offsets here.
1578		#if not table.LookupList.Lookup:
1579		#	table.LookupList = None
1580
1581	if not table.LookupList:
1582		table.FeatureList = None
1583
1584
1585	if table.FeatureList:
1586		self.remove_redundant_langsys()
1587		# Remove unreferenced features
1588		self.prune_features()
1589
1590	# XXX Next two lines disabled because OTS is stupid and
1591	# doesn't like NULL offsets here.
1592	#if table.FeatureList and not table.FeatureList.FeatureRecord:
1593	#	table.FeatureList = None
1594
1595	# Never drop scripts themselves as them just being available
1596	# holds semantic significance.
1597	# XXX Next two lines disabled because OTS is stupid and
1598	# doesn't like NULL offsets here.
1599	#if table.ScriptList and not table.ScriptList.ScriptRecord:
1600	#	table.ScriptList = None
1601
1602	if not table.FeatureList and hasattr(table, 'FeatureVariations'):
1603		table.FeatureVariations = None
1604
1605	if hasattr(table, 'FeatureVariations') and not table.FeatureVariations:
1606		if table.Version == 0x00010001:
1607			table.Version = 0x00010000
1608
1609	return True
1610
1611@_add_method(ttLib.getTableClass('GDEF'))
1612def subset_glyphs(self, s):
1613	glyphs = s.glyphs_gsubed
1614	table = self.table
1615	if table.LigCaretList:
1616		indices = table.LigCaretList.Coverage.subset(glyphs)
1617		table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i] for i in indices]
1618		table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph)
1619	if table.MarkAttachClassDef:
1620		table.MarkAttachClassDef.classDefs = \
1621			{g:v for g,v in table.MarkAttachClassDef.classDefs.items()
1622			 if g in glyphs}
1623	if table.GlyphClassDef:
1624		table.GlyphClassDef.classDefs = \
1625			{g:v for g,v in table.GlyphClassDef.classDefs.items()
1626			 if g in glyphs}
1627	if table.AttachList:
1628		indices = table.AttachList.Coverage.subset(glyphs)
1629		GlyphCount = table.AttachList.GlyphCount
1630		table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i]
1631						for i in indices if i < GlyphCount]
1632		table.AttachList.GlyphCount = len(table.AttachList.AttachPoint)
1633	if hasattr(table, "MarkGlyphSetsDef") and table.MarkGlyphSetsDef:
1634		for coverage in table.MarkGlyphSetsDef.Coverage:
1635			if coverage:
1636				coverage.subset(glyphs)
1637
1638		# TODO: The following is disabled. If enabling, we need to go fixup all
1639		# lookups that use MarkFilteringSet and map their set.
1640		# indices = table.MarkGlyphSetsDef.Coverage = \
1641		#   [c for c in table.MarkGlyphSetsDef.Coverage if c.glyphs]
1642		# TODO: The following is disabled, as ots doesn't like it. Phew...
1643		# https://github.com/khaledhosny/ots/issues/172
1644		# table.MarkGlyphSetsDef.Coverage = [c if c.glyphs else None for c in table.MarkGlyphSetsDef.Coverage]
1645	return True
1646
1647
1648def _pruneGDEF(font):
1649	if 'GDEF' not in font: return
1650	gdef = font['GDEF']
1651	table = gdef.table
1652	if not hasattr(table, 'VarStore'): return
1653
1654	store = table.VarStore
1655
1656	usedVarIdxes = set()
1657
1658	# Collect.
1659	table.collect_device_varidxes(usedVarIdxes)
1660	if 'GPOS' in font:
1661		font['GPOS'].table.collect_device_varidxes(usedVarIdxes)
1662
1663	# Subset.
1664	varidx_map = store.subset_varidxes(usedVarIdxes)
1665
1666	# Map.
1667	table.remap_device_varidxes(varidx_map)
1668	if 'GPOS' in font:
1669		font['GPOS'].table.remap_device_varidxes(varidx_map)
1670
1671@_add_method(ttLib.getTableClass('GDEF'))
1672def prune_post_subset(self, font, options):
1673	table = self.table
1674	# XXX check these against OTS
1675	if table.LigCaretList and not table.LigCaretList.LigGlyphCount:
1676		table.LigCaretList = None
1677	if table.MarkAttachClassDef and not table.MarkAttachClassDef.classDefs:
1678		table.MarkAttachClassDef = None
1679	if table.GlyphClassDef and not table.GlyphClassDef.classDefs:
1680		table.GlyphClassDef = None
1681	if table.AttachList and not table.AttachList.GlyphCount:
1682		table.AttachList = None
1683	if hasattr(table, "VarStore"):
1684		_pruneGDEF(font)
1685		if table.VarStore.VarDataCount == 0:
1686			if table.Version == 0x00010003:
1687				table.Version = 0x00010002
1688	if (not hasattr(table, "MarkGlyphSetsDef") or
1689		not table.MarkGlyphSetsDef or
1690		not table.MarkGlyphSetsDef.Coverage):
1691		table.MarkGlyphSetsDef = None
1692		if table.Version == 0x00010002:
1693			table.Version = 0x00010000
1694	return bool(table.LigCaretList or
1695		    table.MarkAttachClassDef or
1696		    table.GlyphClassDef or
1697		    table.AttachList or
1698		    (table.Version >= 0x00010002 and table.MarkGlyphSetsDef) or
1699		    (table.Version >= 0x00010003 and table.VarStore))
1700
1701@_add_method(ttLib.getTableClass('kern'))
1702def prune_pre_subset(self, font, options):
1703	# Prune unknown kern table types
1704	self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')]
1705	return bool(self.kernTables)
1706
1707@_add_method(ttLib.getTableClass('kern'))
1708def subset_glyphs(self, s):
1709	glyphs = s.glyphs_gsubed
1710	for t in self.kernTables:
1711		t.kernTable = {(a,b):v for (a,b),v in t.kernTable.items()
1712				       if a in glyphs and b in glyphs}
1713	self.kernTables = [t for t in self.kernTables if t.kernTable]
1714	return bool(self.kernTables)
1715
1716@_add_method(ttLib.getTableClass('vmtx'))
1717def subset_glyphs(self, s):
1718	self.metrics = _dict_subset(self.metrics, s.glyphs)
1719	for g in s.glyphs_emptied:
1720		self.metrics[g] = (0,0)
1721	return bool(self.metrics)
1722
1723@_add_method(ttLib.getTableClass('hmtx'))
1724def subset_glyphs(self, s):
1725	self.metrics = _dict_subset(self.metrics, s.glyphs)
1726	for g in s.glyphs_emptied:
1727		self.metrics[g] = (0,0)
1728	return True # Required table
1729
1730@_add_method(ttLib.getTableClass('hdmx'))
1731def subset_glyphs(self, s):
1732	self.hdmx = {sz:_dict_subset(l, s.glyphs) for sz,l in self.hdmx.items()}
1733	for sz in self.hdmx:
1734		for g in s.glyphs_emptied:
1735			self.hdmx[sz][g] = 0
1736	return bool(self.hdmx)
1737
1738@_add_method(ttLib.getTableClass('ankr'))
1739def subset_glyphs(self, s):
1740	table = self.table.AnchorPoints
1741	assert table.Format == 0, "unknown 'ankr' format %s" % table.Format
1742	table.Anchors = {glyph: table.Anchors[glyph] for glyph in s.glyphs
1743					 if glyph in table.Anchors}
1744	return len(table.Anchors) > 0
1745
1746@_add_method(ttLib.getTableClass('bsln'))
1747def closure_glyphs(self, s):
1748	table = self.table.Baseline
1749	if table.Format in (2, 3):
1750		s.glyphs.add(table.StandardGlyph)
1751
1752@_add_method(ttLib.getTableClass('bsln'))
1753def subset_glyphs(self, s):
1754	table = self.table.Baseline
1755	if table.Format in (1, 3):
1756		baselines = {glyph: table.BaselineValues.get(glyph, table.DefaultBaseline)
1757					 for glyph in s.glyphs}
1758		if len(baselines) > 0:
1759			mostCommon, _cnt = Counter(baselines.values()).most_common(1)[0]
1760			table.DefaultBaseline = mostCommon
1761			baselines = {glyph: b for glyph, b in baselines.items()
1762					      if b != mostCommon}
1763		if len(baselines) > 0:
1764			table.BaselineValues = baselines
1765		else:
1766			table.Format = {1: 0, 3: 2}[table.Format]
1767			del table.BaselineValues
1768	return True
1769
1770@_add_method(ttLib.getTableClass('lcar'))
1771def subset_glyphs(self, s):
1772	table = self.table.LigatureCarets
1773	if table.Format in (0, 1):
1774		table.Carets = {glyph: table.Carets[glyph] for glyph in s.glyphs
1775							   if glyph in table.Carets}
1776		return len(table.Carets) > 0
1777	else:
1778		assert False, "unknown 'lcar' format %s" % table.Format
1779
1780@_add_method(ttLib.getTableClass('gvar'))
1781def prune_pre_subset(self, font, options):
1782	if options.notdef_glyph and not options.notdef_outline:
1783		self.variations[font.glyphOrder[0]] = []
1784	return True
1785
1786@_add_method(ttLib.getTableClass('gvar'))
1787def subset_glyphs(self, s):
1788	self.variations = _dict_subset(self.variations, s.glyphs)
1789	self.glyphCount = len(self.variations)
1790	return bool(self.variations)
1791
1792@_add_method(ttLib.getTableClass('HVAR'))
1793def subset_glyphs(self, s):
1794	table = self.table
1795
1796	# TODO Update for retain_gids
1797
1798	used = set()
1799
1800	if table.AdvWidthMap:
1801		if not s.options.retain_gids:
1802			table.AdvWidthMap.mapping = _dict_subset(table.AdvWidthMap.mapping, s.glyphs)
1803		used.update(table.AdvWidthMap.mapping.values())
1804	else:
1805		assert table.LsbMap is None and table.RsbMap is None, "File a bug."
1806		used.update(s.reverseOrigGlyphMap.values())
1807
1808	if table.LsbMap:
1809		if not s.options.retain_gids:
1810			table.LsbMap.mapping = _dict_subset(table.LsbMap.mapping, s.glyphs)
1811		used.update(table.LsbMap.mapping.values())
1812	if table.RsbMap:
1813		if not s.options.retain_gids:
1814			table.RsbMap.mapping = _dict_subset(table.RsbMap.mapping, s.glyphs)
1815		used.update(table.RsbMap.mapping.values())
1816
1817	varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used)
1818
1819	if table.AdvWidthMap:
1820		table.AdvWidthMap.mapping = {k:varidx_map[v] for k,v in table.AdvWidthMap.mapping.items()}
1821	if table.LsbMap:
1822		table.LsbMap.mapping = {k:varidx_map[v] for k,v in table.LsbMap.mapping.items()}
1823	if table.RsbMap:
1824		table.RsbMap.mapping = {k:varidx_map[v] for k,v in table.RsbMap.mapping.items()}
1825
1826	# TODO Return emptiness...
1827	return True
1828
1829@_add_method(ttLib.getTableClass('VVAR'))
1830def subset_glyphs(self, s):
1831	table = self.table
1832
1833	used = set()
1834
1835	if table.AdvHeightMap:
1836		table.AdvHeightMap.mapping = _dict_subset(table.AdvHeightMap.mapping, s.glyphs)
1837		used.update(table.AdvHeightMap.mapping.values())
1838	else:
1839		assert table.TsbMap is None and table.BsbMap is None and table.VOrgMap is None, "File a bug."
1840		used.update(s.reverseOrigGlyphMap.values())
1841	if table.TsbMap:
1842		table.TsbMap.mapping = _dict_subset(table.TsbMap.mapping, s.glyphs)
1843		used.update(table.TsbMap.mapping.values())
1844	if table.BsbMap:
1845		table.BsbMap.mapping = _dict_subset(table.BsbMap.mapping, s.glyphs)
1846		used.update(table.BsbMap.mapping.values())
1847	if table.VOrgMap:
1848		table.VOrgMap.mapping = _dict_subset(table.VOrgMap.mapping, s.glyphs)
1849		used.update(table.VOrgMap.mapping.values())
1850
1851	varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used)
1852
1853	if table.AdvHeightMap:
1854		table.AdvHeightMap.mapping = {k:varidx_map[v] for k,v in table.AdvHeightMap.mapping.items()}
1855	if table.TsbMap:
1856		table.TsbMap.mapping = {k:varidx_map[v] for k,v in table.TsbMap.mapping.items()}
1857	if table.BsbMap:
1858		table.BsbMap.mapping = {k:varidx_map[v] for k,v in table.BsbMap.mapping.items()}
1859	if table.VOrgMap:
1860		table.VOrgMap.mapping = {k:varidx_map[v] for k,v in table.VOrgMap.mapping.items()}
1861
1862	# TODO Return emptiness...
1863	return True
1864
1865@_add_method(ttLib.getTableClass('VORG'))
1866def subset_glyphs(self, s):
1867	self.VOriginRecords = {g:v for g,v in self.VOriginRecords.items()
1868				   if g in s.glyphs}
1869	self.numVertOriginYMetrics = len(self.VOriginRecords)
1870	return True	# Never drop; has default metrics
1871
1872@_add_method(ttLib.getTableClass('opbd'))
1873def subset_glyphs(self, s):
1874	table = self.table.OpticalBounds
1875	if table.Format == 0:
1876		table.OpticalBoundsDeltas = {glyph: table.OpticalBoundsDeltas[glyph]
1877					     for glyph in s.glyphs
1878					     if glyph in table.OpticalBoundsDeltas}
1879		return len(table.OpticalBoundsDeltas) > 0
1880	elif table.Format == 1:
1881		table.OpticalBoundsPoints = {glyph: table.OpticalBoundsPoints[glyph]
1882					     for glyph in s.glyphs
1883					     if glyph in table.OpticalBoundsPoints}
1884		return len(table.OpticalBoundsPoints) > 0
1885	else:
1886		assert False, "unknown 'opbd' format %s" % table.Format
1887
1888@_add_method(ttLib.getTableClass('post'))
1889def prune_pre_subset(self, font, options):
1890	if not options.glyph_names:
1891		self.formatType = 3.0
1892	return True # Required table
1893
1894@_add_method(ttLib.getTableClass('post'))
1895def subset_glyphs(self, s):
1896	self.extraNames = []	# This seems to do it
1897	return True # Required table
1898
1899@_add_method(ttLib.getTableClass('prop'))
1900def subset_glyphs(self, s):
1901	prop = self.table.GlyphProperties
1902	if prop.Format == 0:
1903		return prop.DefaultProperties != 0
1904	elif prop.Format == 1:
1905		prop.Properties = {g: prop.Properties.get(g, prop.DefaultProperties)
1906				   for g in s.glyphs}
1907		mostCommon, _cnt = Counter(prop.Properties.values()).most_common(1)[0]
1908		prop.DefaultProperties = mostCommon
1909		prop.Properties = {g: prop for g, prop in prop.Properties.items()
1910				   if prop != mostCommon}
1911		if len(prop.Properties) == 0:
1912			del prop.Properties
1913			prop.Format = 0
1914			return prop.DefaultProperties != 0
1915		return True
1916	else:
1917		assert False, "unknown 'prop' format %s" % prop.Format
1918
1919@_add_method(ttLib.getTableClass('COLR'))
1920def closure_glyphs(self, s):
1921	decompose = s.glyphs
1922	while decompose:
1923		layers = set()
1924		for g in decompose:
1925			for l in self.ColorLayers.get(g, []):
1926				layers.add(l.name)
1927		layers -= s.glyphs
1928		s.glyphs.update(layers)
1929		decompose = layers
1930
1931@_add_method(ttLib.getTableClass('COLR'))
1932def subset_glyphs(self, s):
1933	self.ColorLayers = {g: self.ColorLayers[g] for g in s.glyphs if g in self.ColorLayers}
1934	return bool(self.ColorLayers)
1935
1936# TODO: prune unused palettes
1937@_add_method(ttLib.getTableClass('CPAL'))
1938def prune_post_subset(self, font, options):
1939	return True
1940
1941@_add_method(otTables.MathGlyphConstruction)
1942def closure_glyphs(self, glyphs):
1943	variants = set()
1944	for v in self.MathGlyphVariantRecord:
1945		variants.add(v.VariantGlyph)
1946	if self.GlyphAssembly:
1947		for p in self.GlyphAssembly.PartRecords:
1948			variants.add(p.glyph)
1949	return variants
1950
1951@_add_method(otTables.MathVariants)
1952def closure_glyphs(self, s):
1953	glyphs = frozenset(s.glyphs)
1954	variants = set()
1955
1956	if self.VertGlyphCoverage:
1957		indices = self.VertGlyphCoverage.intersect(glyphs)
1958		for i in indices:
1959			variants.update(self.VertGlyphConstruction[i].closure_glyphs(glyphs))
1960
1961	if self.HorizGlyphCoverage:
1962		indices = self.HorizGlyphCoverage.intersect(glyphs)
1963		for i in indices:
1964			variants.update(self.HorizGlyphConstruction[i].closure_glyphs(glyphs))
1965
1966	s.glyphs.update(variants)
1967
1968@_add_method(ttLib.getTableClass('MATH'))
1969def closure_glyphs(self, s):
1970	self.table.MathVariants.closure_glyphs(s)
1971
1972@_add_method(otTables.MathItalicsCorrectionInfo)
1973def subset_glyphs(self, s):
1974	indices = self.Coverage.subset(s.glyphs)
1975	self.ItalicsCorrection = [self.ItalicsCorrection[i] for i in indices]
1976	self.ItalicsCorrectionCount = len(self.ItalicsCorrection)
1977	return bool(self.ItalicsCorrectionCount)
1978
1979@_add_method(otTables.MathTopAccentAttachment)
1980def subset_glyphs(self, s):
1981	indices = self.TopAccentCoverage.subset(s.glyphs)
1982	self.TopAccentAttachment = [self.TopAccentAttachment[i] for i in indices]
1983	self.TopAccentAttachmentCount = len(self.TopAccentAttachment)
1984	return bool(self.TopAccentAttachmentCount)
1985
1986@_add_method(otTables.MathKernInfo)
1987def subset_glyphs(self, s):
1988	indices = self.MathKernCoverage.subset(s.glyphs)
1989	self.MathKernInfoRecords = [self.MathKernInfoRecords[i] for i in indices]
1990	self.MathKernCount = len(self.MathKernInfoRecords)
1991	return bool(self.MathKernCount)
1992
1993@_add_method(otTables.MathGlyphInfo)
1994def subset_glyphs(self, s):
1995	if self.MathItalicsCorrectionInfo:
1996		self.MathItalicsCorrectionInfo.subset_glyphs(s)
1997	if self.MathTopAccentAttachment:
1998		self.MathTopAccentAttachment.subset_glyphs(s)
1999	if self.MathKernInfo:
2000		self.MathKernInfo.subset_glyphs(s)
2001	if self.ExtendedShapeCoverage:
2002		self.ExtendedShapeCoverage.subset(s.glyphs)
2003	return True
2004
2005@_add_method(otTables.MathVariants)
2006def subset_glyphs(self, s):
2007	if self.VertGlyphCoverage:
2008		indices = self.VertGlyphCoverage.subset(s.glyphs)
2009		self.VertGlyphConstruction = [self.VertGlyphConstruction[i] for i in indices]
2010		self.VertGlyphCount = len(self.VertGlyphConstruction)
2011
2012	if self.HorizGlyphCoverage:
2013		indices = self.HorizGlyphCoverage.subset(s.glyphs)
2014		self.HorizGlyphConstruction = [self.HorizGlyphConstruction[i] for i in indices]
2015		self.HorizGlyphCount = len(self.HorizGlyphConstruction)
2016
2017	return True
2018
2019@_add_method(ttLib.getTableClass('MATH'))
2020def subset_glyphs(self, s):
2021	s.glyphs = s.glyphs_mathed
2022	self.table.MathGlyphInfo.subset_glyphs(s)
2023	self.table.MathVariants.subset_glyphs(s)
2024	return True
2025
2026@_add_method(ttLib.getTableModule('glyf').Glyph)
2027def remapComponentsFast(self, glyphidmap):
2028	if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
2029		return	# Not composite
2030	data = array.array("B", self.data)
2031	i = 10
2032	more = 1
2033	while more:
2034		flags =(data[i] << 8) | data[i+1]
2035		glyphID =(data[i+2] << 8) | data[i+3]
2036		# Remap
2037		glyphID = glyphidmap[glyphID]
2038		data[i+2] = glyphID >> 8
2039		data[i+3] = glyphID & 0xFF
2040		i += 4
2041		flags = int(flags)
2042
2043		if flags & 0x0001: i += 4	# ARG_1_AND_2_ARE_WORDS
2044		else: i += 2
2045		if flags & 0x0008: i += 2	# WE_HAVE_A_SCALE
2046		elif flags & 0x0040: i += 4	# WE_HAVE_AN_X_AND_Y_SCALE
2047		elif flags & 0x0080: i += 8	# WE_HAVE_A_TWO_BY_TWO
2048		more = flags & 0x0020	# MORE_COMPONENTS
2049
2050	self.data = data.tostring()
2051
2052@_add_method(ttLib.getTableClass('glyf'))
2053def closure_glyphs(self, s):
2054	glyphSet = self.glyphs
2055	decompose = s.glyphs
2056	while decompose:
2057		components = set()
2058		for g in decompose:
2059			if g not in glyphSet:
2060				continue
2061			gl = glyphSet[g]
2062			for c in gl.getComponentNames(self):
2063				components.add(c)
2064		components -= s.glyphs
2065		s.glyphs.update(components)
2066		decompose = components
2067
2068@_add_method(ttLib.getTableClass('glyf'))
2069def prune_pre_subset(self, font, options):
2070	if options.notdef_glyph and not options.notdef_outline:
2071		g = self[self.glyphOrder[0]]
2072		# Yay, easy!
2073		g.__dict__.clear()
2074		g.data = ""
2075	return True
2076
2077@_add_method(ttLib.getTableClass('glyf'))
2078def subset_glyphs(self, s):
2079	self.glyphs = _dict_subset(self.glyphs, s.glyphs)
2080	if not s.options.retain_gids:
2081		indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs]
2082		glyphmap = {o:n for n,o in enumerate(indices)}
2083		for v in self.glyphs.values():
2084			if hasattr(v, "data"):
2085				v.remapComponentsFast(glyphmap)
2086	Glyph = ttLib.getTableModule('glyf').Glyph
2087	for g in s.glyphs_emptied:
2088		self.glyphs[g] = Glyph()
2089		self.glyphs[g].data = ''
2090	self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs or g in s.glyphs_emptied]
2091	# Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset.
2092	return True
2093
2094@_add_method(ttLib.getTableClass('glyf'))
2095def prune_post_subset(self, font, options):
2096	remove_hinting = not options.hinting
2097	for v in self.glyphs.values():
2098		v.trim(remove_hinting=remove_hinting)
2099	return True
2100
2101
2102@_add_method(ttLib.getTableClass('cmap'))
2103def closure_glyphs(self, s):
2104	tables = [t for t in self.tables if t.isUnicode()]
2105
2106	# Close glyphs
2107	for table in tables:
2108		if table.format == 14:
2109			for cmap in table.uvsDict.values():
2110				glyphs = {g for u,g in cmap if u in s.unicodes_requested}
2111				if None in glyphs:
2112					glyphs.remove(None)
2113				s.glyphs.update(glyphs)
2114		else:
2115			cmap = table.cmap
2116			intersection = s.unicodes_requested.intersection(cmap.keys())
2117			s.glyphs.update(cmap[u] for u in intersection)
2118
2119	# Calculate unicodes_missing
2120	s.unicodes_missing = s.unicodes_requested.copy()
2121	for table in tables:
2122		s.unicodes_missing.difference_update(table.cmap)
2123
2124@_add_method(ttLib.getTableClass('cmap'))
2125def prune_pre_subset(self, font, options):
2126	if not options.legacy_cmap:
2127		# Drop non-Unicode / non-Symbol cmaps
2128		self.tables = [t for t in self.tables if t.isUnicode() or t.isSymbol()]
2129	if not options.symbol_cmap:
2130		self.tables = [t for t in self.tables if not t.isSymbol()]
2131	# TODO(behdad) Only keep one subtable?
2132	# For now, drop format=0 which can't be subset_glyphs easily?
2133	self.tables = [t for t in self.tables if t.format != 0]
2134	self.numSubTables = len(self.tables)
2135	return True # Required table
2136
2137@_add_method(ttLib.getTableClass('cmap'))
2138def subset_glyphs(self, s):
2139	s.glyphs = None # We use s.glyphs_requested and s.unicodes_requested only
2140	for t in self.tables:
2141		if t.format == 14:
2142			# TODO(behdad) We drop all the default-UVS mappings
2143			# for glyphs_requested.  So it's the caller's responsibility to make
2144			# sure those are included.
2145			t.uvsDict = {v:[(u,g) for u,g in l
2146					      if g in s.glyphs_requested or u in s.unicodes_requested]
2147				     for v,l in t.uvsDict.items()}
2148			t.uvsDict = {v:l for v,l in t.uvsDict.items() if l}
2149		elif t.isUnicode():
2150			t.cmap = {u:g for u,g in t.cmap.items()
2151				      if g in s.glyphs_requested or u in s.unicodes_requested}
2152		else:
2153			t.cmap = {u:g for u,g in t.cmap.items()
2154				      if g in s.glyphs_requested}
2155	self.tables = [t for t in self.tables
2156			 if (t.cmap if t.format != 14 else t.uvsDict)]
2157	self.numSubTables = len(self.tables)
2158	# TODO(behdad) Convert formats when needed.
2159	# In particular, if we have a format=12 without non-BMP
2160	# characters, either drop format=12 one or convert it
2161	# to format=4 if there's not one.
2162	return True # Required table
2163
2164@_add_method(ttLib.getTableClass('DSIG'))
2165def prune_pre_subset(self, font, options):
2166	# Drop all signatures since they will be invalid
2167	self.usNumSigs = 0
2168	self.signatureRecords = []
2169	return True
2170
2171@_add_method(ttLib.getTableClass('maxp'))
2172def prune_pre_subset(self, font, options):
2173	if not options.hinting:
2174		if self.tableVersion == 0x00010000:
2175			self.maxZones = 1
2176			self.maxTwilightPoints = 0
2177			self.maxStorage = 0
2178			self.maxFunctionDefs = 0
2179			self.maxInstructionDefs = 0
2180			self.maxStackElements = 0
2181			self.maxSizeOfInstructions = 0
2182	return True
2183
2184@_add_method(ttLib.getTableClass('name'))
2185def prune_pre_subset(self, font, options):
2186	nameIDs = set(options.name_IDs)
2187	fvar = font.get('fvar')
2188	if fvar:
2189		nameIDs.update([axis.axisNameID for axis in fvar.axes])
2190		nameIDs.update([inst.subfamilyNameID for inst in fvar.instances])
2191		nameIDs.update([inst.postscriptNameID for inst in fvar.instances
2192				if inst.postscriptNameID != 0xFFFF])
2193	stat = font.get('STAT')
2194	if stat:
2195		if stat.table.AxisValueArray:
2196			nameIDs.update([val_rec.ValueNameID for val_rec in stat.table.AxisValueArray.AxisValue])
2197		nameIDs.update([axis_rec.AxisNameID for axis_rec in stat.table.DesignAxisRecord.Axis])
2198	if '*' not in options.name_IDs:
2199		self.names = [n for n in self.names if n.nameID in nameIDs]
2200	if not options.name_legacy:
2201		# TODO(behdad) Sometimes (eg Apple Color Emoji) there's only a macroman
2202		# entry for Latin and no Unicode names.
2203		self.names = [n for n in self.names if n.isUnicode()]
2204	# TODO(behdad) Option to keep only one platform's
2205	if '*' not in options.name_languages:
2206		# TODO(behdad) This is Windows-platform specific!
2207		self.names = [n for n in self.names
2208				if n.langID in options.name_languages]
2209	if options.obfuscate_names:
2210		namerecs = []
2211		for n in self.names:
2212			if n.nameID in [1, 4]:
2213				n.string = ".\x7f".encode('utf_16_be') if n.isUnicode() else ".\x7f"
2214			elif n.nameID in [2, 6]:
2215				n.string = "\x7f".encode('utf_16_be') if n.isUnicode() else "\x7f"
2216			elif n.nameID == 3:
2217				n.string = ""
2218			elif n.nameID in [16, 17, 18]:
2219				continue
2220			namerecs.append(n)
2221		self.names = namerecs
2222	return True	# Required table
2223
2224
2225# TODO(behdad) OS/2 ulCodePageRange?
2226# TODO(behdad) Drop AAT tables.
2227# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries.
2228# TODO(behdad) Drop empty GSUB/GPOS, and GDEF if no GSUB/GPOS left
2229# TODO(behdad) Drop GDEF subitems if unused by lookups
2230# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF)
2231# TODO(behdad) Text direction considerations.
2232# TODO(behdad) Text script / language considerations.
2233# TODO(behdad) Optionally drop 'kern' table if GPOS available
2234# TODO(behdad) Implement --unicode='*' to choose all cmap'ed
2235# TODO(behdad) Drop old-spec Indic scripts
2236
2237
2238class Options(object):
2239
2240	class OptionError(Exception): pass
2241	class UnknownOptionError(OptionError): pass
2242
2243	# spaces in tag names (e.g. "SVG ", "cvt ") are stripped by the argument parser
2244	_drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC',
2245				'EBSC', 'SVG', 'PCLT', 'LTSH']
2246	_drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill']  # Graphite
2247	_drop_tables_default += ['sbix']  # Color
2248	_no_subset_tables_default = ['avar', 'fvar',
2249				     'gasp', 'head', 'hhea', 'maxp',
2250				     'vhea', 'OS/2', 'loca', 'name', 'cvt',
2251				     'fpgm', 'prep', 'VDMX', 'DSIG', 'CPAL',
2252				     'MVAR', 'cvar', 'STAT']
2253	_hinting_tables_default = ['cvt', 'cvar', 'fpgm', 'prep', 'hdmx', 'VDMX']
2254
2255	# Based on HarfBuzz shapers
2256	_layout_features_groups = {
2257		# Default shaper
2258		'common': ['rvrn', 'ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'],
2259		'fractions': ['frac', 'numr', 'dnom'],
2260		'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'],
2261		'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'],
2262		'ltr': ['ltra', 'ltrm'],
2263		'rtl': ['rtla', 'rtlm'],
2264		# Complex shapers
2265		'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3',
2266			   'cswh', 'mset', 'stch'],
2267		'hangul': ['ljmo', 'vjmo', 'tjmo'],
2268		'tibetan': ['abvs', 'blws', 'abvm', 'blwm'],
2269		'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half',
2270			  'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres',
2271			  'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'],
2272	}
2273	_layout_features_default = _uniq_sort(sum(
2274			iter(_layout_features_groups.values()), []))
2275
2276	def __init__(self, **kwargs):
2277
2278		self.drop_tables = self._drop_tables_default[:]
2279		self.no_subset_tables = self._no_subset_tables_default[:]
2280		self.passthrough_tables = False  # keep/drop tables we can't subset
2281		self.hinting_tables = self._hinting_tables_default[:]
2282		self.legacy_kern = False # drop 'kern' table if GPOS available
2283		self.layout_closure = True
2284		self.layout_features = self._layout_features_default[:]
2285		self.layout_scripts = ['*']
2286		self.ignore_missing_glyphs = False
2287		self.ignore_missing_unicodes = True
2288		self.hinting = True
2289		self.glyph_names = False
2290		self.legacy_cmap = False
2291		self.symbol_cmap = False
2292		self.name_IDs = [0, 1, 2, 3, 4, 5, 6] # https://github.com/fonttools/fonttools/issues/1170#issuecomment-364631225
2293		self.name_legacy = False
2294		self.name_languages = [0x0409] # English
2295		self.obfuscate_names = False # to make webfont unusable as a system font
2296		self.retain_gids = False
2297		self.notdef_glyph = True # gid0 for TrueType / .notdef for CFF
2298		self.notdef_outline = False # No need for notdef to have an outline really
2299		self.recommended_glyphs = False # gid1, gid2, gid3 for TrueType
2300		self.recalc_bounds = False # Recalculate font bounding boxes
2301		self.recalc_timestamp = False # Recalculate font modified timestamp
2302		self.prune_unicode_ranges = True # Clear unused 'ulUnicodeRange' bits
2303		self.recalc_average_width = False # update 'xAvgCharWidth'
2304		self.canonical_order = None # Order tables as recommended
2305		self.flavor = None  # May be 'woff' or 'woff2'
2306		self.with_zopfli = False  # use zopfli instead of zlib for WOFF 1.0
2307		self.desubroutinize = False # Desubroutinize CFF CharStrings
2308		self.verbose = False
2309		self.timing = False
2310		self.xml = False
2311		self.font_number = -1
2312
2313		self.set(**kwargs)
2314
2315	def set(self, **kwargs):
2316		for k,v in kwargs.items():
2317			if not hasattr(self, k):
2318				raise self.UnknownOptionError("Unknown option '%s'" % k)
2319			setattr(self, k, v)
2320
2321	def parse_opts(self, argv, ignore_unknown=[]):
2322		posargs = []
2323		passthru_options = []
2324		for a in argv:
2325			orig_a = a
2326			if not a.startswith('--'):
2327				posargs.append(a)
2328				continue
2329			a = a[2:]
2330			i = a.find('=')
2331			op = '='
2332			if i == -1:
2333				if a.startswith("no-"):
2334					k = a[3:]
2335					if k == "canonical-order":
2336						# reorderTables=None is faster than False (the latter
2337						# still reorders to "keep" the original table order)
2338						v = None
2339					else:
2340						v = False
2341				else:
2342					k = a
2343					v = True
2344				if k.endswith("?"):
2345					k = k[:-1]
2346					v = '?'
2347			else:
2348				k = a[:i]
2349				if k[-1] in "-+":
2350					op = k[-1]+'='	# Op is '-=' or '+=' now.
2351					k = k[:-1]
2352				v = a[i+1:]
2353			ok = k
2354			k = k.replace('-', '_')
2355			if not hasattr(self, k):
2356				if ignore_unknown is True or ok in ignore_unknown:
2357					passthru_options.append(orig_a)
2358					continue
2359				else:
2360					raise self.UnknownOptionError("Unknown option '%s'" % a)
2361
2362			ov = getattr(self, k)
2363			if v == '?':
2364					print("Current setting for '%s' is: %s" % (ok, ov))
2365					continue
2366			if isinstance(ov, bool):
2367				v = bool(v)
2368			elif isinstance(ov, int):
2369				v = int(v)
2370			elif isinstance(ov, str):
2371				v = str(v) # redundant
2372			elif isinstance(ov, list):
2373				if isinstance(v, bool):
2374					raise self.OptionError("Option '%s' requires values to be specified using '='" % a)
2375				vv = v.replace(',', ' ').split()
2376				if vv == ['']:
2377					vv = []
2378				vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv]
2379				if op == '=':
2380					v = vv
2381				elif op == '+=':
2382					v = ov
2383					v.extend(vv)
2384				elif op == '-=':
2385					v = ov
2386					for x in vv:
2387						if x in v:
2388							v.remove(x)
2389				else:
2390					assert False
2391
2392			setattr(self, k, v)
2393
2394		return posargs + passthru_options
2395
2396
2397class Subsetter(object):
2398
2399	class SubsettingError(Exception): pass
2400	class MissingGlyphsSubsettingError(SubsettingError): pass
2401	class MissingUnicodesSubsettingError(SubsettingError): pass
2402
2403	def __init__(self, options=None):
2404
2405		if not options:
2406			options = Options()
2407
2408		self.options = options
2409		self.unicodes_requested = set()
2410		self.glyph_names_requested = set()
2411		self.glyph_ids_requested = set()
2412
2413	def populate(self, glyphs=[], gids=[], unicodes=[], text=""):
2414		self.unicodes_requested.update(unicodes)
2415		if isinstance(text, bytes):
2416			text = text.decode("utf_8")
2417		text_utf32 = text.encode("utf-32-be")
2418		nchars = len(text_utf32)//4
2419		for u in struct.unpack('>%dL' % nchars, text_utf32):
2420			self.unicodes_requested.add(u)
2421		self.glyph_names_requested.update(glyphs)
2422		self.glyph_ids_requested.update(gids)
2423
2424	def _prune_pre_subset(self, font):
2425		for tag in self._sort_tables(font):
2426			if (tag.strip() in self.options.drop_tables or
2427			    (tag.strip() in self.options.hinting_tables and not self.options.hinting) or
2428			    (tag == 'kern' and (not self.options.legacy_kern and 'GPOS' in font))):
2429				log.info("%s dropped", tag)
2430				del font[tag]
2431				continue
2432
2433			clazz = ttLib.getTableClass(tag)
2434
2435			if hasattr(clazz, 'prune_pre_subset'):
2436				with timer("load '%s'" % tag):
2437					table = font[tag]
2438				with timer("prune '%s'" % tag):
2439					retain = table.prune_pre_subset(font, self.options)
2440				if not retain:
2441					log.info("%s pruned to empty; dropped", tag)
2442					del font[tag]
2443					continue
2444				else:
2445					log.info("%s pruned", tag)
2446
2447	def _closure_glyphs(self, font):
2448
2449		realGlyphs = set(font.getGlyphOrder())
2450		glyph_order = font.getGlyphOrder()
2451
2452		self.glyphs_requested = set()
2453		self.glyphs_requested.update(self.glyph_names_requested)
2454		self.glyphs_requested.update(glyph_order[i]
2455					     for i in self.glyph_ids_requested
2456					     if i < len(glyph_order))
2457
2458		self.glyphs_missing = set()
2459		self.glyphs_missing.update(self.glyphs_requested.difference(realGlyphs))
2460		self.glyphs_missing.update(i for i in self.glyph_ids_requested
2461					     if i >= len(glyph_order))
2462		if self.glyphs_missing:
2463			log.info("Missing requested glyphs: %s", self.glyphs_missing)
2464			if not self.options.ignore_missing_glyphs:
2465				raise self.MissingGlyphsSubsettingError(self.glyphs_missing)
2466
2467		self.glyphs = self.glyphs_requested.copy()
2468
2469		self.unicodes_missing = set()
2470		if 'cmap' in font:
2471			with timer("close glyph list over 'cmap'"):
2472				font['cmap'].closure_glyphs(self)
2473				self.glyphs.intersection_update(realGlyphs)
2474		self.glyphs_cmaped = frozenset(self.glyphs)
2475		if self.unicodes_missing:
2476			missing = ["U+%04X" % u for u in self.unicodes_missing]
2477			log.info("Missing glyphs for requested Unicodes: %s", missing)
2478			if not self.options.ignore_missing_unicodes:
2479				raise self.MissingUnicodesSubsettingError(missing)
2480			del missing
2481
2482		if self.options.notdef_glyph:
2483			if 'glyf' in font:
2484				self.glyphs.add(font.getGlyphName(0))
2485				log.info("Added gid0 to subset")
2486			else:
2487				self.glyphs.add('.notdef')
2488				log.info("Added .notdef to subset")
2489		if self.options.recommended_glyphs:
2490			if 'glyf' in font:
2491				for i in range(min(4, len(font.getGlyphOrder()))):
2492					self.glyphs.add(font.getGlyphName(i))
2493				log.info("Added first four glyphs to subset")
2494
2495		if self.options.layout_closure and 'GSUB' in font:
2496			with timer("close glyph list over 'GSUB'"):
2497				log.info("Closing glyph list over 'GSUB': %d glyphs before",
2498						 len(self.glyphs))
2499				log.glyphs(self.glyphs, font=font)
2500				font['GSUB'].closure_glyphs(self)
2501				self.glyphs.intersection_update(realGlyphs)
2502				log.info("Closed glyph list over 'GSUB': %d glyphs after",
2503						 len(self.glyphs))
2504				log.glyphs(self.glyphs, font=font)
2505		self.glyphs_gsubed = frozenset(self.glyphs)
2506
2507		if 'MATH' in font:
2508			with timer("close glyph list over 'MATH'"):
2509				log.info("Closing glyph list over 'MATH': %d glyphs before",
2510						 len(self.glyphs))
2511				log.glyphs(self.glyphs, font=font)
2512				font['MATH'].closure_glyphs(self)
2513				self.glyphs.intersection_update(realGlyphs)
2514				log.info("Closed glyph list over 'MATH': %d glyphs after",
2515						 len(self.glyphs))
2516				log.glyphs(self.glyphs, font=font)
2517		self.glyphs_mathed = frozenset(self.glyphs)
2518
2519		for table in ('COLR', 'bsln'):
2520			if table in font:
2521				with timer("close glyph list over '%s'" % table):
2522					log.info("Closing glyph list over '%s': %d glyphs before",
2523							 table, len(self.glyphs))
2524					log.glyphs(self.glyphs, font=font)
2525					font[table].closure_glyphs(self)
2526					self.glyphs.intersection_update(realGlyphs)
2527					log.info("Closed glyph list over '%s': %d glyphs after",
2528							 table, len(self.glyphs))
2529					log.glyphs(self.glyphs, font=font)
2530
2531		if 'glyf' in font:
2532			with timer("close glyph list over 'glyf'"):
2533				log.info("Closing glyph list over 'glyf': %d glyphs before",
2534						 len(self.glyphs))
2535				log.glyphs(self.glyphs, font=font)
2536				font['glyf'].closure_glyphs(self)
2537				self.glyphs.intersection_update(realGlyphs)
2538				log.info("Closed glyph list over 'glyf': %d glyphs after",
2539						 len(self.glyphs))
2540				log.glyphs(self.glyphs, font=font)
2541		self.glyphs_glyfed = frozenset(self.glyphs)
2542
2543		if 'CFF ' in font:
2544			with timer("close glyph list over 'CFF '"):
2545				log.info("Closing glyph list over 'CFF ': %d glyphs before",
2546						 len(self.glyphs))
2547				log.glyphs(self.glyphs, font=font)
2548				font['CFF '].closure_glyphs(self)
2549				self.glyphs.intersection_update(realGlyphs)
2550				log.info("Closed glyph list over 'CFF ': %d glyphs after",
2551						 len(self.glyphs))
2552				log.glyphs(self.glyphs, font=font)
2553		self.glyphs_cffed = frozenset(self.glyphs)
2554
2555		self.glyphs_retained = frozenset(self.glyphs)
2556
2557		self.glyphs_emptied = frozenset()
2558		if self.options.retain_gids:
2559			self.glyphs_emptied = realGlyphs - self.glyphs_retained
2560			# TODO Drop empty glyphs at the end of GlyphOrder vector.
2561
2562		order = font.getReverseGlyphMap()
2563		self.reverseOrigGlyphMap = {g:order[g] for g in self.glyphs_retained}
2564
2565		log.info("Retaining %d glyphs", len(self.glyphs_retained))
2566
2567		del self.glyphs
2568
2569	def _subset_glyphs(self, font):
2570		for tag in self._sort_tables(font):
2571			clazz = ttLib.getTableClass(tag)
2572
2573			if tag.strip() in self.options.no_subset_tables:
2574				log.info("%s subsetting not needed", tag)
2575			elif hasattr(clazz, 'subset_glyphs'):
2576				with timer("subset '%s'" % tag):
2577					table = font[tag]
2578					self.glyphs = self.glyphs_retained
2579					retain = table.subset_glyphs(self)
2580					del self.glyphs
2581				if not retain:
2582					log.info("%s subsetted to empty; dropped", tag)
2583					del font[tag]
2584				else:
2585					log.info("%s subsetted", tag)
2586			elif self.options.passthrough_tables:
2587				log.info("%s NOT subset; don't know how to subset", tag)
2588			else:
2589				log.warning("%s NOT subset; don't know how to subset; dropped", tag)
2590				del font[tag]
2591
2592		if not self.options.retain_gids:
2593			with timer("subset GlyphOrder"):
2594				glyphOrder = font.getGlyphOrder()
2595				glyphOrder = [g for g in glyphOrder if g in self.glyphs_retained]
2596				font.setGlyphOrder(glyphOrder)
2597				font._buildReverseGlyphOrderDict()
2598
2599	def _prune_post_subset(self, font):
2600		for tag in font.keys():
2601			if tag == 'GlyphOrder': continue
2602			if tag == 'OS/2' and self.options.prune_unicode_ranges:
2603				old_uniranges = font[tag].getUnicodeRanges()
2604				new_uniranges = font[tag].recalcUnicodeRanges(font, pruneOnly=True)
2605				if old_uniranges != new_uniranges:
2606					log.info("%s Unicode ranges pruned: %s", tag, sorted(new_uniranges))
2607				if self.options.recalc_average_width:
2608					widths = [m[0] for m in font["hmtx"].metrics.values() if m[0] > 0]
2609					avg_width = otRound(sum(widths) / len(widths))
2610					if avg_width != font[tag].xAvgCharWidth:
2611						font[tag].xAvgCharWidth = avg_width
2612						log.info("%s xAvgCharWidth updated: %d", tag, avg_width)
2613			clazz = ttLib.getTableClass(tag)
2614			if hasattr(clazz, 'prune_post_subset'):
2615				with timer("prune '%s'" % tag):
2616					table = font[tag]
2617					retain = table.prune_post_subset(font, self.options)
2618				if not retain:
2619					log.info("%s pruned to empty; dropped", tag)
2620					del font[tag]
2621				else:
2622					log.info("%s pruned", tag)
2623
2624	def _sort_tables(self, font):
2625		tagOrder = ['fvar', 'avar', 'gvar', 'name', 'glyf']
2626		tagOrder = {t: i + 1 for i, t in enumerate(tagOrder)}
2627		tags = sorted(font.keys(), key=lambda tag: tagOrder.get(tag, 0))
2628		return [t for t in tags if t != 'GlyphOrder']
2629
2630	def subset(self, font):
2631		self._prune_pre_subset(font)
2632		self._closure_glyphs(font)
2633		self._subset_glyphs(font)
2634		self._prune_post_subset(font)
2635
2636
2637@timer("load font")
2638def load_font(fontFile,
2639	      options,
2640	      allowVID=False,
2641	      checkChecksums=False,
2642	      dontLoadGlyphNames=False,
2643	      lazy=True):
2644
2645	font = ttLib.TTFont(fontFile,
2646			    allowVID=allowVID,
2647			    checkChecksums=checkChecksums,
2648			    recalcBBoxes=options.recalc_bounds,
2649			    recalcTimestamp=options.recalc_timestamp,
2650			    lazy=lazy,
2651			    fontNumber=options.font_number)
2652
2653	# Hack:
2654	#
2655	# If we don't need glyph names, change 'post' class to not try to
2656	# load them.	It avoid lots of headache with broken fonts as well
2657	# as loading time.
2658	#
2659	# Ideally ttLib should provide a way to ask it to skip loading
2660	# glyph names.	But it currently doesn't provide such a thing.
2661	#
2662	if dontLoadGlyphNames:
2663		post = ttLib.getTableClass('post')
2664		saved = post.decode_format_2_0
2665		post.decode_format_2_0 = post.decode_format_3_0
2666		f = font['post']
2667		if f.formatType == 2.0:
2668			f.formatType = 3.0
2669		post.decode_format_2_0 = saved
2670
2671	return font
2672
2673@timer("compile and save font")
2674def save_font(font, outfile, options):
2675	if options.with_zopfli and options.flavor == "woff":
2676		from fontTools.ttLib import sfnt
2677		sfnt.USE_ZOPFLI = True
2678	font.flavor = options.flavor
2679	font.save(outfile, reorderTables=options.canonical_order)
2680
2681def parse_unicodes(s):
2682	import re
2683	s = re.sub (r"0[xX]", " ", s)
2684	s = re.sub (r"[<+>,;&#\\xXuU\n	]", " ", s)
2685	l = []
2686	for item in s.split():
2687		fields = item.split('-')
2688		if len(fields) == 1:
2689			l.append(int(item, 16))
2690		else:
2691			start,end = fields
2692			l.extend(range(int(start, 16), int(end, 16)+1))
2693	return l
2694
2695def parse_gids(s):
2696	l = []
2697	for item in s.replace(',', ' ').split():
2698		fields = item.split('-')
2699		if len(fields) == 1:
2700			l.append(int(fields[0]))
2701		else:
2702			l.extend(range(int(fields[0]), int(fields[1])+1))
2703	return l
2704
2705def parse_glyphs(s):
2706	return s.replace(',', ' ').split()
2707
2708def usage():
2709	print("usage:", __usage__, file=sys.stderr)
2710	print("Try pyftsubset --help for more information.\n", file=sys.stderr)
2711
2712@timer("make one with everything (TOTAL TIME)")
2713def main(args=None):
2714	from os.path import splitext
2715	from fontTools import configLogger
2716
2717	if args is None:
2718		args = sys.argv[1:]
2719
2720	if '--help' in args:
2721		print(__doc__)
2722		return 0
2723
2724	options = Options()
2725	try:
2726		args = options.parse_opts(args,
2727			ignore_unknown=['gids', 'gids-file',
2728							'glyphs', 'glyphs-file',
2729							'text', 'text-file',
2730							'unicodes', 'unicodes-file',
2731							'output-file'])
2732	except options.OptionError as e:
2733		usage()
2734		print("ERROR:", e, file=sys.stderr)
2735		return 2
2736
2737	if len(args) < 2:
2738		usage()
2739		return 1
2740
2741	configLogger(level=logging.INFO if options.verbose else logging.WARNING)
2742	if options.timing:
2743		timer.logger.setLevel(logging.DEBUG)
2744	else:
2745		timer.logger.disabled = True
2746
2747	fontfile = args[0]
2748	args = args[1:]
2749
2750	subsetter = Subsetter(options=options)
2751	outfile = None
2752	glyphs = []
2753	gids = []
2754	unicodes = []
2755	wildcard_glyphs = False
2756	wildcard_unicodes = False
2757	text = ""
2758	for g in args:
2759		if g == '*':
2760			wildcard_glyphs = True
2761			continue
2762		if g.startswith('--output-file='):
2763			outfile = g[14:]
2764			continue
2765		if g.startswith('--text='):
2766			text += g[7:]
2767			continue
2768		if g.startswith('--text-file='):
2769			with open(g[12:], encoding='utf-8') as f:
2770				text += f.read().replace('\n', '')
2771			continue
2772		if g.startswith('--unicodes='):
2773			if g[11:] == '*':
2774				wildcard_unicodes = True
2775			else:
2776				unicodes.extend(parse_unicodes(g[11:]))
2777			continue
2778		if g.startswith('--unicodes-file='):
2779			with open(g[16:]) as f:
2780				for line in f.readlines():
2781					unicodes.extend(parse_unicodes(line.split('#')[0]))
2782			continue
2783		if g.startswith('--gids='):
2784			gids.extend(parse_gids(g[7:]))
2785			continue
2786		if g.startswith('--gids-file='):
2787			with open(g[12:]) as f:
2788				for line in f.readlines():
2789					gids.extend(parse_gids(line.split('#')[0]))
2790			continue
2791		if g.startswith('--glyphs='):
2792			if g[9:] == '*':
2793				wildcard_glyphs = True
2794			else:
2795				glyphs.extend(parse_glyphs(g[9:]))
2796			continue
2797		if g.startswith('--glyphs-file='):
2798			with open(g[14:]) as f:
2799				for line in f.readlines():
2800					glyphs.extend(parse_glyphs(line.split('#')[0]))
2801			continue
2802		glyphs.append(g)
2803
2804	dontLoadGlyphNames = not options.glyph_names and not glyphs
2805	font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames)
2806
2807	if outfile is None:
2808		basename, _ = splitext(fontfile)
2809		if options.flavor is not None:
2810			ext = "." + options.flavor.lower()
2811		else:
2812			ext = ".ttf" if font.sfntVersion == "\0\1\0\0" else ".otf"
2813		outfile = basename + ".subset" + ext
2814
2815	with timer("compile glyph list"):
2816		if wildcard_glyphs:
2817			glyphs.extend(font.getGlyphOrder())
2818		if wildcard_unicodes:
2819			for t in font['cmap'].tables:
2820				if t.isUnicode():
2821					unicodes.extend(t.cmap.keys())
2822		assert '' not in glyphs
2823
2824	log.info("Text: '%s'" % text)
2825	log.info("Unicodes: %s", unicodes)
2826	log.info("Glyphs: %s", glyphs)
2827	log.info("Gids: %s", gids)
2828
2829	subsetter.populate(glyphs=glyphs, gids=gids, unicodes=unicodes, text=text)
2830	subsetter.subset(font)
2831
2832	save_font(font, outfile, options)
2833
2834	if options.verbose:
2835		import os
2836		log.info("Input font:% 7d bytes: %s" % (os.path.getsize(fontfile), fontfile))
2837		log.info("Subset font:% 7d bytes: %s" % (os.path.getsize(outfile), outfile))
2838
2839	if options.xml:
2840		font.saveXML(sys.stdout)
2841
2842	font.close()
2843
2844
2845__all__ = [
2846	'Options',
2847	'Subsetter',
2848	'load_font',
2849	'save_font',
2850	'parse_gids',
2851	'parse_glyphs',
2852	'parse_unicodes',
2853	'main'
2854]
2855
2856if __name__ == '__main__':
2857	sys.exit(main())
2858