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