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