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